diff --git a/src/engine/Instance.v3 b/src/engine/Instance.v3 index 3040c3a90..7b57adad4 100644 --- a/src/engine/Instance.v3 +++ b/src/engine/Instance.v3 @@ -101,23 +101,12 @@ class Instance(module: Module, imports: Array) { } Const(val) => return val; I31(val) => { - var v = evalInitExpr(val); - return Value.I31(u31.view(Values.unbox_i(v))); - } - Array(t, len, elem) => { - var vlen = Values.unbox_i(evalInitExpr(len)); - var vvals = Array.new(vlen); // TODO: check for OOM - var velem = evalInitExpr(elem); - for (i < vvals.length) vvals[i] = velem; - return Value.Ref(HeapArray.new(t.array, vvals)); - } - FixedArray(t, vals) => { - var vvals = Arrays.map(vals, evalInitExpr); - return Value.Ref(HeapArray.new(t.array, vvals)); + return Value.I31(u31.view(Values.unbox_i(evalInitExpr(val)))); } + Array(t, len, elem), + FixedArray(t, vals), Struct(t, vals) => { - var vvals = Arrays.map(vals, evalInitExpr); - return Value.Ref(HeapStruct.new(t.sdecl, vvals)); + return Value.Ref(evalInitExprObject(init)); } ArrayNewData(t, data_index, offset, len) => { var voffset = evalInitExpr(offset); @@ -137,6 +126,100 @@ class Instance(module: Module, imports: Array) { I64_MUL(a, b) => return Value.I64(Values.unbox_w(evalInitExpr(a)) * Values.unbox_w(evalInitExpr(b))); } } + // most init exprs are pure and can be evaluated repeatedly, but object ones are not pure + def initExprCacheObject = HashMap.new(fun (ie: InitExpr) => int.!(ie.tag), InitExpr.==); + def evalInitExprObject(init: InitExpr) -> Object { + if (initExprCacheObject.has(init)) return initExprCacheObject[init]; + match (init) { + FuncRefNull, + RefNull(ht), + ExternRefNull => return null; + Global(global_index, g) => { + return Values.unbox(globals[global_index].get()); + } + FuncRef(func_index, f) => { + return functions[func_index]; + } + Const(val) => return Values.unbox(val); + I31(val) => { + return ObjectI31.new(u31.view(Values.unbox_i(evalInitExpr(val)))); + } + Array(t, len, elem) => { + var vlen = Values.unbox_i(evalInitExpr(len)); + var decl = t.array; + var st = decl.elem_types[0]; + var velem = evalInitExpr(elem); + var obj: Object; + if (ObjReps.arrayMode == ArrayMode.Original) { + var vvals = Array.new(vlen); + for (i < vvals.length) vvals[i] = velem; + obj = HeapArrayValue.new(t.array, vvals); + } else { + obj = ObjectRuntime.evalInitExprObjectArray(t, st, velem, vlen); + } + initExprCacheObject[init] = obj; + return obj; + } + FixedArray(t, vals) => { + var decl = t.array; + var st = decl.elem_types[0]; + var obj: Object; + if (ObjReps.arrayMode == ArrayMode.Original) { + obj = HeapArrayValue.new(t.array, Arrays.map(vals, evalInitExpr)); + } else { + obj = ObjectRuntime.evalInitExprObjectFixedArray(this, t, st, vals); + } + initExprCacheObject[init] = obj; + return obj; + } + Struct(t, vals) => { + var vvals = Arrays.map(vals, evalInitExpr); + var obj: Object; + if (ObjReps.structMode == StructMode.Original) { + obj = HeapStructValue.new(t.sdecl, vvals); + } else { + obj = ObjectRuntime.evalInitExprObjectStruct(t, vvals); + } + initExprCacheObject[init] = obj; + return obj; + } + ArrayNewData(t, data_index, offset, len) => { + var voffset = evalInitExpr(offset); + var vlen = evalInitExpr(len); + return null; // TODO + } + ArrayNewElem(t, elem_index, offset, len) => { + var voffset = evalInitExpr(offset); + var vlen = evalInitExpr(len); + return null; // TODO + } + _ => return null; + } + } + def evalInitExprScalar(init: InitExpr) -> T { + match (init) { + I32(val) => return T.!(u32.view(val)); + I64(val) => return T.!(u64.view(val)); + F32(val) => return T.!(val); + F64(val) => return T.!(val); + V128(low, high) => return T.!((low, high)); + Global(global_index, g) => { + return Values.unbox(globals[global_index].get()); + } + Const(val) => return Values.unbox(val); + I31(val) => { + var v = evalInitExpr(val); + return T.!(u31.view(Values.unbox_i(v))); + } + I32_ADD(a, b) => return T.!(Values.unbox_u(evalInitExpr(a)) + Values.unbox_u(evalInitExpr(b))); + I32_SUB(a, b) => return T.!(Values.unbox_u(evalInitExpr(a)) - Values.unbox_u(evalInitExpr(b))); + I32_MUL(a, b) => return T.!(Values.unbox_u(evalInitExpr(a)) * Values.unbox_u(evalInitExpr(b))); + I64_ADD(a, b) => return T.!(Values.unbox_w(evalInitExpr(a)) + Values.unbox_w(evalInitExpr(b))); + I64_SUB(a, b) => return T.!(Values.unbox_w(evalInitExpr(a)) - Values.unbox_w(evalInitExpr(b))); + I64_MUL(a, b) => return T.!(Values.unbox_w(evalInitExpr(a)) * Values.unbox_w(evalInitExpr(b))); + _ => return T.!(0); + } + } def findExportOfType(matcher: GlobMatcher) -> T { var decls = module.exports; for (i < decls.length) { diff --git a/src/engine/ObjectReps.v3 b/src/engine/ObjectReps.v3 new file mode 100644 index 000000000..645a57fb8 --- /dev/null +++ b/src/engine/ObjectReps.v3 @@ -0,0 +1,882 @@ +// Alternate, possibly faster, representations for Wasm heap objects + +enum ArrayMode(code: int) { + Original(0), // Default value: HeapArrayValue only + Typed(1), // HeapArrayX where X can be one of the storage types +} + +enum StructMode(code: int) { + Original(0), // Default value: Values only + Pair(1) // Having Array (holdings the refs) and Array (holding prims) +} + +def arrayModeFor(code: int) -> ArrayMode { + def modes = [ArrayMode.Original, ArrayMode.Typed]; + return if(code >= 0 && code < modes.length, modes[code], ArrayMode.Original); +} + +def structModeFor(code: int) -> StructMode { + def modes = [StructMode.Original, StructMode.Pair]; + return if(code >= 0 && code < modes.length, modes[code], StructMode.Original); +} + +component ObjReps { + def arrayModeCode = 0; + def arrayMode = arrayModeFor(arrayModeCode); + + def structModeCode = 1; + def structMode = structModeFor(structModeCode); + + // other constants related to representations + def REF_SHIFT = -1i8; // indicates an object slot for StructMode.Pair + def REF_U64_SHIFT = -2i8; // indicates a REF_U64 for StructMode.Pair + + def packFieldOffsets(refs_offset: i32, bytes_offset: i32) -> i32 { + return (refs_offset << 16) | bytes_offset; + } + def unpackFieldOffsets(offset: i32) -> (i32, i32) { + return (offset >>> 16, offset & 0xFFFF); + } +} + +// Paired array representation: an Array holds all the reference fields, +// and an Array holds all the primitive values +class HeapStructPair extends HeapStructGeneric { + def objs: Array; + def bytes: Array; + new(decl: StructDecl, objs, bytes) super(decl) { } + + private def getHelperV(offset: i32, len: int, fetch: Range -> T, wrap: T -> Value) -> Value { + return wrap(fetch(bytes[offset ..+ len])); + } + def getFieldValue(index: u31) -> Value { + var decl = StructDecl.!(this.decl); + var offset = decl.field_offsets[index]; + match (decl.field_shifts[index]) { + ObjReps.REF_SHIFT => return if(HeapType.Cont.?(ValueType.Ref.!(decl.field_types[index].valtype).heap), + Value.Cont(Continuations.fromObject(objs[offset])), + Value.Ref(Object.!(objs[offset]))); + ObjReps.REF_U64_SHIFT => return Value.Cont(getFieldCont(index)); + 0 => return getHelperV(offset, 1, DataReaders.read_range_u32_u8, Values.box_u); + 1 => return getHelperV(offset, 2, DataReaders.read_range_u32_u16, Values.box_u); + 2 => return getHelperV(offset, 4, DataReaders.read_range_u32, + if(decl.field_types[index].valtype == ValueType.I32, Values.box_u, Values.box_fu32)); + 3 => return getHelperV(offset, 8, DataReaders.read_range_u64, + if(decl.field_types[index].valtype == ValueType.I64, Values.box_w, Values.box_du64)); + 4 => return getHelperV<(u64, u64)>(offset, 16, DataReaders.read_range_u128, Values.box_s); + } + return Value.I32(u32.view(-1)); + } + private def getHelperV2(index: u31, len: int, fetch: Range -> T, widen: T -> U, wrap: U -> Value) -> Value { + var decl = StructDecl.!(this.decl); + return wrap(widen(fetch(bytes[decl.field_offsets[index] ..+ len]))); + } + def getFieldSignExtend8Value(index: u31) -> Value { + return getHelperV2(index, 1, DataReaders.read_range_i8, i32.!, Values.box_i); + } + def getFieldSignExtend16Value(index: u31) -> Value { + return getHelperV2(index, 2, DataReaders.read_range_i16, i32.!, Values.box_i); + } + def getFieldZeroExtend8Value(index: u31) -> Value { + return getHelperV2(index, 1, DataReaders.read_range_u8, u32.!, Values.box_u); + } + def getFieldZeroExtend16Value(index: u31) -> Value { + return getHelperV2(index, 2, DataReaders.read_range_u16, u32.!, Values.box_u); + } + private def getHelper(index: u32, len: int, fetch: Range -> T) -> T { + var decl = StructDecl.!(this.decl); + var offset = decl.field_offsets[index]; + return fetch(bytes[offset ..+ len]); + } + def getFieldI32(index: u32) -> i32 { + return getHelper(index, 4, DataReaders.read_range_i32); + } + def getFieldI64(index: u32) -> i64 { + return getHelper(index, 8, DataReaders.read_range_i64); + } + def getFieldI8(index: u32) -> i8 { + return getHelper(index, 1, DataReaders.read_range_i8); + } + def getFieldI16(index: u32) -> i16 { + return getHelper(index, 2, DataReaders.read_range_i16); + } + def getFieldF32(index: u32) -> float { + return getHelper(index, 4, DataReaders.read_range_float); + } + def getFieldF64(index: u32) -> double { + return getHelper(index, 8, DataReaders.read_range_double); + } + def getFieldRef(index: u32) -> Object { + var decl = StructDecl.!(this.decl); + return Object.!(objs[decl.field_offsets[index]]); + } + def getFieldCont(index: u32) -> Continuation { // use this only for REF_U64 Continuations + var decl = StructDecl.!(this.decl); + var offsets = ObjReps.unpackFieldOffsets(decl.field_offsets[index]); + var stack = WasmStack.!(objs[offsets.0]); + var version = DataReaders.read_range_u64(bytes[offsets.1 ..+ 8]); + return Continuations.continuationWithVersion(stack, version); + } + def getFieldV128(index: u32) -> (u64, u64) { + return getHelper<(u64, u64)>(index, 16, DataReaders.read_range_u128); + } + private def setHelperV(index: u32, len: int, val: Value, unbox: Value -> T, store: (Range, T) -> void) { + var decl = StructDecl.!(this.decl); + var offset = decl.field_offsets[index]; + return store(bytes[offset ..+ len], unbox(val)); + } + def setFieldValue(index: u31, val: Value) { + var decl = StructDecl.!(this.decl); + match (decl.field_shifts[index]) { + ObjReps.REF_SHIFT => objs[decl.field_offsets[index]] = if(Value.Cont.?(val), Continuations.getObject(Value.Cont.!(val).val), Value.Ref.!(val).val); + ObjReps.REF_U64_SHIFT => setFieldCont(index, Value.Cont.!(val).val); + 0 => setHelperV(index, 1, val, Values.unbox_u8, DataWriters.write_range_u8); + 1 => setHelperV(index, 2, val, Values.unbox_u16, DataWriters.write_range_u16); + 2 => setHelperV(index, 4, val, if(Value.I32.?(val), Values.unbox_u, Values.unbox_fu32), DataWriters.write_range_u32); + 3 => setHelperV(index, 8, val, if(Value.I64.?(val), Values.unbox_w, Values.unbox_du64), DataWriters.write_range_u64); + 4 => setHelperV<(u64, u64)>(index, 16, val, Values.unbox_s, DataWriters.write_range_u128); + } + } + private def setHelper(index: u32, len: int, val: T, store: (Range, T) -> void) { + var decl = StructDecl.!(this.decl); + var offset = decl.field_offsets[index]; + return store(bytes[offset ..+ len], val); + } + def setFieldI32(index: u32, val: i32) { + setHelper(index, 4, val, DataWriters.write_range_i32); + } + def setFieldI64(index: u32, val: i64) { + setHelper(index, 8, val, DataWriters.write_range_i64); + } + def setFieldI8(index: u32, val: i8) { + setHelper(index, 1, val, DataWriters.write_range_i8); + } + def setFieldI16(index: u32, val: i16) { + setHelper(index, 2, val, DataWriters.write_range_i16); + } + def setFieldF32(index: u32, val: float) { + setHelper(index, 4, val, DataWriters.write_range_float); + } + def setFieldF64(index: u32, val: double) { + setHelper(index, 8, val, DataWriters.write_range_double); + } + def setFieldRef(index: u32, val: Object) { + var decl = StructDecl.!(this.decl); + objs[decl.field_offsets[index]] = val; + } + def setFieldCont(index: u32, val: Continuation) { // use this only for REF_U64 Continuations + var decl = StructDecl.!(this.decl); + var offsets = ObjReps.unpackFieldOffsets(decl.field_offsets[index]); + objs[offsets.0] = Continuations.getStoredStack(val); + DataWriters.write_range_u64(bytes[offsets.1 ..+ 8], Continuations.getStoredVersion(val)); + } + def setFieldV128(index: u32, low: u64, high: u64) { + setHelper<(u64, u64)>(index, 16, (low, high), DataWriters.write_range_u128); + } +} + +// Implementations of arrays of specific types +class HeapArrayI32 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.I32(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u32.view(Value.I32.!(val).val); + } + def getI32(index: u32) -> i32 { + return i32.view(vals[index]); + } + def setI32(index: u32, val: i32) { + vals[index] = u32.view(val); + } +} + +class HeapArrayI31 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.I31(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u31.view(Value.I31.!(val).val); + } + def getI31(index: u32) -> i31 { + return i31.view(vals[index]); + } + def setI31(index: u32, val: i31) { + vals[index] = u31.view(val); + } +} + +class HeapArrayI64 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.I64(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u64.view(Value.I64.!(val).val); + } + def getI64(index: u32) -> i64 { + return i64.view(vals[index]); + } + def setI64(index: u32, val: i64) { + vals[index] = u64.view(val); + } +} + +class HeapArrayI8 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.I32(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u8.view(Value.I32.!(val).val); + } + def getI8(index: u32) -> i8 { + return i8.view(vals[index]); + } + def setI8(index: u32, val: i8) { + vals[index] = u8.view(val); + } +} + +class HeapArrayI16 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.I32(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u16.view(Value.I32.!(val).val); + } + def getI16(index: u32) -> i16 { + return i16.view(vals[index]); + } + def setI16(index: u32, val: i16) { + vals[index] = u16.view(val); + } +} + +class HeapArrayF32 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.F32(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u32.view(Value.F32.!(val).bits); + } + def getF32(index: u32) -> float { + return if(index >= 0 && index < vals.length, float.view(vals[index]), -1); + } + def setF32(index: u32, val: float) { + vals[index] = u32.view(val); + } +} + +class HeapArrayF64 extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.F64(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = u64.view(Value.F64.!(val).bits); + } + def getF64(index: u32) -> double { + return double.view(vals[index]); + } + def setF64(index: u32, val: double) { + vals[index] = u64.view(val); + } +} + +class HeapArrayRef extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + match (vals[index]) { + x: ObjectI31 => return Value.I31(x.val); + x: Object => return Value.Ref(x); + _ => return Values.REF_NULL; + } + } + def setValue(index: u32, val: Value) { + var obj: Object; + match (val) { + Ref(v) => obj = v; + I31(v) => obj = ObjectI31.new(v); + _ => ; + } + vals[index] = obj; + } + def getRef(index: u32) -> Object { + return vals[index]; + } + def setRef(index: u32, val: Object) { + vals[index] = val; + } +} + +class HeapArrayCont extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getValue(index: u32) -> Value { + return Value.Cont(vals[index]); + } + def setValue(index: u32, val: Value) { + vals[index] = Value.Cont.!(val).val; + } + def getCont(index: u32) -> Continuation { + return vals[index]; + } + def setCont(index: u32, val: Continuation) { + vals[index] = val; + } +} + +// A v128 uses two adjacent u64 entries in the vals array +class HeapArrayV128 extends HeapArrayGeneric { + def vals: Array; // The array format: [low 0, high 0, low 1, high 1, ...] + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length >> 1; } + + def getValue(index: u32) -> Value { + var idx = index << 1; + return Value.V128(vals[idx], vals[idx+1]); + } + def setValue(index: u32, val: Value) { + var idx = index << 1; + var v128 = Value.V128.!(val); + vals[idx] = u64.view(v128.low); + vals[idx+1] = u64.view(v128.high); + } + def getV128(index: u32) -> (u64, u64) { + var idx = index << 1; + return (vals[idx], vals[idx+1]); + } + def setV128(index: u32, low: u64, high: u64) { + var idx = index << 1; + vals[idx] = low; + vals[idx+1] = high; + } +} + +component ObjectRuntime { + def STRUCT_NEW(stack: ExecStack, decl: StructDecl) -> HeapStructGeneric { + var bytes = if(decl.num_bytes == 0, null, Array.new(decl.num_bytes)); + var objs = if(decl.num_refs == 0, null, Array.new(decl.num_refs)); + var obj: HeapStructGeneric = HeapStructPair.new(decl, objs, bytes); + for (i = decl.field_types.length - 1; i >= 0; i--) { + var index = u31.!(i); + match (decl.field_shifts[i]) { + 0 => obj.setFieldI8(index, i8.view(stack.popi())); + 1 => obj.setFieldI16(index, i16.view(stack.popi())); + 2 => { + if (decl.field_types[i].valtype == ValueType.I32) { + obj.setFieldI32(index, stack.popi()); + } else { + obj.setFieldF32(index, stack.popf()); + } + } + 3 => { + if (decl.field_types[i].valtype == ValueType.I64) { + obj.setFieldI64(index, stack.popl()); + } else { + obj.setFieldF64(index, stack.popd()); + } + } + 4 => { + var pair = stack.pops(); + obj.setFieldV128(index, pair.0, pair.1); + } + ObjReps.REF_SHIFT => obj.setFieldRef(index, stack.popObject()); + ObjReps.REF_U64_SHIFT => obj.setFieldCont(index, stack.popContinuation()); + } + } + return obj; + } + def STRUCT_NEW_DEFAULT(decl: StructDecl) -> HeapStructGeneric { + var bytes = if(decl.num_bytes == 0, null, Array.new(decl.num_bytes)); + var objs = if(decl.num_refs == 0, null, Array.new(decl.num_refs)); + var obj: HeapStructGeneric = HeapStructPair.new(decl, objs, bytes); + for (i < decl.field_types.length) { + var index = u31.view(i); + match (decl.field_shifts[i]) { + 0 => obj.setFieldI8(index, 0); + 1 => obj.setFieldI16(index, 0); + 2 => { + if (decl.field_types[i].valtype == ValueType.I32) { + obj.setFieldI32(index, 0); + } else { + obj.setFieldF32(index, 0.0f); + } + } + 3 => { + if (decl.field_types[i].valtype == ValueType.I64) { + obj.setFieldI64(index, 0); + } else { + obj.setFieldF64(index, 0.0d); + } + } + 4 => obj.setFieldV128(index, 0, 0); + ObjReps.REF_SHIFT => obj.setFieldRef(index, null); + ObjReps.REF_U64_SHIFT => obj.setFieldCont(index, Continuations.NULL); + } + } + return obj; + } + private def arrayNewHelper(stack: ExecStack, decl: ArrayDecl, elem: T, length: u32, make: (ArrayDecl, Array) -> HeapArrayGeneric) { + var vals = Array.new(u31.!(length)); + for (i < vals.length) vals[i] = elem; + stack.push(Value.Ref(make(decl, vals))); + } + def ARRAY_NEW(stack: ExecStack, decl: ArrayDecl, length: u32) -> Throwable { + match (decl.elem_types[0].pack) { + UNPACKED => { + match (decl.elem_types[0].valtype) { + I32 => arrayNewHelper(stack, decl, stack.popu(), length, HeapArrayI32.new); + I64 => arrayNewHelper(stack, decl, stack.popw(), length, HeapArrayI64.new); + F32 => arrayNewHelper(stack, decl, u32.view(stack.popf()), length, HeapArrayF32.new); + F64 => arrayNewHelper(stack, decl, u64.view(stack.popd()), length, HeapArrayF64.new); + Ref(nullable, heaptype) => { + if (heaptype == HeapType.I31 && !nullable) { + var elem = stack.popV(ValueType.Ref(false, HeapType.I31)); + var val = ObjectI31.!(Value.Ref.!(elem).val).val; + arrayNewHelper(stack, decl, val, length, HeapArrayI31.new); + } else if (HeapType.Cont.?(heaptype)) { + var val = stack.popContinuation(); + arrayNewHelper(stack, decl, val, length, HeapArrayCont.new); + } else { + arrayNewHelper(stack, decl, stack.popObject(), length, HeapArrayRef.new); + } + } + V128 => { + var vals = Array.new(u31.!(length << 1)); + var v128 = stack.pops(); + for (i = 0; i < vals.length; i += 2) { + vals[i ] = v128.0; + vals[i+1] = v128.1; + } + stack.push(Value.Ref(HeapArrayV128.new(decl, vals))); + } + _ => arrayNewHelper(stack, decl, stack.popV(decl.elem_types[0].valtype), length, HeapArrayValue.new); + } + } + PACKED_I8 => arrayNewHelper(stack, decl, u8.view(stack.popu()), length, HeapArrayI8.new); + PACKED_I16 => arrayNewHelper(stack, decl, u16.view(stack.popu()), length, HeapArrayI16.new); + } + return null; + } + private def arrayNewDefaultHelper(stack: ExecStack, decl: ArrayDecl, length: u32, make: (ArrayDecl, Array) -> HeapArrayGeneric) { + var vals = Array.new(u31.!(length)); + stack.push(Value.Ref(make(decl, vals))); + } + def ARRAY_NEW_DEFAULT(stack: ExecStack, decl: ArrayDecl, length: u32) -> Throwable { + match (decl.elem_types[0].pack) { + UNPACKED => { + match (decl.elem_types[0].valtype) { + I32 => arrayNewDefaultHelper(stack, decl, length, HeapArrayI32.new); + I64 => arrayNewDefaultHelper(stack, decl, length, HeapArrayI64.new); + F32 => arrayNewDefaultHelper(stack, decl, length, HeapArrayF32.new); + F64 => arrayNewDefaultHelper(stack, decl, length, HeapArrayF64.new); + Ref(nullable, heaptype) => { + if (heaptype == HeapType.I31 && ! nullable) arrayNewDefaultHelper(stack, decl, length, HeapArrayI31.new); + else if (HeapType.Cont.?(heaptype)) arrayNewDefaultHelper(stack, decl, length, HeapArrayCont.new); + else arrayNewDefaultHelper(stack, decl, length, HeapArrayRef.new); + } + V128 => stack.push(Value.Ref(HeapArrayV128.new(decl, Array.new(u31.!(length << 1))))); + _ => { + var vals = Array.new(u31.!(length)); + var elem = Values.default(decl.elem_types[0].valtype); + for (i < vals.length) vals[i] = elem; + stack.push(Value.Ref(HeapArrayValue.new(decl, vals))); + } + } + } + PACKED_I8 => arrayNewDefaultHelper(stack, decl, length, HeapArrayI8.new); + PACKED_I16 => arrayNewDefaultHelper(stack, decl, length, HeapArrayI16.new); + } + return null; + } + private def arrayNewFixedHelper(stack: ExecStack, decl: ArrayDecl, length: u32, + popper: void -> T, make: (ArrayDecl, Array) -> HeapArrayGeneric) { + var vals = Array.new(u31.!(length)); + for (i = vals.length - 1; i >= 0; i--) vals[i] = popper(); + stack.push(Value.Ref(make(decl, vals))); + } + def ARRAY_NEW_FIXED(stack: ExecStack, decl: ArrayDecl, length: u32) -> Throwable { + match (decl.elem_types[0].pack) { + UNPACKED => { + match (decl.elem_types[0].valtype) { + I32 => arrayNewFixedHelper(stack, decl, length, stack.popu, HeapArrayI32.new); + I64 => arrayNewFixedHelper(stack, decl, length, stack.popw, HeapArrayI64.new); + F32 => arrayNewFixedHelper(stack, decl, length, fun () -> u32 { return u32.view(stack.popf()); }, HeapArrayF32.new); + F64 => arrayNewFixedHelper(stack, decl, length, fun () -> u64 { return u64.view(stack.popd()); }, HeapArrayF64.new); + Ref(nullable, heaptype) => { + if (heaptype == HeapType.I31 && !nullable) + arrayNewFixedHelper(stack, decl, length, fun () -> u31 { return ObjectI31.!(stack.popObject()).val; }, HeapArrayI31.new); + else if (HeapType.Cont.?(heaptype)) + arrayNewFixedHelper(stack, decl, length, stack.popContinuation, HeapArrayCont.new); + else + arrayNewFixedHelper(stack, decl, length, stack.popObject, HeapArrayRef.new); + } + V128 => { + var vals = Array.new(u31.!(length << 1)); + for (i = vals.length - 2; i >= 0; i -= 2) { + var v128 = stack.pops(); + vals[i ] = v128.0; + vals[i+1] = v128.1; + } + stack.push(Value.Ref(HeapArrayV128.new(decl, vals))); + } + _ => { + var t = decl.elem_types[0].valtype; + arrayNewFixedHelper(stack, decl, length, fun () -> Value { return stack.popV(t); }, HeapArrayValue.new); + } + } + } + PACKED_I8 => arrayNewFixedHelper(stack, decl, length, fun () -> u8 { return u8.view(stack.popu()); }, HeapArrayI8.new); + PACKED_I16 => arrayNewFixedHelper(stack, decl, length, fun () -> u16 { return u16.view(stack.popu()); }, HeapArrayI16.new); + } + return null; + } + private def arrayNewDataHelper(stack: ExecStack, instance: Instance, rtt: ArrayDecl, offset: u32, length: u32, data_index: u31, + read: DataReader -> T, make: (ArrayDecl, Array) -> HeapArrayGeneric) -> Throwable { + var r = Runtime.getData(instance, data_index, offset, length, read); + if (!r.0) return stack.trap(TrapReason.MEMORY_OOB); + stack.push(Value.Ref(make(rtt, r.1))); + return null; + } + def ARRAY_NEW_DATA(stack: ExecStack, instance: Instance, rtt: ArrayDecl, ddecl: DataDecl, offset: u32, length: u32, data_index: u31) -> Throwable { + match (rtt.elem_types[0].pack) { + UNPACKED => { + match (rtt.elem_types[0].valtype) { + I32 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, DataReader.read_u32, HeapArrayI32.new); + I64 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, DataReader.read_u64, HeapArrayI64.new); + F32 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, DataReader.read_u32, HeapArrayF32.new); + F64 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, DataReader.read_u64, HeapArrayF64.new); + V128 => return arrayNewDataHelper(stack, instance, rtt, offset, length << 1, data_index, DataReader.read_u64, HeapArrayV128.new); + _ => return stack.trap(TrapReason.ERROR); + } + } + PACKED_I8 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, DataReader.read1, HeapArrayI8.new); + PACKED_I16 => return arrayNewDataHelper(stack, instance, rtt, offset, length, data_index, + fun (d: DataReader) => DataReaders.read_range_u16(d.acquire(2)), HeapArrayI16.new); + } + } + // Note: handles only Ref, not I31 (limitation) and not continuations (should not happen (?)) + def ARRAY_NEW_ELEM(stack: ExecStack, instance: Instance, rtt: ArrayDecl, edecl: ElemDecl, offset: u32, length: u32) -> Throwable { + match (rtt.elem_types[0].pack) { + UNPACKED => { + match (rtt.elem_types[0].valtype) { + Ref(nullable, heaptype) => { + var r = Runtime.getElems(instance, edecl, offset, length); + if (!r.0) return stack.trap(TrapReason.TABLE_OOB); + stack.push(Value.Ref(HeapArrayRef.new(rtt, r.1))); + } + _ => return stack.trap(TrapReason.ERROR); + } + } + _ => return stack.trap(TrapReason.ERROR); + } + return null; + } + def rangeFill(r: Range, val: T) { for (i < r.length) r[i] = val; } + def ARRAY_FILL(obj: HeapArrayGeneric, rtt: ArrayDecl, index: int, size: u32, val: Value) -> Throwable { + match (obj) { + x: HeapArrayValue => rangeFill(x.vals[index ..+ size], val); + x: HeapArrayI32 => rangeFill(x.vals[index ..+ size], Values.unbox_u(val)); + x: HeapArrayI31 => rangeFill(x.vals[index ..+ size], Value.I31.!(val).val); + x: HeapArrayI64 => rangeFill(x.vals[index ..+ size], Values.unbox_w(val)); + x: HeapArrayI8 => rangeFill(x.vals[index ..+ size], Values.unbox_u8(val)); + x: HeapArrayI16 => rangeFill(x.vals[index ..+ size], Values.unbox_u16(val)); + x: HeapArrayF32 => rangeFill(x.vals[index ..+ size], Values.unbox_fu32(val)); + x: HeapArrayF64 => rangeFill(x.vals[index ..+ size], Values.unbox_du64(val)); + x: HeapArrayRef => rangeFill(x.vals[index ..+ size], Value.Ref.!(val).val); + x: HeapArrayCont => rangeFill(x.vals[index ..+ size], Value.Cont.!(val).val); + x: HeapArrayV128 => { + var r = x.vals[index ..+ (size << 1)]; + var v128 = Value.V128.!(val); + for (i = 0; i < r.length; i += 2) { + r[i ] = v128.low; + r[i+1] = v128.high; + } + } + } + return null; + } + private def arrayCopyHelper(stack: ExecStack, elems: A -> Array, + dst: HeapArrayGeneric, src: A, dst_offset: u32, src_offset: u32, size: u32) -> Throwable { + if (A.?(dst)) { + var arr = A.!(dst); + var r = ArrayUtil.safeCopy(elems(arr), dst_offset, elems(src), src_offset, size); + if (!r) return stack.trap(TrapReason.ARRAY_OOB); + return null; + } + return stack.trap(TrapReason.ERROR); + } + def ARRAY_COPY(stack: ExecStack, dst: HeapArrayGeneric, src: HeapArrayGeneric, dst_offset: u32, src_offset: u32, size: u32) -> Throwable { + match (src) { + x: HeapArrayI32 => return arrayCopyHelper(stack, HeapArrayI32.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayI31 => return arrayCopyHelper(stack, HeapArrayI31.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayI64 => return arrayCopyHelper(stack, HeapArrayI64.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayI8 => return arrayCopyHelper(stack, HeapArrayI8.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayI16 => return arrayCopyHelper(stack, HeapArrayI16.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayF32 => return arrayCopyHelper(stack, HeapArrayF32.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayF64 => return arrayCopyHelper(stack, HeapArrayF64.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayRef => return arrayCopyHelper(stack, HeapArrayRef.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayCont => return arrayCopyHelper(stack, HeapArrayCont.vals, dst, x, dst_offset, src_offset, size); + x: HeapArrayV128 => return arrayCopyHelper(stack, HeapArrayV128.vals, dst, x, dst_offset, src_offset, size << 1); + } + return stack.trap(TrapReason.ERROR); + } + private def arrayInitDataHelper(stack: ExecStack, obj: HeapArrayGeneric, getBytes: (Array, u32, u32) -> (bool, Array), elems: A -> Array, + data: Array, dst_offset: u32, src_offset: u32, size: u32) -> Throwable { + var t = getBytes(data, src_offset, size); + if (!t.0) return stack.trap(TrapReason.MEMORY_OOB); + match (obj) { + x: A => { + ArrayUtil.safeCopy(elems(x), dst_offset, t.1, 0, size); + return null; + } + } + return stack.trap(TrapReason.ERROR); + } + def ARRAY_INIT_DATA(stack: ExecStack, obj: HeapArrayGeneric, rtt: ArrayDecl, data: Array, dst_offset: u32, src_offset: u32, size: u32) -> Throwable { + match (rtt.elem_types[0].pack) { + UNPACKED => { + match (rtt.elem_types[0].valtype) { + I32 => return arrayInitDataHelper(stack, obj, bytesToI32s, HeapArrayI32.vals, data, dst_offset, src_offset, size); + I64 => return arrayInitDataHelper(stack, obj, bytesToI64s, HeapArrayI64.vals, data, dst_offset, src_offset, size); + F32 => return arrayInitDataHelper(stack, obj, bytesToI32s, HeapArrayF32.vals, data, dst_offset, src_offset, size); + F64 => return arrayInitDataHelper(stack, obj, bytesToI64s, HeapArrayF64.vals, data, dst_offset, src_offset, size); + V128 => return arrayInitDataHelper(stack, obj, bytesToI64s, HeapArrayV128.vals, data, dst_offset, src_offset, size << 1); + _ => { } + } + } + PACKED_I8 => return arrayInitDataHelper(stack, obj, bytesToI8s, HeapArrayI8.vals, data, dst_offset, src_offset, size); + PACKED_I16 => return arrayInitDataHelper(stack, obj, bytesToI16s, HeapArrayI16.vals, data, dst_offset, src_offset, size); + } + return stack.trap(TrapReason.ERROR); + } + def ARRAY_INIT_ELEM(stack: ExecStack, obj: HeapArrayGeneric, instance: Instance, edecl: ElemDecl, dst_offset: u32, src_offset: u32, size: u32) -> Throwable { + match (obj) { + x: HeapArrayI31 => { + var r = Runtime.getElems(instance, edecl, src_offset, size); + if (!r.0) return stack.trap(TrapReason.ARRAY_OOB); + if (ArrayUtil.boundsCheck(x.vals, dst_offset, size) < 0) return stack.trap(TrapReason.ARRAY_OOB); + for (i < r.1.length) { + x.vals[int.!(dst_offset)+i] = ObjectI31.!(r.1[i]).val; + } + } + x: HeapArrayRef => { + var r = Runtime.getElems(instance, edecl, src_offset, size); + if (!r.0) return stack.trap(TrapReason.ARRAY_OOB); + if (ArrayUtil.boundsCheck(x.vals, dst_offset, size) < 0) return stack.trap(TrapReason.ARRAY_OOB); + for (i < r.1.length) { + x.vals[int.!(dst_offset)+i] = r.1[i]; + } + } + _ => { return stack.trap(TrapReason.ARRAY_OOB); } // on the belief that continuations cannot be Elements + } + return null; + } + def arrayFromVal(decl: ArrayDecl, val: Value, len: int, f: Value -> T, + build: (ArrayDecl, Array) -> HeapArrayGeneric) -> HeapArrayGeneric { + var elem: T = f(val); + var vvals = Array.new(len); + for (i < len) vvals[i] = elem; + return build(decl, vvals); + } + def evalInitExprObjectArray(t: HeapType.Array, st: StorageType, velem: Value, vlen: int) -> HeapArrayGeneric { + var decl = t.array; + match (st.pack) { + UNPACKED => { + match (st.valtype) { + I32 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u32 { return Value.I32.!(v).val; }, HeapArrayI32.new); + I64 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u64 { return Value.I64.!(v).val; }, HeapArrayI64.new); + F32 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u32 { return Value.F32.!(v).bits; }, HeapArrayF32.new); + F64 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u64 { return Value.F64.!(v).bits; }, HeapArrayF64.new); + Ref(nullable, heaptype) => { + if (heaptype == HeapType.I31 && !nullable) { + return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u31 { return Value.I31.!(v).val; }, HeapArrayI31.new); + } else { + return arrayFromVal(decl, velem, vlen, fun (v: Value) -> Object { return Value.Ref.!(v).val; }, HeapArrayRef.new); + } + } + V128 => { + // does not fit the arrayFromVal pattern + var v128 = Value.V128.!(velem); + var vvals = Array.new(vlen << 1); + for (i = 0; i < vvals.length; i += 2) { + vvals[i ] = v128.low; + vvals[i+1] = v128.high; + } + return HeapArrayV128.new(t.array, vvals); + } + _ => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> Value { return v; }, HeapArrayValue.new); + } + } + PACKED_I8 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u8 { return u8.view(Value.I32.!(v).val); }, HeapArrayI8.new); + PACKED_I16 => return arrayFromVal(decl, velem, vlen, fun (v: Value) -> u16 { return u16.view(Value.I32.!(v).val); }, HeapArrayI16.new); + } + } + def arrayFromVals(instance: Instance, decl: ArrayDecl, vals: Array, + f: Value -> T, build: (ArrayDecl, Array) -> HeapArrayGeneric) -> HeapArrayGeneric { + var len = vals.length; + var vvals = Array.new(len); + for (i < len) { + var velem = instance.evalInitExpr(vals[i]); + vvals[i] = f(velem); + } + return build(decl, vvals); + } + def evalInitExprObjectFixedArray(instance: Instance, t: HeapType.Array, st: StorageType, vals: Array) -> HeapArrayGeneric { + var vlen = vals.length; + var decl = t.array; + match (st.pack) { + UNPACKED => { + match (st.valtype) { + I32 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u32 { return Value.I32.!(v).val; }, HeapArrayI32.new); + I64 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u64 { return Value.I64.!(v).val; }, HeapArrayI64.new); + F32 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u32 { return Value.F32.!(v).bits; }, HeapArrayF32.new); + F64 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u64 { return Value.F64.!(v).bits; }, HeapArrayF64.new); + Ref(nullable, heaptype) => { + if (heaptype == HeapType.I31 && !nullable) { + return arrayFromVals(instance, decl, vals, fun (v: Value) -> u31 { return Value.I31.!(v).val; }, HeapArrayI31.new); + } else { + return arrayFromVals(instance, decl, vals, fun (v: Value) -> Object { return Value.Ref.!(v).val; }, HeapArrayRef.new); + } + } + V128 => { + // does not fit the arrayFromVals pattern + var vvals = Array.new(vlen << 1); + for (i = 0; i < vlen; i += 2) { + var velem = instance.evalInitExpr(vals[i]); + var v128 = Value.V128.!(velem); + vvals[i ] = v128.low; + vvals[i+1] = v128.high; + } + return HeapArrayV128.new(t.array, vvals); + } + _ => { + var vvals = Arrays.map(vals, instance.evalInitExpr); + return HeapArrayValue.new(t.array, vvals); + } + } + } + PACKED_I8 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u8 { return u8.view(Value.I32.!(v).val); }, HeapArrayI8.new); + PACKED_I16 => return arrayFromVals(instance, decl, vals, fun (v: Value) -> u16 { return u16.view(Value.I32.!(v).val); }, HeapArrayI16.new); + } + } + def evalInitExprObjectStruct(t: HeapType.Struct, vvals: Array) -> Object { + var decl = t.sdecl; + var bytes = if(decl.num_bytes == 0, null, Array.new(decl.num_bytes)); + var objs = if(decl.num_refs == 0, null, Array.new(decl.num_refs)); + var obj = HeapStructPair.new(decl, objs, bytes); + for (i < decl.field_types.length) { + obj.setFieldValue(u31.view(i), vvals[i]); + } + return obj; + } +} + +def bytesToBytes(data: Array, offset: u32, length: u32) -> (bool, Array) { + var start = ArrayUtil.boundsCheck(data, offset, length); + if (start < 0) return (false, null); + var limit = length + offset; + var bytes = DataReader.new(data).reset(data, start, int.!(limit)).readN(int.!(length)); + return (true, bytes); +} +def bytesToI8s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var start = ArrayUtil.boundsCheck(data, offset, length); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = length + offset; + var d = ExtendedDataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = d.read_u8(); + return (d.ok, vals); +} +def bytesToI8sAsI32s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var start = ArrayUtil.boundsCheck(data, offset, length); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = length + offset; + var d = ExtendedDataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = u32.view(d.read_u8()); + return (d.ok, vals); +} +def bytesToI16s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var nbytes = length << 1; + var start = ArrayUtil.boundsCheck(data, offset, nbytes); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = offset + nbytes; + var d = ExtendedDataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = d.read_u16(); + return (d.ok, vals); +} +def bytesToI16sAsI32s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var nbytes = length << 1; + var start = ArrayUtil.boundsCheck(data, offset, nbytes); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = offset + nbytes; + var d = ExtendedDataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = u32.view(d.read_u16()); + return (d.ok, vals); +} +def bytesToI32s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var nbytes = length << 2; + var start = ArrayUtil.boundsCheck(data, offset, nbytes); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = offset + nbytes; + var d = DataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = d.read_u32(); + return (d.ok, vals); +} +def bytesToI64s(data: Array, offset: u32, length: u32) -> (bool, Array) { + var nbytes = length << 3; + var start = ArrayUtil.boundsCheck(data, offset, nbytes); + if (start < 0) return (false, null); + var vals = Array.new(int.!(length)); + var limit = offset + nbytes; + var d = DataReader.new(data).reset(data, start, int.!(limit)); + for (i < vals.length) vals[i] = d.read_u64(); + return (d.ok, vals); +} + diff --git a/src/engine/Runtime.v3 b/src/engine/Runtime.v3 index b9323c76b..3f78efc4f 100644 --- a/src/engine/Runtime.v3 +++ b/src/engine/Runtime.v3 @@ -37,38 +37,62 @@ component Runtime { // --- GC operations ----------------------------------------------------------------------------------------- def STRUCT_NEW(stack: ExecStack, instance: Instance, struct_index: u31) { var decl = StructDecl.!(instance.heaptypes[struct_index]); - var fields = Array.new(decl.field_types.length); - for (i = fields.length - 1; i >= 0; i--) { - fields[i] = stack.popV(decl.field_types[i].valtype); + var obj: HeapStructGeneric; + if (ObjReps.structMode == StructMode.Original) { + var fields = Array.new(decl.field_types.length); + for (i = fields.length - 1; i >= 0; i--) { + fields[i] = stack.popV(decl.field_types[i].valtype); + } + obj = HeapStructValue.new(decl, fields); + } else { + obj = ObjectRuntime.STRUCT_NEW(stack, decl); } - stack.push(Value.Ref(HeapStruct.new(decl, fields))); + stack.push(Value.Ref(obj)); } def STRUCT_NEW_DEFAULT(stack: ExecStack, instance: Instance, struct_index: u31) { var decl = StructDecl.!(instance.heaptypes[struct_index]); - var fields = Array.new(decl.field_types.length); - for (i < fields.length) { - fields[i] = Values.default(decl.field_types[i].valtype); + var obj: HeapStructGeneric; + if (ObjReps.structMode == StructMode.Original) { + var fields = Array.new(decl.field_types.length); + for (i < fields.length) { + fields[i] = Values.default(decl.field_types[i].valtype); + } + obj = HeapStructValue.new(decl, fields); + } else { + obj = ObjectRuntime.STRUCT_NEW_DEFAULT(decl); } - stack.push(Value.Ref(HeapStruct.new(decl, fields))); + stack.push(Value.Ref(obj)); } def STRUCT_GET(stack: ExecStack, instance: Instance, struct_index: u31, field_index: u31) -> Throwable { var obj = stack.popStruct(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - stack.push(obj.vals[field_index]); + stack.push(obj.getFieldValue(field_index)); return null; } def STRUCT_GET_S(stack: ExecStack, instance: Instance, struct_index: u31, field_index: u31) -> Throwable { var obj = stack.popStruct(); var decl = StructDecl.!(instance.heaptypes[struct_index]); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - stack.push(V3Eval.signExtend(decl.field_types[field_index], obj.vals[field_index])); // XXX: pushi + var val: Value; + match (decl.field_types[field_index].pack) { + PACKED_I8 => val = obj.getFieldSignExtend8Value(field_index); + PACKED_I16 => val = obj.getFieldSignExtend16Value(field_index); + UNPACKED => return stack.trap(TrapReason.ERROR); + } + stack.push(val); // XXX: pushi return null; } def STRUCT_GET_U(stack: ExecStack, instance: Instance, struct_index: u31, field_index: u31) -> Throwable { var obj = stack.popStruct(); var decl = StructDecl.!(instance.heaptypes[struct_index]); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - stack.push(V3Eval.zeroExtend(decl.field_types[field_index], obj.vals[field_index])); // XXX: pushi + var val: Value; + match (decl.field_types[field_index].pack) { + PACKED_I8 => val = obj.getFieldZeroExtend8Value(field_index); + PACKED_I16 => val = obj.getFieldZeroExtend16Value(field_index); + UNPACKED => return stack.trap(TrapReason.ERROR); + } + stack.push(val); // XXX: pushi return null; } def STRUCT_SET(stack: ExecStack, instance: Instance, struct_index: u31, field_index: u31) -> Throwable { @@ -76,36 +100,48 @@ component Runtime { var val = stack.popV(decl.field_types[field_index].valtype); var obj = stack.popStruct(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - obj.vals[field_index] = val; + obj.setFieldValue(field_index, val); return null; } def ARRAY_NEW(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { var decl = ArrayDecl.!(instance.heaptypes[array_index]); var length = stack.popu(); - var elem = stack.popV(decl.elem_types[0].valtype); if (length > Execute.limits.max_array_length) return stack.trap(TrapReason.OOM); - var vals = Array.new(u31.!(length)); - for (i < vals.length) vals[i] = elem; - stack.push(Value.Ref(HeapArray.new(decl, vals))); + if (ObjReps.arrayMode == ArrayMode.Original) { + var vals = Array.new(u31.!(length)); + var elem = stack.popV(decl.elem_types[0].valtype); + for (i < vals.length) vals[i] = elem; + stack.push(Value.Ref(HeapArrayValue.new(decl, vals))); + } else { + return ObjectRuntime.ARRAY_NEW(stack, decl, length); + } return null; } def ARRAY_NEW_DEFAULT(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { var decl = ArrayDecl.!(instance.heaptypes[array_index]); var length = stack.popu(); if (length > Execute.limits.max_array_length) return stack.trap(TrapReason.OOM); - var vals = Array.new(u31.!(length)); - var elem = Values.default(decl.elem_types[0].valtype); - for (i < vals.length) vals[i] = elem; - stack.push(Value.Ref(HeapArray.new(decl, vals))); + if (ObjReps.arrayMode == ArrayMode.Original) { + var vals = Array.new(u31.!(length)); + var elem = Values.default(decl.elem_types[0].valtype); + for (i < vals.length) vals[i] = elem; + stack.push(Value.Ref(HeapArrayValue.new(decl, vals))); + } else { + return ObjectRuntime.ARRAY_NEW_DEFAULT(stack, decl, length); + } return null; } def ARRAY_NEW_FIXED(stack: ExecStack, instance: Instance, array_index: u31, length: u32) -> Throwable { var decl = ArrayDecl.!(instance.heaptypes[array_index]); if (length > Execute.limits.max_array_length) return stack.trap(TrapReason.OOM); - var vals = Array.new(u31.!(length)); - var t = decl.elem_types[0].valtype; - for (i = vals.length - 1; i >= 0; i--) vals[i] = stack.popV(t); - stack.push(Value.Ref(HeapArray.new(decl, vals))); + if (ObjReps.arrayMode == ArrayMode.Original) { + var vals = Array.new(u31.!(length)); + var t = decl.elem_types[0].valtype; + for (i = vals.length - 1; i >= 0; i--) vals[i] = stack.popV(t); + stack.push(Value.Ref(HeapArrayValue.new(decl, vals))); + } else { + return ObjectRuntime.ARRAY_NEW_FIXED(stack, decl, length); + } return null; } def ARRAY_NEW_DATA(stack: ExecStack, instance: Instance, array_index: u31, data_index: u31) -> Throwable { @@ -115,30 +151,42 @@ component Runtime { if (length > Execute.limits.max_array_length) return stack.trap(TrapReason.OOM); var rtt = ArrayDecl.!(instance.heaptypes[array_index]); var ddecl = instance.module.data[data_index]; - var t = bytesToVals(rtt.elem_types[0], ddecl.data, offset, length); - if (!t.0) return stack.trap(TrapReason.MEMORY_OOB); - stack.push(Value.Ref(HeapArray.new(rtt, t.1))); + if (ObjReps.arrayMode == ArrayMode.Original) { + var t = bytesToVals(rtt.elem_types[0], ddecl.data, offset, length); + if (!t.0) return stack.trap(TrapReason.MEMORY_OOB); + stack.push(Value.Ref(HeapArrayValue.new(rtt, t.1))); + } else { + return ObjectRuntime.ARRAY_NEW_DATA(stack, instance, rtt, ddecl, offset, length, data_index); + } return null; } def ARRAY_NEW_ELEM(stack: ExecStack, instance: Instance, array_index: u31, elem_index: u31) -> Throwable { var length = stack.popu(); var offset = stack.popu(); - if (instance.dropped_elems[elem_index]) return stack.trap(TrapReason.MEMORY_OOB); + if (instance.dropped_elems[elem_index]) return stack.trap(TrapReason.ELEM_SEGMENT_DROPPED); if (length > Execute.limits.max_array_length) return stack.trap(TrapReason.OOM); var rtt = ArrayDecl.!(instance.heaptypes[array_index]); - var evals = instance.getElems(elem_index); - var vals = Array.new(u31.!(length)); - var r = copyElemsInto(vals, instance, 0, evals, offset, length); - if (!r) return stack.trap(TrapReason.TABLE_OOB); // TODO: elem out of bounds - stack.push(Value.Ref(HeapArray.new(rtt, vals))); + var edecl = instance.module.elems[elem_index]; + if (ObjReps.arrayMode == ArrayMode.Original) { + var r = getElems(instance, edecl, offset, length); + if (!r.0) return stack.trap(TrapReason.TABLE_OOB); + var vals = Array.new(u31.!(length)); + for (i < vals.length) { + var val = r.1[i]; + vals[i] = if(ObjectI31.?(val), Value.I31(ObjectI31.!(val).val), Value.Ref(val)); + } + stack.push(Value.Ref(HeapArrayValue.new(rtt, vals))); + } else { + return ObjectRuntime.ARRAY_NEW_ELEM(stack, instance, rtt, edecl, offset, length); + } return null; } def ARRAY_GET(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { var index = stack.popu(); var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - if (index >= u32.view(obj.vals.length)) return stack.trap(TrapReason.ARRAY_OOB); - stack.push(obj.vals[index]); + if (index >= u32.view(obj.length())) return stack.trap(TrapReason.ARRAY_OOB); + stack.push(obj.getValue(index)); return null; } def ARRAY_GET_S(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { @@ -146,8 +194,8 @@ component Runtime { var index = stack.popu(); var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - if (index >= u32.view(obj.vals.length)) return stack.trap(TrapReason.ARRAY_OOB); - stack.push(V3Eval.signExtend(decl.elem_types[0], obj.vals[index])); // XXX: pushi + if (index >= u32.view(obj.length())) return stack.trap(TrapReason.ARRAY_OOB); + stack.push(V3Eval.signExtend(decl.elem_types[0], obj.getValue(index))); // XXX: pushi return null; } def ARRAY_GET_U(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { @@ -155,8 +203,8 @@ component Runtime { var index = stack.popu(); var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - if (index >= u32.view(obj.vals.length)) return stack.trap(TrapReason.ARRAY_OOB); - stack.push(V3Eval.zeroExtend(decl.elem_types[0], obj.vals[index])); // XXX: pushi + if (index >= u32.view(obj.length())) return stack.trap(TrapReason.ARRAY_OOB); + stack.push(V3Eval.zeroExtend(decl.elem_types[0], obj.getValue(index))); // XXX: pushi return null; } def ARRAY_SET(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { @@ -165,27 +213,33 @@ component Runtime { var index = stack.popu(); var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - if (index >= u32.view(obj.vals.length)) return stack.trap(TrapReason.ARRAY_OOB); - obj.vals[index] = val; + if (index >= u32.view(obj.length())) return stack.trap(TrapReason.ARRAY_OOB); + obj.setValue(index, val); return null; } def ARRAY_LEN(stack: ExecStack, instance: Instance) -> Throwable { var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - stack.pushi(obj.vals.length); + stack.pushi(obj.length()); return null; } def ARRAY_FILL(stack: ExecStack, instance: Instance, array_index: u31) -> Throwable { var rtt = ArrayDecl.!(instance.heaptypes[array_index]); var size = stack.popu(); - var val = stack.popV(rtt.elem_types[0].valtype); // XXX: polymorphic pop + var stype = rtt.elem_types[0]; + var val = stack.popV(stype.valtype); // XXX: polymorphic pop var offset = stack.popu(); var obj = stack.popArray(); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); - var index = ArrayUtil.boundsCheck(obj.vals, offset, size); + var index = ArrayUtil.boundsCheckWithLength(u31.view(obj.length()), offset, 0, size); if (index < 0) return stack.trap(TrapReason.ARRAY_OOB); - var r = obj.vals[index ..+ size]; - for (i < r.length) r[i] = val; + if (ObjReps.arrayMode == ArrayMode.Original) { + var vals = obj.getArray(); + var r = vals[index ..+ size]; + for (i < r.length) r[i] = val; + } else { + return ObjectRuntime.ARRAY_FILL(obj, rtt, index, size, val); + } return null; } def ARRAY_COPY(stack: ExecStack, instance: Instance, array_index1: u31, array_index2: u31) -> Throwable { @@ -195,9 +249,13 @@ component Runtime { var dst_offset = stack.popu(); var dst = stack.popArray(); if (src == null || dst == null) return stack.trap(TrapReason.NULL_DEREF); - var r = ArrayUtil.safeCopy(dst.vals, dst_offset, src.vals, src_offset, size); - if (!r) return stack.trap(TrapReason.ARRAY_OOB); - return null; + if (ObjReps.arrayMode == ArrayMode.Original) { + var r = ArrayUtil.safeCopy(dst.getArray(), dst_offset, src.getArray(), src_offset, size); + if (!r) return stack.trap(TrapReason.ARRAY_OOB); + return null; + } else { + return ObjectRuntime.ARRAY_COPY(stack, dst, src, dst_offset, src_offset, size); + } } def ARRAY_INIT_DATA(stack: ExecStack, instance: Instance, array_index: u31, data_index: u31) -> Throwable { var size = stack.popu(); @@ -208,11 +266,16 @@ component Runtime { var rtt = ArrayDecl.!(instance.heaptypes[array_index]); if (instance.dropped_data[data_index]) return if(size > 0, stack.trap(TrapReason.DATA_SEGMENT_DROPPED), null); var data = instance.module.data[data_index].data; - if (ArrayUtil.boundsCheck(obj.vals, dst_offset, size) < 0) return stack.trap(TrapReason.ARRAY_OOB); - var t = bytesToVals(rtt.elem_types[0], data, src_offset, size); - if (!t.0) return stack.trap(TrapReason.MEMORY_OOB); - ArrayUtil.safeCopy(obj.vals, dst_offset, t.1, 0, size); - return null; + if (ArrayUtil.boundsCheckWithLength(u31.view(obj.length()), dst_offset, 0, size) < 0) return stack.trap(TrapReason.ARRAY_OOB); + if (ObjReps.arrayMode == ArrayMode.Original) { + var t = bytesToVals(rtt.elem_types[0], data, src_offset, size); + if (!t.0) return stack.trap(TrapReason.MEMORY_OOB); + var vals = obj.getArray(); + ArrayUtil.safeCopy(vals, dst_offset, t.1, 0, size); + return null; + } else { + return ObjectRuntime.ARRAY_INIT_DATA(stack, obj, rtt, data, dst_offset, src_offset, size); + } } def ARRAY_INIT_ELEM(stack: ExecStack, instance: Instance, array_index: u31, elem_index: u31) -> Throwable { var size = stack.popu(); @@ -222,9 +285,18 @@ component Runtime { var rtt = ArrayDecl.!(instance.heaptypes[array_index]); if (obj == null) return stack.trap(TrapReason.NULL_DEREF); if (instance.dropped_elems[elem_index]) return if(size > 0, stack.trap(TrapReason.ELEM_SEGMENT_DROPPED), null); - var evals = instance.getElems(elem_index); - var r = copyElemsInto(obj.vals, instance, dst_offset, evals, src_offset, size); - if (!r) return stack.trap(TrapReason.ARRAY_OOB); + var edecl = instance.module.elems[elem_index]; + if (ObjReps.arrayMode == ArrayMode.Original) { + var vals = HeapArrayValue.!(obj).vals; + var r = getElems(instance, edecl, src_offset, size); + if (!r.0) return stack.trap(TrapReason.ARRAY_OOB); + if (ArrayUtil.boundsCheck(vals, dst_offset, size) < 0) return stack.trap(TrapReason.ARRAY_OOB); + for (i < r.1.length) { + vals[int.!(dst_offset)+i] = Value.Ref(r.1[i]); + } + } else { + return ObjectRuntime.ARRAY_INIT_ELEM(stack, obj, instance, edecl, dst_offset, src_offset, size); + } return null; } // --- Table operations ----------------------------------------------------------------------------------------- @@ -399,8 +471,8 @@ component Runtime { match (ht_val) { BpHeapTypeCode.FUNC.val => return Function.?(obj); BpHeapTypeCode.EXTERN.val => return Object.?(obj); - BpHeapTypeCode.ARRAY.val => return HeapArray.?(obj); - BpHeapTypeCode.STRUCT.val => return HeapStruct.?(obj); + BpHeapTypeCode.ARRAY.val => return HeapArrayGeneric.?(obj); + BpHeapTypeCode.STRUCT.val => return HeapStructGeneric.?(obj); BpHeapTypeCode.ANY.val => return Object.?(obj); BpHeapTypeCode.EQ.val => return HeapObject.?(obj); BpHeapTypeCode.I31.val, // fallthru @@ -423,41 +495,79 @@ component Runtime { } return ArrayUtil.safeCopy(dest, dst_offset, evals, src_offset, size); } -} -def bytesToVals(storage: StorageType, data: Array, offset: u32, length: u32) -> (bool, Array) { // TODO: MaybeTrap - var vals = Array.new(int.!(length)); - var start = ArrayUtil.boundsCheck(data, offset, length); - if (start < 0) return (false, null); - var limit = sizeOfStorage(storage) * length + offset; - if (limit > data.length) return (false, null); - var d = DataReader.new(data).reset(data, start, int.!(limit)); - match (storage.pack) { - UNPACKED => { - match (storage.valtype) { - I32 => for (i < vals.length) vals[i] = Value.I32(d.read_u32()); - I64 => for (i < vals.length) vals[i] = Value.I64(d.read_u64()); - F32 => for (i < vals.length) vals[i] = Value.F32(d.read_u32()); - F64 => for (i < vals.length) vals[i] = Value.F64(d.read_u64()); - V128 => for (i < vals.length) vals[i] = Value.V128(d.read_u64(), d.read_u64()); - _ => ; + def getElems(instance: Instance, elem: ElemDecl, src_offset: u64, size: u64) -> (bool, Array) { + if (elem == null) { + if (ArrayUtil.boundsCheck(null, src_offset, size) < 0) return (false, null); + return (true, Array.new(0)); + } + var dest = Array.new(int.!(size)); + var result = false; + match (elem.details) { + FuncRefs(vals) => { + result = ArrayUtil.safeCopyF(dest, 0, vals, src_offset, size, fun(idx: int) => instance.functions[idx]); + } + Exprs(vals) => { + result = ArrayUtil.safeCopyF(dest, 0, vals, src_offset, size, instance.evalInitExprObject); } } - PACKED_I8 => for (i < vals.length) vals[i] = Value.I32(d.read1()); - PACKED_I16 => for (i < vals.length) vals[i] = Value.I32(d.read1() | (u32.!(d.read1()) << 8)); - } - return (d.ok, vals); -} -def sizeOfStorage(storage: StorageType) -> u32 { - match (storage.pack) { - UNPACKED => { - match (storage.valtype) { - I32, F32 => return 4; - I64, F64 => return 8; - V128 => return 16; - _ => return 8; + return (result, if(result, dest)); + } + def getData(instance: Instance, data_index: u31, src_offset: u64, size: u64, func: DataReader -> T) -> (bool, Array) { + var data = if(!instance.dropped_data[data_index], instance.module.data[data_index]); + if (data == null) return (false, null); + var start = ArrayUtil.boundsCheck(data.data, src_offset, size); + if (start < 0) return (false, null); + var vals = Array.new(int.!(size)); + var d = DataReader.new(data.data).reset(data.data, int.!(src_offset), data.data.length); + for (i < vals.length) vals[i] = func(d); + return (d.ok, if(d.ok, vals)); + } + def bytesToVals(storage: StorageType, data: Array, offset: u32, length: u32) -> (bool, Array) { // TODO: MaybeTrap + var vals = Array.new(int.!(length)); + var size = sizeOfStorage(storage); + var nbytes = length * size; + var start = ArrayUtil.boundsCheck(data, offset, nbytes); + if (start < 0) return (false, null); + var d = ExtendedDataReader.new(data).reset(data, start, start + int.!(nbytes)); + match (storage.pack) { + UNPACKED => { + match (storage.valtype) { + I32 => for (i < vals.length) vals[i] = Value.I32(d.read_u32()); + I64 => for (i < vals.length) vals[i] = Value.I64(d.read_u64()); + F32 => for (i < vals.length) vals[i] = Value.F32(d.read_u32()); + F64 => for (i < vals.length) vals[i] = Value.F64(d.read_u64()); + V128 => for (i < vals.length) vals[i] = Value.V128(d.read_u64(), d.read_u64()); + _ => ; + } + } + PACKED_I8 => for (i < vals.length) vals[i] = Value.I32(d.read_u8()); + PACKED_I16 => for (i < vals.length) vals[i] = Value.I32(d.read_u16()); + } + return (d.ok, vals); + } + def sizeOfStorage(storage: StorageType) -> u32 { + match (storage.pack) { + UNPACKED => { + match (storage.valtype) { + I32, F32 => return 4; + I64, F64 => return 8; + V128 => return 16; + _ => return 8; + } } + PACKED_I8 => return 1; + PACKED_I16 => return 2; } - PACKED_I8 => return 1; - PACKED_I16 => return 2; + } + +} +class ExtendedDataReader extends DataReader { + new(data: Range) super(data) { } + def read_u8() => read1(); + def read_u16() -> u16 { + var range = acquire(2); + if (range.length != 2) return 0; + at(pos + 2); + return DataReaders.read_range_u16(range); } } diff --git a/src/engine/Tuning.v3 b/src/engine/Tuning.v3 index c1c12d3e1..8b535dc80 100644 --- a/src/engine/Tuning.v3 +++ b/src/engine/Tuning.v3 @@ -1,4 +1,4 @@ -// Copyright 2022 Ben L. Titzer. All rights reserved. +// Copyright 2022-2025 Wizard authors. All rights reserved. // See LICENSE for details of Apache 2.0 license. // Contains flags that explicitly disable features to test their impact on performance. diff --git a/src/engine/Type.v3 b/src/engine/Type.v3 index 3b75a94a7..72c4a6d03 100644 --- a/src/engine/Type.v3 +++ b/src/engine/Type.v3 @@ -224,6 +224,9 @@ component ValueTypes { def RefFunc(nullable: bool, x: SigDecl) -> ValueType.Ref { return ValueType.Ref(nullable, HeapType.Func(x)); } + def RefCont(nullable: bool, x: ContDecl) -> ValueType.Ref { + return ValueType.Ref(nullable, HeapType.Cont(x)); + } } // Implementation detail in comparing recursive types. Computes the relation between @@ -400,7 +403,71 @@ class StructDecl extends HeapTypeDecl { def field_types: Array; def defaultable = allHaveDefaultValues(field_types); - new(final: bool, supertypes: Array, field_types) super(final, supertypes) {} + // These fields are used with paired array representation + def var field_shifts: Array; + def var field_offsets: Array; + def var num_bytes: int; + def var num_refs: int; + + new(final: bool, supertypes: Array, field_types) + super(final, supertypes) { + match (ObjReps.structMode) { + Pair => { + // determine field sizes and offsets + field_shifts = Array.new(field_types.length); + field_offsets = Array.new(field_types.length); + for (i < field_types.length) { + var ft = field_types[i]; + match (ft.pack) { + UNPACKED => { + match (ft.valtype) { + I32, F32 => { + field_shifts[i] = 2; + field_offsets[i] = num_bytes; + num_bytes += 4; + } + I64, F64 => { + field_shifts[i] = 3; + field_offsets[i] = num_bytes; + num_bytes += 8; + } + V128 => { + field_shifts[i] = 4; + field_offsets[i] = num_bytes; + num_bytes += 16; + } + Ref(nullable, ht) => { + if (HeapType.Cont.?(ht) && !Continuations.boxed) { + field_shifts[i] = ObjReps.REF_U64_SHIFT; + var refs_offset = num_refs++; + var bytes_offset = num_bytes; + num_bytes += 8; + field_offsets[i] = ObjReps.packFieldOffsets(refs_offset, bytes_offset); + } else { + field_shifts[i] = ObjReps.REF_SHIFT; + field_offsets[i] = num_refs++; + } + } + _ => { } + } + } + PACKED_I8 => { + field_shifts[i] = 0; + field_offsets[i] = num_bytes; + num_bytes += 1; + } + PACKED_I16 => { + field_shifts[i] = 1; + field_offsets[i] = num_bytes; + num_bytes += 2; + } + } + } + } + _ => { } + } + + } def render(buf: StringBuilder) -> StringBuilder { return putUid(buf.put1("struct #%d", heaptype_index)); diff --git a/src/engine/Value.v3 b/src/engine/Value.v3 index e7031b337..4bb338cad 100644 --- a/src/engine/Value.v3 +++ b/src/engine/Value.v3 @@ -1,4 +1,4 @@ -// Copyright 2020 Ben L. Titzer. All rights reserved. +// Copyright 2020-2025 Wizard authors. All rights reserved. // See LICENSE for details of Apache 2.0 license. // WebAssembly program values. @@ -27,13 +27,199 @@ enum ValueKind(code: byte) { // Superclass of all objects referred to by Value.Ref, including external refs. class Object extends Exportable { } +class ObjectI31(val: u31) extends Object { } + // Objects allocated on the "wasm" GC heap, i.e. from the GC proposal. -class HeapObject(decl: HeapTypeDecl, vals: Array) extends Object {} -class HeapStruct extends HeapObject { - new(decl: StructDecl, vals: Array) super(decl, vals) { } + +class HeapObject extends Object { + def decl: HeapTypeDecl; + new(decl) { } +} + +// superclass of struct representations; defines getter/setter interface +class HeapStructGeneric extends HeapObject { + new(decl: StructDecl) super(decl) { } + + def getFieldValue(index: u31) -> Value; + def getFieldSignExtend8Value(index: u31) -> Value; + def getFieldSignExtend16Value(index: u31) -> Value; + def getFieldZeroExtend8Value(index: u31) -> Value; + def getFieldZeroExtend16Value(index: u31) -> Value; + def getFieldI32(index: u32) -> i32; + def getFieldI64(index: u32) -> i64; + def getFieldI8(index: u32) -> i8; + def getFieldI16(index: u32) -> i16; + def getFieldF32(index: u32) -> float; + def getFieldF64(index: u32) -> double; + def getFieldRef(index: u32) -> Object; + def getFieldCont(index: u32) -> Continuation; + def getFieldV128(index: u32) -> (u64, u64); + + def setFieldValue(index: u31, val: Value); + def setFieldI32(index: u32, val: i32); + def setFieldI64(index: u32, val: i64); + def setFieldI8(index: u32, val: i8); + def setFieldI16(index: u32, val: i16); + def setFieldF32(index: u32, val: float); + def setFieldF64(index: u32, val: double); + def setFieldRef(index: u32, val: Object); + def setFieldCont(index: u32, val: Continuation); + def setFieldV128(index: u32, low: u64, high: u64); +} + +// Original Array struct representation +// Provides definitions for Value getters/setter (only) +class HeapStructValue extends HeapStructGeneric { + def vals: Array; + new(decl: StructDecl, vals) super(decl) { } + + def getFieldValue(index: u31) -> Value { return vals[index]; } + def getFieldSignExtend8Value(index: u31) -> Value { return Value.I32(u32.view(i8.view(Value.I32.!(vals[index]).val))); } + def getFieldSignExtend16Value(index: u31) -> Value { return Value.I32(u32.view(i16.view(Value.I32.!(vals[index]).val))); } + def getFieldZeroExtend8Value(index: u31) -> Value { return Value.I32(u32.view(u8.view(Value.I32.!(vals[index]).val))); } + def getFieldZeroExtend16Value(index: u31) -> Value { return Value.I32(u32.view(u16.view(Value.I32.!(vals[index]).val))); } + + def setFieldValue(index: u31, val: Value) { vals[index] = val; } +} + +// superclass of array representations; defines getter/setter interface +class HeapArrayGeneric extends HeapObject { + new(decl: ArrayDecl) super(decl) { } + + def length() -> int; + + // get whole array - useful for copy operations, etc. + def getArray() -> Array; + + def getValues() -> Array { + var vals = Array.new(length()); + for (i < length()) { + vals[i] = getValue(u32.!(i)); + } + return vals; + } + + def getValue(index: u32) -> Value; + def setValue(index: u32, val: Value); + + def getI32(index: u32) -> i32; + def setI32(index: u32, val: i32); + + def getI31(index: u32) -> i31; + def setI31(index: u32, val: i31); + + def getI64(index: u32) -> i64; + def setI64(index: u32, val: i64); + + def getI8(index: u32) -> i8; + def setI8(index: u32, val: i8); + + def getI16(index: u32) -> i16; + def setI16(index: u32, val: i16); + + def getF32(index: u32) -> float; + def setF32(index: u32, val: float); + + def getF64(index: u32) -> double; + def setF64(index: u32, val: double); + + def getRef(index: u32) -> Object; + def setRef(index: u32, val: Object); + + def getCont(index: u32) -> Continuation; + def setCont(index: u32, val: Continuation); + + def getV128(index: u32) -> (u64, u64); + def setV128(index: u32, low: u64, high: u64); + + def getU32(index: u32) -> u32; + def getU64(index: u32) -> u64; + def getU8(index: u32) -> u8; + def getU16(index: u32) -> u16; } -class HeapArray extends HeapObject { - new(decl: ArrayDecl, vals: Array) super(decl, vals) { } // XXX: unboxed prim arrays + +// Original Array array representation +// Note: bounds checks will already have been done +class HeapArrayValue extends HeapArrayGeneric { + def vals: Array; + new(decl: ArrayDecl, vals) super(decl) { } + + def length() -> int { return vals.length; } + + def getArray() -> Array { + return Array.!(vals); + } + def getValues() -> Array { + return vals; + } + + def getValue(index: u32) -> Value { + return vals[index]; + } + def setValue(index: u32, val: Value) { + vals[index] = val; + } + def getI32(index: u32) -> i32 { + return i32.view(Value.I32.!(vals[index]).val); + } + def setI32(index: u32, val: i32) { + vals[index] = Value.I32(u32.view(val)); + } + def getI31(index: u32) -> i31 { + return i31.view(Value.I31.!(vals[index]).val); + } + def setI31(index: u32, val: i31) { + vals[index] = Value.I31(u31.view(val)); + } + def getI64(index: u32) -> i64 { + return i64.view(Value.I64.!(vals[index]).val); + } + def setI64(index: u32, val: i64) { + vals[index] = Value.I64(u64.view(val)); + } + def getI8(index: u32) -> i8 { + return i8.view(Value.I32.!(vals[index]).val); + } + def setI8(index: u32, val: i8) { + vals[index] = Value.I32(u32.view(val)); + } + def getI16(index: u32) -> i16 { + return i16.view(Value.I32.!(vals[index]).val); + } + def setI16(index: u32, val: i16) { + vals[index] = Value.I32(u32.view(val)); + } + def getF32(index: u32) -> float { + return float.view(Value.F32.!(vals[index]).bits); + } + def setF32(index: u32, val: float) { + vals[index] = Value.F32(u32.view(val)); + } + def getF64(index: u32) -> double { + return double.view(Value.F64.!(vals[index]).bits); + } + def setF64(index: u32, val: double) { + vals[index] = Value.F64(u64.view(val)); + } + def getRef(index: u32) -> Object { + return Value.Ref.!(vals[index]).val; + } + def setRef(index: u32, val: Object) { + vals[index] = Value.Ref(val); + } + def getCont(index: u32) -> Continuation { + return Value.Cont.!(vals[index]).val; + } + def setCont(index: u32, val: Continuation) { + vals[index] = Value.Cont(val); + } + def getV128(index: u32) -> (u64, u64) { + var val = Value.V128.!(vals[index]); + return (val.low, val.high); + } + def setV128(index: u32, low: u64, high: u64) { + vals[index] = Value.V128(low, high); + } } // Utilities associated with values. @@ -56,18 +242,17 @@ component Values { def REF_NULL = FUNCREF_NULL; def CONT_NULL = Value.Cont(Continuations.NULL); def NONE = Array.new(0); - def NO_SUPERS = Array.new(0); def render(v: Value, buf: StringBuilder) -> StringBuilder { match (v) { Ref(val) => match (val) { x: HostObject => buf.put1("", x.render); x: WasmFunction => buf.put1("", x.decl.func_index); - x: HeapStruct => { + x: HeapStructGeneric => { var id = if(x.decl == null, -1, x.decl.heaptype_index); buf.put1("", id); } - x: HeapArray => { + x: HeapArrayGeneric => { var id = if(x.decl == null, -1, x.decl.heaptype_index); buf.put1("", id); } @@ -80,7 +265,7 @@ component Values { F32(val) => buf.put1("f32:%x", val); F64(val) => buf.put1("f64:%x", val); V128(low, high) => buf.puts("v128:").putx_64(high).putc('_').putx_64(low); - Cont(val) => buf.put1("", val.render); + Cont(val) => buf.put1("", Continuations.render(val, _)); } return buf; } @@ -98,13 +283,13 @@ component Values { } def isData(v: Value) -> bool { match (v) { - Ref(o) => return HeapObject.?(o) || HeapArray.?(o); + Ref(o) => return HeapObject.?(o) || HeapArrayGeneric.?(o); _ => return false; } } def isArray(v: Value) -> bool { match (v) { - Ref(o) => return HeapArray.?(o); + Ref(o) => return HeapArrayGeneric.?(o); _ => return false; } } @@ -198,6 +383,7 @@ component Values { x: u32 => return box_u(x); x: i64 => return box_l(x); x: u64 => return box_w(x); + x: Continuation => return Value.Cont(x); x: Object => return Value.Ref(x); _ => System.error("BoxError", "no matching boxing operation for Virgil value"); @@ -221,6 +407,7 @@ component Values { F32 => return ValueKind.F32; F64 => return ValueKind.F64; V128 => return ValueKind.V128; + Cont => return Continuations.valueKind; _ => return ValueKind.REF; } } diff --git a/src/engine/WasmStack.v3 b/src/engine/WasmStack.v3 index a15f2ae38..8cab234cf 100644 --- a/src/engine/WasmStack.v3 +++ b/src/engine/WasmStack.v3 @@ -2,12 +2,13 @@ // See LICENSE for details of Apache 2.0 license. // An execution stack. -class ExecStack { +class ExecStack extends Exportable { def popV(t: ValueType) -> Value; def popi() -> i32; def popu() -> u32; def popl() -> i64; def popw() -> u64; + def pops() -> (u64, u64); def popf() -> float; def popd() -> double; def popr() -> Value.Ref; @@ -21,6 +22,7 @@ class ExecStack { def pushu(val: u32); def pushl(val: i64); def pushw(val: u64); + def pushs(low: u64, high: u64); def pushf(val: float); def pushd(val: double); def pushz(val: bool); @@ -40,11 +42,11 @@ class ExecStack { def popa(size: SizeConstraint) -> u64 { return if(size.is64, popw(), popu()); } - def popStruct() -> HeapStruct { - return HeapStruct.!(popObject()); + def popStruct() -> HeapStructGeneric { + return HeapStructGeneric.!(popObject()); } - def popArray() -> HeapArray { - return HeapArray.!(popObject()); + def popArray() -> HeapArrayGeneric { + return HeapArrayGeneric.!(popObject()); } def popFunction() -> Function { return Function.!(popObject()); diff --git a/src/engine/compiler/MacroAssembler.v3 b/src/engine/compiler/MacroAssembler.v3 index a3eb8110b..1636e1466 100644 --- a/src/engine/compiler/MacroAssembler.v3 +++ b/src/engine/compiler/MacroAssembler.v3 @@ -30,9 +30,13 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { var embeddedRefOffsets: Vector; var offsets = Target.getOffsets(); + // define in subclass to support tracing + def setTargetTracing(outf: Range -> void) { } + new() { unimplemented = fatalUnimplemented; newTrapLabel = makeSharedTrapLabel; + if (Trace.asm) setTargetTracing(System.out.putr); } def getOffsets() -> V3Offsets { @@ -143,18 +147,27 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { } // Routines to emit code to access various V3 constructs. + // EBM Broken: some users assume array/index not side-effected def emit_v3_Array_elem_r_rr(kind: ValueKind, dst: Reg, array: Reg, index: Reg) { // Generic code, may be overridden to be more efficient - emit_shlw_r_i(index, logScaleOf(kind)); + emit_shlw_r_i(index, logScaleOf(kind)); // TODO: possibly wrong for REF_U64 (Continuations) emit_addw_r_r(array, index); emit_mov_r_m(kind, dst, MasmAddr(array, getOffsets().Array_contents)); } def emit_v3_Array_elem_r_ri(kind: ValueKind, dst: Reg, array: Reg, index: int) { emit_mov_r_m(kind, dst, MasmAddr(array, getOffsets().Array_contents + index * scaleOf(kind))); } - def emit_v3_Array_length_r_r(dst: Reg, array: Reg) { - emit_mov_r_m(ValueKind.I32, dst, MasmAddr(array, getOffsets().Array_length)); + // EBM Broken: some users assume array/index not side-effected + def emit_v3_Array_elem_rr_r(kind: ValueKind, array: Reg, index: Reg, src: Reg) { + // Generic code, may be overridden to be more efficient + emit_shlw_r_i(index, logScaleOf(kind)); // TODO: possibly wrong for REF_U64 (Continuations) + emit_addw_r_r(array, index); + emit_mov_m_r(kind, MasmAddr(array, getOffsets().Array_contents), src); + } + def emit_v3_Array_elem_ri_r(kind: ValueKind, array: Reg, index: int, src: Reg) { + emit_mov_m_r(kind, MasmAddr(array, getOffsets().Array_contents + index * scaleOf(kind)), src); } + def emit_v3_Array_length_r_r(dst: Reg, array: Reg); def emit_v3_Array_bounds_check_rr(array: Reg, index: Reg, oob_label: MasmLabel) { // Generic code, may be overridden to be more efficient var scratch = regConfig.scratch; @@ -165,6 +178,15 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_v3_HeapArray_vals_r_r(dst: Reg, ptr: Reg) { emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().HeapArray_vals)); } + def emit_v3_HeapStructPair_objs_r_r(dst: Reg, ptr: Reg) { + emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().HeapStructPair_objs)); + } + def emit_v3_HeapStructPair_bytes_r_r(dst: Reg, ptr: Reg) { + emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().HeapStructPair_bytes)); + } + def emit_v3_HeapArrayI32_vals_r_r(dst: Reg, ptr: Reg) { + emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().HeapArrayI32_vals)); + } def emit_v3_Instance_dropped_elems_r_r(dst: Reg, ptr: Reg) { emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().Instance_dropped_elems)); } @@ -257,6 +279,9 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_loaddzx_r_r_r_i(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32); def emit_load_r_r_r_i(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32); + def emit_loadwsx_r_r_r_i_s(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32, scale: byte); + def emit_loadwzx_r_r_r_i_s(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32, scale: byte); + def emit_storeb_r_r_r_i(kind: ValueKind, val: Reg, base: Reg, index: Reg, offset: u32); def emit_storew_r_r_r_i(kind: ValueKind, val: Reg, base: Reg, index: Reg, offset: u32); def emit_store_r_r_r_i(kind: ValueKind, val: Reg, base: Reg, index: Reg, offset: u32); @@ -284,6 +309,7 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_mov_r_Object(reg: Reg, obj: Object); def emit_mov_r_Function(reg: Reg, func: Function); def emit_mov_r_Instance(reg: Reg, instance: Instance); + def emit_movbsx_r_m(kind: ValueKind, reg: Reg, addr: MasmAddr); def emit_mov_r_Continuation(reg: Reg, cont: Continuation); def emit_mov_m_r(kind: ValueKind, addr: MasmAddr, reg: Reg); @@ -356,6 +382,8 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_ref_from_ref_u64(to: Reg, from: Reg); def emit_u64_from_ref_u64(to: Reg, from: Reg); + def emit_ref_u64_from_ref_and_u64_mem(to: Reg, ref_addr: MasmAddr, u64_addr: MasmAddr); + def emit_ref_and_u64_mem_from_ref_u64(ref_addr: MasmAddr, u64_addr: MasmAddr, from: Reg); def emit_mov_r_Cont(to: Reg, cont: Continuation); // stk.state_ = state; diff --git a/src/engine/compiler/SinglePassCompiler.v3 b/src/engine/compiler/SinglePassCompiler.v3 index 616224c22..42d0cd873 100644 --- a/src/engine/compiler/SinglePassCompiler.v3 +++ b/src/engine/compiler/SinglePassCompiler.v3 @@ -1714,19 +1714,143 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl } def visit_STRUCT_GET(struct_index: u31, field_index: u31) { var decl = StructDecl.!(module.heaptypes[struct_index]); - emit_call_runtime_op2n(Opcode.STRUCT_GET, struct_index, field_index, 1, [decl.field_types[field_index].valtype], true); + var stg_type = decl.field_types[field_index]; + var etype = stg_type.valtype; + if ((stg_type.pack != Packedness.UNPACKED) || + (ValueType.Ref.?(etype) && ValueType.Ref.!(etype).heap == HeapType.I31)) { + emit_call_runtime_op2n(Opcode.STRUCT_GET, struct_index, field_index, 1, [etype], true); + return; + } + match (ObjReps.structMode) { + Pair => { + var offset = decl.field_offsets[field_index]; + var kind = ValueTypes.kind(etype); + var str = popReg().reg; + var dst = allocRegTos(kind); + var tmp = allocTmp(ValueKind.REF); + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(str, 0, label); + match (kind) { + REF => { + masm.emit_v3_HeapStructPair_objs_r_r(tmp, str); + offset = offset << 3; + masm.emit_mov_r_m(kind, dst, MasmAddr(tmp, offset + masm.getOffsets().Array_contents)); + } + REF_U64 => { + var offsets = ObjReps.unpackFieldOffsets(offset); + var refs_offset = offsets.0 << 3; + var bytes_offset = offsets.1; + masm.emit_v3_HeapStructPair_objs_r_r(tmp, str); + var tmp1 = allocTmp(ValueKind.REF); + masm.emit_v3_HeapStructPair_bytes_r_r(tmp1, str); + var ref_addr = MasmAddr(tmp, refs_offset + masm.getOffsets().Array_contents); + var u64_addr = MasmAddr(tmp1, bytes_offset + masm.getOffsets().Array_contents); + masm.emit_ref_u64_from_ref_and_u64_mem(dst, ref_addr, u64_addr); + } + _ => { + masm.emit_v3_HeapStructPair_bytes_r_r(tmp, str); + masm.emit_mov_r_m(kind, dst, MasmAddr(tmp, offset + masm.getOffsets().Array_contents)); + } + } + state.push(SpcConsts.kindToFlags(kind) | IN_REG, dst, 0); + } + _ => { + emit_call_runtime_op2n(Opcode.STRUCT_GET, struct_index, field_index, 1, [etype], true); + } + } } def visit_STRUCT_GET_S(struct_index: u31, field_index: u31) { var decl = StructDecl.!(module.heaptypes[struct_index]); - emit_call_runtime_op2n(Opcode.STRUCT_GET_S, struct_index, field_index, 1, [decl.field_types[field_index].valtype], true); + match (ObjReps.structMode) { + Pair => { + var offset = decl.field_offsets[field_index]; + var str = popReg().reg; + var dst = allocRegTos(ValueKind.I32); + var tmp = allocTmp(ValueKind.REF); + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(str, 0, label); + masm.emit_v3_HeapStructPair_bytes_r_r(tmp, str); + match (decl.field_types[field_index].pack) { + PACKED_I8 => masm.emit_loadbsx_r_r_r_i(ValueKind.I32, dst, tmp, NO_REG, u32.!(offset + masm.getOffsets().Array_contents)); + PACKED_I16 => masm.emit_loadwsx_r_r_r_i(ValueKind.I32, dst, tmp, NO_REG, u32.!(offset + masm.getOffsets().Array_contents)); + _ => ; + } + state.push(KIND_I32 | IN_REG, dst, 0); + } + _ => { + emit_call_runtime_op2n(Opcode.STRUCT_GET_S, struct_index, field_index, 1, [decl.field_types[field_index].valtype], true); + } + } } def visit_STRUCT_GET_U(struct_index: u31, field_index: u31) { var decl = StructDecl.!(module.heaptypes[struct_index]); - emit_call_runtime_op2n(Opcode.STRUCT_GET_U, struct_index, field_index, 1, [decl.field_types[field_index].valtype], true); + match (ObjReps.structMode) { + Pair => { + var offset = decl.field_offsets[field_index]; + var str = popReg().reg; + var dst = allocRegTos(ValueKind.I32); + var tmp = allocTmp(ValueKind.REF); + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(str, 0, label); + masm.emit_v3_HeapStructPair_bytes_r_r(tmp, str); + match (decl.field_types[field_index].pack) { + PACKED_I8 => masm.emit_loadbzx_r_r_r_i(ValueKind.I32, dst, tmp, NO_REG, u32.!(offset + masm.getOffsets().Array_contents)); + PACKED_I16 => masm.emit_loadwzx_r_r_r_i(ValueKind.I32, dst, tmp, NO_REG, u32.!(offset + masm.getOffsets().Array_contents)); + _ => ; + } + state.push(KIND_I32 | IN_REG, dst, 0); + } + _ => { + emit_call_runtime_op2n(Opcode.STRUCT_GET_U, struct_index, field_index, 1, [decl.field_types[field_index].valtype], true); + } + } } def visit_STRUCT_SET(struct_index: u31, field_index: u31) { var decl = StructDecl.!(module.heaptypes[struct_index]); - emit_call_runtime_op2n(Opcode.STRUCT_SET, struct_index, field_index, 2, ValueTypes.NONE, true); + var stg_type = decl.field_types[field_index]; + var etype = stg_type.valtype; + if ((stg_type.pack != Packedness.UNPACKED) || + (ValueType.Ref.?(etype) && ValueType.Ref.!(etype).heap == HeapType.I31)) { + emit_call_runtime_op2n(Opcode.STRUCT_SET, struct_index, field_index, 2, ValueTypes.NONE, true); + return; + } + match (ObjReps.structMode) { + Pair => { + var offset = decl.field_offsets[field_index]; + var kind = ValueTypes.kind(etype); + var val = popReg().reg; + var str = popReg().reg; + var dst = allocRegTos(kind); + var tmp = allocTmp(ValueKind.REF); + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(str, 0, label); + match (kind) { + REF => { + masm.emit_v3_HeapStructPair_objs_r_r(tmp, str); + offset = offset << 3; + masm.emit_mov_m_r(kind, MasmAddr(tmp, offset + masm.getOffsets().Array_contents), val); + } + REF_U64 => { + var offsets = ObjReps.unpackFieldOffsets(offset); + var refs_offset = offsets.0 << 3; + var bytes_offset = offsets.1; + masm.emit_v3_HeapStructPair_objs_r_r(tmp, str); + var tmp1 = allocTmp(ValueKind.REF); + masm.emit_v3_HeapStructPair_bytes_r_r(tmp1, str); + var ref_addr = MasmAddr(tmp, refs_offset + masm.getOffsets().Array_contents); + var u64_addr = MasmAddr(tmp1, bytes_offset + masm.getOffsets().Array_contents); + masm.emit_ref_and_u64_mem_from_ref_u64(ref_addr, u64_addr, val); + } + _ => { + masm.emit_v3_HeapStructPair_bytes_r_r(tmp, str); + masm.emit_mov_m_r(kind, MasmAddr(tmp, offset + masm.getOffsets().Array_contents), val); + } + } + } + _ => { + emit_call_runtime_op2n(Opcode.STRUCT_SET, struct_index, field_index, 1, ValueTypes.NONE, true); + } + } } def visit_ARRAY_NEW(ht_index: u31) { emit_call_runtime_op1n(Opcode.ARRAY_NEW, ht_index, 2, ValueTypes.ONE_ARRAYREF_TYPE, true); @@ -1745,28 +1869,144 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl } def visit_ARRAY_GET(ht_index: u31) { var decl = ArrayDecl.!(module.heaptypes[ht_index]); - emit_call_runtime_op1n(Opcode.ARRAY_GET, ht_index, 2, [decl.elem_types[0].valtype], true); + var stg_type = decl.elem_types[0]; + var etype = stg_type.valtype; + var kind = ValueTypes.kind(etype); + // PACKED forms should not get here, but default to run-time call, in case + var useRunTime = false; + if (ObjReps.arrayMode != ArrayMode.Typed) useRunTime = true; + else if (stg_type.pack != Packedness.UNPACKED) useRunTime = true; + else if (ValueType.Ref.?(etype)) { + var ht = ValueType.Ref.!(etype).heap; + match (ht) { + I31 => useRunTime = true; + Cont => useRunTime = (!Continuations.boxed && !CiRuntime.FEATURE_MIXED_ARRAYS); + _ => ; + } + } + if (useRunTime) { + emit_call_runtime_op1n(Opcode.ARRAY_GET, ht_index, 2, [decl.elem_types[0].valtype], true); + return; + } + // only ArrayMode.Typed gets here + var idx = popReg().reg; + var arr = popReg().reg; + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(arr, 0, label); + var tmp = allocTmp(ValueKind.REF); + masm.emit_v3_HeapArrayI32_vals_r_r(tmp, arr); + var oob_label = masm.newTrapLabel(TrapReason.ARRAY_OOB); + masm.emit_v3_Array_bounds_check_rr(tmp, idx, oob_label); + var dst = allocRegTos(kind); + masm.emit_v3_Array_elem_r_rr(kind, dst, tmp, idx); + state.push(SpcConsts.kindToFlags(kind) | IN_REG, dst, 0); } def visit_ARRAY_GET_S(ht_index: u31) { var decl = ArrayDecl.!(module.heaptypes[ht_index]); - emit_call_runtime_op1n(Opcode.ARRAY_GET_S, ht_index, 2, [decl.elem_types[0].valtype], true); + var stg_type = decl.elem_types[0]; + var etype = stg_type.valtype; + var kind = ValueTypes.kind(etype); + match (ObjReps.arrayMode) { + Typed => { + var idx = popReg().reg; + var arr = popReg().reg; + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(arr, 0, label); + var tmp = allocTmp(ValueKind.REF); + masm.emit_v3_HeapArrayI32_vals_r_r(tmp, arr); + var oob_label = masm.newTrapLabel(TrapReason.ARRAY_OOB); + masm.emit_v3_Array_bounds_check_rr(tmp, idx, oob_label); + var dst = allocRegTos(ValueKind.I32); + match (stg_type.pack) { + PACKED_I8 => masm.emit_loadbsx_r_r_r_i(kind, dst, tmp, idx, u32.!(masm.getOffsets().Array_contents)); + PACKED_I16 => masm.emit_loadwsx_r_r_r_i_s(kind, dst, tmp, idx, u32.!(masm.getOffsets().Array_contents), 2); + _ => ; + } + state.push(SpcConsts.KIND_I32 | IN_REG, dst, 0); + } + _ => { + emit_call_runtime_op1n(Opcode.ARRAY_GET_S, ht_index, 2, [decl.elem_types[0].valtype], true); + } + } } def visit_ARRAY_GET_U(ht_index: u31) { var decl = ArrayDecl.!(module.heaptypes[ht_index]); - emit_call_runtime_op1n(Opcode.ARRAY_GET_U, ht_index, 2, [decl.elem_types[0].valtype], true); + var stg_type = decl.elem_types[0]; + var etype = stg_type.valtype; + var kind = ValueTypes.kind(etype); + match (ObjReps.arrayMode) { + Typed => { + var idx = popReg().reg; + var arr = popReg().reg; + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(arr, 0, label); + var tmp = allocTmp(ValueKind.REF); + masm.emit_v3_HeapArrayI32_vals_r_r(tmp, arr); + var oob_label = masm.newTrapLabel(TrapReason.ARRAY_OOB); + masm.emit_v3_Array_bounds_check_rr(tmp, idx, oob_label); + var dst = allocRegTos(ValueKind.I32); + match (stg_type.pack) { + PACKED_I8 => masm.emit_loadbzx_r_r_r_i(kind, dst, tmp, idx, u32.!(masm.getOffsets().Array_contents)); + PACKED_I16 => masm.emit_loadwzx_r_r_r_i_s(kind, dst, tmp, idx, u32.!(masm.getOffsets().Array_contents), 2); + _ => ; + } + state.push(SpcConsts.KIND_I32 | IN_REG, dst, 0); + } + _ => { + emit_call_runtime_op1n(Opcode.ARRAY_GET_U, ht_index, 2, [decl.elem_types[0].valtype], true); + } + } } def visit_ARRAY_SET(ht_index: u31) { - emit_call_runtime_op1n(Opcode.ARRAY_SET, ht_index, 3, ValueTypes.NONE, true); - } - def visit_ARRAY_LEN() { - var sv = popReg(); + var decl = ArrayDecl.!(module.heaptypes[ht_index]); + var stg_type = decl.elem_types[0]; + var etype = stg_type.valtype; + var kind = ValueTypes.kind(etype); + // PACKED forms may get here, but use run-time call for the moment + var useRunTime = false; + if (ObjReps.arrayMode != ArrayMode.Typed) useRunTime = true; + else if (stg_type.pack != Packedness.UNPACKED) useRunTime = true; + else if (ValueType.Ref.?(etype)) { + var ht = ValueType.Ref.!(etype).heap; + match (ht) { + I31 => useRunTime = true; + Cont => useRunTime = (!Continuations.boxed && !CiRuntime.FEATURE_MIXED_ARRAYS); + _ => ; + } + } + if (useRunTime) { + emit_call_runtime_op1n(Opcode.ARRAY_SET, ht_index, 3, ValueTypes.NONE, true); + return; + } + // only ArrayMode.Typed gets here + var val = popReg().reg; + var idx = popReg().reg; + var arr = popReg().reg; var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck - masm.emit_breq_r_l(sv.reg, 0, label); + masm.emit_breq_r_l(arr, 0, label); var tmp = allocTmp(ValueKind.REF); - var dst = allocRegTos(ValueKind.I32); - masm.emit_v3_HeapArray_vals_r_r(tmp, sv.reg); - masm.emit_v3_Array_length_r_r(dst, tmp); - state.push(KIND_I32 | IN_REG, dst, 0); + masm.emit_v3_HeapArrayI32_vals_r_r(tmp, arr); + var oob_label = masm.newTrapLabel(TrapReason.ARRAY_OOB); + masm.emit_v3_Array_bounds_check_rr(tmp, idx, oob_label); + masm.emit_v3_Array_elem_rr_r(kind, tmp, idx, val); + } + def visit_ARRAY_LEN() { + match (ObjReps.arrayMode) { + Original, Typed => { + var sv = popReg().reg; + var label = masm.newTrapLabel(TrapReason.NULL_DEREF); // XXX: constant-fold nullcheck + masm.emit_breq_r_l(sv, 0, label); + var tmp = allocTmp(ValueKind.REF); + var dst = allocRegTos(ValueKind.I32); + masm.emit_v3_HeapArray_vals_r_r(tmp, sv); + masm.emit_v3_Array_length_r_r(dst, tmp); + state.push(KIND_I32 | IN_REG, dst, 0); + } + // use as an easy handler if new array modes are added in the future + // _ => { + // emit_call_runtime_op0n(Opcode.ARRAY_LEN, 1, [ValueType.I32], true); + // } + } } def visit_ARRAY_FILL(ht_index: u31) { emit_call_runtime_op1n(Opcode.ARRAY_FILL, ht_index, 4, ValueTypes.NONE, true); @@ -1935,6 +2175,22 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl var count = u32.!(op.sig.params.length); emit_call_runtime_op2n(op, arg1, arg2, count, op.sig.results, canTrap); } + def emit_call_runtime_op0n(op: Opcode, args: u32, results: Array, canTrap: bool) { + state.emitSaveAll(resolver, runtimeSpillMode); + emit_compute_vsp(regs.vsp, state.sp); + masm.emit_store_curstack_vsp(regs.vsp); + masm.emit_get_curstack(regs.runtime_arg0); + masm.emit_v3_set_X86_64Stack_rsp_r_r(regs.runtime_arg0, regs.sp); + masm.emit_push_X86_64Stack_rsp_r_r(regs.runtime_arg0); + emit_load_instance(regs.runtime_arg1); + masm.emit_call_runtime_op(op); + masm.emit_get_curstack(regs.scratch); + masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); + dropN(args); + for (t in results) state.push(typeToKindFlags(t) | TAG_STORED | IS_STORED, NO_REG, 0); + emit_reload_regs(); + if (!runtimeSpillMode.free_regs) state.emitRestoreAll(resolver); + } def emit_call_runtime_op1n(op: Opcode, arg1: u31, args: u32, results: Array, canTrap: bool) { state.emitSaveAll(resolver, runtimeSpillMode); emit_compute_vsp(regs.vsp, state.sp); diff --git a/src/engine/continuation/BoxedContinuation.v3 b/src/engine/continuation/BoxedContinuation.v3 index 1bed91b0b..cd72528e5 100644 --- a/src/engine/continuation/BoxedContinuation.v3 +++ b/src/engine/continuation/BoxedContinuation.v3 @@ -6,14 +6,18 @@ class Continuation extends Object { new(stack) { } - def render(buf: StringBuilder) -> StringBuilder { - return buf.puts("cont"); - } } component Continuations { def NULL: Continuation = null; def valueKind = ValueKind.REF; + def boxed = true; + + // write this as a component method since having it as a method on Continuation + // leads to its use as a closure, which defeats unboxing in UnboxedContinuation + def render(cont: Continuation, sb: StringBuilder) -> StringBuilder { + return sb.put2("cont(isNull=%z,isUsed=%z)", Continuations.isNull(cont), Continuations.isUsed(cont)); + } def isNull(cont: Continuation) -> bool { return cont == null; } def isUsed(cont: Continuation) -> bool { return cont.stack == null; } @@ -29,4 +33,7 @@ component Continuations { def getStoredStack(cont: Continuation) -> WasmStack { return cont.stack; } def getStoredVersion(cont: Continuation) -> u64 { return 0; } // boxed cont does not store version + + def getObject(cont: Continuation) -> Object { return cont; } + def fromObject(obj: Exportable) -> Continuation { return Continuation.!(obj); } } diff --git a/src/engine/continuation/UnboxedContinuation.v3 b/src/engine/continuation/UnboxedContinuation.v3 index f67cee887..29d26e3de 100644 --- a/src/engine/continuation/UnboxedContinuation.v3 +++ b/src/engine/continuation/UnboxedContinuation.v3 @@ -1,15 +1,18 @@ // Copyright 2025 Wizard authors. All rights reserved. // See LICENSE for details of Apache 2.0 license. -type Continuation(stack: WasmStack, version: u64) #unboxed { - def render(buf: StringBuilder) -> StringBuilder { - return buf.puts("cont"); - } -} +type Continuation(stack: WasmStack, version: u64) #unboxed { } component Continuations { def NULL = Continuation(null, 0); def valueKind = ValueKind.REF_U64; + def boxed = false; + + // write this as a component method since having it as a method on Continuation + // leads to its use as a closure, which defeats unboxing + def render(cont: Continuation, sb: StringBuilder) -> StringBuilder{ + return sb.put2("cont(isNull=%z,version=%d)", cont.stack == null, cont.version); + } def isNull(cont: Continuation) -> bool { return cont.stack == null; } def isUsed(cont: Continuation) -> bool { return cont.stack.version != cont.version; } @@ -25,4 +28,7 @@ component Continuations { def getStoredStack(cont: Continuation) -> WasmStack { return cont.stack; } def getStoredVersion(cont: Continuation) -> u64 { return cont.version; } + + def getObject(cont: Continuation) -> Object; // should never be called + def fromObject(obj: Exportable) -> Continuation; // should never be called } diff --git a/src/engine/v3/V3Interpreter.v3 b/src/engine/v3/V3Interpreter.v3 index ff4ae0d13..63039dc3a 100644 --- a/src/engine/v3/V3Interpreter.v3 +++ b/src/engine/v3/V3Interpreter.v3 @@ -814,7 +814,7 @@ class V3Interpreter extends WasmStack { ARRAY_LEN => { var obj = popArray(); if (obj == null) trap(TrapReason.NULL_DEREF); - else push(Value.I32(u32.view(obj.vals.length))); + else push(Value.I32(u32.view(obj.length()))); } ARRAY_FILL => { var array_index = codeptr.read_uleb31(); @@ -856,12 +856,26 @@ class V3Interpreter extends WasmStack { I31_GET_S => { var v = pop(); if (v == Values.REF_NULL) trap(TrapReason.NULL_DEREF); - else pushi(i31.view(Value.I31.!(v).val)); +// else pushi(i31.view(Value.I31.!(v).val)); + else { + match (v) { + I31(val) => pushi(i31.view(val)); + Ref(obj) => pushi(i31.view(ObjectI31.!(obj).val)); + _ => pushi(0); + } + } } I31_GET_U => { var v = pop(); if (v == Values.REF_NULL) trap(TrapReason.NULL_DEREF); - else pushu(Value.I31.!(v).val); +// else pushu(u31.view(Value.I31.!(v).val)); + else { + match (v) { + I31(val) => pushu(val); + Ref(obj) => pushu(ObjectI31.!(obj).val); + _ => pushu(0); + } + } } REF_TEST, REF_TEST_NULL => { @@ -1247,7 +1261,7 @@ class V3Interpreter extends WasmStack { if (Continuations.isNull(cont)) return void(trap(TrapReason.NULL_DEREF)); if (Continuations.isUsed(cont)) return void(trap(TrapReason.USED_CONTINUATION)); - if (Trace.stack) Trace.OUT.put2("resume_throw %q %q", tag.render, cont.render).ln(); + if (Trace.stack) Trace.OUT.put2("resume_throw %q %q", tag.render, Continuations.render(cont, _)).ln(); var cont_stack = cont.stack; Continuations.setUsed(cont); @@ -1277,7 +1291,7 @@ class V3Interpreter extends WasmStack { var cont = popContinuation(); var ex = Exception.!(popr().val); - if (Trace.stack) Trace.OUT.put1("resume_throw_ref %q", cont.render).ln(); + if (Trace.stack) Trace.OUT.put1("resume_throw_ref %q", Continuations.render(cont, _)).ln(); if (Continuations.isNull(cont)) return void(trap(TrapReason.NULL_DEREF)); if (Continuations.isUsed(cont)) return void(trap(TrapReason.USED_CONTINUATION)); @@ -1785,9 +1799,16 @@ class V3Interpreter extends WasmStack { def popd() -> double { return Values.unbox_d(values.pop()); } def pops() -> (u64, u64) { return Values.unbox_s(values.pop()); } def popr() -> Value.Ref { return Value.Ref.!(values.pop()); } // TODO: i31 - def popObject() -> Object { return Value.Ref.!(values.pop()).val; } - def popArray() -> HeapArray { return HeapArray.!(Value.Ref.!(values.pop()).val); } - def popStruct() -> HeapStruct { return HeapStruct.!(Value.Ref.!(values.pop()).val); } + def popObject() -> Object { + var v = values.pop(); + match (v) { + Ref(val) => return val; + I31(val) => return ObjectI31.new(val); + _ => return null; + } + } + def popArray() -> HeapArrayGeneric { return HeapArrayGeneric.!(Value.Ref.!(values.pop()).val); } + def popStruct() -> HeapStructGeneric { return HeapStructGeneric.!(Value.Ref.!(values.pop()).val); } def popContinuation() -> Continuation { return Continuation.!(Value.Cont.!(values.pop()).val); } def push(val: Value) { values.push(val); } def pushi(val: i32) { values.push(Value.I32(u32.view(val))); } diff --git a/src/engine/x86-64/V3Offsets.v3 b/src/engine/x86-64/V3Offsets.v3 index 5de4559e9..27b751313 100644 --- a/src/engine/x86-64/V3Offsets.v3 +++ b/src/engine/x86-64/V3Offsets.v3 @@ -16,7 +16,9 @@ class V3Offsets { private def mem = NativeWasmMemory.new(null); private def vs = X86_64Stack.new(2u * 4096u); private def acc = X86_64FrameAccessor.new(vs, Pointer.NULL, decl); - private def ha = HeapArray.new(null, []); + private def ha = HeapArrayValue.new(null, []); + private def hsp = HeapStructPair.new(null, null, null); + private def hi = HeapArrayI32.new(null, []); private def cnt = CountProbe.new(); private def metric = Metric.new("", "", ""); private def whamm_Probe = WhammProbe.new(null, []); @@ -78,6 +80,11 @@ class V3Offsets { def HeapArray_vals = int.view(Pointer.atField(ha.vals) - Pointer.atObject(ha)); + def HeapStructPair_objs = int.view(Pointer.atField(hsp.objs) - Pointer.atObject(hsp)); + def HeapStructPair_bytes = int.view(Pointer.atField(hsp.bytes) - Pointer.atObject(hsp)); + + def HeapArrayI32_vals = int.view(Pointer.atField(hi.vals) - Pointer.atObject(hi)); + def CountProbe_count = int.view(Pointer.atField(cnt.count) - Pointer.atObject(cnt)); def Metric_val = int.view(Pointer.atField(metric.val) - Pointer.atObject(metric)); def WhammProbe_trampoline = int.view(Pointer.atField(whamm_Probe.trampoline.spc_entry) - Pointer.atObject(whamm_Probe)); diff --git a/src/engine/x86-64/X86_64Interpreter.v3 b/src/engine/x86-64/X86_64Interpreter.v3 index 29307ca98..3c3d0b93c 100644 --- a/src/engine/x86-64/X86_64Interpreter.v3 +++ b/src/engine/x86-64/X86_64Interpreter.v3 @@ -851,7 +851,7 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { // dispatch through LEB table after skipping LEB and clearing upper bit // not used when encoding is minimal genSkipLeb(); - asm.movd_r_r(r_tmp0, r_tmp2); // EBM check this! + asm.movd_r_r(r_tmp0, r_tmp2); asm.d.and_r_i(r_tmp0, 0x7F); genDispatch0(null, refsndary, false); writeDispatchEntry(refleb, 0x01, leb01_pos); @@ -2274,10 +2274,14 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { asm.movq_r_m(r_tmp0, vsph[-1].value); asm.q.cmp_r_i(r_tmp0, 0); asm.jc_rel_far(X86_64Conds.Z, newTrapLabel(TrapReason.NULL_DEREF)); - asm.movq_r_m(r_tmp0, r_tmp0.plus(offsets.HeapArray_vals)); - asm.movd_r_m(r_tmp0, r_tmp0.plus(offsets.Array_length)); - asm.movd_m_r(vsph[-1].value, r_tmp0); - genTagUpdate(BpTypeCode.I32.code); + match (ObjReps.arrayMode) { + Original, Typed => { + asm.movq_r_m(r_tmp0, r_tmp0.plus(offsets.HeapArray_vals)); + asm.movd_r_m(r_tmp0, r_tmp0.plus(offsets.Array_length)); + asm.movd_m_r(vsph[-1].value, r_tmp0); + genTagUpdate(BpTypeCode.I32.code); + } + } endHandler(); } bindHandler(Opcode.REF_TEST); { diff --git a/src/engine/x86-64/X86_64MacroAssembler.v3 b/src/engine/x86-64/X86_64MacroAssembler.v3 index 20a76af68..d13594f26 100644 --- a/src/engine/x86-64/X86_64MacroAssembler.v3 +++ b/src/engine/x86-64/X86_64MacroAssembler.v3 @@ -25,6 +25,9 @@ class X86_64MacroAssembler extends MacroAssembler { new(w, regConfig: RegConfig) super(Target.tagging, regConfig) { scratch = G(regConfig.scratch); } + def setTargetTracing(outf: Range -> void) { + asm.setTracing(outf); + } def printCodeBytes(sb: StringBuilder, from: u64, to: u64) { for (i = from; i < to; i++) { sb.put1("%x ", w.data[i]); @@ -104,12 +107,33 @@ class X86_64MacroAssembler extends MacroAssembler { F32 => asm.movss_s_m(X(dst), X86_64Addr.new(a, i, 4, getOffsets().Array_contents)); I64, REF => asm.movq_r_m(G(dst), X86_64Addr.new(a, i, 8, getOffsets().Array_contents)); F64 => asm.movsd_s_m(X(dst), X86_64Addr.new(a, i, 8, getOffsets().Array_contents)); - V128, REF_U64 => asm.movdqu_s_m(X(dst), X86_64Addr.new(a, i, 16, getOffsets().Array_contents)); // TODO: can't scale by 16 + V128, REF_U64 => { + asm.movq_r_r(scratch, i); + asm.q.shl_r_i(scratch, 4); // can't scale by 16 + asm.movdqu_s_m(X(dst), X86_64Addr.new(a, scratch, 1, getOffsets().Array_contents)); + } + } + } + def emit_v3_Array_elem_rr_r(kind: ValueKind, array: Reg, index: Reg, src: Reg) { + var a = G(array), i = G(index); + match (kind) { + I32 => asm.movd_m_r(X86_64Addr.new(a, i, 4, getOffsets().Array_contents), G(src)); + F32 => asm.movss_m_s(X86_64Addr.new(a, i, 4, getOffsets().Array_contents), X(src)); + I64, REF => asm.movq_m_r(X86_64Addr.new(a, i, 8, getOffsets().Array_contents), G(src)); + F64 => asm.movsd_m_s(X86_64Addr.new(a, i, 8, getOffsets().Array_contents), X(src)); + V128, REF_U64 => { + asm.movq_r_r(scratch, i); + asm.q.shl_r_i(scratch, 4); // can't scale by 16 + asm.movdqu_m_s(X86_64Addr.new(a, scratch, 1, getOffsets().Array_contents), X(src)); + } } } + def emit_v3_Array_length_r_r(dst: Reg, array: Reg) { + asm.q.movd_r_m(G(dst), X86_64Addr.new(G(array), null, 1, getOffsets().Array_length));; + } def emit_v3_Array_bounds_check_rr(array: Reg, index: Reg, oob_label: MasmLabel) { asm.d.cmp_r_m(G(index), X86_64Addr.new(G(array), null, 1, getOffsets().Array_length)); - asm.jc_rel_far(X86_64Conds.GE, X86_64MasmLabel.!(oob_label).label); + asm.jc_rel_far(X86_64Conds.NC, X86_64MasmLabel.!(oob_label).label); } def emit_v3_Memory_start_r_r(dst: Reg, memobj: Reg) { asm.movq_r_m(G(dst), X86_64Addr.new(G(memobj), null, 1, getOffsets().NativeWasmMemory_start)); @@ -135,7 +159,6 @@ class X86_64MacroAssembler extends MacroAssembler { def emit_pop_X86_64Stack_rsp_r_r(stk: Reg) { asm.q.add_m_i(G(stk).plus(offsets.X86_64Stack_rsp), Pointer.SIZE); } - def emit_loadbsx_r_r_r_i(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32) { var t = handle_large_offset(index, offset); recordCurSourceLoc(); @@ -169,6 +192,16 @@ class X86_64MacroAssembler extends MacroAssembler { recordCurSourceLoc(); asm.q.movd_r_m(G(dst), X86_64Addr.new(G(base), t.0, 1, t.1)); } + def emit_loadwsx_r_r_r_i_s(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32, scale: byte) { + var t = handle_large_offset(index, offset); + recordCurSourceLoc(); + var x = if (kind == ValueKind.I64, asm.q, asm.d).movwsx_r_m(G(dst), X86_64Addr.new(G(base), t.0, scale, t.1)); + } + def emit_loadwzx_r_r_r_i_s(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32, scale: byte) { + var t = handle_large_offset(index, offset); + recordCurSourceLoc(); + var x = if (kind == ValueKind.I64, asm.q, asm.d).movwzx_r_m(G(dst), X86_64Addr.new(G(base), t.0, scale, t.1)); + } def emit_load_r_r_r_i(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32) { var b = G(base), t = handle_large_offset(index, offset); recordCurSourceLoc(); @@ -250,6 +283,14 @@ class X86_64MacroAssembler extends MacroAssembler { V128, REF_U64 => asm.movdqu_s_m(X(reg), addr); } } + def emit_movbsx_r_m(kind: ValueKind, reg: Reg, ma: MasmAddr) { + var addr = A(ma); + match (kind) { + I32 => asm.d.movbsx_r_m(G(reg), addr); + I64 => asm.q.movbsx_r_m(G(reg), addr); + _ => ; + } + } def emit_mov_r_i(reg: Reg, val: int) { asm.movd_r_i(G(reg), val); } @@ -1591,6 +1632,14 @@ class X86_64MacroAssembler extends MacroAssembler { def emit_u64_from_ref_u64(to: Reg, from: Reg) { asm.pextrq_r_s_i(G(to), X(from), 1); } + def emit_ref_u64_from_ref_and_u64_mem(to: Reg, ref_addr: MasmAddr, u64_addr: MasmAddr) { + asm.pinsrq_s_m_i(X(to), A(ref_addr), 0); + asm.pinsrq_s_m_i(X(to), A(u64_addr), 1); + } + def emit_ref_and_u64_mem_from_ref_u64(ref_addr: MasmAddr, u64_addr: MasmAddr, from: Reg) { + asm.pextrq_m_s_i(A(ref_addr), X(from), 0); + asm.pextrq_m_s_i(A(u64_addr), X(from), 1); + } // Reads a 32- or 64-bit unsigned LEB from {rw_ptr} into {w_dest}. def emit_read_uleb(w_dest: X86_64Gpr, rw_ptr: X86_64Gpr, w_scratch1: X86_64Gpr, w_scratch2: X86_64Gpr) -> this { diff --git a/src/engine/x86-64/X86_64SinglePassCompiler.v3 b/src/engine/x86-64/X86_64SinglePassCompiler.v3 index 8e5f0e370..24d41774c 100644 --- a/src/engine/x86-64/X86_64SinglePassCompiler.v3 +++ b/src/engine/x86-64/X86_64SinglePassCompiler.v3 @@ -1005,7 +1005,6 @@ class X86_64SinglePassCompiler extends SinglePassCompiler { def visit_ATOMIC_FENCE(flags: u8) { asm.mfence(); } - // r1 = op(r1) private def do_op1_r(kind: ValueKind, emit: (X86_64Gpr -> T)) -> bool { var sv = popRegToOverwrite(), r = G(sv.reg); diff --git a/src/engine/x86-64/X86_64Stack.v3 b/src/engine/x86-64/X86_64Stack.v3 index cb3e94c93..6c9796c79 100644 --- a/src/engine/x86-64/X86_64Stack.v3 +++ b/src/engine/x86-64/X86_64Stack.v3 @@ -374,7 +374,7 @@ class X86_64Stack extends WasmStack { return Value.Ref(null); } def popContinuation() -> Continuation { - if (FeatureDisable.unboxedConts) { + if (Continuations.boxed) { var ptr = vsp + (valuerep.tag_size - valuerep.slot_size); var cont = ptr.load(); vsp += -(valuerep.slot_size); @@ -393,10 +393,15 @@ class X86_64Stack extends WasmStack { return val; } def peekRef() -> Value { - // TODO[ss]: filter out a Value.Cont after giving it its own BpTypeCode in storage if (valuerep.tagged) { var got = peekTag(); if (!valuerep.maybeRefTag(got)) fatal(Strings.format1("value stack tag mismatch, expected ref, got %x", got)); + if (got == BpTypeCode.CONTREF.code) { + var vp = vsp + (valuerep.tag_size - valuerep.slot_size); + var cont = Continuations.continuationWithVersion(vp.load(), (vp + 8).load()); + Trace.OUT.put1("Peeked continuation from stack: %q", showCont(cont, _)).ln(); + return Value.Cont(cont); + } } return readI31OrObject(vsp + (valuerep.tag_size - valuerep.slot_size)); } @@ -412,6 +417,12 @@ class X86_64Stack extends WasmStack { def popw() -> u64 { return popb64(BpTypeCode.I64.code); } + def popf() -> float { + return float.view(popb32(BpTypeCode.F32.code)); + } + def popd() -> double { + return double.view(popb64(BpTypeCode.F64.code)); + } def popb32(tag: byte) -> u32 { checkTopTag(tag); vsp += -(valuerep.slot_size); @@ -426,10 +437,11 @@ class X86_64Stack extends WasmStack { if (valuerep.tagged) { var got = peekTag(); if (!valuerep.maybeRefTag(got)) fatal(Strings.format1("value stack tag mismatch, expected ref, got %x", got)); + if (got == BpTypeCode.CONTREF.code) fatal("expected ref, got unboxed continuation"); } vsp += -(valuerep.slot_size); var val = (vsp + valuerep.tag_size).load(); - if ((val & 1) == 1) fatal("expected ref, got i31"); + if ((val & 1) == 1) return ObjectI31.new(u31.view(val >> 1)); return (vsp + valuerep.tag_size).load(); } def checkTopTag(tag: byte) -> byte { @@ -497,12 +509,23 @@ class X86_64Stack extends WasmStack { BpTypeCode.F32.code => return Value.F32(vp.load()); BpTypeCode.F64.code => return Value.F64(vp.load()); BpTypeCode.V128.code => return Value.V128(vp.load(), (vp + 8).load()); + + BpTypeCode.CONTREF.code => { + var cont = Continuations.continuationWithVersion(vp.load(), (vp + 8).load()); + Trace.OUT.put1("Read continuation from stack: %q", showCont(cont, _)).ln(); + return Value.Cont(cont); + } + _ => { fatal(Strings.format2("unknown value tag 0x%x @ 0x%x", tag, (tp - Pointer.NULL))); return Values.REF_NULL; } } } + def showCont(cont: Continuation, sb: StringBuilder) -> StringBuilder { + var stk = Continuations.getStoredStack(cont), version = Continuations.getStoredVersion(cont); + return sb.put2("", if(stk == null, 0i64, Pointer.atObject(stk) - Pointer.NULL), version); + } def storeValue(ptr: Pointer, v: Value) { var tag_ptr = ptr; var val_ptr = ptr + valuerep.tag_size; @@ -537,10 +560,11 @@ class X86_64Stack extends WasmStack { (val_ptr + 8).store(u64.view(high)); } Cont(val) => { - if (valuerep.tagged) tag_ptr.store(BpTypeCode.REF_NULL.code); - if (FeatureDisable.unboxedConts) { // boxed continuation + if (Continuations.boxed) { + if (valuerep.tagged) tag_ptr.store(BpTypeCode.REF_NULL.code); (val_ptr + 0).store(val); - } else { // unboxed continuation + } else { + if (valuerep.tagged) tag_ptr.store(BpTypeCode.CONTREF.code); (val_ptr + 0).store(Continuations.getStoredStack(val)); (val_ptr + 8).store(Continuations.getStoredVersion(val)); } diff --git a/src/engine/x86-64/X86_64Target.v3 b/src/engine/x86-64/X86_64Target.v3 index 015db9508..60cc36dce 100644 --- a/src/engine/x86-64/X86_64Target.v3 +++ b/src/engine/x86-64/X86_64Target.v3 @@ -18,7 +18,7 @@ component Target { def forceGC = RiGc.forceGC; def newWasmStack = X86_64StackManager.getFreshStack; def recycleWasmStack = X86_64StackManager.recycleStack; - def tagging = Tagging.new(!FeatureDisable.valueTags, !FeatureDisable.simd); + def tagging = Tagging.new(!FeatureDisable.valueTags, !(FeatureDisable.simd && FeatureDisable.unboxedConts)); private var offsets: V3Offsets; diff --git a/src/modules/wizeng/WizengModule.v3 b/src/modules/wizeng/WizengModule.v3 index bb2323c5d..93e495a67 100644 --- a/src/modules/wizeng/WizengModule.v3 +++ b/src/modules/wizeng/WizengModule.v3 @@ -177,13 +177,14 @@ class WizengModule extends HostModule("wizeng") { def puta(args: Range) -> HostResult { var raw_arr = Value.Ref.!(args[0]).val; if (raw_arr == null) return HostResult.Throw(Trap.new(TrapReason.NULL_DEREF, null, null)); - var arr = HeapArray.!(raw_arr); + var arr = HeapArrayGeneric.!(raw_arr); var offset_u32 = Values.unbox_u(args[1]); var size_u32 = Values.unbox_u(args[2]); - if (ArrayUtil.boundsCheck(arr.vals, offset_u32, size_u32) < 0) { + var vals = arr.getValues(); + if (ArrayUtil.boundsCheck(vals, offset_u32, size_u32) < 0) { return HostResult.Throw(Trap.new(TrapReason.ARRAY_OOB, "when calling wizeng.puta()", null)); } - var result = Ranges.map(arr.vals[offset_u32 ..+ size_u32], Values.unbox_u8); + var result = Ranges.map(vals[offset_u32 ..+ size_u32], Values.unbox_u8); System.write(1, result); return HostResult.Value0; } diff --git a/src/util/ArrayUtil.v3 b/src/util/ArrayUtil.v3 index aab421324..915679904 100644 --- a/src/util/ArrayUtil.v3 +++ b/src/util/ArrayUtil.v3 @@ -12,15 +12,16 @@ component ArrayUtil { def boundsCheck(data: Array, offset: u64, size: u64) -> int { if (data != null) { var l = data.length; - if (offset > l) return int.min; - if (size > l) return int.min; - if ((offset + size) > l) return int.min; + if (offset > l || size > l || (offset + size) > l) return int.min; return int.view(offset); } - if (offset > 0) return int.min; - if (size > 0) return int.min; + if (offset > 0 || size > 0) return int.min; return 0; } + def boundsCheckWithLength(length: u31, offset: u64, index: u64, size: u64) -> int { + var x = offset + index; + return if(x + size > length, int.min, int.!(x)); + } def safeCopy(dst: Array, dst_offset: u64, src: Array, src_offset: u64, size: u64) -> bool { var i = boundsCheck(dst, dst_offset, size); if (i < 0) return false; @@ -44,6 +45,18 @@ component ArrayUtil { } return true; } + def safeCopyFbytes(dst: Array, dst_offset: u64, src: Array, src_offset: u64, size: u64, f: S -> D, g: (Array, u64, D) -> void, esize: u32) -> bool { + var i = boundsCheck(dst, dst_offset, size * esize); + if (i < 0) return false; + var j = boundsCheck(src, src_offset, size); + if (j < 0) return false; + var pos = dst_offset; + for (k < int.!(size)) { + g(dst, pos, f(src[j + k])); + pos += esize; + } + return true; + } def mapInPlace(array: Array, f: T -> T) -> Array { for (i < array.length) array[i] = f(array[i]); return array; diff --git a/test/unittest/CodeWriter.v3 b/test/unittest/CodeWriter.v3 new file mode 100644 index 000000000..402120cc6 --- /dev/null +++ b/test/unittest/CodeWriter.v3 @@ -0,0 +1,76 @@ +// Copyright 2025 Wizard authors. All rights reserved. +// See LICENSE for details of Apache 2.0 license. + +class CodeWriter extends DataWriter { + new() super () { } + def putOp(op: Opcode) -> this { + if (op.prefix == 0) { + putb(op.code & 0xFF); + } else { + putb(op.prefix); + var page = Opcodes.page_by_prefix[op.prefix]; + if (page.oneByte) { + putb(op.code & 0xFF); + } else { + put_sleb32(op.code); + } + } + } + def putValueType(vt: ValueType) -> this { + var tc: i32 = 0; + match (vt) { + I32 => tc = BpTypeCode.I32.val; + I64 => tc = BpTypeCode.I64.val; + F32 => tc = BpTypeCode.F32.val; + F64 => tc = BpTypeCode.F64.val; + V128 => tc = BpTypeCode.V128.val; + Ref(nullable, ht) => { + match (ht) { + ANY => if (nullable) tc = BpTypeCode.ANYREF.val; + EXTERN => if (nullable) tc = BpTypeCode.EXTERNREF.val; + EQ => if (nullable) tc = BpTypeCode.EQREF.val; + I31 => if (nullable) tc = BpTypeCode.I31REF.val; + EXN => if (nullable) tc = BpTypeCode.EXNREF.val; + NONE => if (nullable) tc = BpTypeCode.NULLREF.val; + NOFUNC => if (nullable) tc = BpTypeCode.NULLFUNCREF.val; + NOEXTERN => if (nullable) tc = BpTypeCode.NULLEXTERNREF.val; + NOCONT => if (nullable) tc = BpTypeCode.NULLCONTREF.val; + Func(x) => if (nullable && x == null) + tc = BpTypeCode.FUNCREF.val; + Struct(x) => if (nullable && x == null) + tc = BpTypeCode.STRUCTREF.val; + Array(x) => if (nullable && x == null) + tc = BpTypeCode.ARRAYREF.val; + _ => ; + } + if (tc == 0) + tc = if(nullable, BpTypeCode.REF_NULL.val, BpTypeCode.REF.val); + } + _ => ; + } + put_sleb32(tc); + match (tc) { + BpTypeCode.REF.val, BpTypeCode.REF_NULL.val => + putHeapType(ValueType.Ref.!(vt).heap); + _ => ; + } + } + def putHeapType(ht: HeapType) -> this { + match (ht) { + Func(x) => put_sleb32(if(x == null, BpHeapTypeCode.FUNC.val, x.heaptype_index)); + EXTERN => put_sleb32(BpHeapTypeCode.EXTERN.val); + ANY => put_sleb32(BpHeapTypeCode.ANY.val); + EQ => put_sleb32(BpHeapTypeCode.EQ.val); + I31 => put_sleb32(BpHeapTypeCode.I31.val); + NOFUNC => put_sleb32(BpHeapTypeCode.NOFUNC.val); + NOEXTERN => put_sleb32(BpHeapTypeCode.NOEXTERN.val); + NOCONT => put_sleb32(BpHeapTypeCode.NOCONT.val); + Struct(x) => put_sleb32(if(x == null, BpHeapTypeCode.STRUCT.val, x.heaptype_index)); + Array(x) => put_sleb32(if(x == null, BpHeapTypeCode.ARRAY.val, x.heaptype_index)); + EXN => put_sleb32(BpHeapTypeCode.EXN.val); + NONE => put_sleb32(BpHeapTypeCode.NONE.val); + + _ => ; + } + } +} diff --git a/test/unittest/ExeTest.v3 b/test/unittest/ExeTest.v3 index 7b015399c..1a945b918 100644 --- a/test/unittest/ExeTest.v3 +++ b/test/unittest/ExeTest.v3 @@ -198,6 +198,7 @@ def unused_ = TestTiers.addTests([ ("struct.get", test_struct_get), ("struct.get_su", test_struct_get_su), ("struct.set", test_struct_set), + ("struct_continuations", test_struct_continuations), ("array.new", test_array_new), ("array.new_default", test_array_new_default), ("array.get", test_array_get), @@ -205,6 +206,7 @@ def unused_ = TestTiers.addTests([ ("array.set", test_array_set), ("array.len", test_array_len), ("array.new_fixed", test_array_new_fixed), + ("array_continuations", test_array_continuations), ("ref.i31", test_ref_i31), ("i31.get_s", test_i31_get_s), ("i31.get_u", test_i31_get_u), @@ -2366,7 +2368,7 @@ def test_struct_get(t: ExeTester) { ]); t.extensions |= Extension.GC; t.sig0([ValueTypes.RefStruct(false, st)], SigCache.arr_i); - var obj = HeapStruct.new(st, array_uuu(99, 98, 97)); + var obj = heapStructNew(st, array_uuu(99, 98, 97)); for (f < st.field_types.length) { t.codev([ u8.!(Opcode.LOCAL_GET.code), 0, @@ -2374,7 +2376,7 @@ def test_struct_get(t: ExeTester) { heapIndexByte(st), byte.view(f) ]); for (i in [366u, 2777u, 0x18776655u]) { - obj.vals[f] = Value.I32(i); + obj.setFieldValue(u31.!(f), Value.I32(i)); t.args_r(obj).assert2_u(i); } } @@ -2395,7 +2397,7 @@ def test_struct_get_su(t: ExeTester) { t.sig0([ValueTypes.RefStruct(false, st)], SigCache.arr_i); var vals: Array = [0x87, 0x9765]; - var obj = HeapStruct.new(st, array_uu(vals[0], vals[1])); + var obj = heapStructNew(st, array_uu(vals[0], vals[1])); for (f < 2) { var shift: u5 = if(f == 0, 24, 16); for (opcode in [Opcode.STRUCT_GET_S, Opcode.STRUCT_GET_U]) { @@ -2418,7 +2420,7 @@ def test_struct_set(t: ExeTester) { I32_FIELD ]); t.sig0([ValueTypes.RefStruct(false, st), ValueType.I32], SigCache.arr_v); - var obj = HeapStruct.new(st, array_uuu(99, 98, 97)); + var obj = heapStructNew(st, array_uuu(99, 98, 97)); for (f < st.field_types.length) { t.codev([ u8.!(Opcode.LOCAL_GET.code), 0, @@ -2427,14 +2429,181 @@ def test_struct_set(t: ExeTester) { heapIndexByte(st), byte.view(f) ]); for (i in [366u, 2777u, 0x18776655u]) { - var prev = Arrays.dup(obj.vals); + var prev = Array.new(3); + for (j < 3) { + prev[j] = obj.getFieldValue(u31.!(j)); + } t.argsN([Value.Ref(obj), Values.box_u(i)]).assert2_none(); prev[f] = Values.box_u(i); - t.t.assertar("fields", prev, obj.vals, Values.render); + var cur = Array.new(3); + for (j < 3) { + cur[j] = obj.getFieldValue(u31.!(j)); + } + t.t.assertar("fields", prev, cur, Values.render); } } } +// This test aims to ensure that Continuations are handled properly whether +// boxed or unboxed. +def test_struct_continuations(t: ExeTester) { + t.extensions |= Extension.GC; + var st0 = t.newStruct([I32_FIELD]); // to have a Ref for the main struct + var st0_vt = ValueTypes.RefStruct(true, st0); + def REF_FIELD = StorageType(st0_vt, Packedness.UNPACKED, true); + def sig_ref = HeapType.Func(SigDecl.new(true, [], [ValueType.I32], [])); + def cont_decl = ContDecl.new(true, [], sig_ref); + def cont_vt = ValueTypes.RefCont(true, cont_decl); + def CONT_FIELD = StorageType(cont_vt, Packedness.UNPACKED, true); + + var st = t.newStruct([ // so that there is at least one Ref and one + I32_FIELD, // bytes field before and after the Continuation + REF_FIELD, + CONT_FIELD, + I32_FIELD, + REF_FIELD + ]); + var st_vt = ValueTypes.RefStruct(false, st); + + var obj00 = heapStructNew(st0, [Value.I32(1)]); // some distinct Ref objects + var obj01 = heapStructNew(st0, [Value.I32(2)]); + var obj02 = heapStructNew(st0, [Value.I32(3)]); + var obj03 = heapStructNew(st0, [Value.I32(4)]); + + var stk0 = Target.newWasmStack(); + var stk1 = Target.newWasmStack(); + var cont0 = Continuations.continuationWithVersion(stk0, 0); + var cont1 = Continuations.continuationWithVersion(stk1, 100); + var initial: Array = [Value.I32(1), Value.Ref(obj00), Value.Cont(cont0), Value.I32(2), Value.Ref(obj01)]; + + // create and run function to STRUCT_NEW this struct + t.sig0([ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt], [st_vt]); + var ht = heapIndexByte(st); + var code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.LOCAL_GET).putb(2) + .putOp(Opcode.LOCAL_GET).putb(3) + .putOp(Opcode.LOCAL_GET).putb(4) + .putOp(Opcode.STRUCT_NEW).putb(ht) + .extract(); + t.codev(code); + var res = t.run(initial); + var rtt = (st); + t.assert_struct(res.1, st, rtt, initial); + var objval = Result.Value.!(res.1).vals[0]; + + // update field 0 and check all fields + t.sig0([st_vt, ValueType.I32], [ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.STRUCT_SET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(4) + .extract(); + t.codev(code); + t.argsN([objval, Value.I32(3)]); + initial[0] = Value.I32(3); + t.assert2_res(Result.Value(initial)); + + // update field 1 and check all fields + t.sig0([st_vt, st0_vt], [ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.STRUCT_SET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(4) + .extract(); + t.codev(code); + t.argsN([objval, Value.Ref(obj02)]); + initial[1] = Value.Ref(obj02); + t.assert2_res(Result.Value(initial)); + + // update field 2 and check all fields + t.sig0([st_vt, cont_vt], [ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.STRUCT_SET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(4) + .extract(); + t.codev(code); + t.argsN([objval, Value.Cont(cont1)]); + initial[2] = Value.Cont(cont1); + t.assert2_res(Result.Value(initial)); + + // update field 3 and check all fields + t.sig0([st_vt, ValueType.I32], [ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.STRUCT_SET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(4) + .extract(); + t.codev(code); + t.argsN([objval, Value.I32(4)]); + initial[3] = Value.I32(4); + t.assert2_res(Result.Value(initial)); + + // update field 4 and check all fields + t.sig0([st_vt, st0_vt], [ValueType.I32, st0_vt, cont_vt, ValueType.I32, st0_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.STRUCT_SET).putb(ht).putb(4) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(0) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(1) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(2) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(3) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.STRUCT_GET).putb(ht).putb(4) + .extract(); + t.codev(code); + t.argsN([objval, Value.Ref(obj03)]); + initial[4] = Value.Ref(obj03); + t.assert2_res(Result.Value(initial)); +} + def test_array_new(t: ExeTester) { t.extensions |= Extension.GC; var at = t.newArray([I32_FIELD]); @@ -2484,12 +2653,16 @@ def test_array_get(t: ExeTester) { Opcode.ARRAY_GET.prefix, u8.!(Opcode.ARRAY_GET.code), heapIndexByte(at) ]); for (len < 5) { - var elems = Array.new(len); - var obj = Value.Ref(HeapArray.new(at, elems)); - for (j < len) elems[j] = Value.F64(0x99u + u32.view(len)); + var arr: HeapArrayGeneric; + match (ObjReps.arrayMode) { + Typed => arr = HeapArrayF64.new(at, Array.new(len)); + _ => arr = HeapArrayValue.new(at, Array.new(len)); + } + var obj = Value.Ref(arr); + for (j < len) arr.setValue(u32.!(j), Value.F64(0x99u + u32.view(len))); for (i < len) { - t.argsN([obj, Value.I32(u32.view(i))]).assert2_val(elems[i]); + t.argsN([obj, Value.I32(u32.view(i))]).assert2_val(arr.getValue(u32.!(i))); } t.argsN([obj, Value.I32(99)]).assert2_trap(TrapReason.ARRAY_OOB); t.argsN([obj, Value.I32(0x80000000u)]).assert2_trap(TrapReason.ARRAY_OOB); @@ -2514,10 +2687,32 @@ def test_array_get_su(t: ExeTester) { opcode.prefix, u8.!(opcode.code), heapIndexByte(at) ]); for (len < 5) { - var elems = Array.new(len); - var obj = Value.Ref(HeapArray.new(at, elems)); - for (j < len) elems[j] = Value.I32(0xFF7Fu + u32.view(j)); + var arr: HeapArrayGeneric; + match (ObjReps.arrayMode) { + Original => { + var elems = Array.new(len); + arr = HeapArrayValue.new(at, elems); + for (j < len) elems[j] = Value.I32(0xFF7Fu + u32.view(j)); + } + Typed => { + match (pack) { + PACKED_I8 => { + var elems = Array.new(len); + arr = HeapArrayI8.new(at, elems); + for (j < len) elems[j] = u8.view(0xFF7Fu + u32.view(j)); + } + PACKED_I16 => { + var elems = Array.new(len); + arr = HeapArrayI16.new(at, elems); + for (j < len) elems[j] = u16.view(0xFF7Fu + u32.view(j)); + } + _ => ; + } + } + } + var obj = Value.Ref(arr); + var elems = arr.getValues(); for (i < len) { var expected = unpackI32(Values.unbox_u(elems[i]), pack, opcode == Opcode.ARRAY_GET_S); t.argsN([obj, Value.I32(u32.view(i))]).assert2_u(expected); @@ -2543,15 +2738,22 @@ def test_array_set(t: ExeTester) { u8.!(Opcode.LOCAL_GET.code), 2, Opcode.ARRAY_SET.prefix, u8.!(Opcode.ARRAY_SET.code), heapIndexByte(at) ]); - var obj = HeapArray.new(at, [Value.F64(0x99999), Value.F64(55)]); + var obj: HeapArrayGeneric; + match (ObjReps.arrayMode) { + Typed => obj = HeapArrayF64.new(at, [0x99999u64, 55u64]); + _ => { + var vals: Array = [Value.F64(0x99999), Value.F64(55)]; + obj = HeapArrayValue.new(at, vals); + } + } var ref = Value.Ref(obj); - for (i < obj.vals.length) { + for (i < obj.length()) { for (v in [366u, 2777u, 0x18776655u]) { - var prev = Arrays.dup(obj.vals); + var prev = obj.getValues(); var got = t.run([ref, Value.I32(u32.view(i)), Value.F64(v)]).1; t.assert_req(got, Result.Value([])); prev[i] = Value.F64(v); - t.t.assertar("elems", prev, obj.vals, Values.render); + t.t.assertar("elems", prev, obj.getValues(), Values.render); } } t.argsN([ref, Value.I32(99), Values.F64_0]).assert2_trap(TrapReason.ARRAY_OOB); @@ -2570,7 +2772,12 @@ def test_array_len(t: ExeTester) { Opcode.ARRAY_LEN.prefix, u8.!(Opcode.ARRAY_LEN.code) ]); for (i < 5) { - var obj = Value.Ref(HeapArray.new(at, Array.new(i))); + var arr: HeapArrayGeneric; + match (ObjReps.arrayMode) { + Typed => arr = HeapArrayF64.new(at, Array.new(i)); + _ => arr = HeapArrayValue.new(at, Array.new(i)); + } + var obj = Value.Ref(arr); var r = t.run([obj]); t.assert_req(r.1, i_r(i)); } @@ -2606,6 +2813,99 @@ def test_array_new_fixed(t: ExeTester) { } } +// This test aims to ensure that Continuations are handled properly whether +// boxed or unboxed, and whether array are mixed or not +// EBM TODO +def test_array_continuations(t: ExeTester) { + t.extensions |= Extension.GC; + + def sig_ref = HeapType.Func(SigDecl.new(true, [], [ValueType.I32], [])); + def cont_decl = ContDecl.new(true, [], sig_ref); + def cont_vt = ValueTypes.RefCont(true, cont_decl); + def CONT_FIELD = StorageType(cont_vt, Packedness.UNPACKED, true); + + var stk0 = Target.newWasmStack(); + var stk1 = Target.newWasmStack(); + var stk2 = Target.newWasmStack(); + var stk3 = Target.newWasmStack(); + var stk4 = Target.newWasmStack(); + var stk5 = Target.newWasmStack(); + + var cont0 = Continuations.continuationWithVersion(stk0, 0); + var cont1 = Continuations.continuationWithVersion(stk1, 100); + var cont2 = Continuations.continuationWithVersion(stk2, 200); + var cont3 = Continuations.continuationWithVersion(stk3, 300); + var cont4 = Continuations.continuationWithVersion(stk4, 400); + var cont5 = Continuations.continuationWithVersion(stk5, 500); + var initial: Array = [Value.Cont(cont0), Value.Cont(cont1), Value.Cont(cont2)]; + + var at = t.newArray([CONT_FIELD]); + var at_vt = ValueType.Ref(true, HeapType.Array(at)); + + t.sig0([cont_vt, cont_vt, cont_vt], [at_vt]); + var ht = heapIndexByte(at); + var code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.LOCAL_GET).putb(2) + .putOp(Opcode.ARRAY_NEW_FIXED).putb(ht).putb(3) + .extract(); + t.codev(code); + var res = t.run(initial); + var rtt = (at); + t.assert_array(res.1, at, rtt, initial); + var arrval = Result.Value.!(res.1).vals[0]; + + // check all elements + t.sig0([at_vt], [cont_vt, cont_vt, cont_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(0) + .putOp(Opcode.ARRAY_GET).putb(ht) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(1) + .putOp(Opcode.ARRAY_GET).putb(ht) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(2) + .putOp(Opcode.ARRAY_GET).putb(ht) + .extract(); + t.codev(code); + t.argsN([arrval]); + t.assert2_res(Result.Value(initial)); + + // update element 0 and check all elements + t.sig0([at_vt, ValueType.I32, cont_vt], [cont_vt, cont_vt, cont_vt]); + code = CodeWriter.new() + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.LOCAL_GET).putb(1) + .putOp(Opcode.LOCAL_GET).putb(2) + .putOp(Opcode.ARRAY_SET).putb(ht) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(0) + .putOp(Opcode.ARRAY_GET).putb(ht) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(1) + .putOp(Opcode.ARRAY_GET).putb(ht) + .putOp(Opcode.LOCAL_GET).putb(0) + .putOp(Opcode.I32_CONST).putb(2) + .putOp(Opcode.ARRAY_GET).putb(ht) + .extract(); + t.codev(code); + t.argsN([arrval, Value.I32(0), Value.Cont(cont3)]); + initial[0] = Value.Cont(cont3); + t.assert2_res(Result.Value(initial)); + + // update element 1 and check all elements + t.argsN([arrval, Value.I32(1), Value.Cont(cont4)]); + initial[1] = Value.Cont(cont4); + t.assert2_res(Result.Value(initial)); + + // update element 2 and check all elements + t.argsN([arrval, Value.I32(2), Value.Cont(cont5)]); + initial[2] = Value.Cont(cont5); + t.assert2_res(Result.Value(initial)); +} + def test_ref_i31(t: ExeTester) { t.extensions |= Extension.GC; t.sig0(SigCache.arr_i, [ValueTypes.I31REF]); @@ -2654,8 +2954,8 @@ class RttTestVals(t: ExeTester) { def rtt1 = (st1); def rtt2a = (st2); - def o1 = HeapStruct.new(rtt1, []); - def o2a = HeapStruct.new(rtt2a, array_u(0)); + def o1 = Value.Ref(heapStructNew(rtt1, [])); + def o2a = Value.Ref(heapStructNew(rtt2a, array_u(0))); } def test_ref_test(t: ExeTester) { @@ -2669,8 +2969,8 @@ def test_ref_test(t: ExeTester) { ]); t.args_r(null).assert2_i(0); - t.args_r(x.o1).assert2_i(1); - t.args_r(x.o2a).assert2_i(1); + t.argsN([x.o1]).assert2_i(1); + t.argsN([x.o2a]).assert2_i(1); } def test_ref_test_null(t: ExeTester) { @@ -2684,8 +2984,8 @@ def test_ref_test_null(t: ExeTester) { ]); t.args_r(null).assert2_i(1); - t.args_r(x.o1).assert2_i(1); - t.args_r(x.o2a).assert2_i(1); + t.argsN([x.o1]).assert2_i(1); + t.argsN([x.o2a]).assert2_i(1); } def test_ref_cast(t: ExeTester) { @@ -2699,9 +2999,9 @@ def test_ref_cast(t: ExeTester) { var fail = TrapReason.FAILED_CAST; t.args_r(null).assert2_trap(fail); - t.args_r(x.o1).assert2_trap(fail); - t.args_r(x.o1).assert2_trap_at(fail, [3]); - t.args_r(x.o2a).assert2_r(x.o2a); + t.argsN([x.o1]).assert2_trap(fail); + t.argsN([x.o1]).assert2_trap_at(fail, [3]); + t.argsN([x.o2a]).assert2_val(x.o2a); } def test_ref_cast_null(t: ExeTester) { @@ -2715,9 +3015,9 @@ def test_ref_cast_null(t: ExeTester) { var fail = TrapReason.FAILED_CAST; t.args_r(null).assert2_r(null); - t.args_r(x.o1).assert2_trap(fail); - t.args_r(x.o1).assert2_trap_at(fail, [3]); - t.args_r(x.o2a).assert2_r(x.o2a); + t.argsN([x.o1]).assert2_trap(fail); + t.argsN([x.o1]).assert2_trap_at(fail, [3]); + t.argsN([x.o2a]).assert2_val(x.o2a); } def NULL1: byte = 1; @@ -2734,10 +3034,10 @@ def test_br_on_cast(t: ExeTester) { u8.!(Opcode.UNREACHABLE.code) ]); var fail = TrapReason.UNREACHABLE; - t.args_r(x.o2a).assert2_r(x.o2a); + t.argsN([x.o2a]).assert2_val(x.o2a); t.args_r(null).assert2_trap(fail); - t.args_r(x.o1).assert2_trap(fail); - t.args_r(x.o1).assert2_trap_at(fail, [9]); + t.argsN([x.o1]).assert2_trap(fail); + t.argsN([x.o1]).assert2_trap_at(fail, [9]); } def test_br_on_cast_null(t: ExeTester) { @@ -2752,9 +3052,9 @@ def test_br_on_cast_null(t: ExeTester) { ]); var fail = TrapReason.UNREACHABLE; t.args_r(null).assert2_r(null); - t.args_r(x.o1).assert2_trap(fail); - t.args_r(x.o1).assert2_trap_at(fail, [9]); - t.args_r(x.o2a).assert2_r(x.o2a); + t.argsN([x.o1]).assert2_trap(fail); + t.argsN([x.o1]).assert2_trap_at(fail, [9]); + t.argsN([x.o2a]).assert2_val(x.o2a); } def test_br_on_cast_fail(t: ExeTester) { @@ -2769,9 +3069,9 @@ def test_br_on_cast_fail(t: ExeTester) { ]); var fail = TrapReason.UNREACHABLE; t.args_r(null).assert2_trap(TrapReason.UNREACHABLE); - t.args_r(x.o1).assert2_r(x.o1); - t.args_r(x.o2a).assert2_trap(fail); - t.args_r(x.o2a).assert2_trap_at(fail, [9]); + t.argsN([x.o1]).assert2_val(x.o1); + t.argsN([x.o2a]).assert2_trap(fail); + t.argsN([x.o2a]).assert2_trap_at(fail, [9]); } def test_br_on_cast_fail_null(t: ExeTester) { @@ -2786,9 +3086,9 @@ def test_br_on_cast_fail_null(t: ExeTester) { ]); var fail = TrapReason.UNREACHABLE; t.args_r(null).assert2_trap(fail); - t.args_r(x.o1).assert2_r(x.o1); - t.args_r(x.o2a).assert2_trap(fail); - t.args_r(x.o2a).assert2_trap_at(fail, [9]); + t.argsN([x.o1]).assert2_val(x.o1); + t.argsN([x.o2a]).assert2_trap(fail); + t.argsN([x.o2a]).assert2_trap_at(fail, [9]); } def test_br_on_null(t: ExeTester) { @@ -2872,7 +3172,7 @@ def test_externalize(i: ExeTester) { var st = i.newStruct([ I32_FIELD ]); - var obj = HeapStruct.new(st, array_u(99)); + var obj = heapStructNew(st, array_u(99)); i.args_r(obj).assert2_r(obj); } @@ -3085,3 +3385,24 @@ def test_table_get_traploc(i: ExeTester) { ]; test_traplocs(i.args_u(3), 2, code, TrapReason.TABLE_OOB); } + +def heapStructNew(decl: StructDecl, vals: Array) -> HeapStructGeneric { + match (ObjReps.structMode) { + Original => { + var fields = Array.new(decl.field_types.length); + for (i < fields.length) { + fields[i] = vals[i]; + } + return HeapStructValue.new(decl, fields); + } + Pair => { + var bytes = if(decl.num_bytes == 0, null, Array.new(decl.num_bytes)); + var objs = if(decl.num_refs == 0, null, Array.new(decl.num_refs)); + var obj = HeapStructPair.new(decl, objs, bytes); + for (i < decl.field_types.length) { + obj.setFieldValue(u31.view(i), vals[i]); + } + return obj; + } + } +} \ No newline at end of file diff --git a/test/unittest/ExeTester.v3 b/test/unittest/ExeTester.v3 index 682555774..6eefa7cfa 100644 --- a/test/unittest/ExeTester.v3 +++ b/test/unittest/ExeTester.v3 @@ -211,22 +211,30 @@ class ExeTester(t: Tester, tiering: ExecutionStrategy) extends ModuleBuilder { def assert_struct(got: Result, expected_struct: StructDecl, rtt: HeapTypeDecl, fields: Array) { var r = assert_val1(got, "struct"), ok = r.0, hr = r.1; if (!ok) return; - if (!HeapStruct.?(hr.val)) return t.fail1("expected struct, got %q", got.render); - var obj = HeapStruct.!(hr.val); + if (!HeapStructGeneric.?(hr.val)) return t.fail1("expected struct, got %q", got.render); + var obj = HeapStructGeneric.!(hr.val); if (obj.decl != expected_struct) return t.fail2("expected heap %q, got %q", expected_struct.render, obj.decl.render); if (rtt != null && obj.decl != rtt) return t.fail2("expected rtt %q, got %q", rtt.render, obj.decl.render); - t.assertar("fields", fields, obj.vals, Values.render); + var vals = Array.new(expected_struct.field_types.length); + for (i < vals.length) { + vals[i] = obj.getFieldValue(u31.!(i)); + } + t.assertar("fields", fields, vals, Values.render); } def assert_array(got: Result, expected_array: ArrayDecl, rtt: HeapTypeDecl, elems: Array) { var r = assert_val1(got, "array"), ok = r.0, hr = r.1; if (!ok) return; - if (!HeapArray.?(hr.val)) return t.fail1("expected array, got %q", got.render); - var obj = HeapArray.!(hr.val); + if (!HeapArrayGeneric.?(hr.val)) return t.fail1("expected array, got %q", got.render); + var obj = HeapArrayGeneric.!(hr.val); if (obj.decl != expected_array) return t.fail2("expected heap %q, got %q", expected_array.render, obj.decl.render); if (rtt != null && obj.decl != rtt) return t.fail2("expected rtt %q, got %q", rtt.render, obj.decl.render); - t.assertar("elements", elems, obj.vals, Values.render); + var vals = Array.new(obj.length()); + for (i < vals.length) { + vals[i] = obj.getValue(u31.!(i)); + } + t.assertar("elements", elems, vals, Values.render); } } diff --git a/test/unittest/WasmStackTest.v3 b/test/unittest/WasmStackTest.v3 index 6f9c724d8..5974bf266 100644 --- a/test/unittest/WasmStackTest.v3 +++ b/test/unittest/WasmStackTest.v3 @@ -203,7 +203,7 @@ def test_resume_host_tc_wasm(t: WasmStackTester) { } def bindArgs(s: WasmStack) { - var a = HeapObject.new(null, []); + var a = HeapObject.new(null); var h = HostObject.new(); var f = HostFunction.new("host_a_minus_2b", SigCache.ii_i, host_a_minus_2b); s.bind([Value.Ref(a), Value.Ref(h), Value.Ref(f)]); @@ -251,7 +251,7 @@ def test_gc_stacks1(t: WasmStackTester) { var f = t.makeFunc(); s1.reset(f); - var a = HeapObject.new(null, []); + var a = HeapObject.new(null); s1.bind([Value.Ref(a), Value.I31(666777), Value.Ref(f)]); Target.forceGC(); diff --git a/test/wasm-spec/SpecTestParser.v3 b/test/wasm-spec/SpecTestParser.v3 index 327ea961a..9e2485313 100644 --- a/test/wasm-spec/SpecTestParser.v3 +++ b/test/wasm-spec/SpecTestParser.v3 @@ -844,11 +844,11 @@ type ExpectedValue { _ => return false; } RefArray => match (that) { - Ref(val) => return HeapArray.?(val); + Ref(val) => return HeapArrayGeneric.?(val); _ => return false; } RefStruct => match (that) { - Ref(val) => return HeapStruct.?(val); + Ref(val) => return HeapStructGeneric.?(val); _ => return false; } RefEq => match (that) {