Avoid trampolines where optional arguments are constant literals, see #102; Fix temporary local flags not being cleared; Fix inlined temporary locals not being free'd; Fix inlined flows not breaking after returns; Allow changetype of u32s, i.e. function pointers

This commit is contained in:
dcodeIO
2018-05-11 16:31:56 +02:00
parent ef9b43740d
commit e415377cda
35 changed files with 3424 additions and 4059 deletions

View File

@ -1945,13 +1945,6 @@ export function compileCall(
reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0"
);
return module.createUnreachable();
} else if (typeArguments[0].kind != TypeKind.USIZE) { // any usize
compiler.error(
DiagnosticCode.Operation_not_supported,
reportNode.range
);
compiler.currentType = typeArguments[0];
return module.createUnreachable();
}
if (operands.length != 1) {
compiler.error(
@ -1963,11 +1956,11 @@ export function compileCall(
}
arg0 = compiler.compileExpressionRetainType(
operands[0],
compiler.options.usizeType,
typeArguments[0],
WrapMode.NONE
);
compiler.currentType = typeArguments[0];
if (compiler.currentType.kind != TypeKind.USIZE) {
if (compiler.currentType.size != typeArguments[0].size) {
compiler.error(
DiagnosticCode.Operation_not_supported,
reportNode.range

View File

@ -5073,10 +5073,14 @@ export class Compiler extends DiagnosticEmitter {
// Compile the called function's body in the scope of the inlined flow
var bodyStatement = assert(declaration.body);
if (bodyStatement.kind == NodeKind.BLOCK) { // it's ok to unwrap the block here
if (bodyStatement.kind == NodeKind.BLOCK) {
let statements = (<BlockStatement>bodyStatement).statements;
for (let i = 0, k = statements.length; i < k; ++i) {
body.push(this.compileStatement(statements[i]));
let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
body.push(stmt);
if (flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
}
}
} else {
body.push(this.compileStatement(bodyStatement));
@ -5290,28 +5294,51 @@ export class Compiler extends DiagnosticEmitter {
var returnType = instance.signature.returnType;
var isCallImport = instance.is(CommonFlags.MODULE_IMPORT);
// fill up omitted arguments with zeroes
// fill up omitted arguments with their initializers, if constant, otherwise with zeroes.
if (numOperands < maxOperands) {
if (!operands) {
operands = new Array(maxOperands);
operands.length = 0;
}
let parameterTypes = instance.signature.parameterTypes;
let parameterNodes = instance.prototype.declaration.signature.parameterTypes;
let allOptionalsAreConstant = true;
for (let i = numArguments; i < maxArguments; ++i) {
operands.push(parameterTypes[i].toNativeZero(module));
let initializer = assert(parameterNodes[i].initializer);
if (initializer.kind != NodeKind.LITERAL) {
// TODO: other kinds might be constant as well
allOptionalsAreConstant = false;
break;
}
}
if (!isCallImport) { // call the trampoline
let original = instance;
instance = this.ensureTrampoline(instance);
if (!this.compileFunction(instance)) return module.createUnreachable();
instance.flow.flags = original.flow.flags;
this.program.instancesLookup.set(instance.internalName, instance); // so canOverflow can find it
let nativeReturnType = returnType.toNativeType();
this.currentType = returnType;
return module.createBlock(null, [
module.createSetGlobal(this.ensureArgcVar(), module.createI32(numArguments)),
module.createCall(instance.internalName, operands, nativeReturnType)
], nativeReturnType);
if (allOptionalsAreConstant) { // inline into the call
for (let i = numArguments; i < maxArguments; ++i) {
operands.push(
this.compileExpression(
<Expression>parameterNodes[i].initializer,
parameterTypes[i],
ConversionKind.IMPLICIT,
WrapMode.NONE
)
);
}
} else { // otherwise fill up with zeroes and call the trampoline
for (let i = numArguments; i < maxArguments; ++i) {
operands.push(parameterTypes[i].toNativeZero(module));
}
if (!isCallImport) {
let original = instance;
instance = this.ensureTrampoline(instance);
if (!this.compileFunction(instance)) return module.createUnreachable();
instance.flow.flags = original.flow.flags;
this.program.instancesLookup.set(instance.internalName, instance); // so canOverflow can find it
let nativeReturnType = returnType.toNativeType();
this.currentType = returnType;
return module.createBlock(null, [
module.createSetGlobal(this.ensureArgcVar(), module.createI32(numArguments)),
module.createCall(instance.internalName, operands, nativeReturnType)
], nativeReturnType);
}
}
}
@ -6211,8 +6238,8 @@ export class Compiler extends DiagnosticEmitter {
getExpressionType(condExprPrecomp) == NativeType.I32
) {
return getConstValueI32(condExprPrecomp)
? this.compileExpression(ifThen, contextualType, ConversionKind.IMPLICIT, WrapMode.NONE)
: this.compileExpression(ifElse, contextualType, ConversionKind.IMPLICIT, WrapMode.NONE);
? this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE)
: this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
// Otherwise recompile to the original and let the optimizer decide
} else /* if (condExpr != condExprPrecomp) <- not guaranteed */ {

View File

@ -37,9 +37,11 @@ abstract class ExportsWalker {
/** Program reference. */
program: Program;
/** Whether to include private members */
private includePrivate: bool;
includePrivate: bool;
/** Elements still to do. */
todo: Element[] = [];
/** Already seen elements. */
private seen: Set<Element> = new Set();
seen: Set<Element> = new Set();
/** Constructs a new Element walker. */
constructor(program: Program, includePrivate: bool = false) {
@ -50,6 +52,8 @@ abstract class ExportsWalker {
/** Walks all exports and calls the respective handlers. */
walk(): void {
for (let element of this.program.moduleLevelExports.values()) this.visitElement(element);
var todo = this.todo;
for (let i = 0; i < todo.length; ) this.visitElement(todo[i]);
}
/** Visits an element.*/

View File

@ -239,7 +239,8 @@ export class MemorySegment {
export class Module {
ref: ModuleRef;
out: usize;
private cachedByValue: usize;
/** Maximum number of pages when targeting WASM32. */
static readonly MAX_MEMORY_WASM32: Index = 0xffff;
@ -250,7 +251,7 @@ export class Module {
static create(): Module {
var module = new Module();
module.ref = _BinaryenModuleCreate();
module.out = allocate_memory(16);
module.cachedByValue = allocate_memory(16);
return module;
}
@ -259,7 +260,7 @@ export class Module {
try {
let module = new Module();
module.ref = _BinaryenModuleRead(cArr, buffer.length);
module.out = allocate_memory(3 * 8); // LLVM C-ABI, max used is 3 * usize
module.cachedByValue = allocate_memory(3 * 8); // LLVM C-ABI, max used is 3 * usize
return module;
} finally {
free_memory(changetype<usize>(cArr));
@ -309,25 +310,25 @@ export class Module {
// constants
createI32(value: i32): ExpressionRef {
var out = this.out;
var out = this.cachedByValue;
_BinaryenLiteralInt32(out, value);
return _BinaryenConst(this.ref, out);
}
createI64(valueLow: i32, valueHigh: i32 = 0): ExpressionRef {
var out = this.out;
var out = this.cachedByValue;
_BinaryenLiteralInt64(out, valueLow, valueHigh);
return _BinaryenConst(this.ref, out);
}
createF32(value: f32): ExpressionRef {
var out = this.out;
var out = this.cachedByValue;
_BinaryenLiteralFloat32(out, value);
return _BinaryenConst(this.ref, out);
}
createF64(value: f64): ExpressionRef {
var out = this.out;
var out = this.cachedByValue;
_BinaryenLiteralFloat64(out, value);
return _BinaryenConst(this.ref, out);
}
@ -672,24 +673,25 @@ export class Module {
}
}
private tempName: usize = 0;
private hasTempFunc: bool = false;
private cachedTemporaryName: usize = 0;
private hasTemporaryFunction: bool = false;
addTemporaryFunction(result: NativeType, paramTypes: NativeType[] | null, body: ExpressionRef): FunctionRef {
this.hasTempFunc = assert(!this.hasTempFunc);
if (!this.tempName) this.tempName = allocString(""); // works because strings are interned
this.hasTemporaryFunction = assert(!this.hasTemporaryFunction);
var tempName = this.cachedTemporaryName;
if (!tempName) this.cachedTemporaryName = tempName = allocString(""); // works because strings are interned
var cArr = allocI32Array(paramTypes);
try {
let typeRef = _BinaryenAddFunctionType(this.ref, this.tempName, result, cArr, paramTypes ? paramTypes.length : 0);
return _BinaryenAddFunction(this.ref, this.tempName, typeRef, 0, 0, body);
let typeRef = _BinaryenAddFunctionType(this.ref, tempName, result, cArr, paramTypes ? paramTypes.length : 0);
return _BinaryenAddFunction(this.ref, tempName, typeRef, 0, 0, body);
} finally {
free_memory(cArr);
}
}
removeTemporaryFunction(): void {
this.hasTempFunc = !assert(this.hasTempFunc);
var tempName = assert(this.tempName);
this.hasTemporaryFunction = !assert(this.hasTemporaryFunction);
var tempName = assert(this.cachedTemporaryName);
_BinaryenRemoveFunction(this.ref, tempName);
_BinaryenRemoveFunctionType(this.ref, tempName);
}
@ -927,6 +929,19 @@ export class Module {
}
}
private cachedPrecomputeName: usize = 0;
private cachedPrecomputeNames: usize = 0;
precomputeFunction(func: FunctionRef): void {
var names = this.cachedPrecomputeNames;
if (!names) {
let name = allocString("precompute");
this.cachedPrecomputeName = name;
this.cachedPrecomputeNames = names = allocI32Array([ name ]);
}
_BinaryenFunctionRunPasses(func, this.ref, names, 1);
}
validate(): bool {
return _BinaryenModuleValidate(this.ref) == 1;
}
@ -936,7 +951,7 @@ export class Module {
}
toBinary(sourceMapUrl: string | null): BinaryModule {
var out = this.out;
var out = this.cachedByValue;
var cStr = allocString(sourceMapUrl);
var binaryPtr: usize = 0;
var sourceMapPtr: usize = 0;
@ -965,9 +980,13 @@ export class Module {
}
dispose(): void {
if (!this.ref) return; // sic
assert(this.ref);
free_memory(this.cachedByValue);
free_memory(this.cachedTemporaryName);
free_memory(this.cachedPrecomputeName);
free_memory(this.cachedPrecomputeNames);
_BinaryenModuleDispose(this.ref);
free_memory(this.out);
this.ref = 0;
}
createRelooper(): Relooper {
@ -1295,13 +1314,6 @@ export class Relooper {
var relooper = new Relooper();
relooper.module = module;
relooper.ref = _RelooperCreate();
return relooper;
}
static createStub(module: Module): Relooper {
var relooper = new Relooper();
relooper.module = module;
relooper.ref = 0;
return relooper;
}

View File

@ -2923,6 +2923,7 @@ export class Function extends Element {
if (temps && temps.length) {
local = temps.pop();
local.type = type;
local.flags = CommonFlags.NONE;
} else {
local = this.addLocal(type);
}
@ -3704,6 +3705,7 @@ export class Flow {
return existingLocal;
}
}
scopedLocal.set(CommonFlags.SCOPED);
this.scopedLocals.set(name, scopedLocal);
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
this.setLocalWrapped(scopedLocal.index, wrapped);

View File

@ -167,70 +167,170 @@ export enum Token {
}
export function tokenFromKeyword(text: string): Token {
switch (text) {
case "abstract": return Token.ABSTRACT;
case "as": return Token.AS;
case "async": return Token.ASYNC;
case "await": return Token.AWAIT;
case "break": return Token.BREAK;
case "case": return Token.CASE;
case "catch": return Token.CATCH;
case "class": return Token.CLASS;
case "continue": return Token.CONTINUE;
case "const": return Token.CONST;
case "constructor": return Token.CONSTRUCTOR;
case "debugger": return Token.DEBUGGER;
case "declare": return Token.DECLARE;
case "default": return Token.DEFAULT;
case "delete": return Token.DELETE;
case "do": return Token.DO;
case "else": return Token.ELSE;
case "enum": return Token.ENUM;
case "export": return Token.EXPORT;
case "extends": return Token.EXTENDS;
case "false": return Token.FALSE;
case "finally": return Token.FINALLY;
case "for": return Token.FOR;
case "from": return Token.FROM;
case "function": return Token.FUNCTION;
case "get": return Token.GET;
case "if": return Token.IF;
case "implements": return Token.IMPLEMENTS;
case "import": return Token.IMPORT;
case "in": return Token.IN;
case "instanceof": return Token.INSTANCEOF;
case "interface": return Token.INTERFACE;
case "is": return Token.IS;
case "keyof": return Token.KEYOF;
case "let": return Token.LET;
case "module": return Token.MODULE;
case "namespace": return Token.NAMESPACE;
case "new": return Token.NEW;
case "null": return Token.NULL;
case "of": return Token.OF;
case "package": return Token.PACKAGE;
case "private": return Token.PRIVATE;
case "protected": return Token.PROTECTED;
case "public": return Token.PUBLIC;
case "readonly": return Token.READONLY;
case "return": return Token.RETURN;
case "set": return Token.SET;
case "static": return Token.STATIC;
case "super": return Token.SUPER;
case "switch": return Token.SWITCH;
case "this": return Token.THIS;
case "throw": return Token.THROW;
case "true": return Token.TRUE;
case "try": return Token.TRY;
case "type": return Token.TYPE;
case "typeof": return Token.TYPEOF;
case "var": return Token.VAR;
case "void": return Token.VOID;
case "while": return Token.WHILE;
case "with": return Token.WITH;
case "yield": return Token.YIELD;
default: return Token.INVALID;
switch (text.length && text.charCodeAt(0)) {
case CharCode.a: {
switch (text) {
case "abstract": return Token.ABSTRACT;
case "as": return Token.AS;
case "async": return Token.ASYNC;
case "await": return Token.AWAIT;
}
break;
}
case CharCode.b: {
switch (text) {
case "break": return Token.BREAK;
}
break;
}
case CharCode.c: {
switch (text) {
case "case": return Token.CASE;
case "catch": return Token.CATCH;
case "class": return Token.CLASS;
case "continue": return Token.CONTINUE;
case "const": return Token.CONST;
case "constructor": return Token.CONSTRUCTOR;
}
break;
}
case CharCode.d: {
switch (text) {
case "debugger": return Token.DEBUGGER;
case "declare": return Token.DECLARE;
case "default": return Token.DEFAULT;
case "delete": return Token.DELETE;
case "do": return Token.DO;
}
break;
}
case CharCode.e: {
switch (text) {
case "else": return Token.ELSE;
case "enum": return Token.ENUM;
case "export": return Token.EXPORT;
case "extends": return Token.EXTENDS;
}
break;
}
case CharCode.f: {
switch (text) {
case "false": return Token.FALSE;
case "finally": return Token.FINALLY;
case "for": return Token.FOR;
case "from": return Token.FROM;
case "function": return Token.FUNCTION;
}
break;
}
case CharCode.g: {
switch (text) {
case "get": return Token.GET;
}
break;
}
case CharCode.i: {
switch (text) {
case "if": return Token.IF;
case "implements": return Token.IMPLEMENTS;
case "import": return Token.IMPORT;
case "in": return Token.IN;
case "instanceof": return Token.INSTANCEOF;
case "interface": return Token.INTERFACE;
case "is": return Token.IS;
}
break;
}
case CharCode.k: {
switch (text) {
case "keyof": return Token.KEYOF;
}
break;
}
case CharCode.l: {
switch (text) {
case "let": return Token.LET;
}
break;
}
case CharCode.m: {
switch (text) {
case "module": return Token.MODULE;
}
break;
}
case CharCode.n: {
switch (text) {
case "namespace": return Token.NAMESPACE;
case "new": return Token.NEW;
case "null": return Token.NULL;
}
break;
}
case CharCode.o: {
switch (text) {
case "of": return Token.OF;
}
break;
}
case CharCode.p: {
switch (text) {
case "package": return Token.PACKAGE;
case "private": return Token.PRIVATE;
case "protected": return Token.PROTECTED;
case "public": return Token.PUBLIC;
}
break;
}
case CharCode.r: {
switch (text) {
case "readonly": return Token.READONLY;
case "return": return Token.RETURN;
}
break;
}
case CharCode.s: {
switch (text) {
case "set": return Token.SET;
case "static": return Token.STATIC;
case "super": return Token.SUPER;
case "switch": return Token.SWITCH;
}
break;
}
case CharCode.t: {
switch (text) {
case "this": return Token.THIS;
case "throw": return Token.THROW;
case "true": return Token.TRUE;
case "try": return Token.TRY;
case "type": return Token.TYPE;
case "typeof": return Token.TYPEOF;
}
break;
}
case CharCode.v: {
switch (text) {
case "var": return Token.VAR;
case "void": return Token.VOID;
}
break;
}
case CharCode.w: {
switch (text) {
case "while": return Token.WHILE;
case "with": return Token.WITH;
}
break;
}
case CharCode.y: {
switch (text) {
case "yield": return Token.YIELD;
}
break;
}
}
return Token.INVALID;
}
export function tokenIsAlsoIdentifier(token: Token): bool {