JohnEarnest/Octo

Use of :next and :unpack VS :macro with Forward Use on XO-Chip

Chromatophore opened this issue · 2 comments

Octo currently provides two keywords, :next and :unpack, which are often used for self modifying code. They relate strictly to address labels, and are limited in their power. For example, you can only define a pair of registers to use for the whole program for :unpack. Vaguely:

:unpack allows you to set aliased addresses to the upper and lower bytes of a computed 12 bit address.
:next provides a +1 offset when defining an address label, for addressing into eg the integer byte in vN := ##

With XO-Chip, you often deal with a) 16 bit addresses (vs 12 bit addresses) and b) the command i := long #### is prevalent (which takes the form 0xF0 00 ## ##, the ## ## is at a +2 offset to a label defined immediately prior to the instruction).

Macros are currently the de facto solution to implementing a 16 bit :unpack and a :next-but-plus-2 on this stage. However, (I believe) because a :macro is a general tool, and can result in code of uncertain lengths, they can not support ##forward use## of address labels within them, unlike the older commands.

The result is disparities such as the following (an excerpt)

# This is, effectively, a macro version of unpack for long addresses
:macro long_indirect ADDRESS {
	:calc hi { 0xFF & ADDRESS >> 8 }
	:calc lo { 0xFF & ADDRESS }
	v0 := hi
	v1 := lo
}

	...

	# Possible:
	long_indirect prior_address
	
	# Not Possible
	# long_indirect after_address

	# Possible:
	:unpack 0xA prior_address
	
	# Possible:
	:unpack 0xA after_address

	# Possible:
	i := prior_address
	load v1
	
	# Possible
	i := after_address
	load v1

(Full example here: http://johnearnest.github.io/Octo/index.html?key=gCNW_DyY )

I believe that the intent is to depreciate the existing :unpack and :next commands, as they require special handling within the interpreter, vs macros, which have general application. But, this means that certain approaches to writing source files are, effectively, being pushed towards being unsupported, especially in the XO-Chip space.

Forward use of address labels is something that I personally use and corresponds with the way I write programs and use this interpreter. When I consider problems I have had with using :macro and :calc, in every instance, it relates to address labelling & forward use. The two things I consistently want to do, but apparently can not, are:

a) Place the computed memory address of a label (which appears later in the program) into bytes of the source code.
b) Apply a fixed offset to a label, and use that in earlier code.

These are, in essence, what :unpack and :next do, and as yet, :macro is not complete replacement for them. i := long after_address is fine and accepted, but there is no way to ask for something as simple as i := long ( after_address + 4 )

Work arounds do exist, such as writing i := long ## ## as:

	# i := long
	0xF0 0x00
				: target_address
				0x00 0x00

Allows you to write into the target address. Actually getting v0 and v1 to be the upper and lower bytes of a 16 bit address you want is much harder.

Obviously, these workarounds are pretty weird and to some extent represent the problems with eg :unpack. I can see two ways to resolve the issues I experience:

One option is to petition to continue support for, and to modify, the existing commands. One approach could be :unpack long after_address. Another could be to interpret :next after_address as :off after_address 1 and allow for more versatile offsets at time of definition, or, allow the use a modifier when a label is invoked, such as i := long after_address+4 (I often define multiple labels just to address inside of a data structure)

A second approach would be to allow for a 2nd type of macro, which works much the same as the current one, but that is post processed, at the same time as labels are known & resolved within the rest of the code. In this case, I believe the developer would have to inform the compiler of how many bytes a macro will be generating. While inconvenient, the byte length of the macro above, long_indirect is obviously fixed, and I believe this to be much less inconvenient & more readable than current workarounds. This is based on my I belief that by padding the rom with the appropriate number of bytes and resolving it once the label positions are known, that the issues I have been faced with would be resolvable. I may be mistaken, however.

Which approach, if either, do you think would be more favourable?

A third approach could be to conceal some of these considerations into things which appear as instructions. We currently have v0 := 26, v0 := vF, and v0 := constant. You could support syntax such as eg v0 := high address_label, which is not wildly distinct from i := long address_label.

Generally, I would posit that (compile time) offset addresses are actually only particularly desirable when writing i := commands. I can't think of any significant reason why you would wish to jump to, or call, an address label with an offset and not be able to write that out with address labels, where as with i := offsets, modifying code and dealing with data structures in particular is simplified. Perhaps there is some modifier syntax that could be employed there, too?

I think that the combination of :unpack long NNNN and :pointer NNNN, with full support for forward-reference, address the need here. Closing. Feel free to reopen if further discussion is necessary.