From a6677bc495ad0a4d2aa8f940df1ecbdc3e15a263 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 13 Aug 2020 07:55:17 +0200 Subject: [PATCH 1/3] Extern classes experiment --- lib/loader/index.d.ts | 3 + lib/loader/index.js | 28 +++++ src/ast.ts | 4 +- src/program.ts | 12 +- src/types.ts | 4 +- std/assembly/bindings/DOM.ts | 106 ++++++++++++++++++ std/assembly/bindings/Reflect.ts | 4 - std/assembly/bindings/console.ts | 10 -- std/assembly/index.d.ts | 4 +- tests/compiler/bindings/dom.js | 11 ++ tests/compiler/bindings/dom.json | 8 ++ tests/compiler/bindings/dom.optimized.wat | 36 ++++++ tests/compiler/bindings/dom.ts | 9 ++ tests/compiler/bindings/dom.untouched.wat | 44 ++++++++ .../features/reference-types.optimized.wat | 36 +----- tests/compiler/features/reference-types.ts | 42 ------- .../features/reference-types.untouched.wat | 42 +------ tests/features.json | 2 +- 18 files changed, 264 insertions(+), 141 deletions(-) create mode 100644 std/assembly/bindings/DOM.ts delete mode 100644 std/assembly/bindings/Reflect.ts delete mode 100644 std/assembly/bindings/console.ts create mode 100644 tests/compiler/bindings/dom.js create mode 100644 tests/compiler/bindings/dom.json create mode 100644 tests/compiler/bindings/dom.optimized.wat create mode 100644 tests/compiler/bindings/dom.ts create mode 100644 tests/compiler/bindings/dom.untouched.wat diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index bc6854277a..ab4a6a6542 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -122,3 +122,6 @@ export declare function demangle>( exports: Record, extendedExports?: Record ): T; + +/** Binds a JavaScript object to its corresponding WebAssembly imports. */ +export declare function bind(name: string, obj: Record, base?: Record); diff --git a/lib/loader/index.js b/lib/loader/index.js index 85b757be93..0d828beacc 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -426,3 +426,31 @@ function demangle(exports, extendedExports = {}) { } exports.demangle = demangle; + +/** Binds a JavaScript object to its corresponding WebAssembly imports. */ +function bind(name, obj, base) { + if (!base) base = {}; + var isPrototype = false; + for (let i = 0; i < 2; ++i) { + const prefix = isPrototype ? name + "#" : name + "."; + for (const [key, { value }] of Object.entries(Object.getOwnPropertyDescriptors(obj))) { + if (typeof value === "function") { + base[prefix + key] = isPrototype + ? (thisArg, ...args) => thisArg[key](...args) + : value; + } else { + base[prefix + "get:" + key] = isPrototype + ? (thisArg) => thisArg[key] + : () => obj[key]; + base[prefix + "set:" + key] = isPrototype + ? (thisArg, value) => thisArg[key] = value + : (value) => obj[key] = value; + } + } + if (!(obj = obj.prototype)) break; + isPrototype = true; + } + return base; +} + +exports.bind = bind; diff --git a/src/ast.ts b/src/ast.ts index b3b4df6258..546385693a 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -951,7 +951,8 @@ export enum DecoratorKind { EXTERNAL, BUILTIN, LAZY, - UNSAFE + UNSAFE, + EXTERN } export namespace DecoratorKind { @@ -968,6 +969,7 @@ export namespace DecoratorKind { } case CharCode.e: { if (nameStr == "external") return DecoratorKind.EXTERNAL; + if (nameStr == "extern") return DecoratorKind.EXTERN; break; } case CharCode.f: { diff --git a/src/program.ts b/src/program.ts index 63fdf560da..863f970ab4 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1710,7 +1710,8 @@ export class Program extends DiagnosticEmitter { this.checkDecorators(declaration.decorators, DecoratorFlags.GLOBAL | DecoratorFlags.FINAL | - DecoratorFlags.UNMANAGED + DecoratorFlags.UNMANAGED | + DecoratorFlags.EXTERN ) ); if (!parent.add(name, element)) return null; @@ -2582,7 +2583,9 @@ export enum DecoratorFlags { /** Is compiled lazily. */ LAZY = 1 << 9, /** Is considered unsafe code. */ - UNSAFE = 1 << 10 + UNSAFE = 1 << 10, + /** Is an extern class. */ + EXTERN = 1 << 11, } export namespace DecoratorFlags { @@ -2602,6 +2605,7 @@ export namespace DecoratorFlags { case DecoratorKind.BUILTIN: return DecoratorFlags.BUILTIN; case DecoratorKind.LAZY: return DecoratorFlags.LAZY; case DecoratorKind.UNSAFE: return DecoratorFlags.UNSAFE; + case DecoratorKind.EXTERN: return DecoratorFlags.EXTERN; default: return DecoratorFlags.NONE; } } @@ -4101,7 +4105,9 @@ export class Class extends TypedElement { this.decoratorFlags = prototype.decoratorFlags; this.typeArguments = typeArguments; var usizeType = program.options.usizeType; - var type = new Type(usizeType.kind, usizeType.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, usizeType.size); + var type = this.hasDecorator(DecoratorFlags.EXTERN) + ? new Type(Type.externref.kind, Type.externref.flags, 0) + : new Type(usizeType.kind, usizeType.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, usizeType.size); type.classReference = this; this.setType(type); diff --git a/src/types.ts b/src/types.ts index 671daec53a..32a6676d75 100644 --- a/src/types.ts +++ b/src/types.ts @@ -250,14 +250,14 @@ export class Type { /** Tests if this type represents a class. */ get isClass(): bool { - return this.isInternalReference + return this.isReference ? this.classReference !== null : false; } /** Gets the underlying class of this type, if any. */ getClass(): Class | null { - return this.isInternalReference + return this.isReference ? this.classReference : null; } diff --git a/std/assembly/bindings/DOM.ts b/std/assembly/bindings/DOM.ts new file mode 100644 index 0000000000..db85992635 --- /dev/null +++ b/std/assembly/bindings/DOM.ts @@ -0,0 +1,106 @@ +@extern +export declare class Object { + static is(value1: Object, value2: Object): bool; + constructor(other: Object); + hasOwnProperty(prop: String): bool; + toString(): String; + // ... +} + +@extern +export declare class String extends Object { + static fromCharCode(code: i32): String; + static fromCodePoint(code: i32): String; + constructor(other: Object); + get length(): i32; + charAt(index: i32): String; + charCodeAt(index: i32): i32; + codePointAt(index: i32): i32; + concat(other: String): String; + endsWith(other: String): bool; + includes(other: String): bool; + indexOf(other: String): i32; + lastIndexOf(other: String): i32; + localeCompare(other: String): i32; + match(pattern: RegExp): Array; + matchAll(pattern: RegExp): Array; + normalize(): String; + padEnd(targetLength: i32, padString: String): String; + padStart(targetLength: i32, padString: String): String; + repeat(count: i32): String; + replace(pattern: String, replacement: String): String; + replaceAll(pattern: String, replacement: String): String; + search(pattern: RegExp): i32; + slice(beginIndex: i32, endIndex?: i32): String; + // ... +} +export declare function newString(value: string): String; + +@extern +export declare class Number extends Object { + // ... +} +export declare function newNumber(value: i32): Number; + +@extern +export declare class Array extends Object { + constructor(length: i32); + get length(): i32; + // ... +} + +@extern +export declare class RegExp extends Object { + // ... +} + +@extern +export declare class console { + static assert(value: Object): void; + static clear(): void; + static error(value: Object): void; + static info(value: Object): void; + static log(value: Object): void; + static time(label: Object): externref; + static timeEnd(label: Object): void; + static timeLog(label: Object): void; + static trace(): void; + static warn(value: Object): void; +} + +@extern +export declare class HTMLCanvasElement { + // ... +} + +@extern +export declare class CanvasRenderingContext2D { + get canvas(): HTMLCanvasElement; + + // state + save(): void; + restore(): void; + + // transformations + scale(x: f64, y: f64): void; + rotate(angle: f64): void; + translate(x: f64, y: f64): void; + transform(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64): void; + setTransform(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64): void; + + // compositing + get globalAlpha(): f64; + set globalAlpha(value: f64); + get globalCompositeOperation(): String; + set globalCompositeOperation(value: String); + + // ... +} + +@extern +export declare class Reflect { + static get(target: Object, propertyKey: Object/* , receiver: externref */): Object; + static has(target: Object, propertyKey: Object): bool; + static set(target: Object, propertyKey: Object, value: Object/* , receiver: externref */): Object; + static apply(target: Object, thisArgument: Object, argumentsList: Object): Object; +} diff --git a/std/assembly/bindings/Reflect.ts b/std/assembly/bindings/Reflect.ts deleted file mode 100644 index 411833bf23..0000000000 --- a/std/assembly/bindings/Reflect.ts +++ /dev/null @@ -1,4 +0,0 @@ -export declare function get(target: externref, propertyKey: externref/* , receiver: externref */): externref; -export declare function has(target: externref, propertyKey: externref): bool; -export declare function set(target: externref, propertyKey: externref, value: externref/* , receiver: externref */): externref; -export declare function apply(target: externref, thisArgument: externref, argumentsList: externref): externref; diff --git a/std/assembly/bindings/console.ts b/std/assembly/bindings/console.ts deleted file mode 100644 index 8dea45426d..0000000000 --- a/std/assembly/bindings/console.ts +++ /dev/null @@ -1,10 +0,0 @@ -export declare function assert(value: externref): void; -export declare function clear(): void; -export declare function error(value: externref): void; -export declare function info(value: externref): void; -export declare function log(value: externref): void; -export declare function time(label: externref): externref; -export declare function timeEnd(label: externref): void; -export declare function timeLog(label: externref): void; -export declare function trace(): void; -export declare function warn(value: externref): void; diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 1a1534de31..ad3c4abd97 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -1855,5 +1855,5 @@ declare function external(...args: any[]): any; /** Annotates a global for lazy compilation. */ declare function lazy(...args: any[]): any; -/** Annotates a function as the explicit start function. */ -declare function start(...args: any[]): any; +/** Annotates a class as being backed by `externref`. */ +declare function extern(constructor: Function): void; diff --git a/tests/compiler/bindings/dom.js b/tests/compiler/bindings/dom.js new file mode 100644 index 0000000000..0413663b61 --- /dev/null +++ b/tests/compiler/bindings/dom.js @@ -0,0 +1,11 @@ +const loader = require("../../../lib/loader"); + +exports.preInstantiate = function(imports, exports) { + const DOM = imports["DOM"] = { + newString(ptr) { return "hello world"; }, + newNumber(value) { return value; } + }; + loader.bind("String", String, DOM); + loader.bind("Object", Object, DOM); + loader.bind("console", console, DOM); +}; diff --git a/tests/compiler/bindings/dom.json b/tests/compiler/bindings/dom.json new file mode 100644 index 0000000000..ab18e4017a --- /dev/null +++ b/tests/compiler/bindings/dom.json @@ -0,0 +1,8 @@ +{ + "asc_flags": [ + "--runtime none" + ], + "features": [ + "reference-types" + ] +} diff --git a/tests/compiler/bindings/dom.optimized.wat b/tests/compiler/bindings/dom.optimized.wat new file mode 100644 index 0000000000..f83e1d628c --- /dev/null +++ b/tests/compiler/bindings/dom.optimized.wat @@ -0,0 +1,36 @@ +(module + (type $i32_=>_externref (func (param i32) (result externref))) + (type $none_=>_none (func)) + (type $externref_=>_none (func (param externref))) + (type $externref_=>_i32 (func (param externref) (result i32))) + (type $externref_=>_externref (func (param externref) (result externref))) + (type $externref_i32_=>_externref (func (param externref i32) (result externref))) + (import "DOM" "newString" (func $~lib/bindings/DOM/newString (param i32) (result externref))) + (import "DOM" "console.log" (func $~lib/bindings/DOM/console.log (param externref))) + (import "DOM" "String#get:length" (func $~lib/bindings/DOM/String#get:length (param externref) (result i32))) + (import "DOM" "newNumber" (func $~lib/bindings/DOM/newNumber (param i32) (result externref))) + (import "DOM" "String#repeat" (func $~lib/bindings/DOM/String#repeat (param externref i32) (result externref))) + (import "DOM" "Object#toString" (func $~lib/bindings/DOM/Object#toString (param externref) (result externref))) + (memory $0 1) + (data (i32.const 1024) "\16\00\00\00\01\00\00\00\01\00\00\00\16\00\00\00h\00e\00l\00l\00o\00 \00w\00o\00r\00l\00d") + (export "memory" (memory $0)) + (start $~start) + (func $~start + (local $0 externref) + i32.const 1040 + call $~lib/bindings/DOM/newString + local.tee $0 + call $~lib/bindings/DOM/console.log + local.get $0 + call $~lib/bindings/DOM/String#get:length + call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/console.log + local.get $0 + i32.const 2 + call $~lib/bindings/DOM/String#repeat + call $~lib/bindings/DOM/String#get:length + call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/Object#toString + call $~lib/bindings/DOM/console.log + ) +) diff --git a/tests/compiler/bindings/dom.ts b/tests/compiler/bindings/dom.ts new file mode 100644 index 0000000000..eac50117ec --- /dev/null +++ b/tests/compiler/bindings/dom.ts @@ -0,0 +1,9 @@ +import * as dom from "bindings/DOM"; + +function test(): void { + const str = dom.newString("hello world"); + dom.console.log(str); + dom.console.log(dom.newNumber(str.length)); + dom.console.log(dom.newNumber(str.repeat(2).length).toString()); +} +test(); diff --git a/tests/compiler/bindings/dom.untouched.wat b/tests/compiler/bindings/dom.untouched.wat new file mode 100644 index 0000000000..67d9825a36 --- /dev/null +++ b/tests/compiler/bindings/dom.untouched.wat @@ -0,0 +1,44 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_externref (func (param i32) (result externref))) + (type $externref_=>_none (func (param externref))) + (type $externref_=>_i32 (func (param externref) (result i32))) + (type $externref_=>_externref (func (param externref) (result externref))) + (type $externref_i32_=>_externref (func (param externref i32) (result externref))) + (import "DOM" "newString" (func $~lib/bindings/DOM/newString (param i32) (result externref))) + (import "DOM" "console.log" (func $~lib/bindings/DOM/console.log (param externref))) + (import "DOM" "String#get:length" (func $~lib/bindings/DOM/String#get:length (param externref) (result i32))) + (import "DOM" "newNumber" (func $~lib/bindings/DOM/newNumber (param i32) (result externref))) + (import "DOM" "String#repeat" (func $~lib/bindings/DOM/String#repeat (param externref i32) (result externref))) + (import "DOM" "Object#toString" (func $~lib/bindings/DOM/Object#toString (param externref) (result externref))) + (memory $0 1) + (data (i32.const 16) "\16\00\00\00\01\00\00\00\01\00\00\00\16\00\00\00h\00e\00l\00l\00o\00 \00w\00o\00r\00l\00d\00") + (table $0 1 funcref) + (export "memory" (memory $0)) + (start $~start) + (func $bindings/dom/test + (local $0 externref) + i32.const 32 + call $~lib/bindings/DOM/newString + local.set $0 + local.get $0 + call $~lib/bindings/DOM/console.log + local.get $0 + call $~lib/bindings/DOM/String#get:length + call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/console.log + local.get $0 + i32.const 2 + call $~lib/bindings/DOM/String#repeat + call $~lib/bindings/DOM/String#get:length + call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/Object#toString + call $~lib/bindings/DOM/console.log + ) + (func $start:bindings/dom + call $bindings/dom/test + ) + (func $~start + call $start:bindings/dom + ) +) diff --git a/tests/compiler/features/reference-types.optimized.wat b/tests/compiler/features/reference-types.optimized.wat index 8f9e7c11e3..ba47ccbe52 100644 --- a/tests/compiler/features/reference-types.optimized.wat +++ b/tests/compiler/features/reference-types.optimized.wat @@ -1,48 +1,14 @@ (module (type $externref_=>_externref (func (param externref) (result externref))) - (type $none_=>_none (func)) - (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) - (type $externref_=>_none (func (param externref))) - (type $externref_externref_=>_i32 (func (param externref externref) (result i32))) - (type $externref_externref_=>_externref (func (param externref externref) (result externref))) - (import "reference-types" "someObject" (global $features/reference-types/someObject externref)) - (import "reference-types" "someKey" (global $features/reference-types/someKey externref)) - (import "Reflect" "has" (func $~lib/bindings/Reflect/has (param externref externref) (result i32))) - (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) - (import "console" "log" (func $~lib/bindings/console/log (param externref))) - (import "Reflect" "get" (func $~lib/bindings/Reflect/get (param externref externref) (result externref))) (import "reference-types" "external" (func $features/reference-types/external (param externref) (result externref))) - (memory $0 1) - (data (i32.const 1024) "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") + (memory $0 0) (export "memory" (memory $0)) (export "external" (func $features/reference-types/external)) (export "internal" (func $features/reference-types/internal)) - (start $~start) (func $features/reference-types/internal (param $0 externref) (result externref) local.get $0 call $features/reference-types/external call $features/reference-types/external call $features/reference-types/external ) - (func $~start - global.get $features/reference-types/someObject - global.get $features/reference-types/someKey - call $~lib/bindings/Reflect/has - i32.eqz - if - i32.const 0 - i32.const 1040 - i32.const 19 - i32.const 1 - call $~lib/builtins/abort - unreachable - end - global.get $features/reference-types/someObject - call $~lib/bindings/console/log - global.get $features/reference-types/someKey - call $~lib/bindings/console/log - global.get $features/reference-types/someObject - global.get $features/reference-types/someKey - call $~lib/bindings/Reflect/get - call $~lib/bindings/console/log ) diff --git a/tests/compiler/features/reference-types.ts b/tests/compiler/features/reference-types.ts index fa2fa17d89..f3708f3694 100644 --- a/tests/compiler/features/reference-types.ts +++ b/tests/compiler/features/reference-types.ts @@ -8,45 +8,3 @@ export function internal(a: externref): externref { var d = external(c); return d; } - -// can use reflection to work with externref values - -import * as Reflect from "bindings/Reflect"; - -declare const someObject: externref; -declare const someKey: externref; - -assert(Reflect.has(someObject, someKey)); - -// can call JS bindings with externref values - -import * as console from "bindings/console"; - -console.log(someObject); -console.log(someKey); -console.log(Reflect.get(someObject, someKey)); - -// TODO: can represent and recognize 'null' for both externref and funcref -/* var nullGlobal: externref; -assert(!nullGlobal); -nullGlobal = null; -assert(!nullGlobal); -var nullGlobalInit: externref = null; -assert(!nullGlobalInit); -{ - let nullLocal: externref; - assert(!nullLocal); - nullLocal = null; - assert(!nullLocal); - let nullLocalInit: externref = null; - assert(!nullLocalInit); -} - -// funcref can represent function references - -function someFunc(): void {} -var funcGlobal: externref = someFunc; -{ - let funcLocal: externref = someFunc; -} -*/ \ No newline at end of file diff --git a/tests/compiler/features/reference-types.untouched.wat b/tests/compiler/features/reference-types.untouched.wat index 0f9a373a7c..34a27b31c5 100644 --- a/tests/compiler/features/reference-types.untouched.wat +++ b/tests/compiler/features/reference-types.untouched.wat @@ -1,48 +1,11 @@ (module - (type $none_=>_none (func)) (type $externref_=>_externref (func (param externref) (result externref))) - (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) - (type $externref_=>_none (func (param externref))) - (type $externref_externref_=>_i32 (func (param externref externref) (result i32))) - (type $externref_externref_=>_externref (func (param externref externref) (result externref))) - (import "reference-types" "someObject" (global $features/reference-types/someObject externref)) - (import "reference-types" "someKey" (global $features/reference-types/someKey externref)) - (import "Reflect" "has" (func $~lib/bindings/Reflect/has (param externref externref) (result i32))) - (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) - (import "console" "log" (func $~lib/bindings/console/log (param externref))) - (import "Reflect" "get" (func $~lib/bindings/Reflect/get (param externref externref) (result externref))) (import "reference-types" "external" (func $features/reference-types/external (param externref) (result externref))) - (memory $0 1) - (data (i32.const 16) "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") + (memory $0 0) (table $0 1 funcref) (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 - global.get $features/reference-types/someObject - global.get $features/reference-types/someKey - call $~lib/bindings/Reflect/has - i32.const 0 - i32.ne - i32.eqz - if - i32.const 0 - i32.const 32 - i32.const 19 - i32.const 1 - call $~lib/builtins/abort - unreachable - end - global.get $features/reference-types/someObject - call $~lib/bindings/console/log - global.get $features/reference-types/someKey - call $~lib/bindings/console/log - global.get $features/reference-types/someObject - global.get $features/reference-types/someKey - call $~lib/bindings/Reflect/get - call $~lib/bindings/console/log - ) (func $features/reference-types/internal (param $0 externref) (result externref) (local $1 externref) (local $2 externref) @@ -58,7 +21,4 @@ local.set $3 local.get $3 ) - (func $~start - call $start:features/reference-types - ) ) diff --git a/tests/features.json b/tests/features.json index aa20113fe6..fc4884d8c5 100644 --- a/tests/features.json +++ b/tests/features.json @@ -22,7 +22,7 @@ "--enable reference-types" ], "v8_flags": [ - "--experimental-wasm-reftypes" + "--experimental-wasm-anyref" ] }, "bigint-integration": { From e9efa8a25bea2553d345063b264410ed818b79d5 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 13 Aug 2020 08:08:48 +0200 Subject: [PATCH 2/3] disable broken eslint rule --- .eslintrc.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5df19ecc09..ad2ed945fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -122,7 +122,9 @@ module.exports = { "args": "none", "ignoreRestSiblings": false } - ] + ], + // Broken rule, demands return type annotations on setters. + "@typescript-eslint/explicit-module-boundary-types": "off" } }, From 6bd494a49d71d3c9367a7e52fb2b74c02a075c8a Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 14 Aug 2020 03:20:31 +0200 Subject: [PATCH 3/3] so strange --- lib/loader/index.js | 8 ++- src/compiler.ts | 31 ++++++--- src/resolver.ts | 9 ++- std/assembly/bindings/DOM.ts | 85 ++++++++++++++++++++--- tests/compiler/bindings/dom.js | 17 ++++- tests/compiler/bindings/dom.json | 3 +- tests/compiler/bindings/dom.optimized.wat | 63 ++++++++++++++--- tests/compiler/bindings/dom.ts | 21 ++++-- tests/compiler/bindings/dom.untouched.wat | 84 ++++++++++++++++++---- 9 files changed, 268 insertions(+), 53 deletions(-) diff --git a/lib/loader/index.js b/lib/loader/index.js index 0d828beacc..7da0540148 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -83,6 +83,8 @@ function preInstantiate(imports) { return extendedExports; } +exports.preInstantiate = preInstantiate; + /** Prepares the final module once instantiation is complete. */ function postInstantiate(extendedExports, instance) { const exports = instance.exports; @@ -293,6 +295,8 @@ function postInstantiate(extendedExports, instance) { return demangle(exports, extendedExports); } +exports.postInstantiate = postInstantiate; + function isResponse(src) { return typeof Response !== "undefined" && src instanceof Response; } @@ -436,7 +440,9 @@ function bind(name, obj, base) { for (const [key, { value }] of Object.entries(Object.getOwnPropertyDescriptors(obj))) { if (typeof value === "function") { base[prefix + key] = isPrototype - ? (thisArg, ...args) => thisArg[key](...args) + ? key === "constructor" + ? (...args) => new value(...args) + : (thisArg, ...args) => thisArg[key](...args) : value; } else { base[prefix + "get:" + key] = isPrototype diff --git a/src/compiler.ts b/src/compiler.ts index 99de950cd2..6a84f9a936 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -7859,7 +7859,7 @@ export class Compiler extends DiagnosticEmitter { var parameterTypes = instance.signature.parameterTypes; var maxArguments = parameterTypes.length; var maxOperands = maxArguments; - if (instance.is(CommonFlags.INSTANCE)) { + if (instance.signature.thisType) { ++minOperands; ++maxOperands; --numArguments; @@ -9375,13 +9375,25 @@ export class Compiler extends DiagnosticEmitter { } if (!classInstance) return module.unreachable(); if (contextualType == Type.void) constraints |= Constraints.WILL_DROP; - var ctor = this.ensureConstructor(classInstance, expression); - if (!ctor.hasDecorator(DecoratorFlags.INLINE)) { - // Inlined ctors haven't been compiled yet and are checked upon inline - // compilation of their body instead. - this.checkFieldInitialization(classInstance, expression); + if (classInstance.hasDecorator(DecoratorFlags.EXTERN)) { + let ctor = classInstance.constructorInstance; + if (!ctor) { + this.error( + DiagnosticCode.Cannot_create_an_instance_of_an_abstract_class, + expression.range + ); + return module.unreachable(); + } + return this.compileCallDirect(ctor, expression.args, expression); + } else { + let ctor = this.ensureConstructor(classInstance, expression); + if (!ctor.hasDecorator(DecoratorFlags.INLINE)) { + // Inlined ctors haven't been compiled yet and are checked upon inline + // compilation of their body instead. + this.checkFieldInitialization(classInstance, expression); + } + return this.compileInstantiate(ctor, expression.args, constraints, expression); } - return this.compileInstantiate(ctor, expression.args, constraints, expression); } /** Gets the compiled constructor of the specified class or generates one if none is present. */ @@ -9391,6 +9403,7 @@ export class Compiler extends DiagnosticEmitter { /** Report node. */ reportNode: Node ): Function { + assert(!classInstance.hasDecorator(DecoratorFlags.EXTERN)); var instance = classInstance.constructorInstance; if (instance) { // shortcut if already compiled @@ -9574,7 +9587,9 @@ export class Compiler extends DiagnosticEmitter { ctorInstance, argumentExpressions, reportNode, - this.makeZero(this.options.usizeType, reportNode), + classInstance.hasDecorator(DecoratorFlags.EXTERN) + ? 0 + : this.makeZero(classInstance.type, reportNode), constraints ); if (getExpressionType(expr) != NativeType.None) { // possibly WILL_DROP diff --git a/src/resolver.ts b/src/resolver.ts index 2fcbe9e270..599bff8b8c 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -37,7 +37,8 @@ import { IndexSignature, isTypedElement, InterfacePrototype, - DeclaredElement + DeclaredElement, + DecoratorFlags } from "./program"; import { @@ -2656,8 +2657,10 @@ export class Resolver extends DiagnosticEmitter { if (!thisType) return null; ctxTypes.set(CommonNames.this_, thisType); } else if (classInstance) { - thisType = classInstance.type; - ctxTypes.set(CommonNames.this_, thisType); + if (!(classInstance.hasDecorator(DecoratorFlags.EXTERN) && prototype.is(CommonFlags.CONSTRUCTOR))) { + thisType = classInstance.type; + ctxTypes.set(CommonNames.this_, thisType); + } } // resolve parameter types diff --git a/std/assembly/bindings/DOM.ts b/std/assembly/bindings/DOM.ts index db85992635..555a68ff98 100644 --- a/std/assembly/bindings/DOM.ts +++ b/std/assembly/bindings/DOM.ts @@ -1,14 +1,54 @@ @extern export declare class Object { + @external("Object#constructor") static new(): Object; + static assign(target: Object, source: Object): Object; + static create(proto: Object): Object; + static defineProperties(obj: Object, props: Object): Object; + static defineProperty(obj: Object, prop: String, descriptor: Object): Object; + static entries(obj: Object): Array; + static freeze(obj: Object): Object; + static fromEntries(iterable: Object): Object; + static getOwnPropertyDescriptor(obj: Object, prop: String): Object; + static getOwnPropertyDescriptors(obj: Object): Object; + static getOwnPropertyNames(obj: Object): Array; + static getOwnPropertySymbols(obj: Object): Array; + static getPrototypeOf(obj: Object): Object; static is(value1: Object, value2: Object): bool; + static isExtensible(obj: Object): bool; + static isFrozen(obj: Object): bool; + static isSealed(obj: Object): bool; + static keys(obj: Object): Array; + static preventExtensions(obj: Object): Object; + static seal(obj: Object): Object; + static setPrototypeOf(obj: Object, prototype: Object): Object; + static values(obj: Object): Array; constructor(other: Object); hasOwnProperty(prop: String): bool; + isPrototypeOf(obj: Object): bool; + propertyIsEnumerable(prop: String): bool; + toLocaleString(): String; toString(): String; - // ... +} + +@extern +export declare class Number extends Object { + @external("Number#constructor") static new(value: T): Number; + constructor(other: Object); + toString(radix?: i32): String; + valueOf(): f64; +} + +@extern +export declare class Boolean extends Object { + @external("Boolean#constructor") static new(value: T): Boolean; + constructor(other: Object); + toString(): String; + valueOf(): bool; } @extern export declare class String extends Object { + @external("newString") static new(value: string): String; static fromCharCode(code: i32): String; static fromCodePoint(code: i32): String; constructor(other: Object); @@ -32,26 +72,51 @@ export declare class String extends Object { replaceAll(pattern: String, replacement: String): String; search(pattern: RegExp): i32; slice(beginIndex: i32, endIndex?: i32): String; + toString(): String; // ... } -export declare function newString(value: string): String; @extern -export declare class Number extends Object { +export declare class Array extends Object { + @external("Array#constructor") static new(value: T): Array; + static from(arrayLike: Object): Array; + static isArray(value: Object): bool; + constructor(length?: i32); + concat(other: Object): Array; + copyWithin(target: i32, start: i32, end: i32): Array; + toString(): String; // ... } -export declare function newNumber(value: i32): Number; @extern -export declare class Array extends Object { - constructor(length: i32); - get length(): i32; - // ... +export declare class Function extends Object { } @extern export declare class RegExp extends Object { - // ... + static get $1(): String; + static get $2(): String; + static get $3(): String; + static get $4(): String; + static get $5(): String; + static get $6(): String; + static get $7(): String; + static get $8(): String; + static get $9(): String; + constructor(pattern: String, flags: String); + get dotAll(): bool; + get flags(): String; + get length(): i32; + get global(): bool; + get ignoreCase(): bool; + get multiline(): bool; + get source(): String; + get sticky(): bool; + get unicode(): bool; + get lastIndex(): i32; + exec(str: String): Array; + test(str: String): bool; + toString(): String; } @extern @@ -99,8 +164,8 @@ export declare class CanvasRenderingContext2D { @extern export declare class Reflect { + static apply(target: Object, thisArgument: Object, argumentsList: Object): Object; static get(target: Object, propertyKey: Object/* , receiver: externref */): Object; static has(target: Object, propertyKey: Object): bool; static set(target: Object, propertyKey: Object, value: Object/* , receiver: externref */): Object; - static apply(target: Object, thisArgument: Object, argumentsList: Object): Object; } diff --git a/tests/compiler/bindings/dom.js b/tests/compiler/bindings/dom.js index 0413663b61..f1e6a6a01a 100644 --- a/tests/compiler/bindings/dom.js +++ b/tests/compiler/bindings/dom.js @@ -1,11 +1,22 @@ const loader = require("../../../lib/loader"); +var extendedExports; + exports.preInstantiate = function(imports, exports) { const DOM = imports["DOM"] = { - newString(ptr) { return "hello world"; }, - newNumber(value) { return value; } + newString(ptr) { return extendedExports.__getString(ptr); } }; - loader.bind("String", String, DOM); loader.bind("Object", Object, DOM); + loader.bind("Number", Number, DOM); + loader.bind("Boolean", Boolean, DOM); + loader.bind("String", String, DOM); + loader.bind("Array", Array, DOM); + loader.bind("Function", Function, DOM); + loader.bind("RegExp", RegExp, DOM); loader.bind("console", console, DOM); + extendedExports = loader.preInstantiate(imports); +}; + +exports.postInstantiate = function(instance) { + loader.postInstantiate(extendedExports, instance); }; diff --git a/tests/compiler/bindings/dom.json b/tests/compiler/bindings/dom.json index ab18e4017a..9f7a98819d 100644 --- a/tests/compiler/bindings/dom.json +++ b/tests/compiler/bindings/dom.json @@ -1,6 +1,7 @@ { "asc_flags": [ - "--runtime none" + "--runtime none", + "--explicitStart" ], "features": [ "reference-types" diff --git a/tests/compiler/bindings/dom.optimized.wat b/tests/compiler/bindings/dom.optimized.wat index f83e1d628c..6b66f27e7d 100644 --- a/tests/compiler/bindings/dom.optimized.wat +++ b/tests/compiler/bindings/dom.optimized.wat @@ -1,36 +1,79 @@ (module (type $i32_=>_externref (func (param i32) (result externref))) + (type $externref_i32_=>_externref (func (param externref i32) (result externref))) + (type $externref_externref_=>_externref (func (param externref externref) (result externref))) (type $none_=>_none (func)) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) (type $externref_=>_none (func (param externref))) (type $externref_=>_i32 (func (param externref) (result i32))) - (type $externref_=>_externref (func (param externref) (result externref))) - (type $externref_i32_=>_externref (func (param externref i32) (result externref))) - (import "DOM" "newString" (func $~lib/bindings/DOM/newString (param i32) (result externref))) + (type $externref_externref_=>_i32 (func (param externref externref) (result i32))) + (import "DOM" "newString" (func $~lib/bindings/DOM/String.new (param i32) (result externref))) (import "DOM" "console.log" (func $~lib/bindings/DOM/console.log (param externref))) (import "DOM" "String#get:length" (func $~lib/bindings/DOM/String#get:length (param externref) (result i32))) - (import "DOM" "newNumber" (func $~lib/bindings/DOM/newNumber (param i32) (result externref))) + (import "DOM" "Number#constructor" (func $~lib/bindings/DOM/Number.new (param i32) (result externref))) (import "DOM" "String#repeat" (func $~lib/bindings/DOM/String#repeat (param externref i32) (result externref))) - (import "DOM" "Object#toString" (func $~lib/bindings/DOM/Object#toString (param externref) (result externref))) + (import "DOM" "Number#toString" (func $~lib/bindings/DOM/Number#toString (param externref i32) (result externref))) + (import "DOM" "RegExp#constructor" (func $~lib/bindings/DOM/RegExp#constructor (param externref externref) (result externref))) + (import "DOM" "RegExp#test" (func $~lib/bindings/DOM/RegExp#test (param externref externref) (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (import "DOM" "RegExp#exec" (func $~lib/bindings/DOM/RegExp#exec (param externref externref) (result externref))) (memory $0 1) (data (i32.const 1024) "\16\00\00\00\01\00\00\00\01\00\00\00\16\00\00\00h\00e\00l\00l\00o\00 \00w\00o\00r\00l\00d") + (data (i32.const 1072) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00b") + (data (i32.const 1104) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00g") + (data (i32.const 1136) "\08\00\00\00\01\00\00\00\01\00\00\00\08\00\00\00a\00b\00b\00a") + (data (i32.const 1168) "\1e\00\00\00\01\00\00\00\01\00\00\00\1e\00\00\00b\00i\00n\00d\00i\00n\00g\00s\00/\00d\00o\00m\00.\00t\00s") + (global $~started (mut i32) (i32.const 0)) + (export "_start" (func $~start)) (export "memory" (memory $0)) - (start $~start) (func $~start (local $0 externref) + (local $1 externref) + global.get $~started + if + return + else + i32.const 1 + global.set $~started + end i32.const 1040 - call $~lib/bindings/DOM/newString + call $~lib/bindings/DOM/String.new local.tee $0 call $~lib/bindings/DOM/console.log local.get $0 call $~lib/bindings/DOM/String#get:length - call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/Number.new call $~lib/bindings/DOM/console.log local.get $0 i32.const 2 call $~lib/bindings/DOM/String#repeat call $~lib/bindings/DOM/String#get:length - call $~lib/bindings/DOM/newNumber - call $~lib/bindings/DOM/Object#toString + call $~lib/bindings/DOM/Number.new + i32.const 10 + call $~lib/bindings/DOM/Number#toString + call $~lib/bindings/DOM/console.log + i32.const 1088 + call $~lib/bindings/DOM/String.new + i32.const 1120 + call $~lib/bindings/DOM/String.new + call $~lib/bindings/DOM/RegExp#constructor + local.tee $0 + i32.const 1152 + call $~lib/bindings/DOM/String.new + local.tee $1 + call $~lib/bindings/DOM/RegExp#test + i32.eqz + if + i32.const 0 + i32.const 1184 + i32.const 16 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/bindings/DOM/RegExp#exec call $~lib/bindings/DOM/console.log ) ) diff --git a/tests/compiler/bindings/dom.ts b/tests/compiler/bindings/dom.ts index eac50117ec..f26eb653e1 100644 --- a/tests/compiler/bindings/dom.ts +++ b/tests/compiler/bindings/dom.ts @@ -1,9 +1,20 @@ import * as dom from "bindings/DOM"; -function test(): void { - const str = dom.newString("hello world"); +function testString(): void { + const str = dom.String.new("hello world"); dom.console.log(str); - dom.console.log(dom.newNumber(str.length)); - dom.console.log(dom.newNumber(str.repeat(2).length).toString()); + dom.console.log(dom.Number.new(str.length)); + dom.console.log(dom.Number.new(str.repeat(2).length).toString(10)); } -test(); +testString(); + +function testRegExp(): void { + const pattern = dom.String.new("b"); + const flags = dom.String.new("g"); + const re = new dom.RegExp(pattern, flags); + const input = dom.String.new("abba"); + assert(re.test(input)); + const match = re.exec(input); + dom.console.log(match); +} +testRegExp(); diff --git a/tests/compiler/bindings/dom.untouched.wat b/tests/compiler/bindings/dom.untouched.wat index 67d9825a36..9702c30f0d 100644 --- a/tests/compiler/bindings/dom.untouched.wat +++ b/tests/compiler/bindings/dom.untouched.wat @@ -1,44 +1,104 @@ (module (type $none_=>_none (func)) (type $i32_=>_externref (func (param i32) (result externref))) + (type $externref_i32_=>_externref (func (param externref i32) (result externref))) + (type $externref_externref_=>_externref (func (param externref externref) (result externref))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) (type $externref_=>_none (func (param externref))) (type $externref_=>_i32 (func (param externref) (result i32))) - (type $externref_=>_externref (func (param externref) (result externref))) - (type $externref_i32_=>_externref (func (param externref i32) (result externref))) - (import "DOM" "newString" (func $~lib/bindings/DOM/newString (param i32) (result externref))) + (type $externref_externref_=>_i32 (func (param externref externref) (result i32))) + (import "DOM" "newString" (func $~lib/bindings/DOM/String.new (param i32) (result externref))) (import "DOM" "console.log" (func $~lib/bindings/DOM/console.log (param externref))) (import "DOM" "String#get:length" (func $~lib/bindings/DOM/String#get:length (param externref) (result i32))) - (import "DOM" "newNumber" (func $~lib/bindings/DOM/newNumber (param i32) (result externref))) + (import "DOM" "Number#constructor" (func $~lib/bindings/DOM/Number.new (param i32) (result externref))) (import "DOM" "String#repeat" (func $~lib/bindings/DOM/String#repeat (param externref i32) (result externref))) - (import "DOM" "Object#toString" (func $~lib/bindings/DOM/Object#toString (param externref) (result externref))) + (import "DOM" "Number#toString" (func $~lib/bindings/DOM/Number#toString (param externref i32) (result externref))) + (import "DOM" "RegExp#constructor" (func $~lib/bindings/DOM/RegExp#constructor (param externref externref) (result externref))) + (import "DOM" "RegExp#test" (func $~lib/bindings/DOM/RegExp#test (param externref externref) (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (import "DOM" "RegExp#exec" (func $~lib/bindings/DOM/RegExp#exec (param externref externref) (result externref))) (memory $0 1) (data (i32.const 16) "\16\00\00\00\01\00\00\00\01\00\00\00\16\00\00\00h\00e\00l\00l\00o\00 \00w\00o\00r\00l\00d\00") + (data (i32.const 64) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00b\00") + (data (i32.const 96) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00g\00") + (data (i32.const 128) "\08\00\00\00\01\00\00\00\01\00\00\00\08\00\00\00a\00b\00b\00a\00") + (data (i32.const 160) "\1e\00\00\00\01\00\00\00\01\00\00\00\1e\00\00\00b\00i\00n\00d\00i\00n\00g\00s\00/\00d\00o\00m\00.\00t\00s\00") (table $0 1 funcref) + (global $~started (mut i32) (i32.const 0)) + (export "_start" (func $~start)) (export "memory" (memory $0)) - (start $~start) - (func $bindings/dom/test + (func $bindings/dom/testString (local $0 externref) i32.const 32 - call $~lib/bindings/DOM/newString + call $~lib/bindings/DOM/String.new local.set $0 local.get $0 call $~lib/bindings/DOM/console.log local.get $0 call $~lib/bindings/DOM/String#get:length - call $~lib/bindings/DOM/newNumber + call $~lib/bindings/DOM/Number.new call $~lib/bindings/DOM/console.log local.get $0 i32.const 2 call $~lib/bindings/DOM/String#repeat call $~lib/bindings/DOM/String#get:length - call $~lib/bindings/DOM/newNumber - call $~lib/bindings/DOM/Object#toString + call $~lib/bindings/DOM/Number.new + i32.const 10 + call $~lib/bindings/DOM/Number#toString + call $~lib/bindings/DOM/console.log + ) + (func $bindings/dom/testRegExp + (local $0 externref) + (local $1 externref) + (local $2 externref) + (local $3 externref) + (local $4 externref) + i32.const 80 + call $~lib/bindings/DOM/String.new + local.set $0 + i32.const 112 + call $~lib/bindings/DOM/String.new + local.set $1 + local.get $0 + local.get $1 + call $~lib/bindings/DOM/RegExp#constructor + local.set $2 + i32.const 144 + call $~lib/bindings/DOM/String.new + local.set $3 + local.get $2 + local.get $3 + call $~lib/bindings/DOM/RegExp#test + i32.const 0 + i32.ne + i32.eqz + if + i32.const 0 + i32.const 176 + i32.const 16 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + local.get $2 + local.get $3 + call $~lib/bindings/DOM/RegExp#exec + local.set $4 + local.get $4 call $~lib/bindings/DOM/console.log ) (func $start:bindings/dom - call $bindings/dom/test + call $bindings/dom/testString + call $bindings/dom/testRegExp ) (func $~start + global.get $~started + if + return + else + i32.const 1 + global.set $~started + end call $start:bindings/dom ) )