Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extern classes experiment #1435

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc.js
Expand Up @@ -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"
}
},

Expand Down
3 changes: 3 additions & 0 deletions lib/loader/index.d.ts
Expand Up @@ -122,3 +122,6 @@ export declare function demangle<T extends Record<string,unknown>>(
exports: Record<string,unknown>,
extendedExports?: Record<string,unknown>
): T;

/** Binds a JavaScript object to its corresponding WebAssembly imports. */
export declare function bind(name: string, obj: Record<string,unknown>, base?: Record<string,unknown>);
34 changes: 34 additions & 0 deletions lib/loader/index.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -426,3 +430,33 @@ 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
? key === "constructor"
? (...args) => new value(...args)
: (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;
4 changes: 3 additions & 1 deletion src/ast.ts
Expand Up @@ -951,7 +951,8 @@ export enum DecoratorKind {
EXTERNAL,
BUILTIN,
LAZY,
UNSAFE
UNSAFE,
EXTERN
}

export namespace DecoratorKind {
Expand All @@ -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: {
Expand Down
31 changes: 23 additions & 8 deletions src/compiler.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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. */
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions src/program.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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);

Expand Down
9 changes: 6 additions & 3 deletions src/resolver.ts
Expand Up @@ -37,7 +37,8 @@ import {
IndexSignature,
isTypedElement,
InterfacePrototype,
DeclaredElement
DeclaredElement,
DecoratorFlags
} from "./program";

import {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Expand Up @@ -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;
}
Expand Down
171 changes: 171 additions & 0 deletions std/assembly/bindings/DOM.ts
@@ -0,0 +1,171 @@
@extern
export declare class Object {
Copy link
Member

@MaxGraey MaxGraey Aug 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about move Object, String, Array and etc to bindings/JS namespace instead bindings/DOM? But rest of WebAPI objects (HTMLCanvasElement, CanvasRenderingContext2D, ...) keep here

@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<T extends number>(value: T): Number;
constructor(other: Object);
toString(radix?: i32): String;
valueOf(): f64;
}

@extern
export declare class Boolean extends Object {
@external("Boolean#constructor") static new<T extends number>(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);
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;
toString(): String;
// ...
}

@extern
export declare class Array extends Object {
@external("Array#constructor") static new<T extends number>(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;
// ...
}

@extern
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
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 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;
}