diff --git a/lib/loader/index.js b/lib/loader/index.js index 8ed61bd0bb..076273abde 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -75,6 +75,10 @@ function preInstantiate(imports) { const memory = baseModule.memory || env.memory; console.log("trace: " + getString(memory, mesg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", ")); } + const ref = (imports.ref = imports.ref || {}); + ref.null = null; + ref.is_null = function(v) { return v == null; } + ref.eq = function(a, b) { return a === b; } imports.Math = imports.Math || Math; imports.Date = imports.Date || Date; diff --git a/src/builtins.ts b/src/builtins.ts index 0290b16c6c..d8bb07ac21 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -572,6 +572,11 @@ export namespace BuiltinSymbols { export const Float32Array = "~lib/typedarray/Float32Array"; export const Float64Array = "~lib/typedarray/Float64Array"; + // std/reference.ts + export const ref_null = "~lib/reference/ref.null"; + export const ref_is_null = "~lib/reference/ref.is_null"; + export const ref_eq = "~lib/reference/ref.eq"; + // compiler generated export const started = "~lib/started"; export const argc = "~lib/argc"; diff --git a/src/compiler.ts b/src/compiler.ts index 863c5ca940..0ba11288b3 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -2681,7 +2681,7 @@ export class Compiler extends DiagnosticEmitter { } } } else { - if (isManaged) { + if (type.is(TypeFlags.REFERENCE)) { // This is necessary because the first use (and assign) of the local could be taking place // in a loop, subsequently marking it retained, but the second iteration of the loop // still wouldn't release whatever is assigned in the first. Likewise, if the variable wasn't @@ -2692,7 +2692,9 @@ export class Compiler extends DiagnosticEmitter { this.makeZero(type) ) ); - flow.setLocalFlag(local.index, LocalFlags.CONDITIONALLY_RETAINED); + if (isManaged) { + flow.setLocalFlag(local.index, LocalFlags.CONDITIONALLY_RETAINED); + } } else if (local.type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) { flow.setLocalFlag(local.index, LocalFlags.WRAPPED); } @@ -3739,12 +3741,9 @@ export class Compiler extends DiagnosticEmitter { break; } case TypeKind.ANYREF: { - // TODO: ref.eq - this.error( - DiagnosticCode.Not_implemented, - expression.range - ); - expr = module.unreachable(); + let ref_eq = assert(this.program.refEqInstance); + assert(this.compileFunction(ref_eq)); + expr = module.call(ref_eq.internalName, [ leftExpr, rightExpr], NativeType.I32); break; } default: { @@ -3836,12 +3835,11 @@ export class Compiler extends DiagnosticEmitter { break; } case TypeKind.ANYREF: { - // TODO: !ref.eq - this.error( - DiagnosticCode.Not_implemented, - expression.range + let ref_eq = assert(this.program.refEqInstance); + assert(this.compileFunction(ref_eq)); + expr = module.unary(UnaryOp.EqzI32, + module.call(ref_eq.internalName, [ leftExpr, rightExpr], NativeType.I32) ); - expr = module.unreachable(); break; } default: { @@ -7168,7 +7166,7 @@ export class Compiler extends DiagnosticEmitter { this.currentType = signatureReference.type.asNullable(); return module.i32(0); } - // TODO: anyref context yields 0 + return this.makeZero(contextualType); // anyref } this.currentType = options.usizeType; return options.isWasm64 @@ -8971,6 +8969,11 @@ export class Compiler extends DiagnosticEmitter { case TypeKind.F32: return module.f32(0); case TypeKind.F64: return module.f64(0); case TypeKind.V128: return module.v128(v128_zero); + case TypeKind.ANYREF: { + let ref_null = assert(this.program.refNull); + assert(this.compileGlobal(ref_null)); + return module.global_get(ref_null.internalName, NativeType.Anyref); + } } } @@ -9069,9 +9072,13 @@ export class Compiler extends DiagnosticEmitter { flow.freeTempLocal(temp); return ret; } - // case TypeKind.ANYREF: { - // TODO: !ref.is_null - // } + case TypeKind.ANYREF: { + let ref_is_null = assert(this.program.refIsNullInstance); + assert(this.compileFunction(ref_is_null)); + return module.unary(UnaryOp.EqzI32, + module.call(ref_is_null.internalName, [ expr ], NativeType.I32) + ); + } default: { assert(false); return module.i32(0); diff --git a/src/program.ts b/src/program.ts index fdda734d7f..6ccde6523b 100644 --- a/src/program.ts +++ b/src/program.ts @@ -101,6 +101,10 @@ import { Flow } from "./flow"; +import { + BuiltinSymbols +} from "./builtins"; + /** Represents a yet unresolved `import`. */ class QueuedImport { constructor( @@ -465,6 +469,13 @@ export class Program extends DiagnosticEmitter { /** RT `__allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` */ allocArrayInstance: Function; + /** Temporary filler for the `ref.null` instruction. */ + refNull: Global | null = null; + /** Temporary filler for the `ref.is_null` instruction. */ + refIsNullInstance: Function | null = null; + /** Temporary filler for the `ref.eq` instruction. */ + refEqInstance: Function | null = null; + /** Next class id. */ nextClassId: u32 = 0; /** Next signature id. */ @@ -966,6 +977,11 @@ export class Program extends DiagnosticEmitter { this.instanceofInstance = this.requireFunction(CommonSymbols.instanceof_); this.visitInstance = this.requireFunction(CommonSymbols.visit); this.allocArrayInstance = this.requireFunction(CommonSymbols.allocArray); + if (options.hasFeature(Feature.REFERENCE_TYPES)) { + this.refNull = this.require(BuiltinSymbols.ref_null, ElementKind.GLOBAL); + this.refIsNullInstance = this.requireFunction(BuiltinSymbols.ref_is_null); + this.refEqInstance = this.requireFunction(BuiltinSymbols.ref_eq); + } // mark module exports, i.e. to apply proper wrapping behavior on the boundaries for (let file of this.filesByName.values()) { diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 12a5c09127..3c97941a31 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -36,7 +36,7 @@ declare type f64 = number; /** A 128-bit vector. */ declare type v128 = object; /** A host reference. */ -declare type anyref = object; +declare type anyref = object | null; // Compiler hints diff --git a/std/assembly/reference.ts b/std/assembly/reference.ts index e3ca7a6d72..159f0b901e 100644 --- a/std/assembly/reference.ts +++ b/std/assembly/reference.ts @@ -1,3 +1,14 @@ +/** Filler implementations. */ +@sealed @unmanaged +export declare abstract class ref { + @lazy @external("ref", "null") + static readonly null: anyref; + @external("ref", "is_null") + static is_null(ref: anyref): bool; + @external("ref", "eq") + static eq(a: anyref, b: anyref): bool; +} + /** Host reference abstraction. */ @sealed @unmanaged export abstract class Anyref { diff --git a/tests/binaryen/anyref-locals.js b/tests/binaryen/anyref-locals.js new file mode 100644 index 0000000000..a81df1fc17 --- /dev/null +++ b/tests/binaryen/anyref-locals.js @@ -0,0 +1,59 @@ +var binaryen = require("binaryen"); +// var mod = binaryen.parseText(` +// (module +// (import "ref" "null" (global $ref_null anyref)) +// (import "ref" "is_null" (func $ref_is_null (param anyref) (result i32))) +// (import "ref" "eq" (func $ref_eq (param anyref anyref) (result i32))) +// (func $test (result i32) +// (local $0 anyref) +// (local $1 anyref) +// (local.set $0 +// (global.get $ref_null) +// ) +// (local.set $1 +// (global.get $ref_null) +// ) +// (drop +// (call $ref_is_null +// (local.get $0) +// ) +// ) +// (return +// (call $ref_eq +// (local.get $0) +// (local.get $1) +// ) +// ) +// ) +// (export $test $test) +// )`); +var mod = binaryen.parseText(` +(module + (import "ref" "null" (global $ref_null anyref)) + (import "ref" "is_null" (func $ref_is_null (param anyref) (result i32))) + (func $test + (local $0 anyref) + (local.set $0 + (global.get $ref_null) + ) + (drop + (call $ref_is_null + (local.get $0) + ) + ) + ;; remove this to make it work: + (drop + (call $ref_is_null + (local.get $0) + ) + ) + ) + (export $test $test) +)`); +mod.setFeatures(binaryen.Features.ReferenceTypes); +if (!mod.validate()) console.log(":-("); +else console.log(mod.emitText()); + +mod.optimize(); +if (!mod.validate()) console.log(":-("); +else console.log("-- optimized --\n", mod.emitText()); diff --git a/tests/compiler.js b/tests/compiler.js index 3f286b89df..b2d23e120d 100644 --- a/tests/compiler.js +++ b/tests/compiler.js @@ -313,7 +313,12 @@ function testInstantiate(basename, binaryBuffer, name, glue) { }, Math, Date, - Reflect + Reflect, + ref: { + null: null, + is_null: (ref) => ref == null, + eq: (a, b) => a === b + } }; if (glue.preInstantiate) { console.log(colorsUtil.white(" [preInstantiate]")); diff --git a/tests/compiler/features/reference-types.optimized.wat b/tests/compiler/features/reference-types.optimized.wat index 5fb12cfaa9..f3a49f11b0 100644 --- a/tests/compiler/features/reference-types.optimized.wat +++ b/tests/compiler/features/reference-types.optimized.wat @@ -4,13 +4,18 @@ (type $FUNCSIG$va (func (param anyref))) (type $FUNCSIG$aaa (func (param anyref anyref) (result anyref))) (type $FUNCSIG$v (func)) + (type $FUNCSIG$ia (func (param anyref) (result i32))) + (type $FUNCSIG$vaa (func (param anyref anyref))) (type $FUNCSIG$aa (func (param anyref) (result anyref))) (import "reference-types" "someObject" (global $features/reference-types/someObject anyref)) (import "reference-types" "someKey" (global $features/reference-types/someKey anyref)) + (import "ref" "null" (global $~lib/reference/ref.null anyref)) (import "Reflect" "has" (func $~lib/bindings/Reflect/has (param anyref anyref) (result i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import "console" "log" (func $~lib/bindings/console/log (param anyref))) (import "Reflect" "get" (func $~lib/bindings/Reflect/get (param anyref anyref) (result anyref))) + (import "ref" "is_null" (func $~lib/reference/ref.is_null (param anyref) (result i32))) + (import "ref" "eq" (func $~lib/reference/ref.eq (param anyref anyref) (result i32))) (import "reference-types" "external" (func $features/reference-types/external (param anyref) (result anyref))) (memory $0 1) (data (i32.const 8) "6\00\00\00\01\00\00\00\01\00\00\006\00\00\00f\00e\00a\00t\00u\00r\00e\00s\00/\00r\00e\00f\00e\00r\00e\00n\00c\00e\00-\00t\00y\00p\00e\00s\00.\00t\00s") @@ -18,7 +23,81 @@ (export "external" (func $features/reference-types/external)) (export "internal" (func $features/reference-types/internal)) (start $start) - (func $start:features/reference-types (; 5 ;) (type $FUNCSIG$v) + (func $features/reference-types/testGlobalFillers (; 7 ;) (type $FUNCSIG$v) + global.get $~lib/reference/ref.null + call $~lib/reference/ref.is_null + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 36 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/reference/ref.null + global.get $~lib/reference/ref.null + call $~lib/reference/ref.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 37 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/reference/ref.null + global.get $~lib/reference/ref.null + call $~lib/reference/ref.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 38 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + ) + (func $features/reference-types/testLocalFillers2 (; 8 ;) (type $FUNCSIG$vaa) (param $0 anyref) (param $1 anyref) + local.get $0 + call $~lib/reference/ref.is_null + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 54 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/reference/ref.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 56 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/reference/ref.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 57 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + ) + (func $start:features/reference-types (; 9 ;) (type $FUNCSIG$v) global.get $features/reference-types/someObject global.get $features/reference-types/someKey call $~lib/bindings/Reflect/has @@ -39,17 +118,32 @@ global.get $features/reference-types/someKey call $~lib/bindings/Reflect/get call $~lib/bindings/console/log + call $features/reference-types/testGlobalFillers + global.get $~lib/reference/ref.null + call $~lib/reference/ref.is_null + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 45 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/reference/ref.null + global.get $~lib/reference/ref.null + call $features/reference-types/testLocalFillers2 ) - (func $features/reference-types/internal (; 6 ;) (type $FUNCSIG$aa) (param $0 anyref) (result anyref) + (func $features/reference-types/internal (; 10 ;) (type $FUNCSIG$aa) (param $0 anyref) (result anyref) local.get $0 call $features/reference-types/external call $features/reference-types/external call $features/reference-types/external ) - (func $start (; 7 ;) (type $FUNCSIG$v) + (func $start (; 11 ;) (type $FUNCSIG$v) call $start:features/reference-types ) - (func $null (; 8 ;) (type $FUNCSIG$v) + (func $null (; 12 ;) (type $FUNCSIG$v) nop ) ) diff --git a/tests/compiler/features/reference-types.ts b/tests/compiler/features/reference-types.ts index 61a75ead93..35129f9a89 100644 --- a/tests/compiler/features/reference-types.ts +++ b/tests/compiler/features/reference-types.ts @@ -25,3 +25,35 @@ import * as console from "bindings/console"; console.log(someObject); console.log(someKey); console.log(Reflect.get(someObject, someKey)); + +// can initialize and compare anyref globals and locals using fillers +// for ref.null, ref.is_null and ref.eq + +var a: anyref; +var b: anyref = null; + +function testGlobalFillers(): void { + assert(!a); + assert(a == b); + assert(!(a != b)); +} +testGlobalFillers(); + +function testLocalFillers(): void { + var a: anyref; + var b: anyref = null; + assert(!a); + // FIXME: 'Assertion failed: false, at: ./src/literal.h,87,makeFromInt32' as soon + // as 'a' is used twice + // assert(a == b); + // assert(!(a != b)); +} +testLocalFillers(); + +function testLocalFillers2(a: anyref, b: anyref): void { + assert(!a); + // TODO: Works when these are arguments + assert(a == b); + assert(!(a != b)); +} +testLocalFillers2(null, null); diff --git a/tests/compiler/features/reference-types.untouched.wat b/tests/compiler/features/reference-types.untouched.wat index b73dd87ff1..14cc13d431 100644 --- a/tests/compiler/features/reference-types.untouched.wat +++ b/tests/compiler/features/reference-types.untouched.wat @@ -4,23 +4,137 @@ (type $FUNCSIG$va (func (param anyref))) (type $FUNCSIG$aaa (func (param anyref anyref) (result anyref))) (type $FUNCSIG$v (func)) + (type $FUNCSIG$ia (func (param anyref) (result i32))) + (type $FUNCSIG$vaa (func (param anyref anyref))) (type $FUNCSIG$aa (func (param anyref) (result anyref))) (import "reference-types" "someObject" (global $features/reference-types/someObject anyref)) (import "reference-types" "someKey" (global $features/reference-types/someKey anyref)) + (import "ref" "null" (global $~lib/reference/ref.null anyref)) (import "Reflect" "has" (func $~lib/bindings/Reflect/has (param anyref anyref) (result i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import "console" "log" (func $~lib/bindings/console/log (param anyref))) (import "Reflect" "get" (func $~lib/bindings/Reflect/get (param anyref anyref) (result anyref))) + (import "ref" "is_null" (func $~lib/reference/ref.is_null (param anyref) (result i32))) + (import "ref" "eq" (func $~lib/reference/ref.eq (param anyref anyref) (result i32))) (import "reference-types" "external" (func $features/reference-types/external (param anyref) (result anyref))) (memory $0 1) (data (i32.const 8) "6\00\00\00\01\00\00\00\01\00\00\006\00\00\00f\00e\00a\00t\00u\00r\00e\00s\00/\00r\00e\00f\00e\00r\00e\00n\00c\00e\00-\00t\00y\00p\00e\00s\00.\00t\00s\00") (table $0 1 funcref) (elem (i32.const 0) $null) + (global $features/reference-types/a (mut anyref) (global.get $~lib/reference/ref.null)) + (global $features/reference-types/b (mut anyref) (global.get $~lib/reference/ref.null)) (export "memory" (memory $0)) (export "external" (func $features/reference-types/external)) (export "internal" (func $features/reference-types/internal)) (start $start) - (func $start:features/reference-types (; 5 ;) (type $FUNCSIG$v) + (func $features/reference-types/testGlobalFillers (; 7 ;) (type $FUNCSIG$v) + global.get $features/reference-types/a + call $~lib/reference/ref.is_null + i32.eqz + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 36 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + global.get $features/reference-types/a + global.get $features/reference-types/b + call $~lib/reference/ref.eq + i32.const 0 + i32.ne + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 37 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + global.get $features/reference-types/a + global.get $features/reference-types/b + call $~lib/reference/ref.eq + i32.eqz + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 38 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + ) + (func $features/reference-types/testLocalFillers (; 8 ;) (type $FUNCSIG$v) + (local $0 anyref) + (local $1 anyref) + global.get $~lib/reference/ref.null + local.set $0 + global.get $~lib/reference/ref.null + local.set $1 + local.get $0 + call $~lib/reference/ref.is_null + i32.eqz + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 45 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + ) + (func $features/reference-types/testLocalFillers2 (; 9 ;) (type $FUNCSIG$vaa) (param $0 anyref) (param $1 anyref) + local.get $0 + call $~lib/reference/ref.is_null + i32.eqz + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 54 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/reference/ref.eq + i32.const 0 + i32.ne + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 56 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/reference/ref.eq + i32.eqz + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 57 + i32.const 2 + call $~lib/builtins/abort + unreachable + end + ) + (func $start:features/reference-types (; 10 ;) (type $FUNCSIG$v) global.get $features/reference-types/someObject global.get $features/reference-types/someKey call $~lib/bindings/Reflect/has @@ -43,8 +157,13 @@ global.get $features/reference-types/someKey call $~lib/bindings/Reflect/get call $~lib/bindings/console/log + call $features/reference-types/testGlobalFillers + call $features/reference-types/testLocalFillers + global.get $~lib/reference/ref.null + global.get $~lib/reference/ref.null + call $features/reference-types/testLocalFillers2 ) - (func $features/reference-types/internal (; 6 ;) (type $FUNCSIG$aa) (param $0 anyref) (result anyref) + (func $features/reference-types/internal (; 11 ;) (type $FUNCSIG$aa) (param $0 anyref) (result anyref) (local $1 anyref) (local $2 anyref) (local $3 anyref) @@ -59,9 +178,9 @@ local.set $3 local.get $3 ) - (func $start (; 7 ;) (type $FUNCSIG$v) + (func $start (; 12 ;) (type $FUNCSIG$v) call $start:features/reference-types ) - (func $null (; 8 ;) (type $FUNCSIG$v) + (func $null (; 13 ;) (type $FUNCSIG$v) ) )