diff --git a/assembly.d.ts b/assembly.d.ts index d20654f6..3003ff33 100644 --- a/assembly.d.ts +++ b/assembly.d.ts @@ -30,36 +30,36 @@ declare type f64 = number; // builtins /** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */ -declare function clz(value: T): T; +declare function clz(value: T): T; /** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */ -declare function ctz(value: T): T; +declare function ctz(value: T): T; /** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */ -declare function popcnt(value: T): T; +declare function popcnt(value: T): T; /** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */ -declare function rotl(value: T, shift: T): T; +declare function rotl(value: T, shift: T): T; /** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */ -declare function rotr(value: T, shift: T): T; +declare function rotr(value: T, shift: T): T; /** Computes the absolute value of a 32-bit or 64-bit float. */ -declare function abs(value: T): T; +declare function abs(value: T): T; /** Performs the ceiling operation on a 32-bit or 64-bit float. */ -declare function ceil(value: T): T; +declare function ceil(value: T): T; /** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */ -declare function copysign(x: T, y: T): T; +declare function copysign(x: T, y: T): T; /** Performs the floor operation on a 32-bit or 64-bit float. */ -declare function floor(value: T): T; +declare function floor(value: T): T; /** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function max(left: T, right: T): T; +declare function max(left: T, right: T): T; /** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function min(left: T, right: T): T; +declare function min(left: T, right: T): T; /** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */ -declare function nearest(value: T): T; +declare function nearest(value: T): T; /** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */ -declare function reinterpret(value: T1): T2; +declare function reinterpret(value: T1): T2; /** Calculates the square root of a 32-bit or 64-bit float. */ -declare function sqrt(value: T): T; +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; +declare function trunc(value: T): T; /** Returns the current memory size in units of pages. One page is 64kb. */ declare function current_memory(): i32; @@ -76,14 +76,14 @@ declare function store(offset: usize, value: T): void; declare function sizeof(): usize; /** NaN (not a number) as a 32-bit or 64-bit float depending on context. */ -declare const NaN: number; +declare const NaN: f32 | f64; /** Positive infinity as a 32-bit or 64-bit float depending on context. */ -declare const Infinity: number; +declare const Infinity: f32 | f64; /** Tests if a 32-bit or 64-bit float is NaN. */ -declare function isNaN(value: T): bool; +declare function isNaN(value: T): bool; /** Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity. */ -declare function isFinite(value: T): bool; +declare function isFinite(value: T): bool; /** Traps if the specified value is `false`. */ declare function assert(isTrue: bool): void; diff --git a/src/compiler.ts b/src/compiler.ts index 49794986..bbb8f7b9 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -88,6 +88,8 @@ export class Options { noEmit: bool = false; /** If true, compiles everything instead of just reachable code. */ noTreeShaking: bool = false; + /** If true, replaces assertions with nops. */ + noDebug: bool = false; } const enum ConversionKind { @@ -1398,16 +1400,73 @@ export class Compiler extends DiagnosticEmitter { const functionPrototype: FunctionPrototype = element; let functionInstance: Function | null = null; if (functionPrototype.isBuiltin) { + const k: i32 = expression.typeArguments.length; + const resolvedTypeArguments: Type[] = new Array(k); sb.length = 0; - for (let i: i32 = 0, k: i32 = expression.typeArguments.length; i < k; ++i) { - let type: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports - if (!type) + for (let i: i32 = 0; i < k; ++i) { + let resolvedType: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports + if (!resolvedType) return this.module.createUnreachable(); - sb.push(type.toString()); + resolvedTypeArguments[i] = resolvedType; + sb.push(resolvedType.toString()); } + functionInstance = functionPrototype.instances.get(sb.join(",")); if (!functionInstance) { - // TODO: sizeof, load, store, see program.ts/initializeBuiltins + let arg0: ExpressionRef, arg1: ExpressionRef; + + if (functionPrototype.internalName == "sizeof") { // no parameters + this.currentType = this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32; + if (k != 1) { + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); + return this.module.createUnreachable(); + } + if (expression.arguments.length != 0) { + this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "0", expression.arguments.length.toString()); + return this.module.createUnreachable(); + } + return this.options.target == Target.WASM64 + ? this.module.createI64(resolvedTypeArguments[0].byteSize, 0) + : this.module.createI32(resolvedTypeArguments[0].byteSize); + + } else if (functionPrototype.internalName == "load") { + this.currentType = resolvedTypeArguments[0]; + if (k != 1) { + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); + return this.module.createUnreachable(); + } + if (expression.arguments.length != 1) { + this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "1", expression.arguments.length.toString()); + return this.module.createUnreachable(); + } + arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports + this.currentType = resolvedTypeArguments[0]; + if (!arg0) + return this.module.createUnreachable(); + return this.module.createLoad(resolvedTypeArguments[0].byteSize, resolvedTypeArguments[0].isSignedInteger, arg0, typeToNativeType(resolvedTypeArguments[0])); + + } else if (functionPrototype.internalName == "store") { + this.currentType = Type.void; + if (k != 1) { + this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); + return this.module.createUnreachable(); + } + if (expression.arguments.length != 2) { + this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "2", expression.arguments.length.toString()); + return this.module.createUnreachable(); + } + arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports + this.currentType = Type.void; + if (!arg0) + return this.module.createUnreachable(); + arg1 = this.compileExpression(expression.arguments[1], resolvedTypeArguments[0], ConversionKind.IMPLICIT); + this.currentType = Type.void; + if (!arg1) + return this.module.createUnreachable(); + return this.module.createStore(resolvedTypeArguments[0].byteSize, arg0, arg1, typeToNativeType(resolvedTypeArguments[0])); + } + this.error(DiagnosticCode.Operation_not_supported, expression.range); + return this.module.createUnreachable(); } } else { // TODO: infer type arguments from parameter types if omitted @@ -1458,8 +1517,8 @@ export class Compiler extends DiagnosticEmitter { this.currentType = functionInstance.returnType; - let tempLocal: Local; if (functionInstance.isBuiltin) { + let tempLocal: Local; switch (functionInstance.template.internalName) { case "clz": // i32/i64.clz @@ -1570,11 +1629,6 @@ export class Compiler extends DiagnosticEmitter { case "unreachable": return this.module.createUnreachable(); - case "sizeof": // T.size - if (this.options.target == Target.WASM64) - return this.module.createI64(Math.ceil(functionInstance.typeArguments[0].size / 8), 0); - return this.module.createI32(Math.ceil(functionInstance.typeArguments[0].size / 8)); - case "isNaN": // value != value if (functionInstance.typeArguments[0] == Type.f64) { tempLocal = this.currentFunction.addLocal(Type.f64); @@ -1626,10 +1680,12 @@ export class Compiler extends DiagnosticEmitter { break; case "assert": - return this.module.createIf( - this.module.createUnary(UnaryOp.EqzI32, operands[0]), - this.module.createUnreachable() - ); + return this.options.noDebug + ? this.module.createNop() + : this.module.createIf( + this.module.createUnary(UnaryOp.EqzI32, operands[0]), + this.module.createUnreachable() + ); } this.error(DiagnosticCode.Operation_not_supported, reportNode.range); return this.module.createUnreachable(); diff --git a/src/glue/js.d.ts b/src/glue/js.d.ts index 8badbc75..27af38aa 100644 --- a/src/glue/js.d.ts +++ b/src/glue/js.d.ts @@ -11,6 +11,6 @@ declare type f32 = number; declare type f64 = number; declare type bool = boolean; -// Raw memory access (here: Binaryen memory, T=u8) -declare function store(ptr: usize, val: u8): void; -declare function load(ptr: usize): u8; +// Raw memory access (here: Binaryen memory) +declare function store(ptr: usize, val: T): void; +declare function load(ptr: usize): T; diff --git a/src/program.ts b/src/program.ts index 820a20da..c2bf4c7f 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1149,19 +1149,21 @@ function initializeBuiltins(program: Program): void { addGenericUnaryBuiltin(program, "sqrt", genericFloat); addGenericUnaryBuiltin(program, "trunc", genericFloat); - addBuiltin(program, "current_memory", [], usize); - addBuiltin(program, "grow_memory", [ usize ], usize); - addBuiltin(program, "unreachable", [], Type.void); + addSimpleBuiltin(program, "current_memory", [], usize); + addSimpleBuiltin(program, "grow_memory", [ usize ], usize); + addSimpleBuiltin(program, "unreachable", [], Type.void); addGenericUnaryTestBuiltin(program, "isNaN", genericFloat); addGenericUnaryTestBuiltin(program, "isFinite", genericFloat); - addBuiltin(program, "assert", [ Type.bool ], Type.void); + addSimpleBuiltin(program, "assert", [ Type.bool ], Type.void); - // TODO: load, store, sizeof - // sizeof, for example, has varying Ts but really shouldn't provide an instance for each class + addGenericAnyBuiltin(program, "sizeof"); + addGenericAnyBuiltin(program, "load"); + addGenericAnyBuiltin(program, "store"); } -function addBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) { +/** Adds a simple (non-generic) builtin. */ +function addSimpleBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) { let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); prototype.isGeneric = false; prototype.isBuiltin = true; @@ -1173,6 +1175,7 @@ function addBuiltin(program: Program, name: string, parameterTypes: Type[], retu program.elements.set(name, prototype); } +/** Adds a generic unary builtin that takes and returns a value of its generic type. */ function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): void { let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); prototype.isGeneric = true; @@ -1184,6 +1187,7 @@ function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): program.elements.set(name, prototype); } +/** Adds a generic binary builtin that takes two and returns a value of its generic type. */ function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): void { let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); prototype.isGeneric = true; @@ -1195,6 +1199,7 @@ function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): program.elements.set(name, prototype); } +/** Adds a generic unary builtin that alwways returns `bool`. */ function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void { let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); prototype.isGeneric = true; @@ -1205,3 +1210,12 @@ function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[ } program.elements.set(name, prototype); } + +/** Adds a special generic builtin that takes any type argument. */ +function addGenericAnyBuiltin(program: Program, name: string): void { + let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); + prototype.isGeneric = true; + prototype.isBuiltin = true; + program.elements.set(name, prototype); + // instances are hard coded in compiler.ts +} diff --git a/src/types.ts b/src/types.ts index fea29639..b28aa58e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,7 @@ export class Type { kind: TypeKind; size: i32; + byteSize: i32; classType: Class | null; nullable: bool = false; nullableType: Type | null = null; // cached, of this type @@ -36,6 +37,7 @@ export class Type { constructor(kind: TypeKind, size: i32) { this.kind = kind; this.size = size; + this.byteSize = Math.ceil(size / 8); this.classType = null; } diff --git a/tests/compiler/binary.wast b/tests/compiler/binary.wast index 2f275ada..9a26e7e6 100644 --- a/tests/compiler/binary.wast +++ b/tests/compiler/binary.wast @@ -838,6 +838,9 @@ isNaN isFinite assert + sizeof + load + store binary/b binary/i binary/I diff --git a/tests/compiler/builtins.ts b/tests/compiler/builtins.ts index 71816910..ccf5a35b 100644 --- a/tests/compiler/builtins.ts +++ b/tests/compiler/builtins.ts @@ -104,3 +104,20 @@ s = grow_memory(1); if (0) unreachable(); assert(true); + +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); +sizeof(); + +i = load(4); +store(4, i); diff --git a/tests/compiler/builtins.wast b/tests/compiler/builtins.wast index 68aa2d5a..c7a188c1 100644 --- a/tests/compiler/builtins.wast +++ b/tests/compiler/builtins.wast @@ -475,6 +475,54 @@ ) (unreachable) ) + (drop + (i32.const 1) + ) + (drop + (i32.const 2) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 8) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 1) + ) + (drop + (i32.const 1) + ) + (drop + (i32.const 2) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 8) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 4) + ) + (drop + (i32.const 8) + ) + (set_global $builtins/i + (i32.load + (i32.const 4) + ) + ) + (i32.store + (i32.const 4) + (get_global $builtins/i) + ) ) ) (; @@ -499,6 +547,9 @@ isNaN isFinite assert + sizeof + load + store builtins/b builtins/i builtins/I diff --git a/tests/compiler/do.wast b/tests/compiler/do.wast index 4dbdd366..e3a41b6c 100644 --- a/tests/compiler/do.wast +++ b/tests/compiler/do.wast @@ -77,6 +77,9 @@ isNaN isFinite assert + sizeof + load + store do/loopDo do/loopDoInDo [program.exports] diff --git a/tests/compiler/export.wast b/tests/compiler/export.wast index 219bc5cf..546bd965 100644 --- a/tests/compiler/export.wast +++ b/tests/compiler/export.wast @@ -46,6 +46,9 @@ isNaN isFinite assert + sizeof + load + store export/add export/sub export/a diff --git a/tests/compiler/if.wast b/tests/compiler/if.wast index 00437e2a..cccbdc97 100644 --- a/tests/compiler/if.wast +++ b/tests/compiler/if.wast @@ -68,6 +68,9 @@ isNaN isFinite assert + sizeof + load + store if/ifThenElse if/ifThen if/ifThenElseBlock diff --git a/tests/compiler/import.wast b/tests/compiler/import.wast index 168866b4..1481eccc 100644 --- a/tests/compiler/import.wast +++ b/tests/compiler/import.wast @@ -60,6 +60,9 @@ isNaN isFinite assert + sizeof + load + store export/add export/sub export/a diff --git a/tests/compiler/literals.wast b/tests/compiler/literals.wast index 845292be..776d19ad 100644 --- a/tests/compiler/literals.wast +++ b/tests/compiler/literals.wast @@ -161,6 +161,9 @@ isNaN isFinite assert + sizeof + load + store [program.exports] ;) diff --git a/tests/compiler/reexport.wast b/tests/compiler/reexport.wast index 79bd0241..3ac882ef 100644 --- a/tests/compiler/reexport.wast +++ b/tests/compiler/reexport.wast @@ -62,6 +62,9 @@ isNaN isFinite assert + sizeof + load + store export/add export/sub export/a diff --git a/tests/compiler/switch.wast b/tests/compiler/switch.wast index e292a04b..7704c19e 100644 --- a/tests/compiler/switch.wast +++ b/tests/compiler/switch.wast @@ -167,6 +167,9 @@ isNaN isFinite assert + sizeof + load + store switch/doSwitch switch/doSwitchDefaultFirst switch/doSwitchDefaultOmitted diff --git a/tests/compiler/unary.wast b/tests/compiler/unary.wast index c2a61148..f79cbe41 100644 --- a/tests/compiler/unary.wast +++ b/tests/compiler/unary.wast @@ -655,6 +655,9 @@ isNaN isFinite assert + sizeof + load + store unary/i unary/I unary/f diff --git a/tests/compiler/while.wast b/tests/compiler/while.wast index a5af328e..daa4a241 100644 --- a/tests/compiler/while.wast +++ b/tests/compiler/while.wast @@ -86,6 +86,9 @@ isNaN isFinite assert + sizeof + load + store while/loopWhile while/loopWhileInWhile [program.exports]