Skip to content

Implement preliminary creation of Wasm GC types #2537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 21, 2022
310 changes: 310 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ import {
SURROGATE_HIGH,
SURROGATE_LOW
} from "./util";
import {
Type,
TypeFlags,
TypeKind
} from "./types";
import {
ElementKind,
Field
} from "./program";
import * as binaryen from "./glue/binaryen";

/** A Binaryen-compatible index. */
Expand Down Expand Up @@ -3597,3 +3606,304 @@ export function needsExplicitUnreachable(expr: ExpressionRef): bool {
}
return true;
}

// TypeBuilder

const DEBUG_TYPEBUILDER = false;

/** Ensures that the given potentially complex type has a corresponding GC type. */
export function ensureType(type: Type): TypeRef {
// Obtain basic type if applicable
if (type == Type.void) return TypeRef.None;
var typeRef = tryEnsureBasicType(type);
if (typeRef) return typeRef;

// From here on we are dealing with heap types independent of nullability.
// Nullability is applied again when returning the final type.
var originalType = type;
type = type.nonNullableType;

// Obtain cached type if already built. Guaranteed to be not a temp type.
if (typeRef = type.ref) {
return binaryen._BinaryenTypeFromHeapType(
binaryen._BinaryenTypeGetHeapType(typeRef),
originalType.is(TypeFlags.NULLABLE) // apply nullability
);
}

// Otherwise use a type builder
if (ASC_TARGET) {
// @ts-ignore: Wasm only
assert(sizeof<usize>() == 4); // ABI code below assumes 32-bit pointers
}
binaryen._BinaryenSetTypeSystem(TypeSystem.Nominal);
var builder = binaryen._TypeBuilderCreate(0);
var seen = new Map<Type,HeapTypeRef>();
prepareType(builder, seen, type); // drop temp return
var size = binaryen._TypeBuilderGetSize(builder);
var out = binaryen._malloc(max(4 * size, 8)); // either each heap type or index + reason
if (!binaryen._TypeBuilderBuildAndDispose(builder, out, out, out + 4)) {
let errorIndex = binaryen.__i32_load(out);
let errorReason = binaryen.__i32_load(out + 4);
binaryen._free(out);
throw new Error(`type builder error at index ${errorIndex}: ${TypeBuilderErrorReason.toString(errorReason)}`);
}

// Assign all the built types to their respective non-nullable type
for (let _keys = Map_keys(seen), i = 0, k = _keys.length; i < k; ++i) {
let seenType = _keys[i];
assert(!seenType.is(TypeFlags.NULLABLE)); // non-nullable only
let heapType = <HeapTypeRef>binaryen.__i32_load(out + 4 * i);
let fullType = binaryen._BinaryenTypeFromHeapType(heapType, false);
assert(!seenType.ref);
seenType.ref = fullType;
if (DEBUG_TYPEBUILDER) {
console.log(` set ${seenType.toString()}`);
}
}
binaryen._free(out);

// Initial type should now exist in its non-nullable variant
if (DEBUG_TYPEBUILDER) {
console.log(` finalize ${type.toString()}`);
}
typeRef = assert(type.ref);
return binaryen._BinaryenTypeFromHeapType(
binaryen._BinaryenTypeGetHeapType(typeRef),
originalType.is(TypeFlags.NULLABLE) // apply nullability
);
}

/** Obtains the basic type of the given type, if any. */
function tryEnsureBasicType(type: Type): TypeRef {
switch (type.kind) {
case TypeKind.BOOL:
case TypeKind.I8:
case TypeKind.U8:
case TypeKind.I16:
case TypeKind.U16:
case TypeKind.I32:
case TypeKind.U32: return TypeRef.I32;
case TypeKind.I64:
case TypeKind.U64: return TypeRef.I64;
case TypeKind.ISIZE:
case TypeKind.USIZE: {
if (type.isInternalReference) break; // non-basic
return type.size == 64 ? TypeRef.I64 : TypeRef.I32;
}
case TypeKind.F32: return TypeRef.F32;
case TypeKind.F64: return TypeRef.F64;
case TypeKind.V128: return TypeRef.V128;
case TypeKind.FUNCREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.Func, type.is(TypeFlags.NULLABLE));
}
case TypeKind.EXTERNREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.Ext, type.is(TypeFlags.NULLABLE));
}
case TypeKind.ANYREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.Any, type.is(TypeFlags.NULLABLE));
}
case TypeKind.EQREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.Eq, type.is(TypeFlags.NULLABLE));
}
case TypeKind.I31REF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.I31, type.is(TypeFlags.NULLABLE));
}
case TypeKind.DATAREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.Data, type.is(TypeFlags.NULLABLE));
}
case TypeKind.STRINGREF: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.String, type.is(TypeFlags.NULLABLE));
}
case TypeKind.STRINGVIEW_WTF8: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.StringviewWTF8, type.is(TypeFlags.NULLABLE));
}
case TypeKind.STRINGVIEW_WTF16: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.StringviewWTF16, type.is(TypeFlags.NULLABLE));
}
case TypeKind.STRINGVIEW_ITER: {
return binaryen._BinaryenTypeFromHeapType(HeapTypeRef.StringviewIter, type.is(TypeFlags.NULLABLE));
}
case TypeKind.VOID: assert(false); // invalid here
}
return 0; // non-basic
}

/** Determines the packed GC type of the given type, if applicable. */
function determinePackedType(type: Type): PackedType {
switch (type.kind) {
case TypeKind.BOOL:
case TypeKind.I8:
case TypeKind.U8: return PackedType.I8;
case TypeKind.I16:
case TypeKind.U16: return PackedType.I16;
}
return PackedType.NotPacked;
}

/** Recursively prepares the given GC type, potentially returning a temporary type. */
function prepareType(builder: binaryen.TypeBuilderRef, seen: Map<Type,HeapTypeRef>, type: Type): TypeRef {
// Obtain basic type if applicable
if (type == Type.void) return TypeRef.None;
var typeRef = tryEnsureBasicType(type);
if (typeRef) return typeRef;

assert(!type.is(TypeFlags.NULLABLE)); // operating on non-nullable types only

// Reuse existing type
if (typeRef = type.ref) return typeRef;

// Reuse seen temporary type if it exists
if (seen.has(type)) {
if (DEBUG_TYPEBUILDER) {
console.log(` prepare ${type.toString()} (seen)`);
}
return changetype<HeapTypeRef>(seen.get(type));
}

if (DEBUG_TYPEBUILDER) {
console.log(`prepare ${type.toString()}`);
}

// Otherwise construct a new class type. Note that arrays are not supported, as these would
// have to involve a Wasm-level `array`, either wrapped in `Array` or `Uint8Array` etc., or
// directly representing an `ArrayBuffer` or `StaticArray`. TBD.
var classReference = type.getClass();
if (classReference) {
// Make sure the base type has been built prior, at a lower index
let base = classReference.base;
let baseRef: HeapTypeRef = 0;
if (base) baseRef = prepareType(builder, seen, base.type); // might be temporary, is non-nullable

// Block this index with a temporary type and cache
let index = binaryen._TypeBuilderGetSize(builder);
binaryen._TypeBuilderGrow(builder, 1);
if (DEBUG_TYPEBUILDER) {
console.log(` block [${index}]: ${type.toString()}`);
}
let heapTypeRef = binaryen._TypeBuilderGetTempHeapType(builder, index);
typeRef = binaryen._TypeBuilderGetTempRefType(builder, heapTypeRef, false);
seen.set(type, typeRef);

// Populate the struct type (TODO: names)
let fieldTypes = new Array<TypeRef>();
let packedTypes = new Array<PackedType>();
let fieldMutables = new Array<u32>();
let members = classReference.members;
if (members) {
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
let member = _values[i];
if (member.kind != ElementKind.FIELD) continue;
let field = <Field>member;
let fieldType = field.type;
if (DEBUG_TYPEBUILDER) {
console.log(` field ${fieldType.toString()}`);
}
if (fieldType.is(TypeFlags.NULLABLE)) {
fieldTypes.push(
binaryen._TypeBuilderGetTempRefType(
builder,
binaryen._BinaryenTypeGetHeapType(
prepareType(builder, seen, fieldType.nonNullableType)
),
true
)
);
} else {
fieldTypes.push(prepareType(builder, seen, fieldType));
}
packedTypes.push(determinePackedType(fieldType));
fieldMutables.push(1);
}
}
let cArrFT = allocPtrArray(fieldTypes);
let cArrPT = allocU32Array(packedTypes);
let cArrFM = allocU32Array(fieldMutables);
if (DEBUG_TYPEBUILDER) {
console.log(` concretize [${index}]: ${type.toString()}`);
}
binaryen._TypeBuilderSetStructType(builder, index, cArrFT, cArrPT, cArrFM, fieldTypes.length);
if (base) {
if (DEBUG_TYPEBUILDER) {
console.log(` set super [${index}]: ${type.toString()} <: ${base.type.toString()} ${baseRef == base.type.ref ? " (known)" : ""}`);
}
binaryen._TypeBuilderSetSubType(builder, index, binaryen._BinaryenTypeGetHeapType(baseRef));
}
binaryen._free(cArrFM);
binaryen._free(cArrPT);
binaryen._free(cArrFT);
return typeRef;
}

// Respectively a new signature type
var signatureReference = type.getSignature();
if (signatureReference) {

// Block this index with a temporary type and cache
let index = binaryen._TypeBuilderGetSize(builder);
binaryen._TypeBuilderGrow(builder, 1);
let tempTypeRef = binaryen._TypeBuilderGetTempRefType(
builder,
binaryen._TypeBuilderGetTempHeapType(builder, index),
false
);
seen.set(type, tempTypeRef);

let paramTypes = new Array<TypeRef>();
let resultTypes = new Array<TypeRef>();
let parameterTypes = signatureReference.parameterTypes;
for (let i = 0, k = parameterTypes.length; i < k; ++i) {
let paramType = parameterTypes[i];
if (paramType.is(TypeFlags.NULLABLE)) {
paramTypes.push(
binaryen._TypeBuilderGetTempRefType(
builder,
binaryen._BinaryenTypeGetHeapType(
prepareType(builder, seen, paramType.nonNullableType)
),
true
)
);
} else {
paramTypes.push(prepareType(builder, seen, paramType));
}
}
let returnType = signatureReference.returnType;
resultTypes.push(
returnType == Type.void
? TypeRef.None
: returnType.is(TypeFlags.NULLABLE)
? binaryen._TypeBuilderGetTempRefType(
builder,
binaryen._BinaryenTypeGetHeapType(
prepareType(builder, seen, returnType.nonNullableType)
),
true
)
: prepareType(builder, seen, returnType)
);
let tempParamType: TypeRef;
if (paramTypes.length > 1) {
let cArrPT = allocPtrArray(paramTypes);
tempParamType = binaryen._TypeBuilderGetTempTupleType(builder, cArrPT, paramTypes.length);
binaryen._free(cArrPT);
} else {
tempParamType = paramTypes.length ? paramTypes[0] : TypeRef.None;
}
let tempResultType: TypeRef;
if (resultTypes.length > 1) {
let cArrRT = allocPtrArray(resultTypes);
tempResultType = binaryen._TypeBuilderGetTempTupleType(builder, cArrRT, resultTypes.length);
binaryen._free(cArrRT);
} else {
tempResultType = resultTypes[0];
}
if (DEBUG_TYPEBUILDER) {
console.log(` concretize [${index}]: ${type.toString()}`);
}
binaryen._TypeBuilderSetSignatureType(builder, index, tempParamType, tempResultType);
return tempTypeRef;
}

throw new Error(`unexpected complex type: ${type.toString()}`);
}
9 changes: 7 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
import {
TypeRef,
createType,
HeapTypeRef
HeapTypeRef,
ensureType
} from "./module";

import * as binaryen from "./glue/binaryen";
Expand Down Expand Up @@ -139,6 +140,8 @@ export class Type {
private _nonNullableType: Type | null = null;
/** Respective nullable type, if non-nullable. */
private _nullableType: Type | null = null;
/** Cached Binaryen type reference. */
ref: TypeRef = 0;

/** Constructs a new resolved type. */
constructor(kind: TypeKind, flags: TypeFlags, size: u32) {
Expand Down Expand Up @@ -586,7 +589,6 @@ export class Type {
/** Converts this type to its respective type reference. */
toRef(): TypeRef {
switch (this.kind) {
default: assert(false); // TODO: Concrete struct, array and signature types
case TypeKind.BOOL:
case TypeKind.I8:
case TypeKind.I16:
Expand Down Expand Up @@ -633,6 +635,9 @@ export class Type {
}
case TypeKind.VOID: return TypeRef.None;
}
// TODO: not used yet
assert(false);
return ensureType(this);
}

// Types
Expand Down