Initial support for exact heap types - #7396
Conversation
The length macro used in a type test in type-builder.cpp was causing extremely long compile times in some compilers. Use a lambda instead to fix it. This makes the error messages less useful when a test fails, but under normal circumstances the test should not be failing, so this is a good trade off. Fixes #7383.
The custom descriptors proposal has moved exactness from reference types to defined (but not abstract) heap types. Since we only use a bit in the heap type representation to represent sharedness for abstract heap types, we can conveniently reuse the same bit to represent exactness for heap types. Implement basic support for representing exact heap types and taking them into account in canonicalization. Also ensure that other operations like getting the rec group of a heap type or looking up its structure work properly on exact heap types.
| // Bits 0-2 are used by the Type representation, so need to be left free. | ||
| // Bit 3 determines whether the basic heap type is shared (1) or unshared (0). | ||
| // Bits 0-1 are used by the Type representation, so need to be left free. | ||
| // Bit 2 determines whether basic heap type is shared (1) or unshared (0). |
There was a problem hiding this comment.
| // Bit 2 determines whether basic heap type is shared (1) or unshared (0). | |
| // Bit 2 determines whether a basic heap type is shared (1) or unshared (0). |
| HeapTypeInfo* getHeapTypeInfo(HeapType ht) { | ||
| assert(!ht.isBasic()); | ||
| return (HeapTypeInfo*)ht.getID(); | ||
| return (HeapTypeInfo*)(ht.with(Inexact).getID()); |
There was a problem hiding this comment.
This clears the exact bit in the ID, recovering the value of the pointer to the underlying HeapTypeInfo.
There was a problem hiding this comment.
Then I'm surprised we don't need to clear out the other 2 reserved bits? In what way is bit 2 different than 0-1?
There was a problem hiding this comment.
Bits 0-1 are used in the Type representation and the RecGroup representation, but are always zero for HeapType.
There was a problem hiding this comment.
I see, thanks.
Perhaps we can add a helper for zeroing out the relevant bits? That would be clearer than with(Inexact) and it would also be much easier to update in the future if we add more special bits.
|
It is convenient to reuse the same bit, but still, maybe it makes sense to back out the old exactness? I just imagine that if I run into some exactness-related bug, debugging it will be much harder if the issue could be one of two exactness representations. |
|
Yes, I will definitely back out the exactness on reference types, but I wanted to wait until we had a chance to validate that there are no huge issues with putting the exactness on the heap types instead. |
|
The problem with the Emscripten tests is that the 8-byte alignment provided by Emscripten's allocator means that the addresses of allocated HeapTypeInfos might use bit 3 and conflict with our use of it to represent exactness. To fix this problem I could either increase the alignment of all |
We decided that Custom Descriptors should introduce exact heap types rather than exact reference types. Although these new features are very similar, the APIs we need to change for them are completely different. One option would have been to keep the existing exact reference type implementation while additionally implementing exact heap types, but there are not enough free bits in the type implementation to have both at once without increasing the alignment of HeapTypeInfo allocations. Portably increasing the alignment is annoying enough that it's easier to just eagerly remove exact reference types to free up the bit for use with exact heap types. Fully or partially revert the following PRs: - #7371 - #7365 - #7360 - #7357 - #7356 - #7355 - #7354 - #7353 - #7347 - #7342 - #7328 Keep the new `.with(...)` Type APIs and the relevant parts of the type relations gtest that were introduced as part of the reverted work.
Now that we aren't supporting exact reference types, we no longer need to leave bit 2 free for use by the Type representation. Shift the basic HeapType representations down to start at bit 2 instead of bit 3.
| } | ||
|
|
||
| constexpr TypeID getID() const { return id; } | ||
| constexpr TypeID getRawID() const { |
There was a problem hiding this comment.
Please add a comment explaining the difference.
And, in particular - when would getID() still be used?
There was a problem hiding this comment.
For example when hashing a type or in the C API to convert the type to an integer.
| ht = ht.with(Inexact); | ||
| if (auto it = canonicalized.find(ht); it != canonicalized.end()) { | ||
| *type = Type(it->second, type->getNullability()); | ||
| *type = Type(it->second.with(exact), type->getNullability()); |
There was a problem hiding this comment.
But if exactness is part of the heap type, why do we want this behavior?
There was a problem hiding this comment.
This is replacing non-canonical heap types with canonical heap types. If $t' is the canonical version of $t, we need to update uses of (exact $t) to be (exact $t') instead. The exact type will not appear in the canonicalized map because we are canonicalizing type definitions represented by the inexact defined types. (exact $t) and $t refer to the same type definition, so it would be redundant to store the exact version in the map.
There was a problem hiding this comment.
I see, thanks. Why does Shared not need to be handled similarly here? Because for non-basic types we store it on HeapTypeInfo?
There was a problem hiding this comment.
Right. Sharedness is actually part of the type definition, unlike exactness.
| HeapTypeInfo* getHeapTypeInfo(HeapType ht) { | ||
| assert(!ht.isBasic()); | ||
| return (HeapTypeInfo*)ht.getID(); | ||
| return (HeapTypeInfo*)(ht.getRawID()); |
There was a problem hiding this comment.
I don't feel strongly but getRawID could maybe be getPointer.
There was a problem hiding this comment.
What do you think about getDefinitionID or getBaseID?
There was a problem hiding this comment.
Hmm, no real preference between those. Current name is fine.
| ht = ht.with(Inexact); | ||
| if (auto it = canonicalized.find(ht); it != canonicalized.end()) { | ||
| *type = Type(it->second, type->getNullability()); | ||
| *type = Type(it->second.with(exact), type->getNullability()); |
There was a problem hiding this comment.
I see, thanks. Why does Shared not need to be handled similarly here? Because for non-basic types we store it on HeapTypeInfo?
Revert the following PRs that introduced exactness on HeapType: - #7446 - #7444 - #7432 - #7412 - #7396 Keep only the changes to wasm-type-printing.cpp to fix the assertions that subclasses of TypeNameGeneratorBase correctly override getNames. Although putting exactness on heap types makes the most sense in the Custom Descriptors spec, it is not a great fit for how HeapType is used in Binaryen. In almost all cases, HeapType is used to represent heap type definitions rather than the dynamic types of values. Letting HeapType represent exact heap types is not useful in those cases and opens up a new class of possible bugs. To avoid these bugs, we had been introducing a new HeapTypeDef type to represent heap type definitions, but nearly all uses of HeapType would have had to have been replaced with HeapTypeDef. Rather than introduce HeapTypeDef as a third type alongside Type and HeapType, it will be simpler to continue using HeapType to represent heap type definitions and Type to represent the dynamic types of values, including their exactness. While exactness will syntactically be part of the heap type, it will be part of the `Type` in Binaryen IR. A follow-on PR will restore the original work on exact references, and PRs following that one will update the encoding, parsing, and printing of exact references to match the current spec.
Revert the following PRs that introduced exactness on HeapType: - #7446 - #7444 - #7432 - #7412 - #7396 Keep only the changes to wasm-type-printing.cpp to fix the assertions that subclasses of TypeNameGeneratorBase correctly override getNames. Although putting exactness on heap types makes the most sense in the Custom Descriptors spec, it is not a great fit for how HeapType is used in Binaryen. In almost all cases, HeapType is used to represent heap type definitions rather than the dynamic types of values. Letting HeapType represent exact heap types is not useful in those cases and opens up a new class of possible bugs. To avoid these bugs, we had been introducing a new HeapTypeDef type to represent heap type definitions, but nearly all uses of HeapType would have had to have been replaced with HeapTypeDef. Rather than introduce HeapTypeDef as a third type alongside Type and HeapType, it will be simpler to continue using HeapType to represent heap type definitions and Type to represent the dynamic types of values, including their exactness. While exactness will syntactically be part of the heap type, it will be part of the `Type` in Binaryen IR. A follow-on PR will restore the original work on exact references, and PRs following that one will update the encoding, parsing, and printing of exact references to match the current spec.
The custom descriptors proposal has moved exactness from reference types
to defined (but not abstract) heap types. Since we only use a bit in the
heap type representation to represent sharedness for abstract heap
types, we can conveniently reuse the same bit to represent exactness for
heap types.
Implement basic support for representing exact heap types and taking
them into account in canonicalization. Also ensure that other operations
like getting the rec group of a heap type or looking up its structure
work properly on exact heap types.