mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-19 18:01:31 +00:00
initial __runtime_instanceof
This commit is contained in:
@ -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. */
|
||||
|
@ -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);
|
||||
|
15
src/flow.ts
15
src/flow.ts
@ -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
|
||||
|
Reference in New Issue
Block a user