initial __runtime_instanceof

This commit is contained in:
dcode
2019-04-02 21:30:47 +02:00
parent d85a43892f
commit a639a42f0d
66 changed files with 654 additions and 157 deletions

View File

@ -54,7 +54,6 @@ import {
import {
ElementKind,
FunctionPrototype,
Class,
Field,
Global,
DecoratorFlags
@ -479,6 +478,7 @@ export namespace BuiltinSymbols {
// std/runtime.ts
export const runtime_id = "~lib/runtime/__runtime_id";
export const runtime_instanceof = "~lib/runtime/__runtime_instanceof";
export const runtime_allocate = "~lib/runtime/runtime.allocate";
export const runtime_reallocate = "~lib/runtime/runtime.reallocate";
export const runtime_register = "~lib/runtime/runtime.register";
@ -3652,6 +3652,20 @@ export function compileCall(
}
return module.createI32(classReference.ensureId());
}
case BuiltinSymbols.runtime_instanceof: {
if (
checkTypeAbsent(typeArguments, reportNode, prototype) |
checkArgsRequired(operands, 2, reportNode, compiler)
) {
compiler.currentType = Type.void;
return module.createUnreachable();
}
let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
let arg1 = compiler.compileExpression(operands[1], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
compiler.needsInstanceOf = true;
compiler.currentType = Type.bool;
return module.createCall(BuiltinSymbols.runtime_instanceof, [ arg0, arg1 ], NativeType.I32);
}
case BuiltinSymbols.gc_mark_roots: {
if (
checkTypeAbsent(typeArguments, reportNode, prototype) |
@ -3660,7 +3674,7 @@ export function compileCall(
compiler.currentType = Type.void;
return module.createUnreachable();
}
compiler.needsTraverse = true;
compiler.needsMark = true;
compiler.currentType = Type.void;
return module.createCall(BuiltinSymbols.gc_mark_roots, null, NativeType.None);
}
@ -3674,7 +3688,7 @@ export function compileCall(
}
let arg0 = compiler.compileExpression(operands[0], Type.u32, ConversionKind.IMPLICIT, WrapMode.NONE);
let arg1 = compiler.compileExpression(operands[1], compiler.options.usizeType, ConversionKind.IMPLICIT, WrapMode.NONE);
compiler.needsTraverse = true;
compiler.needsMark = true;
compiler.currentType = Type.void;
return module.createCall(BuiltinSymbols.gc_mark_members, [ arg0, arg1 ], NativeType.None);
}
@ -4061,7 +4075,7 @@ export function compileAbort(
]);
}
/** Compiles the mark_roots function if required. */
/** Compiles the `__gc_mark_roots` function. */
export function compileMarkRoots(compiler: Compiler): void {
var module = compiler.module;
var exprs = new Array<ExpressionRef>();
@ -4113,6 +4127,7 @@ export function compileMarkRoots(compiler: Compiler): void {
);
}
/** Compiles the `__gc_mark_members` function. */
export function compileMarkMembers(compiler: Compiler): void {
var program = compiler.program;
var module = compiler.module;
@ -4221,6 +4236,81 @@ export function compileMarkMembers(compiler: Compiler): void {
module.addFunction(BuiltinSymbols.gc_mark_members, ftype, [ nativeSizeType ], current);
}
/** Compiles the `__runtime_instanceof` function. */
export function compileInstanceOf(compiler: Compiler): void {
var program = compiler.program;
var module = compiler.module;
var managedClasses = program.managedClasses;
var ftype = compiler.ensureFunctionType([ Type.i32, Type.i32 ], Type.i32); // $0 instanceof $1 -> bool
// NOTE: There are multiple ways to model this. The one chosen here is to compute
// all possibilities in a branchless expression, growing linearly with the number
// of chained base classes.
//
// switch ($0) {
// case ANIMAL_ID: {
// return ($1 == ANIMAL_ID);
// }
// case CAT_ID: {
// return ($1 == CAT_ID) | ($1 == ANIMAL_ID);
// }
// case BLACKCAT_ID: {
// return ($1 == BLACKCAT_ID) | ($1 == CAT_ID) | ($1 == ANIMAL_ID);
// }
// }
// return false;
//
// Another one would be an inner br_table, but class id distribution in larger
// programs in unclear, possibly leading to lots of holes in that table that
// could either degenerate into multiple ifs when compiling for size or to
// huge tables when compiling for speed.
//
// Maybe a combination of both could be utilized, like statically analyzing the
// ids and make a decision based on profiling experience?
var names: string[] = [ "nope" ];
var blocks = new Array<ExpressionRef[]>();
for (let [id, instance] of managedClasses) {
names.push(instance.internalName);
let condition = module.createBinary(BinaryOp.EqI32,
module.createGetLocal(1, NativeType.I32),
module.createI32(id)
);
let base = instance.base;
while (base) {
condition = module.createBinary(BinaryOp.OrI32,
condition,
module.createBinary(BinaryOp.EqI32,
module.createGetLocal(1, NativeType.I32),
module.createI32(base.ensureId())
)
);
base = base.base;
}
blocks.push([
module.createReturn(condition)
]);
}
var current: ExpressionRef;
if (blocks.length) {
current = module.createBlock(names[1], [
module.createSwitch(names, "nope", module.createGetLocal(0, NativeType.I32))
]);
for (let i = 0, k = blocks.length; i < k; ++i) {
blocks[i].unshift(current);
current = module.createBlock(i == k - 1 ? "nope" : names[i + 2], blocks[i]);
}
current = module.createBlock(null, [
current,
module.createReturn(module.createI32(0))
]);
} else {
current = module.createReturn(module.createI32(0));
}
module.addFunction(BuiltinSymbols.runtime_instanceof, ftype, null, current);
}
// Helpers
/** Evaluates the constant type of a type argument *or* expression. */

View File

@ -8,6 +8,7 @@ import {
compileAbort,
compileMarkRoots,
compileMarkMembers,
compileInstanceOf,
BuiltinSymbols
} from "./builtins";
@ -310,8 +311,10 @@ export class Compiler extends DiagnosticEmitter {
argcVar: GlobalRef = 0;
/** Argument count helper setter. */
argcSet: FunctionRef = 0;
/** Indicates whether the traverseRoots function must be generated. */
needsTraverse: bool = false;
/** Indicates whether the __gc_mark_* functions must be generated. */
needsMark: bool = false;
/** Indicates whether the __runtime_instanceof function must be generated. */
needsInstanceOf: bool = false;
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
static compile(program: Program, options: Options | null = null): Module {
@ -393,12 +396,17 @@ export class Compiler extends DiagnosticEmitter {
if (!explicitStartFunction) module.setStart(funcRef);
}
// compile gc integration if necessary
if (this.needsTraverse) {
// compile gc features if utilized
if (this.needsMark) {
compileMarkRoots(this);
compileMarkMembers(this);
}
// compile runtime features if utilized
if (this.needsInstanceOf) {
compileInstanceOf(this);
}
// update the heap base pointer
var memoryOffset = this.memoryOffset;
memoryOffset = i64_align(memoryOffset, options.usizeType.byteSize);

View File

@ -812,11 +812,16 @@ export class Flow {
// overflows if the call does not return a wrapped value or the conversion does
case ExpressionId.Call: {
let program = this.parentFunction.program;
let instance = assert(program.instancesByName.get(assert(getCallTarget(expr))));
assert(instance.kind == ElementKind.FUNCTION);
let returnType = (<Function>instance).signature.returnType;
return !(<Function>instance).flow.is(FlowFlags.RETURNS_WRAPPED)
|| canConversionOverflow(returnType, type);
let instancesByName = program.instancesByName;
let instanceName = assert(getCallTarget(expr));
if (instancesByName.has(instanceName)) {
let instance = instancesByName.get(instanceName)!;
assert(instance.kind == ElementKind.FUNCTION);
let returnType = (<Function>instance).signature.returnType;
return !(<Function>instance).flow.is(FlowFlags.RETURNS_WRAPPED)
|| canConversionOverflow(returnType, type);
}
return false; // assume no overflow for builtins
}
// doesn't technically overflow