Index to @loadw: signed or unsigned?

Note: Thanks to Ben Cressey for bringing this topic to my attention.

The Praxis Z-machine tester contains code that looks like this:

@loadw addr (-1) -> val;

What it is attempting to test is clear, but I’m not sure that @loadw is supposed to interpret the index argument as signed. The spec, in §15, explicitly lists some operations as signed, @loadw not among them. It seems to me that these would be the exceptions proving the general rule of unsigned.

Most interpreters I’ve tested (fizmo, rezrov, zoom, nitfol, zeal, xzip, zmpp, and zig) do not pass this test. Frotz passes because, even though it uses unsigned arithmetic in z_loadw(), it stores the result in a 16-bit object, yielding the Praxix-expected value. ZLR and Parchment also pass.

I suspect this isn’t a common construct, since nobody has appeared to complain even though interpreters differ; but I’m wondering if anybody has insight into which is correct? An Infocom story using an index > $7fff would be nice…

I wrote that test, and I don’t have any great argument in favor of it – I’m sure that I tested it in Frotz and then said “Must be right.” (Then I got Parchment to pass; I was writing Parchment signedness patches at the time.)

Okay, thinking about it… I don’t believe that the argument is necessarily signed, but I do believe that the final address is treated as a 16-bit unsigned value. (As you note, this is what Frotz does.)

@loadw with large values is occasionally brought up as a way to reach outside the 64k RAM playground. (That is, you could do “@loadw $FFFE $0001” to read from address $10000.) Given that this can’t work in Frotz, I have no compunction about saying that it’s illegal, and that addresses should be clipped to 16 bits. If that’s the case, the signedness of the @loadw arguments doesn’t matter.

Ok, that seems like a very reasonable approach. It’s more sensible than what I was doing previously (getting a value above 64K and signaling error). Thanks for the input.

I know this is an old discussion, but just to clarify: Where @cas says

Frotz passes because, even though it uses unsigned arithmetic in z_loadw(), it stores the result in a 16-bit object, yielding the Praxix-expected value.

Does that mean that Frotz does unsigned arithmetic to add the word-index parameter to the array parameter, then retrieve the value from resulting address (ignoring any overflow in the addition) to store it in the val parameter?

And where @zarf says

I don’t believe that the argument is necessarily signed, but I do believe that the final address is treated as a 16-bit unsigned value. (As you note, this is what Frotz does.)

Does the phrase “final address” mean the same as “resulting address” in my description above?

If so, then that doesn’t seem to discourage treating it as unsigned. But there is a related issue that zarf points to with

(That is, you could do “@loadw $FFFE $0001” to read from address $10000.) Given that this can’t work in Frotz…

Am I correct in understanding that the issue is that the instruction would have to mean “retrieve the word value composed of the two bytes at $FFFF and $10000,” and the latter is inherently non-meaningful as a 16-bit address?

If yes, couldn’t such an instruction be interpreted as "retrieve the word value composed of the two bytes at $FFFF and $0000" instead?

Frotz does this:

zword addr = zargs[0] + 2 * zargs[1];

where zargs is an array of unsigned 16-bit int. The result on most systems will be at least a 32-bit int due to promotion, and thus potentially larger than 64K. But it’s then stored into a 16-bit unsigned int, keeping only the bottom 16 bits.

In short, it’s just making use of 16-bit unsigned wrapping.

And if you look at modem Frotz, it explicitly masks off the bottom 16 bits on TOPS-20, presumably because there is no 16-bit type there.

I don’t mean to speak for Zarf, but yes, that implies that unsigned arithmetic is fine so long as you ultimately discard bits above 16.

The point here is that you could argue that memory above 16 bits is accessible because you can pass a base address plus index that add up to a value above 64K. But since Frotz reduces the value to 16 bits, that’s basically de facto standard behavior.

There nothing technical preventing access above 16 bits, and the Standard may be a bit murky there, but convention says addresses are reduced this way.

Ah, OK. I didn’t realize that the index would be in word units, too. So @loadw $FFFE $0001 would mean "fetch the word made of bytes at $10000 and $10001" on Z-Machine, but this gets translated to "fetch the word made of bytes at $0000 and $0001" due to the wrapping/clipping. Thank you for clarifying.