Gc: Should packed loads have sign-extending variants?

Created on 3 Dec 2018  路  8Comments  路  Source: WebAssembly/gc

I have some questions to understand the packed types. Are the follow points right?

  • This value types can only occur on arrays and structs.
  • i8 and i16 can NOT occur on function parameters, function locals or return parameters
  • i8 and i16 have a negative type value like i32. For example -0x05 and -0x06. They must not be declared in the type section.
  • Are the values signed or unsigned if I access struct.get/array.get? How will it extends to a local of i32?
  • Are there traps on struct.set/array.set if the value is to large? Or will the high part cut?

Most helpful comment

  • This value types can only occur on arrays and structs.
  • i8 and i16 can NOT occur on function parameters, function locals or return parameters

Yes, as you can see from the grammar, they can only appear as <storagetype>. They are not value types.

  • i8 and i16 have a negative type value like i32. For example -0x05 and -0x06. They must not be declared in the type section.

The proposal doesn't fix opcodes yet, but yes, that would be the idea.

They cannot be declared in the type section, as the grammar specifies.

  • Are the values signed or unsigned if I access struct.get/array.get? How will it extends to a local of i32?

Good question. This definitely still needs sorting out. As sketched, the MVP assumes that a choice is fixed regarding sign extension and that you use e.g. explicit masking or sign extension instruction if the other semantics is needed. The (older) overview on the other hand mentions explicit load_packed instructions which include a choice of sign extension semantics.

  • Are there traps on struct.set/array.set if the value is to large? Or will the high part cut?

They would be truncating, just like store instructions.

All 8 comments

  • This value types can only occur on arrays and structs.
  • i8 and i16 can NOT occur on function parameters, function locals or return parameters

Yes, as you can see from the grammar, they can only appear as <storagetype>. They are not value types.

  • i8 and i16 have a negative type value like i32. For example -0x05 and -0x06. They must not be declared in the type section.

The proposal doesn't fix opcodes yet, but yes, that would be the idea.

They cannot be declared in the type section, as the grammar specifies.

  • Are the values signed or unsigned if I access struct.get/array.get? How will it extends to a local of i32?

Good question. This definitely still needs sorting out. As sketched, the MVP assumes that a choice is fixed regarding sign extension and that you use e.g. explicit masking or sign extension instruction if the other semantics is needed. The (older) overview on the other hand mentions explicit load_packed instructions which include a choice of sign extension semantics.

  • Are there traps on struct.set/array.set if the value is to large? Or will the high part cut?

They would be truncating, just like store instructions.

Thoughts on the signedness of packed types:

  • For wasm instructions that access memory, there are different opcodes for signed and unsigned loads by necessity, since memory is essentially untyped. That allows the same memory location to be interpreted in different ways and allows a code generator to combine sign extension with the load from memory, since these are available as a single instruction on most ISAs.
  • However, for packed storage types, encoding the signedness into the opcode is a bit awkward, since signedness only applies to packed types, and not to reftypes or non-packed types. We could maybe support this with a flags field, but that is also kind of kludgy. (Maybe we need flags for future evolution with atomicity annotations; unclear).
  • That leaves putting the signedness into the packed type itself, implying we'd have i8 and u8 and i16 and u16 packed types. That removes the need for putting this information into the opcode, but means that, e.g. i8 values cannot also be loaded as u8. I think this is fine, since these rare cases can be handled with manual sign/zero-extension. We'd also have to decide if i8 is structurally compatible with u8 from the type system perspective.
  • Another option is just to have either signed or unsigned. I would vote against this, since the unsupported one will be slightly less efficient due to the need for manual extensions (unless the code generator does smarter instruction selection, covering the extension too), and it would be weird to be less efficient than using memory for this narrow case. But if we decided to go with just one signedness, I'd prefer unsigned, rather than signed, since this seems more convenient for bitmasking.

From my point of view (Java to WebAssembly compiler) I would prefer different types for signed and unsigned data. Then I can simple match the Java data type "byte" to i8 and the Java data type "char" to u16.

The same is valid for every other programming language.

If we want to be consistent with the rest of Wasm then we should probably have instructions with an _s/u suffix selecting sign extension semantics for packed loads. Whether these are separate instructions (like suggested in the Overview) or flags on the existing instruction is mainly a binary encoding detail. I agree that using a flag field might come in handy for atomics later.

Adjusted MVP.md to add a sign extension suffix.

This seems to already have been decided, but here is another vote towards not encoding signedness in the type. LLVM had this, but decided to drop it as it made the type system more complex than needed: http://nondot.org/sabre/LLVMNotes/TypeSystemChanges.txt

In fact, I would go even further and say that perhaps packed loads should probably avoid sign extension as well. If there is a sign extension instruction (I don't know whether it already exists), it should be trivial for an implementor to fuse an unsigned load and sign extension into a single sign-extending load on supported platforms. Separating these two may lead to slightly increased code size, but probably simplifies the implementation while leaving room for optimizations.

It seems the original clarification was addressed.The current proposal does not encode the sign into the type, but instead provides load/store instructions with sign extension to the supported wasm value types (i32 or i64).

The remaining question is whether these instructions should sign-extend at all, or always zero-extend. The threads proposal was faced with a similar choice, and we opted to only include the zero-extend operations, and provide separate sign-extension instructions. Those have since been standardized. So it seems reasonable to follow suit here and provide only zero-extending packed loads, and require the use of i32.extend8_s etc. if sign-extension is desired.

I don't feel strongly, but it seems like we would be inconsistent with memory instructions if we didn't provide both options. IIRC, the original argument for those was that we want to provide as separate instructions what is already available as separate instructions on the hardware.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

binji picture binji  路  16Comments

tlively picture tlively  路  8Comments

titzer picture titzer  路  8Comments

PoignardAzur picture PoignardAzur  路  15Comments

RossTate picture RossTate  路  7Comments