Initial GC integration (#196)

This commit is contained in:
Daniel Wirtz
2018-08-02 18:23:02 +02:00
committed by GitHub
parent 671121bf70
commit dc0f271fc2
139 changed files with 7370 additions and 5016 deletions

View File

@ -134,7 +134,7 @@ export function compileCall(
compiler.currentType = Type.bool;
if (!type) return module.createUnreachable();
let classType = type.classReference;
return classType != null && classType.lookupOverload(OperatorKind.INDEXED_GET) != null
return classType !== null && classType.lookupOverload(OperatorKind.INDEXED_GET) !== null
? module.createI32(1)
: module.createI32(0);
}
@ -175,6 +175,19 @@ export function compileCall(
compiler.currentType = Type.bool;
return module.createI32(getExpressionId(expr) == ExpressionId.Const ? 1 : 0);
}
case "isManaged": { // isManaged<T>() -> bool
if (!compiler.program.hasGC) {
compiler.currentType = Type.bool;
return module.createI32(0);
}
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.bool;
if (!type) return module.createUnreachable();
let classType = type.classReference;
return classType !== null && !classType.hasDecorator(DecoratorFlags.UNMANAGED)
? module.createI32(1)
: module.createI32(0);
}
// math
@ -2881,25 +2894,46 @@ export function compileAllocate(
assert(classInstance.program == program);
var module = compiler.module;
var options = compiler.options;
var allocateInstance = program.memoryAllocateInstance;
if (!allocateInstance) {
program.error(
DiagnosticCode.Cannot_find_name_0,
reportNode.range, "memory.allocate"
);
return module.createUnreachable();
}
if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
compiler.currentType = classInstance.type;
return module.createCall(
allocateInstance.internalName, [
options.isWasm64
? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset)
],
options.nativeSizeType
);
// __gc_allocate(size, markFn)
if (program.hasGC && classInstance.type.isManaged(program)) {
let allocateInstance = assert(program.gcAllocateInstance);
if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
compiler.currentType = classInstance.type;
return module.createCall(
allocateInstance.internalName, [
options.isWasm64
? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset),
module.createI32(
ensureGCHook(compiler, classInstance)
)
],
options.nativeSizeType
);
// memory.allocate(size)
} else {
let allocateInstance = program.memoryAllocateInstance;
if (!allocateInstance) {
program.error(
DiagnosticCode.Cannot_find_name_0,
reportNode.range, "memory.allocate"
);
return module.createUnreachable();
}
if (!compiler.compileFunction(allocateInstance)) return module.createUnreachable();
compiler.currentType = classInstance.type;
return module.createCall(
allocateInstance.internalName, [
options.isWasm64
? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset)
],
options.nativeSizeType
);
}
}
/** Compiles an abort wired to the conditionally imported 'abort' function. */
@ -2921,7 +2955,7 @@ export function compileAbort(
? compiler.compileExpression(message, stringType, ConversionKind.IMPLICIT, WrapMode.NONE)
: stringType.toNativeZero(module);
var filenameArg = compiler.compileStaticString(reportNode.range.source.normalizedPath);
var filenameArg = compiler.ensureStaticString(reportNode.range.source.normalizedPath);
compiler.currentType = Type.void;
return module.createBlock(null, [
@ -2988,3 +3022,122 @@ export function compileIterateRoots(compiler: Compiler): void {
: module.createNop()
);
}
/** Ensures that the specified class's GC hook exists and returns its function table index. */
export function ensureGCHook(
compiler: Compiler,
classInstance: Class
): u32 {
var program = compiler.program;
assert(classInstance.type.isManaged(program));
// check if the GC hook has already been created
{
let existingIndex = classInstance.gcHookIndex;
if (existingIndex != <u32>-1) return existingIndex;
}
// check if the class implements a custom GC function (only valid for internals)
var members = classInstance.members;
if (classInstance.prototype.declaration.range.source.isLibrary) {
if (members !== null && members.has("__gc")) {
let gcPrototype = assert(members.get("__gc"));
assert(gcPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
let gcInstance = assert(program.resolver.resolveFunction(<FunctionPrototype>gcPrototype, null));
assert(gcInstance.is(CommonFlags.PRIVATE | CommonFlags.INSTANCE));
assert(!gcInstance.isAny(CommonFlags.AMBIENT | CommonFlags.VIRTUAL));
assert(gcInstance.signature.parameterTypes.length == 0);
assert(gcInstance.signature.returnType == Type.void);
gcInstance.internalName = classInstance.internalName + "~gc";
assert(compiler.compileFunction(gcInstance));
let index = compiler.ensureFunctionTableEntry(gcInstance);
classInstance.gcHookIndex = index;
return index;
}
}
var module = compiler.module;
var options = compiler.options;
var nativeSizeType = options.nativeSizeType;
var nativeSizeSize = options.usizeType.byteSize;
var body = new Array<ExpressionRef>();
// nothing to mark if 'this' is null
body.push(
module.createIf(
module.createUnary(
options.isWasm64
? UnaryOp.EqzI64
: UnaryOp.EqzI32,
module.createGetLocal(0, nativeSizeType)
),
module.createReturn()
)
);
// remember the function index so we don't recurse infinitely
var functionTable = compiler.functionTable;
var gcHookIndex = functionTable.length;
functionTable.push(0);
classInstance.gcHookIndex = gcHookIndex;
// if the class extends a base class, call its hook first (calls mark)
var baseInstance = classInstance.base;
if (baseInstance) {
assert(baseInstance.type.isManaged(program));
body.push(
module.createCallIndirect(
module.createI32(
ensureGCHook(compiler, <Class>baseInstance.type.classReference)
),
[
module.createGetLocal(0, nativeSizeType)
],
nativeSizeType == NativeType.I64 ? "Iv" : "iv"
)
);
// if this class is the top-most base class, mark the instance
} else {
body.push(
module.createCall(assert(program.gcMarkInstance).internalName, [
module.createGetLocal(0, nativeSizeType)
], NativeType.None)
);
}
// mark instances assigned to own fields that are again references
if (members) {
for (let member of members.values()) {
if (member.kind == ElementKind.FIELD) {
if ((<Field>member).parent === classInstance) {
let type = (<Field>member).type;
if (type.isManaged(program)) {
let offset = (<Field>member).memoryOffset;
assert(offset >= 0);
body.push(
module.createCall(assert(program.gcMarkInstance).internalName, [
module.createLoad(
nativeSizeSize,
false,
module.createGetLocal(0, nativeSizeType),
nativeSizeType,
offset
)
], NativeType.None)
);
}
}
}
}
}
// add the function to the module and return its table index
functionTable[gcHookIndex] = module.addFunction(
classInstance.internalName + "~gc",
compiler.ensureFunctionType(null, Type.void, options.usizeType),
null,
module.createBlock(null, body)
);
return gcHookIndex;
}

View File

@ -36,7 +36,8 @@ import {
getGetLocalIndex,
getBlockChildCount,
getBlockChild,
getBlockName
getBlockName,
needsExplicitUnreachable
} from "./module";
import {
@ -74,7 +75,7 @@ import {
} from "./program";
import {
Resolver
Resolver, ReportMode
} from "./resolver";
import {
@ -155,6 +156,8 @@ import {
} from "./types";
import {
writeI8,
writeI16,
writeI32,
writeI64,
writeF32,
@ -282,7 +285,7 @@ export class Compiler extends DiagnosticEmitter {
/** Map of already compiled static string segments. */
stringSegments: Map<string,MemorySegment> = new Map();
/** Function table being compiled. */
functionTable: Function[] = [];
functionTable: FunctionRef[] = [];
/** Argument count helper global. */
argcVar: GlobalRef = 0;
/** Argument count helper setter. */
@ -391,11 +394,7 @@ export class Compiler extends DiagnosticEmitter {
var functionTableSize = functionTable.length;
var functionTableExported = false;
if (functionTableSize) {
let entries = new Array<FunctionRef>(functionTableSize);
for (let i = 0; i < functionTableSize; ++i) {
entries[i] = functionTable[i].ref;
}
module.setFunctionTable(entries);
module.setFunctionTable(functionTable);
module.addTableExport("0", "table");
functionTableExported = true;
}
@ -1474,7 +1473,7 @@ export class Compiler extends DiagnosticEmitter {
// insert the trampoline if the function has optional parameters
func = this.ensureTrampoline(func);
}
functionTable.push(func);
functionTable.push(func.ref);
func.functionTableIndex = index;
return index;
}
@ -1581,7 +1580,10 @@ export class Compiler extends DiagnosticEmitter {
default: stmts.push(stmt);
case ExpressionId.Nop:
}
if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
if (flow.isAny(FlowFlags.ANY_TERMINATING)) {
if (needsExplicitUnreachable(stmt)) stmts.push(this.module.createUnreachable());
break;
}
}
return stmts;
}
@ -1682,7 +1684,7 @@ export class Compiler extends DiagnosticEmitter {
);
parentFlow.inherit(flow);
return module.createBlock(breakLabel, [
var block: ExpressionRef[] = [
module.createLoop(continueLabel,
terminated
? body // skip trailing continue if unnecessary
@ -1691,7 +1693,9 @@ export class Compiler extends DiagnosticEmitter {
module.createBreak(continueLabel, condExpr)
], NativeType.None)
)
], terminated ? NativeType.Unreachable : NativeType.None);
];
if (terminated) block.push(module.createUnreachable());
return module.createBlock(breakLabel, block);
}
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
@ -1754,7 +1758,10 @@ export class Compiler extends DiagnosticEmitter {
var incrExpr = statement.incrementor
? this.compileExpression(<Expression>statement.incrementor, Type.void, ConversionKind.IMPLICIT, WrapMode.NONE)
: 0;
var bodyExpr = this.compileStatement(statement.statement);
var bodyStatement = statement.statement;
var bodyExpr = bodyStatement.kind == NodeKind.BLOCK && (<BlockStatement>bodyStatement).statements.length == 1
? this.compileStatement((<BlockStatement>bodyStatement).statements[0])
: this.compileStatement(bodyStatement);
// Switch back to the parent flow
currentFunction.flow = flow.free();
@ -1766,7 +1773,6 @@ export class Compiler extends DiagnosticEmitter {
FlowFlags.CONTINUES |
FlowFlags.CONDITIONALLY_CONTINUES
);
var terminated = alwaysTrue && flow.isAny(FlowFlags.ANY_TERMINATING);
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
@ -1793,18 +1799,10 @@ export class Compiler extends DiagnosticEmitter {
);
breakBlock.push(
module.createLoop(repeatLabel,
module.createBlock(null, repeatBlock, NativeType.None)
)
module.createLoop(repeatLabel, module.createBlock(null, repeatBlock, NativeType.None))
);
return module.createBlock(
breakLabel,
breakBlock,
terminated
? NativeType.Unreachable
: NativeType.None
);
return module.createBlock(breakLabel, breakBlock);
}
compileIfStatement(statement: IfStatement): ExpressionRef {
@ -2249,7 +2247,6 @@ export class Compiler extends DiagnosticEmitter {
var body = this.compileStatement(statement.statement);
var alwaysTrue = false; // TODO
var alwaysReturns = alwaysTrue && flow.is(FlowFlags.RETURNS);
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
// Switch back to the parent flow
@ -2264,7 +2261,7 @@ export class Compiler extends DiagnosticEmitter {
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
var expr = module.createBlock(breakLabel, [
return module.createBlock(breakLabel, [
module.createLoop(continueLabel,
module.createIf(condExpr,
terminated
@ -2275,8 +2272,7 @@ export class Compiler extends DiagnosticEmitter {
], NativeType.None)
)
)
], alwaysReturns ? NativeType.Unreachable : NativeType.None);
return expr;
]);
}
// expressions
@ -4808,6 +4804,7 @@ export class Compiler extends DiagnosticEmitter {
);
let tempLocalIndex = tempLocal.index;
// TODO: simplify if valueWithCorrectType has no side effects
// TODO: call __gc_link here if a GC is present
return module.createBlock(null, [
module.createSetLocal(tempLocalIndex, valueWithCorrectType),
module.createStore(
@ -4820,6 +4817,7 @@ export class Compiler extends DiagnosticEmitter {
module.createGetLocal(tempLocalIndex, nativeType)
], nativeType);
} else {
// TODO: call __gc_link here if a GC is present
return module.createStore(
type.byteSize,
thisExpr,
@ -5107,7 +5105,11 @@ export class Compiler extends DiagnosticEmitter {
// indirect call: index argument with signature (non-generic, can't be inlined)
case ElementKind.LOCAL: {
if (signature = (<Local>target).type.signatureReference) {
indexArg = module.createGetLocal((<Local>target).index, NativeType.I32);
if ((<Local>target).is(CommonFlags.INLINED)) {
indexArg = module.createI32(i64_low((<Local>target).constantIntegerValue));
} else {
indexArg = module.createGetLocal((<Local>target).index, NativeType.I32);
}
break;
} else {
this.error(
@ -6094,6 +6096,7 @@ export class Compiler extends DiagnosticEmitter {
return this.compileArrayLiteral(
assert(classType.typeArguments)[0],
(<ArrayLiteralExpression>expression).elementExpressions,
false, // TODO: isConst?
expression
);
}
@ -6204,7 +6207,7 @@ export class Compiler extends DiagnosticEmitter {
}
case LiteralKind.STRING: {
assert(!implicitNegate);
return this.compileStaticString((<StringLiteralExpression>expression).value);
return this.compileStringLiteral(<StringLiteralExpression>expression);
}
case LiteralKind.OBJECT: {
assert(!implicitNegate);
@ -6220,241 +6223,282 @@ export class Compiler extends DiagnosticEmitter {
return module.createUnreachable();
}
compileStaticString(stringValue: string): ExpressionRef {
/** Ensures that the specified string exists in static memory and returns a pointer to it. */
ensureStaticString(stringValue: string): ExpressionRef {
var program = this.program;
var module = this.module;
var options = this.options;
var stringSegments = this.stringSegments;
var needsGCHeader = program.hasGC;
var stringSegment: MemorySegment | null = stringSegments.get(stringValue);
if (!stringSegment) {
var stringSegment: MemorySegment;
var stringOffset: I64;
if (!stringSegments.has(stringValue)) {
let stringLength = stringValue.length;
let stringBuffer = new Uint8Array(4 + stringLength * 2);
stringBuffer[0] = stringLength & 0xff;
stringBuffer[1] = (stringLength >>> 8) & 0xff;
stringBuffer[2] = (stringLength >>> 16) & 0xff;
stringBuffer[3] = (stringLength >>> 24) & 0xff;
let stringSize = 4 + stringLength * 2;
let offset = 0;
let gcHeaderSize = program.gcHeaderSize;
if (needsGCHeader) {
stringSize += gcHeaderSize;
offset += gcHeaderSize;
}
let stringBuffer = new Uint8Array(stringSize);
stringBuffer[offset ] = stringLength & 0xff;
stringBuffer[offset + 1] = (stringLength >>> 8) & 0xff;
stringBuffer[offset + 2] = (stringLength >>> 16) & 0xff;
stringBuffer[offset + 3] = (stringLength >>> 24) & 0xff;
for (let i = 0; i < stringLength; ++i) {
stringBuffer[4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
stringBuffer[5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
stringBuffer[offset + 4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
stringBuffer[offset + 5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
}
stringSegment = this.addMemorySegment(stringBuffer, options.usizeType.byteSize);
stringSegments.set(stringValue, stringSegment);
if (needsGCHeader) {
stringOffset = i64_add(stringSegment.offset, i64_new(gcHeaderSize, 0));
} else {
stringOffset = stringSegment.offset;
}
} else {
stringSegment = <MemorySegment>stringSegments.get(stringValue);
stringOffset = stringSegment.offset;
}
if (program.typesLookup.has("string")) {
let stringType = <Type>program.typesLookup.get("string");
this.currentType = stringType;
} else {
this.currentType = options.usizeType;
}
var stringOffset = stringSegment.offset;
var stringType = this.program.typesLookup.get("string");
this.currentType = stringType ? stringType : options.usizeType;
if (options.isWasm64) {
return module.createI64(i64_low(stringOffset), i64_high(stringOffset));
} else {
assert(i64_is_i32(stringOffset));
return module.createI32(i64_low(stringOffset));
}
assert(i64_is_i32(stringOffset));
return module.createI32(i64_low(stringOffset));
}
compileArrayLiteral(elementType: Type, expressions: (Expression | null)[], reportNode: Node): ExpressionRef {
var isStatic = true;
compileStringLiteral(expression: StringLiteralExpression): ExpressionRef {
return this.ensureStaticString(expression.value);
}
/** Ensures that the specified array exists in static memory and returns a pointer to it. */
ensureStaticArray(elementType: Type, values: ExpressionRef[]): ExpressionRef {
var length = values.length;
var byteSize = elementType.byteSize;
var byteLength = length * byteSize;
var usizeTypeSize = this.options.usizeType.byteSize;
// determine the size of the Array header
var arrayHeaderSize = (usizeTypeSize + 4 + 7) & ~7; // .buffer_ + .length_ + alignment
var arrayTotalSize = arrayHeaderSize;
// determine the size of the ArrayBuffer
var bufferHeaderSize = (4 + 7) & ~7; // .byteLength + alignment
var bufferTotalSize = 1 << (32 - clz(byteLength + bufferHeaderSize - 1)); // see internals
var program = this.program;
var needsGC = program.hasGC;
var gcHeaderSize = program.gcHeaderSize;
var offset = 0;
if (needsGC) {
offset += gcHeaderSize; // start writing after GC header
arrayTotalSize += gcHeaderSize;
bufferTotalSize += gcHeaderSize;
}
// create a compound segment holding both the the Array header and the ArrayBuffer
var buffer = new Uint8Array(arrayHeaderSize + bufferTotalSize);
var segment = this.addMemorySegment(buffer);
// write the Array header first
if (usizeTypeSize == 8) {
writeI64(i64_add(segment.offset, i64_new(arrayHeaderSize)), buffer, offset); // .buffer_
offset += 8;
} else {
assert(i64_is_u32(segment.offset));
writeI32(i64_low(segment.offset) + arrayHeaderSize, buffer, offset); // .buffer_
offset += 4;
}
writeI32(length, buffer, offset); // .length_
offset += 4;
assert(((offset + 7) & ~7) == arrayTotalSize); // incl. GC header if applicable
// append the ArrayBuffer
offset = arrayTotalSize;
if (needsGC) offset += gcHeaderSize;
writeI32(byteLength, buffer, offset); // .byteLength
offset += bufferHeaderSize; // align
var nativeType = elementType.toNativeType();
switch (nativeType) {
case NativeType.I32: {
switch (byteSize) {
case 1: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI8(getConstValueI32(value), buffer, offset);
offset += 1;
}
break;
}
case 2: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI16(getConstValueI32(value), buffer, offset);
offset += 2;
}
break;
}
case 4: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI32(getConstValueI32(value), buffer, offset);
offset += 4;
}
break;
}
default: assert(false);
}
break;
}
case NativeType.I64: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeI64(i64_new(getConstValueI64Low(value), getConstValueI64High(value)), buffer, offset);
offset += 8;
}
break;
}
case NativeType.F32: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeF32(getConstValueF32(value), buffer, offset);
offset += 4;
}
break;
}
case NativeType.F64: {
for (let i = 0; i < length; ++i) {
let value = values[i];
assert(getExpressionType(value) == nativeType);
assert(getExpressionId(value) == ExpressionId.Const);
writeF64(getConstValueF64(value), buffer, offset);
offset += 8;
}
break;
}
default: assert(false);
}
assert(offset <= arrayTotalSize + bufferTotalSize); // might have empty trailing space
var arrayPrototype = this.program.arrayPrototype;
if (arrayPrototype) {
let arrayInstance = this.resolver.resolveClass(arrayPrototype, [ elementType ], null, ReportMode.REPORT);
if (!arrayInstance) {
this.currentType = this.options.usizeType;
return this.module.createUnreachable();
}
this.currentType = arrayInstance.type;
} else {
this.currentType = this.options.usizeType;
}
// return a pointer at the array header (skip GC header if present)
var address = segment.offset;
if (needsGC) address = i64_add(address, i64_new(gcHeaderSize, 0));
if (usizeTypeSize == 8) {
return this.module.createI64(i64_low(address), i64_high(address));
} else {
assert(i64_is_u32(address));
return this.module.createI32(i64_low(address));
}
}
compileArrayLiteral(
elementType: Type,
expressions: (Expression | null)[],
isConst: bool,
reportNode: Node
): ExpressionRef {
var module = this.module;
// obtain the array type
// find out whether all elements are constant (array is static)
var length = expressions.length;
var values = new Array<ExpressionRef>(length);
var nativeElementType = elementType.toNativeType();
var isStatic = true;
for (let i = 0; i < length; ++i) {
values[i] = expressions[i]
? this.compileExpression(<Expression>expressions[i], elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
: elementType.toNativeZero(module);
if (isStatic) {
let expr = module.precomputeExpression(values[i]);
if (getExpressionId(expr) == ExpressionId.Const) {
assert(getExpressionType(expr) == nativeElementType);
} else {
if (isConst) {
this.warning(
DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
reportNode.range
);
}
isStatic = false;
}
}
}
// make a static array if possible
if (isStatic) return this.ensureStaticArray(elementType, values);
// otherwise obtain the array type
var arrayPrototype = assert(this.program.arrayPrototype);
if (!arrayPrototype || arrayPrototype.kind != ElementKind.CLASS_PROTOTYPE) return module.createUnreachable();
var arrayInstance = this.resolver.resolveClass(<ClassPrototype>arrayPrototype, [ elementType ]);
if (!arrayInstance) return module.createUnreachable();
var arrayType = arrayInstance.type;
var elementCount = expressions.length;
if (elementCount) { // non-empty static or dynamic
let nativeElementType = elementType.toNativeType();
let values: usize;
let byteLength: usize;
switch (nativeElementType) {
case NativeType.I32: {
values = changetype<usize>(new Int32Array(elementCount));
byteLength = elementCount * 4;
break;
}
case NativeType.I64: {
values = changetype<usize>(new Array<I64>(elementCount));
byteLength = elementCount * 8;
break;
}
case NativeType.F32: {
values = changetype<usize>(new Float32Array(elementCount));
byteLength = elementCount * 4;
break;
}
case NativeType.F64: {
values = changetype<usize>(new Float64Array(elementCount));
byteLength = elementCount * 8;
break;
}
default: {
assert(false);
return module.createUnreachable();
}
}
// precompute value expressions
let exprs = new Array<ExpressionRef>(elementCount);
let expr: BinaryenExpressionRef;
for (let i = 0; i < elementCount; ++i) {
exprs[i] = expressions[i]
? this.compileExpression(<Expression>expressions[i], elementType, ConversionKind.IMPLICIT, WrapMode.NONE)
: elementType.toNativeZero(module);
if (isStatic) {
expr = module.precomputeExpression(exprs[i]);
if (getExpressionId(expr) == ExpressionId.Const) {
assert(getExpressionType(expr) == nativeElementType);
switch (nativeElementType) {
case NativeType.I32: {
changetype<i32[]>(values)[i] = getConstValueI32(expr);
break;
}
case NativeType.I64: {
changetype<I64[]>(values)[i] = i64_new(
getConstValueI64Low(expr),
getConstValueI64High(expr)
);
break;
}
case NativeType.F32: {
changetype<f32[]>(values)[i] = getConstValueF32(expr);
break;
}
case NativeType.F64: {
changetype<f64[]>(values)[i] = getConstValueF64(expr);
break;
}
default: assert(false); // checked above
}
} else {
// TODO: emit a warning if declared 'const'
// if (isConst) {
// this.warn(
// DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
// reportNode.range
// );
// }
isStatic = false;
}
}
}
let usizeTypeSize = this.options.usizeType.byteSize;
if (isStatic) { // non-empty, all elements can be precomputed
// Create a combined static memory segment composed of:
// Array struct + ArrayBuffer struct + aligned ArrayBuffer data
let arraySize = usizeTypeSize + 4; // buffer_ & length_
let bufferHeaderSize = (4 + 7) & ~7; // aligned byteLength (8)
let bufferTotalSize = 1 << (32 - clz(byteLength + bufferHeaderSize - 1)); // see internals
let data = new Uint8Array(arraySize + bufferTotalSize);
let segment = this.addMemorySegment(data);
let offset = 0;
// write Array struct
if (usizeTypeSize == 8) {
writeI64(i64_add(segment.offset, i64_new(arraySize)), data, offset); // buffer_ @ segment[arSize]
offset += 8;
} else {
assert(i64_high(segment.offset) == 0);
writeI32(i64_low(segment.offset) + arraySize, data, offset); // buffer_ @ segment[arSize]
offset += 4;
}
writeI32(elementCount, data, offset); // length_
offset += 4;
assert(offset == arraySize);
// write ArrayBuffer struct
writeI32(byteLength, data, offset);
offset += bufferHeaderSize; // incl. alignment
// write ArrayBuffer data
switch (nativeElementType) {
case NativeType.I32: {
for (let i = 0; i < elementCount; ++i) {
writeI32(changetype<i32[]>(values)[i], data, offset); offset += 4;
}
break;
}
case NativeType.I64: {
for (let i = 0; i < elementCount; ++i) {
writeI64(changetype<I64[]>(values)[i], data, offset); offset += 8;
}
break;
}
case NativeType.F32: {
for (let i = 0; i < elementCount; ++i) {
writeF32(changetype<f32[]>(values)[i], data, offset); offset += 4;
}
break;
}
case NativeType.F64: {
for (let i = 0; i < elementCount; ++i) {
writeF64(changetype<f64[]>(values)[i], data, offset); offset += 8;
}
break;
}
default: {
assert(false);
return module.createUnreachable();
}
}
assert(offset <= arraySize + bufferTotalSize);
this.currentType = arrayType;
return usizeTypeSize == 8
? module.createI64(
i64_low(segment.offset),
i64_high(segment.offset)
)
: module.createI32(
i64_low(segment.offset)
);
} else { // non-empty, some elements can't be precomputed
this.currentType = arrayType;
let setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true);
if (!setter) {
this.error(
DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
reportNode.range, arrayInstance.internalName
);
return module.createUnreachable();
}
let nativeArrayType = arrayType.toNativeType();
let currentFunction = this.currentFunction;
let tempLocal = currentFunction.getTempLocal(arrayType, false);
let stmts = new Array<ExpressionRef>(2 + elementCount);
let index = 0;
stmts[index++] = module.createSetLocal(tempLocal.index,
this.makeCallDirect(assert(arrayInstance.constructorInstance), [
module.createI32(0), // this
module.createI32(elementCount)
])
);
for (let i = 0; i < elementCount; ++i) {
stmts[index++] = this.makeCallDirect(setter, [
module.createGetLocal(tempLocal.index, nativeArrayType), // this
module.createI32(i),
exprs[i]
]);
}
assert(index + 1 == stmts.length);
stmts[index] = module.createGetLocal(tempLocal.index, nativeArrayType);
currentFunction.freeTempLocal(tempLocal);
this.currentType = arrayType;
return module.createBlock(null, stmts, nativeArrayType);
}
} else { // empty, TBD: cache this somehow?
this.currentType = arrayType;
return this.makeCallDirect(assert(arrayInstance.constructorInstance), [
// and compile an explicit instantiation
this.currentType = arrayType;
var setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true);
if (!setter) {
this.error(
DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
reportNode.range, arrayInstance.internalName
);
return module.createUnreachable();
}
var nativeArrayType = arrayType.toNativeType();
var currentFunction = this.currentFunction;
var tempLocal = currentFunction.getTempLocal(arrayType, false);
var stmts = new Array<ExpressionRef>(2 + length);
var index = 0;
stmts[index++] = module.createSetLocal(tempLocal.index,
this.makeCallDirect(assert(arrayInstance.constructorInstance), [
module.createI32(0), // this
module.createI32(0)
module.createI32(length)
])
);
for (let i = 0; i < length; ++i) {
stmts[index++] = this.makeCallDirect(setter, [
module.createGetLocal(tempLocal.index, nativeArrayType), // this
module.createI32(i),
values[i]
]);
}
assert(index + 1 == stmts.length);
stmts[index] = module.createGetLocal(tempLocal.index, nativeArrayType);
currentFunction.freeTempLocal(tempLocal);
this.currentType = arrayType;
return module.createBlock(null, stmts, nativeArrayType);
}
compileObjectLiteral(expression: ObjectLiteralExpression, contextualType: Type): ExpressionRef {

View File

@ -16,14 +16,14 @@ export type ImportRef = usize;
export type ExportRef = usize;
export type Index = u32;
export enum NativeType {
None = _BinaryenTypeNone(),
I32 = _BinaryenTypeInt32(),
I64 = _BinaryenTypeInt64(),
F32 = _BinaryenTypeFloat32(),
F64 = _BinaryenTypeFloat64(),
Unreachable = _BinaryenTypeUnreachable(),
Auto = _BinaryenTypeAuto()
export const enum NativeType {
None = 0, // _BinaryenTypeNone(),
I32 = 1, // _BinaryenTypeInt32(),
I64 = 2, // _BinaryenTypeInt64(),
F32 = 3, // _BinaryenTypeFloat32(),
F64 = 4, // _BinaryenTypeFloat64(),
Unreachable = 5, // _BinaryenTypeUnreachable(),
Auto = -1 // _BinaryenTypeAuto()
}
export enum ExpressionId {
@ -1580,3 +1580,26 @@ export class BinaryModule {
/** Source map, if generated. */
sourceMap: string | null;
}
/** Tests if an expression needs an explicit 'unreachable' when it is the terminating statement. */
export function needsExplicitUnreachable(expr: ExpressionRef): bool {
// not applicable if pushing a value to the stack
switch (_BinaryenExpressionGetType(expr)) {
case NativeType.I32:
case NativeType.I64:
case NativeType.F32:
case NativeType.F64: return false;
}
switch (_BinaryenExpressionGetId(expr)) {
case ExpressionId.Unreachable:
case ExpressionId.Return: return false;
case ExpressionId.Break: return _BinaryenBreakGetCondition(expr) != 0;
case ExpressionId.Block: {
if (!_BinaryenBlockGetName(expr)) { // can't break out of it
let numChildren = _BinaryenBlockGetNumChildren(expr); // last child needs unreachable
return numChildren > 0 && needsExplicitUnreachable(_BinaryenBlockGetChild(expr, numChildren - 1));
}
}
}
return true;
}

View File

@ -341,6 +341,17 @@ export class Program extends DiagnosticEmitter {
/** Memory allocation function. */
memoryAllocateInstance: Function | null = null;
/** Whether a garbage collector is present or not. */
hasGC: bool = false;
/** Garbage collector allocation function. */
gcAllocateInstance: Function | null = null;
/** Garbage collector link function called when a managed object is referenced from a parent. */
gcLinkInstance: Function | null = null;
/** Garbage collector mark function called to on reachable managed objects. */
gcMarkInstance: Function | null = null;
/** Size of a managed object header. */
gcHeaderSize: u32 = 0;
/** Currently processing filespace. */
currentFilespace: Filespace;
@ -658,6 +669,48 @@ export class Program extends DiagnosticEmitter {
}
}
}
// register GC hooks if present
if (
this.elementsLookup.has("__gc_allocate") &&
this.elementsLookup.has("__gc_link") &&
this.elementsLookup.has("__gc_mark")
) {
// __gc_allocate(usize, (ref: usize) => void): usize
let element = <Element>this.elementsLookup.get("__gc_allocate");
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
let gcAllocateInstance = assert(this.resolver.resolveFunction(<FunctionPrototype>element, null));
let signature = gcAllocateInstance.signature;
assert(signature.parameterTypes.length == 2);
assert(signature.parameterTypes[0] == this.options.usizeType);
assert(signature.parameterTypes[1].signatureReference);
assert(signature.returnType == this.options.usizeType);
// __gc_link(usize, usize): void
element = <Element>this.elementsLookup.get("__gc_link");
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
let gcLinkInstance = assert(this.resolver.resolveFunction(<FunctionPrototype>element, null));
signature = gcLinkInstance.signature;
assert(signature.parameterTypes.length == 2);
assert(signature.parameterTypes[0] == this.options.usizeType);
assert(signature.parameterTypes[1] == this.options.usizeType);
assert(signature.returnType == Type.void);
// __gc_mark(usize): void
element = <Element>this.elementsLookup.get("__gc_mark");
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
let gcMarkInstance = assert(this.resolver.resolveFunction(<FunctionPrototype>element, null));
signature = gcMarkInstance.signature;
assert(signature.parameterTypes.length == 1);
assert(signature.parameterTypes[0] == this.options.usizeType);
assert(signature.returnType == Type.void);
this.gcAllocateInstance = gcAllocateInstance;
this.gcLinkInstance = gcLinkInstance;
this.gcMarkInstance = gcMarkInstance;
this.gcHeaderSize = (2 * options.usizeType.byteSize + 4 + 7) & ~7; // TODO: hardcoded atm
this.hasGC = true;
}
}
/** Sets a constant integer value. */
@ -2771,6 +2824,8 @@ export class Class extends Element {
constructorInstance: Function | null = null;
/** Operator overloads. */
overloads: Map<OperatorKind,Function> | null = null;
/** Function index of the GC hook. */
gcHookIndex: u32 = <u32>-1;
/** Constructs a new class. */
constructor(

View File

@ -377,7 +377,9 @@ export class Resolver extends DiagnosticEmitter {
case ElementKind.GLOBAL:
case ElementKind.LOCAL:
case ElementKind.FIELD: {
let classReference = (<VariableLikeElement>target).type.classReference;
let type = (<VariableLikeElement>target).type;
assert(type != Type.void);
let classReference = type.classReference;
if (!classReference) {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,

View File

@ -5,7 +5,9 @@
import {
Class,
FunctionTarget
FunctionTarget,
Program,
DecoratorFlags
} from "./program";
import {
@ -134,6 +136,15 @@ export class Type {
}
}
/** Tests if this is a managed type that needs GC hooks. */
isManaged(program: Program): bool {
if (program.hasGC) {
let classReference = this.classReference;
return classReference !== null && !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
}
return false;
}
/** Computes the sign-extending shift in the target type. */
computeSmallIntegerShift(targetType: Type): u32 {
return targetType.size - this.size;

View File

@ -1,5 +1,27 @@
/** @module util *//***/
/** Reads an 8-bit integer from the specified buffer. */
export function readI8(buffer: Uint8Array, offset: i32): i32 {
return buffer[offset];
}
/** Writes an 8-bit integer to the specified buffer. */
export function writeI8(value: i32, buffer: Uint8Array, offset: i32): void {
buffer[offset] = value;
}
/** Reads a 16-bit integer from the specified buffer. */
export function readI16(buffer: Uint8Array, offset: i32): i32 {
return buffer[offset ]
| buffer[offset + 1] << 8;
}
/** Writes a 16-bit integer to the specified buffer. */
export function writeI16(value: i32, buffer: Uint8Array, offset: i32): void {
buffer[offset ] = value;
buffer[offset + 1] = value >>> 8;
}
/** Reads a 32-bit integer from the specified buffer. */
export function readI32(buffer: Uint8Array, offset: i32): i32 {
return buffer[offset ]