From ce2cc7e11d8f61848bf48d47e47ff4e684cdb784 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 12 Feb 2020 17:22:45 +0100 Subject: [PATCH] Handle instanceof of entire classes --- src/ast.ts | 5 + src/builtins.ts | 54 +++- src/compiler.ts | 94 +++++- tests/compiler/instanceof-class.json | 5 + tests/compiler/instanceof-class.optimized.wat | 182 ++++++++++++ tests/compiler/instanceof-class.ts | 18 ++ tests/compiler/instanceof-class.untouched.wat | 280 ++++++++++++++++++ 7 files changed, 629 insertions(+), 9 deletions(-) create mode 100644 tests/compiler/instanceof-class.json create mode 100644 tests/compiler/instanceof-class.optimized.wat create mode 100644 tests/compiler/instanceof-class.ts create mode 100644 tests/compiler/instanceof-class.untouched.wat diff --git a/src/ast.ts b/src/ast.ts index 5fd69274c8..01dc394da8 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1094,6 +1094,11 @@ export class NamedTypeNode extends TypeNode { name: TypeName; /** Type argument references. */ typeArguments: TypeNode[] | null; + + get hasTypeArguments(): bool { + var typeArguments = this.typeArguments; + return typeArguments !== null && typeArguments.length > 0; + } } /** Represents a function type. */ diff --git a/src/builtins.ts b/src/builtins.ts index d14d9677bc..72e9fd6d4d 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -64,7 +64,7 @@ import { Global, DecoratorFlags, Element, - Class + ClassPrototype } from "./program"; import { @@ -4971,6 +4971,58 @@ export function compileRTTI(compiler: Compiler): void { } } +/** Compiles a class-specific instanceof helper, checking a ref against all concrete instances. */ +export function compileClassInstanceOf(compiler: Compiler, prototype: ClassPrototype): void { + var module = compiler.module; + var nativeSizeType = compiler.options.nativeSizeType; + var instanceofInstance = assert(prototype.program.instanceofInstance); + compiler.compileFunction(instanceofInstance); + + var stmts = new Array(); + + // if (!ref) return false + stmts.push( + module.if( + module.unary( + nativeSizeType == NativeType.I64 + ? UnaryOp.EqzI64 + : UnaryOp.EqzI32, + module.local_get(0, nativeSizeType) + ), + module.return( + module.i32(0) + ) + ) + ); + + // if (__instanceof(ref, ID[i])) return true + var instances = prototype.instances; + if (instances !== null && instances.size) { + for (let instance of instances.values()) { + stmts.push( + module.if( + module.call(instanceofInstance.internalName, [ + module.local_get(0, nativeSizeType), + module.i32(instance.id) + ], NativeType.I32), + module.return( + module.i32(1) + ) + ) + ); + } + } + + // return false + stmts.push( + module.return( + module.i32(0) + ) + ); + + module.addFunction(prototype.internalName + "~instanceof", nativeSizeType, NativeType.I32, null, module.flatten(stmts)); +} + // Helpers /** Evaluates the constant type of a type argument *or* expression. */ diff --git a/src/compiler.ts b/src/compiler.ts index 458afc9b1f..313a9d934d 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -9,6 +9,7 @@ import { compileVisitGlobals, compileVisitMembers, compileRTTI, + compileClassInstanceOf, } from "./builtins"; import { @@ -160,6 +161,8 @@ import { UnaryPostfixExpression, UnaryPrefixExpression, + NamedTypeNode, + nodeIsConstantValue, findDecorator, isTypeOmitted @@ -335,6 +338,8 @@ export class Compiler extends DiagnosticEmitter { inlineStack: Function[] = []; /** Lazily compiled library functions. */ lazyLibraryFunctions: Set = new Set(); + /** Pending class-specific instanceof helpers. */ + pendingClassInstanceOf: Set = new Set(); /** Compiles a {@link Program} to a {@link Module} using the specified options. */ static compile(program: Program): Module { @@ -455,6 +460,11 @@ export class Compiler extends DiagnosticEmitter { } } while (lazyLibraryFunctions.size); + // compile pending class-specific instanceof helpers + for (let prototype of this.pendingClassInstanceOf.values()) { + compileClassInstanceOf(this, prototype); + } + // finalize runtime features module.removeGlobal(BuiltinNames.rtti_base); if (this.runtimeFeatures & RuntimeFeatures.RTTI) compileRTTI(this); @@ -7666,21 +7676,42 @@ export class Compiler extends DiagnosticEmitter { contextualType: Type, constraints: Constraints ): ExpressionRef { - var module = this.module; - // NOTE that this differs from TypeScript in that the rhs is a type, not an expression. at the - // time of implementation, this seemed more useful because dynamic rhs expressions are not - // possible in AS anyway. also note that the code generated below must preserve side-effects of - // the LHS expression even when the result is a constant, i.e. return a block dropping `expr`. var flow = this.currentFlow; - var expr = this.compileExpression(expression.expression, this.options.usizeType); - var actualType = this.currentType; + var isType = expression.isType; + + // Mimic `instanceof CLASS` + if (isType.kind == NodeKind.NAMEDTYPE) { + let namedType = isType; + if (!(namedType.isNullable || namedType.hasTypeArguments)) { + let element = this.resolver.resolveTypeName(namedType.name, flow.actualFunction, ReportMode.SWALLOW); + if (element !== null && element.kind == ElementKind.CLASS_PROTOTYPE) { + let prototype = element; + if (prototype.is(CommonFlags.GENERIC)) { + return this.makeInstanceofClass(expression, prototype); + } + } + } + } + + // Fall back to `instanceof TYPE` var expectedType = this.resolver.resolveType( expression.isType, flow.actualFunction, makeMap(flow.contextualTypeArguments) ); + if (!expectedType) { + this.currentType = Type.bool; + return this.module.unreachable(); + } + return this.makeInstanceofType(expression, expectedType); + } + + private makeInstanceofType(expression: InstanceOfExpression, expectedType: Type): ExpressionRef { + var module = this.module; + var flow = this.currentFlow; + var expr = this.compileExpression(expression.expression, expectedType); + var actualType = this.currentType; this.currentType = Type.bool; - if (!expectedType) return module.unreachable(); // instanceof - must be exact if (!expectedType.is(TypeFlags.REFERENCE)) { @@ -7802,6 +7833,53 @@ export class Compiler extends DiagnosticEmitter { ], NativeType.I32); } + private makeInstanceofClass(expression: InstanceOfExpression, prototype: ClassPrototype): ExpressionRef { + var module = this.module; + var expr = this.compileExpression(expression.expression, Type.auto); + var actualType = this.currentType; + var nativeSizeType = actualType.toNativeType(); + + this.currentType = Type.bool; + + // exclusively interested in class references here + var classReference = actualType.classReference; + if (actualType.is(TypeFlags.REFERENCE) && classReference !== null) { + + // static check + if (classReference.extends(prototype)) { + + // instanceof - LHS must be != 0 + if (actualType.is(TypeFlags.NULLABLE)) { + return module.binary( + nativeSizeType == NativeType.I64 + ? BinaryOp.NeI64 + : BinaryOp.NeI32, + expr, + this.makeZero(actualType) + ); + + // is just `true` + } else { + return module.block(null, [ + module.drop(expr), + module.i32(1) + ], NativeType.I32); + } + + // dynamic check against all possible concrete ids + } else if (prototype.extends(classReference.prototype)) { + this.pendingClassInstanceOf.add(prototype); + return module.call(prototype.internalName + "~instanceof", [ expr ], NativeType.I32); + } + } + + // false + return module.block(null, [ + module.drop(expr), + module.i32(0) + ], NativeType.I32); + } + private compileLiteralExpression( expression: LiteralExpression, contextualType: Type, diff --git a/tests/compiler/instanceof-class.json b/tests/compiler/instanceof-class.json new file mode 100644 index 0000000000..b1da366ff4 --- /dev/null +++ b/tests/compiler/instanceof-class.json @@ -0,0 +1,5 @@ +{ + "asc_flags": [ + "--runtime none" + ] +} \ No newline at end of file diff --git a/tests/compiler/instanceof-class.optimized.wat b/tests/compiler/instanceof-class.optimized.wat new file mode 100644 index 0000000000..b040de24e5 --- /dev/null +++ b/tests/compiler/instanceof-class.optimized.wat @@ -0,0 +1,182 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $i32_=>_none (func (param i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (memory $0 1) + (data (i32.const 16) "&\00\00\00\01\00\00\00\01\00\00\00&\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00-\00c\00l\00a\00s\00s\00.\00t\00s") + (data (i32.const 80) "\07\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\04\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\05") + (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) + (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) + (global $instanceof-class/a (mut i32) (i32.const 0)) + (global $instanceof-class/b (mut i32) (i32.const 0)) + (export "memory" (memory $0)) + (start $~start) + (func $~lib/rt/stub/maybeGrowMemory (; 1 ;) (param $0 i32) + (local $1 i32) + (local $2 i32) + local.get $0 + memory.size + local.tee $2 + i32.const 16 + i32.shl + local.tee $1 + i32.gt_u + if + local.get $2 + local.get $0 + local.get $1 + i32.sub + i32.const 65535 + i32.add + i32.const -65536 + i32.and + i32.const 16 + i32.shr_u + local.tee $1 + local.get $2 + local.get $1 + i32.gt_s + select + memory.grow + i32.const 0 + i32.lt_s + if + local.get $1 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + local.get $0 + global.set $~lib/rt/stub/offset + ) + (func $~lib/rt/stub/__alloc (; 2 ;) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + global.get $~lib/rt/stub/offset + i32.const 16 + i32.add + local.tee $2 + i32.const 16 + i32.add + call $~lib/rt/stub/maybeGrowMemory + local.get $2 + i32.const 16 + i32.sub + local.tee $1 + i32.const 16 + i32.store + local.get $1 + i32.const 1 + i32.store offset=4 + local.get $1 + local.get $0 + i32.store offset=8 + local.get $1 + i32.const 0 + i32.store offset=12 + local.get $2 + ) + (func $start:instanceof-class (; 3 ;) + (local $0 i32) + i32.const 144 + global.set $~lib/rt/stub/startOffset + i32.const 144 + global.set $~lib/rt/stub/offset + i32.const 3 + call $~lib/rt/stub/__alloc + local.tee $0 + i32.eqz + if + i32.const 4 + call $~lib/rt/stub/__alloc + local.set $0 + end + local.get $0 + global.set $instanceof-class/a + i32.const 6 + call $~lib/rt/stub/__alloc + local.tee $0 + i32.eqz + if + i32.const 5 + call $~lib/rt/stub/__alloc + local.set $0 + end + local.get $0 + global.set $instanceof-class/b + global.get $instanceof-class/b + call $instanceof-class/Child~instanceof + i32.eqz + if + i32.const 0 + i32.const 32 + i32.const 17 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + ) + (func $~start (; 4 ;) + call $start:instanceof-class + ) + (func $~lib/rt/__instanceof (; 5 ;) (param $0 i32) (param $1 i32) (result i32) + local.get $0 + i32.const 16 + i32.sub + i32.load offset=8 + local.tee $0 + i32.const 80 + i32.load + i32.le_u + if + loop $do-continue|0 + local.get $0 + local.get $1 + i32.eq + if + i32.const 1 + return + end + local.get $0 + i32.const 3 + i32.shl + i32.const 84 + i32.add + i32.load offset=4 + local.tee $0 + br_if $do-continue|0 + end + end + i32.const 0 + ) + (func $instanceof-class/Child~instanceof (; 6 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + return + end + local.get $0 + i32.const 3 + call $~lib/rt/__instanceof + if + i32.const 1 + return + end + local.get $0 + i32.const 6 + call $~lib/rt/__instanceof + if + i32.const 1 + return + end + i32.const 0 + ) +) diff --git a/tests/compiler/instanceof-class.ts b/tests/compiler/instanceof-class.ts new file mode 100644 index 0000000000..7f85869576 --- /dev/null +++ b/tests/compiler/instanceof-class.ts @@ -0,0 +1,18 @@ +class Parent { +} + +class Child extends Parent { +} + +class SomethingElse { +} + +var a: Child = new Child(); +assert(a instanceof Child); // static true +assert(a instanceof Parent); // static true +assert(!(a instanceof SomethingElse)); // static false + +var b: Parent = new Child(); +assert(b instanceof Parent); // static true +assert(b instanceof Child); // dynamic true (checks Child, Child) +assert(!(b instanceof SomethingElse)); // static false diff --git a/tests/compiler/instanceof-class.untouched.wat b/tests/compiler/instanceof-class.untouched.wat new file mode 100644 index 0000000000..996d342856 --- /dev/null +++ b/tests/compiler/instanceof-class.untouched.wat @@ -0,0 +1,280 @@ +(module + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $none_=>_none (func)) + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (type $i32_=>_none (func (param i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (memory $0 1) + (data (i32.const 16) "&\00\00\00\01\00\00\00\01\00\00\00&\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00-\00c\00l\00a\00s\00s\00.\00t\00s\00") + (data (i32.const 80) "\07\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\04\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\10\00\00\00\05\00\00\00") + (table $0 1 funcref) + (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) + (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) + (global $instanceof-class/a (mut i32) (i32.const 0)) + (global $instanceof-class/b (mut i32) (i32.const 0)) + (global $~lib/rt/__rtti_base i32 (i32.const 80)) + (global $~lib/heap/__heap_base i32 (i32.const 140)) + (export "memory" (memory $0)) + (start $~start) + (func $~lib/rt/stub/maybeGrowMemory (; 1 ;) (param $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + memory.size + local.set $1 + local.get $1 + i32.const 16 + i32.shl + local.set $2 + local.get $0 + local.get $2 + i32.gt_u + if + local.get $0 + local.get $2 + i32.sub + i32.const 65535 + i32.add + i32.const 65535 + i32.const -1 + i32.xor + i32.and + i32.const 16 + i32.shr_u + local.set $3 + local.get $1 + local.tee $4 + local.get $3 + local.tee $5 + local.get $4 + local.get $5 + i32.gt_s + select + local.set $4 + local.get $4 + memory.grow + i32.const 0 + i32.lt_s + if + local.get $3 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + local.get $0 + global.set $~lib/rt/stub/offset + ) + (func $~lib/rt/stub/__alloc (; 2 ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + local.get $0 + i32.const 1073741808 + i32.gt_u + if + unreachable + end + global.get $~lib/rt/stub/offset + i32.const 16 + i32.add + local.set $2 + local.get $0 + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + local.tee $3 + i32.const 16 + local.tee $4 + local.get $3 + local.get $4 + i32.gt_u + select + local.set $5 + local.get $2 + local.get $5 + i32.add + call $~lib/rt/stub/maybeGrowMemory + local.get $2 + i32.const 16 + i32.sub + local.set $6 + local.get $6 + local.get $5 + i32.store + local.get $6 + i32.const 1 + i32.store offset=4 + local.get $6 + local.get $1 + i32.store offset=8 + local.get $6 + local.get $0 + i32.store offset=12 + local.get $2 + ) + (func $~lib/rt/stub/__retain (; 3 ;) (param $0 i32) (result i32) + local.get $0 + ) + (func $instanceof-class/Parent#constructor (; 4 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + i32.const 4 + call $~lib/rt/stub/__alloc + call $~lib/rt/stub/__retain + local.set $0 + end + local.get $0 + ) + (func $instanceof-class/Child#constructor (; 5 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + i32.const 3 + call $~lib/rt/stub/__alloc + call $~lib/rt/stub/__retain + local.set $0 + end + local.get $0 + call $instanceof-class/Parent#constructor + local.set $0 + local.get $0 + ) + (func $instanceof-class/Parent#constructor (; 6 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + i32.const 5 + call $~lib/rt/stub/__alloc + call $~lib/rt/stub/__retain + local.set $0 + end + local.get $0 + ) + (func $instanceof-class/Child#constructor (; 7 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + i32.const 6 + call $~lib/rt/stub/__alloc + call $~lib/rt/stub/__retain + local.set $0 + end + local.get $0 + call $instanceof-class/Parent#constructor + local.set $0 + local.get $0 + ) + (func $start:instanceof-class (; 8 ;) + global.get $~lib/heap/__heap_base + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + global.set $~lib/rt/stub/startOffset + global.get $~lib/rt/stub/startOffset + global.set $~lib/rt/stub/offset + i32.const 0 + call $instanceof-class/Child#constructor + global.set $instanceof-class/a + i32.const 0 + call $instanceof-class/Child#constructor + global.set $instanceof-class/b + global.get $instanceof-class/b + call $instanceof-class/Child~instanceof + i32.eqz + if + i32.const 0 + i32.const 32 + i32.const 17 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + ) + (func $~start (; 9 ;) + call $start:instanceof-class + ) + (func $~lib/rt/__instanceof (; 10 ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + local.get $0 + i32.const 16 + i32.sub + i32.load offset=8 + local.set $2 + global.get $~lib/rt/__rtti_base + local.set $3 + local.get $2 + local.get $3 + i32.load + i32.le_u + if + loop $do-continue|0 + local.get $2 + local.get $1 + i32.eq + if + i32.const 1 + return + end + local.get $3 + i32.const 4 + i32.add + local.get $2 + i32.const 8 + i32.mul + i32.add + i32.load offset=4 + local.tee $2 + local.set $4 + local.get $4 + br_if $do-continue|0 + end + end + i32.const 0 + ) + (func $instanceof-class/Child~instanceof (; 11 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 0 + return + end + local.get $0 + i32.const 3 + call $~lib/rt/__instanceof + if + i32.const 1 + return + end + local.get $0 + i32.const 6 + call $~lib/rt/__instanceof + if + i32.const 1 + return + end + i32.const 0 + return + ) +)