diff --git a/examples/tlsf/assembly/tlsf.ts b/examples/tlsf/assembly/tlsf.ts index 4a3a6a71..75ed8cce 100644 --- a/examples/tlsf/assembly/tlsf.ts +++ b/examples/tlsf/assembly/tlsf.ts @@ -229,9 +229,8 @@ class Control extends BlockHeader { // Empty lists point here, indicating free const offset = BlockHeader.SIZE + sizeof(); return load( changetype(this) - + offset + fl_index * sizeof() - ); + , offset); } /** @@ -242,10 +241,9 @@ class Control extends BlockHeader { // Empty lists point here, indicating free const offset = BlockHeader.SIZE + sizeof(); return store( changetype(this) - + offset + fl_index * sizeof(), sl_map - ); + , offset); } /** @@ -256,9 +254,8 @@ class Control extends BlockHeader { // Empty lists point here, indicating free const offset = BlockHeader.SIZE + (1 + FL_INDEX_COUNT) * sizeof(); return load( changetype(this) - + offset + (fli * SL_INDEX_COUNT + sli) * sizeof() - ); + , offset); } /** @@ -269,10 +266,9 @@ class Control extends BlockHeader { // Empty lists point here, indicating free const offset = BlockHeader.SIZE + (1 + FL_INDEX_COUNT) * sizeof(); return store( changetype(this) - + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof(), block - ); + , offset); } ///////////////////////////////// Methods /////////////////////////////////// @@ -651,9 +647,11 @@ function test_ffs_fls(): i32 { rv += (ffs(0x80008000) == 15) ? 0 : 0x20; rv += (fls(0x80000008) == 31) ? 0 : 0x40; rv += (fls(0x7FFFFFFF) == 30) ? 0 : 0x80; - rv += (fls(0x80000000) == 31) ? 0 : 0x100; - rv += (fls(0x100000000) == 32) ? 0 : 0x200; - rv += (fls(0xffffffffffffffff) == 63) ? 0 : 0x400; + if (sizeof() == 8) { + rv += (fls(0x80000000) == 31) ? 0 : 0x100; + rv += (fls(0x100000000) == 32) ? 0 : 0x200; + rv += (fls(0xffffffffffffffff) == 63) ? 0 : 0x400; + } return rv; } diff --git a/examples/ugc/assembly/ugc.ts b/examples/ugc/assembly/ugc.ts index 821016c4..7232fc82 100644 --- a/examples/ugc/assembly/ugc.ts +++ b/examples/ugc/assembly/ugc.ts @@ -273,7 +273,7 @@ function gc_scan_fn(control: Control, header: ObjectHeader | null): void { } else { // visit all referenced objects using the compiler's knowledge of this // object's layout - var classId = load(changetype(header) + ObjectHeader.SIZE); + var classId = load(changetype(header), ObjectHeader.SIZE); // switch (classId) { // arrays // strings diff --git a/src/builtins.ts b/src/builtins.ts index 0e5d4aa1..24e9ed90 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -29,7 +29,8 @@ import { HostOp, NativeType, ExpressionRef, - FunctionTypeRef + FunctionTypeRef, + ExpressionId } from "./module"; import { @@ -211,6 +212,8 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty var type: Type, ftype: FunctionTypeRef; + var offset: i32; + // NOTE that some implementations below make use of the select expression where straight-forward. // whether worth or not should probably be tested once it's known if/how embedders handle it. // search: createSelect @@ -1337,11 +1340,14 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty // memory access - case "load": // load(offset: usize) -> T - if (operands.length != 1) { + case "load": // load(offset: usize, constantOffset?: usize) -> T + if (operands.length < 1 || operands.length > 2) { if (!(typeArguments && typeArguments.length == 1)) compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0"); - compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10)); + if (operands.length < 1) + compiler.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10)); + else + compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10)); return module.createUnreachable(); } if (!(typeArguments && typeArguments.length == 1)) { @@ -1351,15 +1357,21 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty return module.createUnreachable(); } arg0 = compiler.compileExpression(operands[0], usizeType); + offset = operands.length == 2 ? evaluateConstantOffset(compiler, operands[1]) : 0; // reports + if (offset < 0) + return module.createUnreachable(); compiler.currentType = typeArguments[0]; - return module.createLoad(typeArguments[0].byteSize, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType()); + return module.createLoad(typeArguments[0].byteSize, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType(), offset); - case "store": // store(offset: usize, value: T) -> void + case "store": // store(offset: usize, value: T, constantOffset?: usize) -> void compiler.currentType = Type.void; - if (operands.length != 2) { + if (operands.length < 2 || operands.length > 3) { if (typeArguments && typeArguments.length != 1) compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments.length.toString(10)); - compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10)); + if (operands.length < 2) + compiler.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10)); + else + compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "3", operands.length.toString(10)); return module.createUnreachable(); } if (typeArguments) { @@ -1374,8 +1386,11 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty arg1 = compiler.compileExpression(operands[1], Type.i32, ConversionKind.NONE); } type = compiler.currentType; + offset = operands.length == 3 ? evaluateConstantOffset(compiler, operands[2]) : 0; // reports + if (offset < 0) + return module.createUnreachable(); compiler.currentType = Type.void; - return module.createStore(type.byteSize, arg0, arg1, type.toNativeType()); + return module.createStore(type.byteSize, arg0, arg1, type.toNativeType(), offset); case "sizeof": // sizeof() -> usize compiler.currentType = usizeType; @@ -1827,6 +1842,34 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty return module.createUnreachable(); } +function evaluateConstantOffset(compiler: Compiler, expression: Expression): i32 { + var expr: ExpressionRef; + var value: i32; + if (compiler.options.target == Target.WASM64) { + expr = compiler.precomputeExpression(expression, Type.i64); + if ( + _BinaryenExpressionGetId(expr) != ExpressionId.Const || + _BinaryenExpressionGetType(expr) != NativeType.I64 || + _BinaryenConstGetValueI64High(expr) != 0 || + (value = _BinaryenConstGetValueI64Low(expr)) < 0 + ) { + compiler.error(DiagnosticCode.Operation_not_supported, expression.range); + value = -1; + } + } else { + expr = compiler.precomputeExpression(expression, Type.i32); + if ( + _BinaryenExpressionGetId(expr) != ExpressionId.Const || + _BinaryenExpressionGetType(expr) != NativeType.I32 || + (value = _BinaryenConstGetValueI32(expr)) < 0 + ) { + compiler.error(DiagnosticCode.Operation_not_supported, expression.range); + value = -1; + } + } + return value; +} + /** Compiles a memory allocation for an instance of the specified class. */ export function compileAllocate(compiler: Compiler, cls: Class, reportNode: Node): ExpressionRef { var program = cls.program; diff --git a/std/assembly.d.ts b/std/assembly.d.ts index 09d9db95..74249a52 100644 --- a/std/assembly.d.ts +++ b/std/assembly.d.ts @@ -155,9 +155,9 @@ declare function sqrt(value: T): T; /** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */ declare function trunc(value: T): T; /** Loads a value of the specified type from memory. Equivalent to dereferncing a pointer in other languages. */ -declare function load(offset: usize): T; +declare function load(ptr: usize, constantOffset?: usize): T; /** Stores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value. */ -declare function store(offset: usize, value: T): void; +declare function store(ptr: usize, value: T, constantOffset?: usize): void; /** Returns the current memory size in units of pages. One page is 64kb. */ declare function current_memory(): i32; /** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */ diff --git a/std/portable.d.ts b/std/portable.d.ts index a4dfe478..5baf54ca 100644 --- a/std/portable.d.ts +++ b/std/portable.d.ts @@ -123,9 +123,9 @@ declare function free_memory(ptr: usize): void; /** Copies n bytes from the specified source to the specified destination in memory. These regions may overlap. */ declare function move_memory(destination: usize, source: usize, n: usize): void; /** Loads a value of the specified type from memory. Type must be `u8`. */ -declare function load(offset: usize): T; +declare function load(ptr: usize, constantOffset?: usize): T; /** Stores a value of the specified type to memory. Type must be `u8`. */ -declare function store(offset: usize, value: T): void; +declare function store(ptr: usize, value: T, constantOffset?: usize): void; /** Emits an unreachable operation that results in a runtime error when executed. */ declare function unreachable(): any; // sic diff --git a/std/portable/heap.js b/std/portable/heap.js index ecca6ab4..3e578055 100644 --- a/std/portable/heap.js +++ b/std/portable/heap.js @@ -29,11 +29,15 @@ function move_memory(dest, src, n) { }; globalScope["store"] = -function store(ptr, val) { +function store(ptr, val, off) { + if (typeof off === "number") + ptr += off; HEAP[ptr] = val; }; globalScope["load"] = function load(ptr) { + if (typeof off === "number") + ptr += off; return HEAP[ptr]; }; diff --git a/tests/compiler/builtins.optimized.wast b/tests/compiler/builtins.optimized.wast index 49cc25cd..2021bb2c 100644 --- a/tests/compiler/builtins.optimized.wast +++ b/tests/compiler/builtins.optimized.wast @@ -354,6 +354,66 @@ (i32.const 8) ) ) + (set_global $builtins/i + (i32.load + (i32.const 8) + ) + ) + (i32.store + (i32.const 8) + (get_global $builtins/i) + ) + (i32.store + (i32.const 8) + (i32.load + (i32.const 8) + ) + ) + (set_global $builtins/I + (i64.load + (i32.const 8) + ) + ) + (i64.store + (i32.const 8) + (get_global $builtins/I) + ) + (i64.store + (i32.const 8) + (i64.load + (i32.const 8) + ) + ) + (set_global $builtins/f + (f32.load + (i32.const 8) + ) + ) + (f32.store + (i32.const 8) + (get_global $builtins/f) + ) + (f32.store + (i32.const 8) + (f32.load + (i32.const 8) + ) + ) + (set_global $builtins/F + (f64.load + (i32.const 8) + ) + ) + (f64.store + (i32.const 8) + (get_global $builtins/F) + ) + (f64.store + (i32.const 8) + (f64.load + (i32.const 8) + ) + ) (set_global $builtins/i (i32.const 1067450368) ) diff --git a/tests/compiler/builtins.ts b/tests/compiler/builtins.ts index 23db9ced..4fc478d4 100644 --- a/tests/compiler/builtins.ts +++ b/tests/compiler/builtins.ts @@ -115,6 +115,16 @@ store(8, load(8)); F = load(8); store(8, F); store(8, load(8)); +const constantOffset: usize = 8; +i = load(0, constantOffset); store(0, i, constantOffset); +store(0, load(0, constantOffset), constantOffset); +I = load(0, constantOffset); store(0, I, constantOffset); +store(0, load(0, constantOffset), constantOffset); +f = load(0, constantOffset); store(0, f, constantOffset); +store(0, load(0, constantOffset), constantOffset); +F = load(0, constantOffset); store(0, F, constantOffset); +store(0, load(0, constantOffset), constantOffset); + // reinterpretation reinterpret(1.25); diff --git a/tests/compiler/builtins.wast b/tests/compiler/builtins.wast index 997c3e36..7eff9698 100644 --- a/tests/compiler/builtins.wast +++ b/tests/compiler/builtins.wast @@ -1,10 +1,12 @@ (module + (type $i (func (result i32))) (type $v (func)) (global $builtins/b (mut i32) (i32.const 0)) (global $builtins/i (mut i32) (i32.const 0)) (global $builtins/I (mut i64) (i64.const 0)) (global $builtins/f (mut f32) (f32.const 0)) (global $builtins/F (mut f64) (f64.const 0)) + (global $builtins/constantOffset i32 (i32.const 8)) (global $builtins/s (mut i32) (i32.const 0)) (global $i8.MIN_VALUE i32 (i32.const -128)) (global $i8.MAX_VALUE i32 (i32.const 127)) @@ -748,6 +750,66 @@ (i32.const 8) ) ) + (set_global $builtins/i + (i32.load offset=8 + (i32.const 0) + ) + ) + (i32.store offset=8 + (i32.const 0) + (get_global $builtins/i) + ) + (i32.store offset=8 + (i32.const 0) + (i32.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/I + (i64.load offset=8 + (i32.const 0) + ) + ) + (i64.store offset=8 + (i32.const 0) + (get_global $builtins/I) + ) + (i64.store offset=8 + (i32.const 0) + (i64.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/f + (f32.load offset=8 + (i32.const 0) + ) + ) + (f32.store offset=8 + (i32.const 0) + (get_global $builtins/f) + ) + (f32.store offset=8 + (i32.const 0) + (f32.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/F + (f64.load offset=8 + (i32.const 0) + ) + ) + (f64.store offset=8 + (i32.const 0) + (get_global $builtins/F) + ) + (f64.store offset=8 + (i32.const 0) + (f64.load offset=8 + (i32.const 0) + ) + ) (drop (i32.reinterpret/f32 (f32.const 1.25) @@ -1391,6 +1453,7 @@ GLOBAL: builtins/I GLOBAL: builtins/f GLOBAL: builtins/F + GLOBAL: builtins/constantOffset GLOBAL: builtins/s FUNCTION_PROTOTYPE: builtins/test [program.exports] diff --git a/tests/compiler/showcase.optimized.wast b/tests/compiler/showcase.optimized.wast index 1a7a7cf4..ac340c10 100644 --- a/tests/compiler/showcase.optimized.wast +++ b/tests/compiler/showcase.optimized.wast @@ -3598,6 +3598,66 @@ (i32.const 8) ) ) + (set_global $builtins/i + (i32.load + (i32.const 8) + ) + ) + (i32.store + (i32.const 8) + (get_global $builtins/i) + ) + (i32.store + (i32.const 8) + (i32.load + (i32.const 8) + ) + ) + (set_global $builtins/I + (i64.load + (i32.const 8) + ) + ) + (i64.store + (i32.const 8) + (get_global $builtins/I) + ) + (i64.store + (i32.const 8) + (i64.load + (i32.const 8) + ) + ) + (set_global $builtins/f + (f32.load + (i32.const 8) + ) + ) + (f32.store + (i32.const 8) + (get_global $builtins/f) + ) + (f32.store + (i32.const 8) + (f32.load + (i32.const 8) + ) + ) + (set_global $builtins/F + (f64.load + (i32.const 8) + ) + ) + (f64.store + (i32.const 8) + (get_global $builtins/F) + ) + (f64.store + (i32.const 8) + (f64.load + (i32.const 8) + ) + ) (set_global $builtins/i (i32.const 1067450368) ) diff --git a/tests/compiler/showcase.wast b/tests/compiler/showcase.wast index 939f28bb..68c7c25f 100644 --- a/tests/compiler/showcase.wast +++ b/tests/compiler/showcase.wast @@ -1,6 +1,6 @@ (module - (type $ii (func (param i32) (result i32))) (type $i (func (result i32))) + (type $ii (func (param i32) (result i32))) (type $iii (func (param i32 i32) (result i32))) (type $fff (func (param f32 f32) (result f32))) (type $FFF (func (param f64 f64) (result f64))) @@ -33,6 +33,7 @@ (global $builtins/I (mut i64) (i64.const 0)) (global $builtins/f (mut f32) (f32.const 0)) (global $builtins/F (mut f64) (f64.const 0)) + (global $builtins/constantOffset i32 (i32.const 8)) (global $builtins/s (mut i32) (i32.const 0)) (global $i8.MIN_VALUE i32 (i32.const -128)) (global $i8.MAX_VALUE i32 (i32.const 127)) @@ -5134,6 +5135,66 @@ (i32.const 8) ) ) + (set_global $builtins/i + (i32.load offset=8 + (i32.const 0) + ) + ) + (i32.store offset=8 + (i32.const 0) + (get_global $builtins/i) + ) + (i32.store offset=8 + (i32.const 0) + (i32.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/I + (i64.load offset=8 + (i32.const 0) + ) + ) + (i64.store offset=8 + (i32.const 0) + (get_global $builtins/I) + ) + (i64.store offset=8 + (i32.const 0) + (i64.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/f + (f32.load offset=8 + (i32.const 0) + ) + ) + (f32.store offset=8 + (i32.const 0) + (get_global $builtins/f) + ) + (f32.store offset=8 + (i32.const 0) + (f32.load offset=8 + (i32.const 0) + ) + ) + (set_global $builtins/F + (f64.load offset=8 + (i32.const 0) + ) + ) + (f64.store offset=8 + (i32.const 0) + (get_global $builtins/F) + ) + (f64.store offset=8 + (i32.const 0) + (f64.load offset=8 + (i32.const 0) + ) + ) (drop (i32.reinterpret/f32 (f32.const 1.25) @@ -6307,6 +6368,7 @@ GLOBAL: builtins/I GLOBAL: builtins/f GLOBAL: builtins/F + GLOBAL: builtins/constantOffset GLOBAL: builtins/s FUNCTION_PROTOTYPE: builtins/test FUNCTION_PROTOTYPE: memcpy/memcpy