Skip to content

Commit 0b54d74

Browse files
authored
[NFC] Encode reference types with bit packing (WebAssembly#7142)
Value types were previously represented internally as either enum values for "basic," i.e. non-reference, non-tuple types or pointers to `TypeInfo` structs encoding either references or tuples. Update the representation of reference types to use one bit to encode nullability and the rest of the bits to encode the referenced heap type. This allows canonical reference types to be created with a single logical or rather than by taking a lock on a global type store and doing a hash map lookup to canonicalize. This change is a massive performance improvement and dramatically improves how performance scales with threads because the removed lock was highly contended. Even with a single core, the performance of an O3 optimization pipeline on a WasmGC module improves by 6%. With 8 cores, the improvement increases to 29% and with all 128 threads on my machine, the improvement reaches 46%. The full new encoding of types is as follows: - If the type ID is within the range of the basic types, the type is the corresponding basic type. - Otherwise, if bit 0 is set, the type is a tuple and the rest of the bits are a canonical pointer to the tuple. - Otherwise, the type is a reference type. Bit 1 determines the nullability and the rest of the bits encode the heap type. Also update the encodings of basic heap types so they no longer use the low two bits to avoid conflicts with the use of those bits in the encoding of types.
1 parent 2f6f42c commit 0b54d74

File tree

4 files changed

+150
-454
lines changed

4 files changed

+150
-454
lines changed

src/wasm-type.h

Lines changed: 61 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,27 @@ class HeapType {
9696
uintptr_t id;
9797

9898
public:
99-
// Bit zero indicates whether the type is `shared`, so we need to leave it
100-
// free.
99+
// Bits 0 and 1 are used by the Type representation, so need to be left free.
100+
// Bit 2 determines whether the basic heap type is shared (1) or unshared (0).
101101
enum BasicHeapType : uint32_t {
102-
ext = 0 << 1,
103-
func = 1 << 1,
104-
cont = 2 << 1,
105-
any = 3 << 1,
106-
eq = 4 << 1,
107-
i31 = 5 << 1,
108-
struct_ = 6 << 1,
109-
array = 7 << 1,
110-
exn = 8 << 1,
111-
string = 9 << 1,
112-
none = 10 << 1,
113-
noext = 11 << 1,
114-
nofunc = 12 << 1,
115-
nocont = 13 << 1,
116-
noexn = 14 << 1,
102+
ext = 1 << 3,
103+
func = 2 << 3,
104+
cont = 3 << 3,
105+
any = 4 << 3,
106+
eq = 5 << 3,
107+
i31 = 6 << 3,
108+
struct_ = 7 << 3,
109+
array = 8 << 3,
110+
exn = 9 << 3,
111+
string = 10 << 3,
112+
none = 11 << 3,
113+
noext = 12 << 3,
114+
nofunc = 13 << 3,
115+
nocont = 14 << 3,
116+
noexn = 15 << 3,
117117
};
118-
static constexpr BasicHeapType _last_basic_type = BasicHeapType(noexn + 1);
118+
static constexpr BasicHeapType _last_basic_type =
119+
BasicHeapType(noexn + (1 << 2));
119120

120121
// BasicHeapType can be implicitly upgraded to HeapType
121122
constexpr HeapType(BasicHeapType id) : id(id) {}
@@ -213,7 +214,7 @@ class HeapType {
213214
// Get the shared or unshared version of this basic heap type.
214215
constexpr BasicHeapType getBasic(Shareability share) const {
215216
assert(isBasic());
216-
return BasicHeapType(share == Shared ? (id | 1) : (id & ~1));
217+
return BasicHeapType(share == Shared ? (id | 4) : (id & ~4));
217218
}
218219

219220
// (In)equality must be defined for both HeapType and BasicHeapType because it
@@ -261,63 +262,29 @@ class HeapType {
261262
std::string toString() const;
262263
};
263264

264-
// Internal only.
265-
struct TypeInfo {
266-
using type_t = Type;
267-
// Used in assertions to ensure that temporary types don't leak into the
268-
// global store.
269-
bool isTemp = false;
270-
enum Kind {
271-
TupleKind,
272-
RefKind,
273-
} kind;
274-
struct Ref {
275-
HeapType heapType;
276-
Nullability nullability;
277-
};
278-
union {
279-
Tuple tuple;
280-
Ref ref;
281-
};
282-
283-
TypeInfo(const Tuple& tuple);
284-
TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {}
285-
TypeInfo(HeapType heapType, Nullability nullable)
286-
: kind(RefKind), ref{heapType, nullable} {}
287-
TypeInfo(const TypeInfo& other);
288-
~TypeInfo();
289-
290-
constexpr bool isTuple() const { return kind == TupleKind; }
291-
constexpr bool isRef() const { return kind == RefKind; }
292-
293-
// If this TypeInfo represents a Type that can be represented more simply,
294-
// return that simpler Type. For example, this handles eliminating singleton
295-
// tuple types.
296-
std::optional<Type> getCanonical() const;
297-
298-
bool operator==(const TypeInfo& other) const;
299-
bool operator!=(const TypeInfo& other) const { return !(*this == other); }
300-
};
301-
302265
class Type {
303266
// The `id` uniquely represents each type, so type equality is just a
304-
// comparison of the ids. For basic types the `id` is just the `BasicType`
305-
// enum value below, and for constructed types the `id` is the address of the
306-
// canonical representation of the type, making lookups cheap for all types.
267+
// comparison of the ids. The basic types are packed at the bottom of the
268+
// expressible range, and after that tuple types are distinguished by having
269+
// bit 0 set. When that bit is masked off, they are pointers to the underlying
270+
// vectors of types. Otherwise, the type is a reference type, and is
271+
// represented as a heap type with bit 1 set iff the reference type is
272+
// nullable.
273+
//
307274
// Since `Type` is really just a single integer, it should be passed by value.
308275
// This is a uintptr_t rather than a TypeID (uint64_t) to save memory on
309276
// 32-bit platforms.
310277
uintptr_t id;
311278

312279
public:
313280
enum BasicType : uint32_t {
314-
none,
315-
unreachable,
316-
i32,
317-
i64,
318-
f32,
319-
f64,
320-
v128,
281+
none = 0,
282+
unreachable = 1,
283+
i32 = 2,
284+
i64 = 3,
285+
f32 = 4,
286+
f64 = 5,
287+
v128 = 6,
321288
};
322289
static constexpr BasicType _last_basic_type = v128;
323290

@@ -338,7 +305,8 @@ class Type {
338305

339306
// Construct from a heap type description. Also covers construction from
340307
// Signature, Struct or Array via implicit conversion to HeapType.
341-
Type(HeapType, Nullability nullable);
308+
Type(HeapType heapType, Nullability nullable)
309+
: Type(heapType.getID() | (nullable == Nullable ? 2 : 0)) {}
342310

343311
// Predicates
344312
// Compound Concrete
@@ -376,74 +344,37 @@ class Type {
376344
// Tuples, refs, etc. are quickly handled using isBasic(), leaving the non-
377345
// basic case for the underlying implementation.
378346

379-
bool isTuple() const {
380-
if (isBasic()) {
381-
return false;
382-
} else {
383-
return getTypeInfo(*this)->isTuple();
384-
}
385-
}
386-
387-
bool isRef() const {
388-
if (isBasic()) {
389-
return false;
390-
} else {
391-
return getTypeInfo(*this)->isRef();
392-
}
393-
}
394-
395-
bool isFunction() const {
396-
if (isBasic()) {
397-
return false;
398-
} else {
399-
auto* info = getTypeInfo(*this);
400-
return info->isRef() && info->ref.heapType.isFunction();
401-
}
402-
}
403-
404-
bool isData() const {
405-
if (isBasic()) {
406-
return false;
407-
} else {
408-
auto* info = getTypeInfo(*this);
409-
return info->isRef() && info->ref.heapType.isData();
410-
}
411-
}
412-
413-
// Checks whether a type is a reference and is nullable. This returns false
414-
// for a value that is not a reference, that is, for which nullability is
415-
// irrelevant.
416-
bool isNullable() const {
417-
if (isRef()) {
418-
return getTypeInfo(*this)->ref.nullability == Nullable;
419-
} else {
420-
return false;
421-
}
347+
// TODO: Experiment with leaving bit 0 free in basic types.
348+
bool isTuple() const { return !isBasic() && (id & 1); }
349+
const Tuple& getTuple() const {
350+
assert(isTuple());
351+
return *(Tuple*)(id & ~1);
422352
}
423353

424-
// Checks whether a type is a reference and is non-nullable. This returns
425-
// false for a value that is not a reference, that is, for which nullability
426-
// is irrelevant. (For that reason, this is only the negation of isNullable()
427-
// on references, but both return false on non-references.)
428-
bool isNonNullable() const {
429-
if (isRef()) {
430-
return getTypeInfo(*this)->ref.nullability == NonNullable;
431-
} else {
432-
return false;
433-
}
354+
bool isRef() const { return !isBasic() && !(id & 1); }
355+
bool isNullable() const { return isRef() && (id & 2); }
356+
bool isNonNullable() const { return isRef() && !(id & 2); }
357+
HeapType getHeapType() const {
358+
assert(isRef());
359+
return HeapType(id & ~2);
434360
}
435361

362+
bool isFunction() const { return isRef() && getHeapType().isFunction(); }
436363
bool isSignature() const { return isRef() && getHeapType().isSignature(); }
364+
bool isData() const { return isRef() && getHeapType().isData(); }
437365

438366
// Whether this type is only inhabited by null values.
439-
bool isNull() const;
440-
bool isStruct() const;
441-
bool isArray() const;
442-
bool isExn() const;
443-
bool isString() const;
367+
bool isNull() const { return isRef() && getHeapType().isBottom(); }
368+
bool isStruct() const { return isRef() && getHeapType().isStruct(); }
369+
bool isArray() const { return isRef() && getHeapType().isArray(); }
370+
bool isExn() const { return isRef() && getHeapType().isExn(); }
371+
bool isString() const { return isRef() && getHeapType().isString(); }
444372
bool isDefaultable() const;
445373

446-
Nullability getNullability() const;
374+
// TODO: Allow this only for reference types.
375+
Nullability getNullability() const {
376+
return isNullable() ? Nullable : NonNullable;
377+
}
447378

448379
private:
449380
template<bool (Type::*pred)() const> bool hasPredicate() {
@@ -489,20 +420,6 @@ class Type {
489420
// Returns the feature set required to use this type.
490421
FeatureSet getFeatures() const;
491422

492-
// Returns the tuple, assuming that this is a tuple type. Note that it is
493-
// normally simpler to use operator[] and size() on the Type directly.
494-
HeapType getHeapType() const {
495-
assert(isRef());
496-
return getTypeInfo(*this)->ref.heapType;
497-
}
498-
499-
// Gets the heap type corresponding to this type, assuming that it is a
500-
// reference type.
501-
const Tuple& getTuple() const {
502-
assert(isTuple());
503-
return getTypeInfo(*this)->tuple;
504-
}
505-
506423
// Returns a number type based on its size in bytes and whether it is a float
507424
// type.
508425
static Type get(unsigned byteSize, bool float_);
@@ -565,7 +482,9 @@ class Type {
565482

566483
std::string toString() const;
567484

568-
size_t size() const;
485+
size_t size() const {
486+
return isTuple() ? getTuple().size() : size_t(id != Type::none);
487+
}
569488

570489
struct Iterator : ParentIndexIterator<const Type*, Iterator> {
571490
using value_type = Type;
@@ -583,15 +502,8 @@ class Type {
583502
return std::make_reverse_iterator(begin());
584503
}
585504
const Type& operator[](size_t i) const { return *Iterator{{this, i}}; }
586-
587-
static TypeInfo* getTypeInfo(Type type) {
588-
assert(!type.isBasic());
589-
return (TypeInfo*)type.getID();
590-
}
591505
};
592506

593-
inline bool Type::isNull() const { return isRef() && getHeapType().isBottom(); }
594-
595507
namespace HeapTypes {
596508

597509
constexpr HeapType ext = HeapType::ext;

0 commit comments

Comments
 (0)