directize

This commit is contained in:
dcode
2019-04-02 10:12:57 +02:00
parent 1ada854830
commit 3bcd32f3ba
91 changed files with 4225 additions and 4615 deletions

View File

@ -135,6 +135,7 @@ export namespace BuiltinSymbols {
export const changetype = "~lib/builtins/changetype";
export const assert = "~lib/builtins/assert";
export const unchecked = "~lib/builtins/unchecked";
export const call_direct = "~lib/builtins/call_direct";
export const call_indirect = "~lib/builtins/call_indirect";
export const instantiate = "~lib/builtins/instantiate";
@ -477,14 +478,17 @@ export namespace BuiltinSymbols {
export const memory_reset = "~lib/memory/memory.reset";
// std/runtime.ts
export const classId = "~lib/runtime/classId";
export const iterateRoots = "~lib/runtime/iterateRoots";
export const runtime_id = "~lib/runtime/__runtime_id";
export const runtime_allocate = "~lib/runtime/runtime.allocate";
export const runtime_reallocate = "~lib/runtime/runtime.reallocate";
export const runtime_register = "~lib/runtime/runtime.register";
export const runtime_discard = "~lib/runtime/runtime.discard";
export const runtime_makeArray = "~lib/runtime/runtime.makeArray";
// std/gc.ts
export const gc_mark_roots = "~lib/gc/__gc_mark_roots";
export const gc_mark_members = "~lib/gc/__gc_mark_members";
// std/typedarray.ts
export const Int8Array = "~lib/typedarray/Int8Array";
export const Uint8Array = "~lib/typedarray/Uint8Array";
@ -532,6 +536,8 @@ export function compileCall(
// below, but rather done to make this file easier to work with. If there was a general rule it'd
// most likely be "three or more instructions that only differ in their actual opcode".
var directize = false;
switch (prototype.internalName) {
// === Static type evaluation =================================================================
@ -2337,6 +2343,7 @@ export function compileCall(
if (!alreadyUnchecked) flow.unset(FlowFlags.UNCHECKED_CONTEXT);
return expr;
}
case BuiltinSymbols.call_direct: directize = true;
case BuiltinSymbols.call_indirect: { // call_indirect<T?>(target: Function | u32, ...args: *[]) -> T
if (
checkTypeOptional(typeArguments, reportNode, compiler, true) |
@ -2370,14 +2377,21 @@ export function compileCall(
let typeRef = module.getFunctionTypeBySignature(nativeReturnType, nativeParamTypes);
if (!typeRef) typeRef = module.addFunctionType(typeName, nativeReturnType, nativeParamTypes);
compiler.currentType = returnType;
// if the index expression is precomputable to a constant value, emit a direct call
if (getExpressionId(arg0 = module.precomputeExpression(arg0)) == ExpressionId.Const) {
assert(getExpressionType(arg0) == NativeType.I32);
let index = getConstValueI32(arg0);
let functionTable = compiler.functionTable;
if (index >= 0 && index < functionTable.length) {
return module.createCall(functionTable[index], operandExprs, nativeReturnType);
if (directize) {
// if the index expression is precomputable to a constant value, emit a direct call
if (getExpressionId(arg0 = module.precomputeExpression(arg0)) == ExpressionId.Const) {
assert(getExpressionType(arg0) == NativeType.I32);
let index = getConstValueI32(arg0);
let functionTable = compiler.functionTable;
if (index >= 0 && index < functionTable.length) {
return module.createCall(functionTable[index], operandExprs, nativeReturnType);
}
}
compiler.error(
DiagnosticCode.Operation_not_supported,
operands[0].range
);
return module.createUnreachable();
}
// of course this can easily result in a 'RuntimeError: function signature mismatch' trap and
// thus must be used with care. it exists because it *might* be useful in specific scenarios.
@ -3624,7 +3638,7 @@ export function compileCall(
// === Internal runtime =======================================================================
case BuiltinSymbols.classId: {
case BuiltinSymbols.runtime_id: {
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.u32;
if (!type) return module.createUnreachable();
@ -3636,38 +3650,21 @@ export function compileCall(
);
return module.createUnreachable();
}
let classId = classReference.ensureClassId(compiler); // involves compile steps
let id = classReference.ensureId(compiler); // involves compile steps
compiler.currentType = Type.u32;
return module.createI32(classId);
return module.createI32(id);
}
case BuiltinSymbols.iterateRoots: {
case BuiltinSymbols.gc_mark_roots: {
if (
checkTypeAbsent(typeArguments, reportNode, prototype) |
checkArgsRequired(operands, 1, reportNode, compiler)
checkArgsRequired(operands, 0, reportNode, compiler)
) {
compiler.currentType = Type.void;
return module.createUnreachable();
}
let expr = compiler.compileExpressionRetainType(operands[0], Type.u32, WrapMode.NONE);
let type = compiler.currentType;
let signatureReference = type.signatureReference;
if (
!type.is(TypeFlags.REFERENCE) ||
!signatureReference ||
signatureReference.parameterTypes.length != 1 ||
signatureReference.parameterTypes[0] != compiler.options.usizeType
) {
compiler.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
reportNode.range, type.toString(), "(ref: usize) => void"
);
compiler.currentType = Type.void;
return module.createUnreachable();
}
// just emit a call even if the function doesn't yet exist
compiler.needsIterateRoots = true;
compiler.needsTraverse = true;
compiler.currentType = Type.void;
return module.createCall("~iterateRoots", [ expr ], NativeType.None);
return module.createCall(BuiltinSymbols.gc_mark_roots, null, NativeType.None);
}
}
@ -4052,13 +4049,15 @@ export function compileAbort(
]);
}
/** Compiles the iterateRoots function if required. */
export function compileIterateRoots(compiler: Compiler): void {
/** Compiles the mark_roots function if required. */
export function compileMarkRoots(compiler: Compiler): void {
var module = compiler.module;
var exprs = new Array<ExpressionRef>();
var typeName = Signature.makeSignatureString([ Type.i32 ], Type.void);
var typeRef = compiler.ensureFunctionType([ Type.i32 ], Type.void);
var typeRef = compiler.ensureFunctionType(null, Type.void);
var nativeSizeType = compiler.options.nativeSizeType;
var markRef = assert(compiler.program.markRef);
compiler.compileFunction(markRef);
for (let element of compiler.program.elementsByName.values()) {
if (element.kind != ElementKind.GLOBAL) continue;
@ -4073,43 +4072,51 @@ export function compileIterateRoots(compiler: Compiler): void {
let value = global.constantIntegerValue;
if (i64_low(value) || i64_high(value)) {
exprs.push(
module.createCallIndirect(
module.createGetLocal(0, NativeType.I32),
[
compiler.options.isWasm64
? module.createI64(i64_low(value), i64_high(value))
: module.createI32(i64_low(value))
],
typeName
)
module.createCall(markRef.internalName, [
compiler.options.isWasm64
? module.createI64(i64_low(value), i64_high(value))
: module.createI32(i64_low(value))
], NativeType.None)
);
}
} else {
exprs.push(
module.createIf(
module.createTeeLocal(
1,
0,
module.createGetGlobal(global.internalName, nativeSizeType)
),
module.createCallIndirect(
module.createGetLocal(0, NativeType.I32),
[
module.createGetLocal(1, nativeSizeType)
],
typeName
)
module.createCall(markRef.internalName, [
module.createGetLocal(0, nativeSizeType)
], NativeType.None)
)
);
}
}
}
module.addFunction("~iterateRoots", typeRef, [ nativeSizeType ],
module.addFunction(BuiltinSymbols.gc_mark_roots, typeRef, [ nativeSizeType ],
exprs.length
? module.createBlock(null, exprs)
: module.createNop()
);
}
// TODO
export function compileMarkMembers(compiler: Compiler): void {
var module = compiler.module;
var ftype = compiler.ensureFunctionType(null, Type.void);
var names = new Array<string>();
var current = module.createSwitch(names, "invalid", module.createGetLocal(0, NativeType.I32));
module.addFunction(BuiltinSymbols.gc_mark_members, ftype, [], module.createBlock(null, [
module.createBlock("invalid", [
current
]),
module.createUnreachable()
]));
}
// Helpers
/** Evaluates the constant type of a type argument *or* expression. */

View File

@ -6,7 +6,7 @@
import {
compileCall as compileBuiltinCall,
compileAbort,
compileIterateRoots,
compileMarkRoots,
BuiltinSymbols
} from "./builtins";
@ -309,8 +309,8 @@ export class Compiler extends DiagnosticEmitter {
argcVar: GlobalRef = 0;
/** Argument count helper setter. */
argcSet: FunctionRef = 0;
/** Indicates whether the iterateRoots function must be generated. */
needsIterateRoots: bool = false;
/** Indicates whether the traverseRoots function must be generated. */
needsTraverse: bool = false;
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
static compile(program: Program, options: Options | null = null): Module {
@ -444,7 +444,10 @@ export class Compiler extends DiagnosticEmitter {
}
// set up gc
if (this.needsIterateRoots) compileIterateRoots(this);
if (this.needsTraverse) {
compileMarkRoots(this);
// compileMarkMembers(this);
}
// expose module capabilities
var capabilities = Capability.NONE;
@ -1409,7 +1412,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
let length = stringValue.length;
let buffer = new Uint8Array(rtHeaderSize + (length << 1));
program.writeRuntimeHeader(buffer, 0, stringInstance.ensureClassId(this), length << 1);
program.writeRuntimeHeader(buffer, 0, stringInstance.ensureId(this), length << 1);
for (let i = 0; i < length; ++i) {
writeI16(stringValue.charCodeAt(i), buffer, rtHeaderSize + (i << 1));
}
@ -1435,7 +1438,7 @@ export class Compiler extends DiagnosticEmitter {
var runtimeHeaderSize = program.runtimeHeaderSize;
var buf = new Uint8Array(runtimeHeaderSize + byteLength);
program.writeRuntimeHeader(buf, 0, bufferInstance.ensureClassId(this), byteLength);
program.writeRuntimeHeader(buf, 0, bufferInstance.ensureId(this), byteLength);
var pos = runtimeHeaderSize;
var nativeType = elementType.toNativeType();
switch (nativeType) {
@ -1522,7 +1525,7 @@ export class Compiler extends DiagnosticEmitter {
var arrayLength = i32(bufferLength / elementType.byteSize);
var buf = new Uint8Array(runtimeHeaderSize + arrayInstanceSize);
program.writeRuntimeHeader(buf, 0, arrayInstance.ensureClassId(this), arrayInstanceSize);
program.writeRuntimeHeader(buf, 0, arrayInstance.ensureId(this), arrayInstanceSize);
var bufferAddress32 = i64_low(bufferSegment.offset) + runtimeHeaderSize;
assert(!program.options.isWasm64); // TODO
@ -5950,6 +5953,7 @@ export class Compiler extends DiagnosticEmitter {
// create the trampoline element
var trampolineSignature = new Signature(originalParameterTypes, commonReturnType, commonThisType);
trampolineSignature.requiredParameters = maxArguments;
trampolineSignature.parameterNames = originalSignature.parameterNames;
trampoline = new Function(
original.name + "|trampoline",
original.prototype,
@ -6122,12 +6126,15 @@ export class Compiler extends DiagnosticEmitter {
)
)
) { // inline into the call
let previousFlow = this.currentFlow;
this.currentFlow = instance.flow;
operands.push(this.compileExpression(
<Expression>parameterNodes[i].initializer,
parameterTypes[i],
ConversionKind.IMPLICIT,
WrapMode.NONE
));
this.currentFlow = previousFlow;
continue;
}
}
@ -6821,7 +6828,7 @@ export class Compiler extends DiagnosticEmitter {
// makeArray(length, classId, alignLog2, staticBuffer)
let expr = this.makeCallDirect(assert(program.makeArrayInstance), [
module.createI32(length),
module.createI32(arrayInstance.ensureClassId(this)),
module.createI32(arrayInstance.ensureId(this)),
program.options.isWasm64
? module.createI64(elementType.alignLog2)
: module.createI32(elementType.alignLog2),
@ -6855,7 +6862,7 @@ export class Compiler extends DiagnosticEmitter {
module.createSetLocal(tempThis.index,
this.makeCallDirect(makeArrayInstance, [
module.createI32(length),
module.createI32(arrayInstance.ensureClassId(this)),
module.createI32(arrayInstance.ensureId(this)),
program.options.isWasm64
? module.createI64(elementType.alignLog2)
: module.createI32(elementType.alignLog2),
@ -8116,7 +8123,7 @@ export class Compiler extends DiagnosticEmitter {
? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset)
], reportNode),
module.createI32(classInstance.ensureClassId(this))
module.createI32(classInstance.ensureId(this))
], reportNode);
}
}
@ -8321,7 +8328,7 @@ export class Compiler extends DiagnosticEmitter {
module.createBreak(label,
module.createBinary(BinaryOp.EqI32, // classId == class.id
module.createTeeLocal(idTemp.index, idExpr),
module.createI32(classInstance.ensureClassId(this))
module.createI32(classInstance.ensureId(this))
),
module.createI32(1) // ? true
)
@ -8337,24 +8344,24 @@ export class Compiler extends DiagnosticEmitter {
return module.createBlock(label, conditions, NativeType.I32);
}
/** Reserves the function index / class id for the following `makeIterate` operation. */
makeIterateReserve(classInstance: Class): u32 {
/** Reserves the function index / class id for the following `makeTraverse` operation. */
makeTraverseReserve(classInstance: Class): u32 {
var functionTable = this.functionTable;
var functionIndex = functionTable.length;
functionTable.push(classInstance.iterateName);
functionTable.push(classInstance.internalName + "~traverse");
return functionIndex;
}
/** Makes the managed iteration function of the specified class. */
makeIterate(classInstance: Class, functionIndex: i32): void {
/** Makes the managed traversal function of the specified class. */
makeTraverse(classInstance: Class, functionIndex: i32): void {
var program = this.program;
assert(classInstance.type.isManaged(program));
// check if the class implements a custom iteration function (only valid for library elements)
var members = classInstance.members;
if (classInstance.isDeclaredInLibrary) {
if (members !== null && members.has("__iterate")) {
let iterPrototype = members.get("__iterate")!;
if (members !== null && members.has("__traverse")) {
let iterPrototype = members.get("__traverse")!;
assert(iterPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
let iterInstance = assert(program.resolver.resolveFunction(<FunctionPrototype>iterPrototype, null));
assert(iterInstance.is(CommonFlags.PRIVATE | CommonFlags.INSTANCE));
@ -8362,10 +8369,9 @@ export class Compiler extends DiagnosticEmitter {
assert(!iterInstance.isAny(CommonFlags.AMBIENT | CommonFlags.VIRTUAL));
let signature = iterInstance.signature;
let parameterTypes = signature.parameterTypes;
assert(parameterTypes.length == 1);
assert(parameterTypes[0].signatureReference);
assert(parameterTypes.length == 0);
assert(signature.returnType == Type.void);
iterInstance.internalName = classInstance.iterateName;
iterInstance.internalName = classInstance.internalName + "~traverse";
assert(this.compileFunction(iterInstance));
this.ensureFunctionTableEntry(iterInstance);
return;
@ -8394,29 +8400,28 @@ export class Compiler extends DiagnosticEmitter {
// remember the function index so we don't recurse infinitely
var functionTable = this.functionTable;
var functionName = classInstance.iterateName;
var functionName = classInstance.internalName + "~traverse";
assert(functionIndex < functionTable.length);
assert(functionTable[functionIndex] == functionName);
var fnSig = Signature.makeSignatureString([ usizeType ], Type.void);
this.ensureFunctionType([ usizeType ], Type.void);
// if the class extends a base class, call its hook first
var baseInstance = classInstance.base;
if (baseInstance) {
let baseType = baseInstance.type;
let baseClassId = baseInstance.ensureClassId(this);
let baseClassId = baseInstance.ensureId(this);
assert(baseType.isManaged(program));
body.push(
// BASECLASS~iterate.call(this, fn)
// BASECLASS~traverse.call(this)
module.createCall(functionTable[baseClassId], [
module.createGetLocal(0, nativeSizeType),
module.createGetLocal(1, NativeType.I32)
module.createGetLocal(0, nativeSizeType)
], NativeType.None)
);
}
// iterate references assigned to own fields
var markRef = assert(program.markRef);
var hasRefFields = false;
// traverse references assigned to own fields
if (members) {
for (let member of members.values()) {
if (member.kind == ElementKind.FIELD) {
@ -8424,13 +8429,14 @@ export class Compiler extends DiagnosticEmitter {
let fieldType = (<Field>member).type;
if (fieldType.isManaged(program)) {
let fieldClass = fieldType.classReference!;
let fieldClassId = fieldClass.ensureClassId(this);
let fieldClassId = fieldClass.ensureId(this);
let fieldOffset = (<Field>member).memoryOffset;
assert(fieldOffset >= 0);
hasRefFields = true;
body.push(
// if ($2 = value) { fn($2); FIELDCLASS~iterate($2, fn); }
// if ($1 = value) FIELDCLASS~traverse($1)
module.createIf(
module.createTeeLocal(2,
module.createTeeLocal(1,
module.createLoad(
nativeSizeSize,
false,
@ -8440,15 +8446,11 @@ export class Compiler extends DiagnosticEmitter {
)
),
module.createBlock(null, [
module.createCallIndirect(
module.createGetLocal(1, NativeType.I32),
[
module.createGetLocal(2, nativeSizeType)
], fnSig
),
module.createCall(markRef.internalName, [
module.createGetLocal(1, nativeSizeType)
], NativeType.None),
module.createCall(functionTable[fieldClassId], [
module.createGetLocal(2, nativeSizeType),
module.createGetLocal(1, NativeType.I32)
module.createGetLocal(1, nativeSizeType)
], NativeType.None)
])
)
@ -8459,10 +8461,12 @@ export class Compiler extends DiagnosticEmitter {
}
}
if (hasRefFields) this.compileFunction(markRef);
// add the function to the module and return its table index
module.addFunction(
functionName,
this.ensureFunctionType([ Type.u32 ], Type.void, options.usizeType),
this.ensureFunctionType(null, Type.void, options.usizeType),
members ? [ nativeSizeType ] : null,
module.createBlock(null, body)
);

View File

@ -389,6 +389,8 @@ export class Program extends DiagnosticEmitter {
retainRef: Function | null = null;
/** Reference release implementation, if present: `__ref_release(ref: usize): void` */
releaseRef: Function | null = null;
/** Reference mark implementation, if present: `__ref_mark(ref: usize): void` */
markRef: Function | null = null;
/** Next class id. */
nextClassId: u32 = 1;
@ -861,6 +863,9 @@ export class Program extends DiagnosticEmitter {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.unlinkRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
}
element = assert(this.lookupGlobal("__ref_mark"));
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
this.markRef = this.resolver.resolveFunction(<FunctionPrototype>element, null);
this.collectorKind = CollectorKind.TRACING;
} else if (element = this.lookupGlobal("__ref_retain")) {
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
@ -3012,7 +3017,7 @@ export class Class extends TypedElement {
private _id: u32 = 0;
/** Ensures that this class has an id. */
ensureClassId(compiler: Compiler): i32 {
ensureId(compiler: Compiler): i32 {
var id = this._id;
if (!id) {
assert(!this.hasDecorator(DecoratorFlags.UNMANAGED));
@ -3022,8 +3027,8 @@ export class Class extends TypedElement {
// class's id so it can call the id directly, which avoids to generate
// a helper function with a big switch mapping ids to function indexes.
// here: might be called recursively in makeIterate, so reserve the id.
this._id = id = compiler.makeIterateReserve(this);
compiler.makeIterate(this, id);
this._id = id = compiler.makeTraverseReserve(this);
compiler.makeTraverse(this, id);
} else {
// counting GC or none just increments without any iterate functions
this._id = id = program.nextClassId++;
@ -3053,11 +3058,6 @@ export class Class extends TypedElement {
);
}
/** Gets the name of this class's GC iteration function. */
get iterateName(): string {
return this.internalName + "~iterate";
}
/** Constructs a new class. */
constructor(
/** Name incl. type parameters, i.e. `Foo<i32>`. */