Remove noMemory option; Omit 'start' if 'main' is present; Unwrap more unnecessary blocks

This commit is contained in:
dcodeIO
2018-06-27 19:03:39 +02:00
parent 0d64c9661a
commit 27e61f8f67
42 changed files with 5337 additions and 3741 deletions

View File

@ -33,7 +33,10 @@ import {
getConstValueF32,
getConstValueF64,
getFunctionBody,
getGetLocalIndex
getGetLocalIndex,
getBlockChildCount,
getBlockChild,
getBlockName
} from "./module";
import {
@ -167,8 +170,6 @@ export class Options {
noTreeShaking: bool = false;
/** If true, replaces assertions with nops. */
noAssert: bool = false;
/** If true, does not set up a memory. */
noMemory: bool = false;
/** If true, imports the memory provided by the embedder. */
importMemory: bool = false;
/** If true, imports the function table provided by the embedder. */
@ -254,9 +255,9 @@ export class Compiler extends DiagnosticEmitter {
/** Current type in compilation. */
currentType: Type = Type.void;
/** Start function being compiled. */
startFunction: Function;
startFunctionInstance: Function;
/** Start function statements. */
startFunctionBody: ExpressionRef[] = [];
startFunctionBody: ExpressionRef[];
/** Counting memory offset. */
memoryOffset: I64;
/** Memory segments being compiled. */
@ -298,15 +299,11 @@ export class Compiler extends DiagnosticEmitter {
// initialize lookup maps, built-ins, imports, exports, etc.
program.initialize(options);
// set up the start function wrapping top-level statements, of all files.
var startFunctionPrototype = assert(program.elementsLookup.get("start"));
assert(startFunctionPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
var startFunctionInstance = new Function(
<FunctionPrototype>startFunctionPrototype,
startFunctionPrototype.internalName,
new Signature([], Type.void)
);
this.startFunction = startFunctionInstance;
// set up the start function
var startFunctionInstance = new Function(program.startFunction, "start", new Signature([], Type.void));
this.startFunctionInstance = startFunctionInstance;
var startFunctionBody = new Array<ExpressionRef>();
this.startFunctionBody = startFunctionBody;
this.currentFunction = startFunctionInstance;
// compile entry file(s) while traversing reachable elements
@ -315,9 +312,8 @@ export class Compiler extends DiagnosticEmitter {
if (sources[i].isEntry) this.compileSource(sources[i]);
}
// compile the start function if not empty
var startFunctionBody = this.startFunctionBody;
if (startFunctionBody.length) {
// compile the start function if not empty or called by main
if (startFunctionBody.length || program.mainFunction !== null) {
let signature = startFunctionInstance.signature;
let funcRef = module.addFunction(
startFunctionInstance.internalName,
@ -330,43 +326,41 @@ export class Compiler extends DiagnosticEmitter {
module.createBlock(null, startFunctionBody)
);
startFunctionInstance.finalize(module, funcRef);
module.setStart(funcRef);
if (!program.mainFunction) module.setStart(funcRef);
}
// set up static memory segments and the heap base pointer
if (!options.noMemory) {
let memoryOffset = this.memoryOffset;
memoryOffset = i64_align(memoryOffset, options.usizeType.byteSize);
this.memoryOffset = memoryOffset;
if (options.isWasm64) {
module.addGlobal(
"HEAP_BASE",
NativeType.I64,
false,
module.createI64(i64_low(memoryOffset), i64_high(memoryOffset))
);
} else {
module.addGlobal(
"HEAP_BASE",
NativeType.I32,
false,
module.createI32(i64_low(memoryOffset))
);
}
// determine initial page size
let pages = i64_shr_u(i64_align(memoryOffset, 0x10000), i64_new(16, 0));
module.setMemory(
i64_low(pages),
this.options.isWasm64
? Module.MAX_MEMORY_WASM64
: Module.MAX_MEMORY_WASM32,
this.memorySegments,
options.target,
"memory"
var memoryOffset = this.memoryOffset;
memoryOffset = i64_align(memoryOffset, options.usizeType.byteSize);
this.memoryOffset = memoryOffset;
if (options.isWasm64) {
module.addGlobal(
"HEAP_BASE",
NativeType.I64,
false,
module.createI64(i64_low(memoryOffset), i64_high(memoryOffset))
);
} else {
module.addGlobal(
"HEAP_BASE",
NativeType.I32,
false,
module.createI32(i64_low(memoryOffset))
);
}
// determine initial page size
var pages = i64_shr_u(i64_align(memoryOffset, 0x10000), i64_new(16, 0));
module.setMemory(
i64_low(pages),
this.options.isWasm64
? Module.MAX_MEMORY_WASM64
: Module.MAX_MEMORY_WASM32,
this.memorySegments,
options.target,
"memory"
);
// import memory if requested (default memory is named '0' by Binaryen)
if (options.importMemory) module.addMemoryImport("0", "env", "memory");
@ -415,7 +409,7 @@ export class Compiler extends DiagnosticEmitter {
// compile top-level statements
var noTreeShaking = this.options.noTreeShaking;
var isEntry = source.isEntry;
var startFunction = this.startFunction;
var startFunctionInstance = this.startFunctionInstance;
var startFunctionBody = this.startFunctionBody;
var statements = source.statements;
for (let i = 0, k = statements.length; i < k; ++i) {
@ -478,7 +472,7 @@ export class Compiler extends DiagnosticEmitter {
}
default: { // otherwise a top-level statement that is part of the start function's body
let previousFunction = this.currentFunction;
this.currentFunction = startFunction;
this.currentFunction = startFunctionInstance;
startFunctionBody.push(this.compileStatement(statement));
this.currentFunction = previousFunction;
break;
@ -900,7 +894,7 @@ export class Compiler extends DiagnosticEmitter {
let flow = instance.flow;
let stmt: ExpressionRef;
if (body.kind == NodeKind.EXPRESSION) { // () => expression
assert(!instance.isAny(CommonFlags.CONSTRUCTOR | CommonFlags.GET | CommonFlags.SET));
assert(!instance.isAny(CommonFlags.CONSTRUCTOR | CommonFlags.GET | CommonFlags.SET | CommonFlags.MAIN));
assert(instance.is(CommonFlags.ARROW));
stmt = this.compileExpression(
(<ExpressionStatement>body).expression,
@ -913,7 +907,8 @@ export class Compiler extends DiagnosticEmitter {
flow.finalize();
} else {
assert(body.kind == NodeKind.BLOCK);
stmt = this.compileStatement(body);
let stmts = this.compileStatements((<BlockStatement>body).statements);
if (instance.is(CommonFlags.MAIN)) stmts.unshift(module.createCall("start", null, NativeType.None));
flow.finalize();
if (isConstructor) {
let nativeSizeType = this.options.nativeSizeType;
@ -924,21 +919,15 @@ export class Compiler extends DiagnosticEmitter {
// if all branches are guaranteed to allocate, skip the final conditional allocation
if (flow.is(FlowFlags.ALLOCATES)) {
stmt = module.createBlock(null, [
stmt,
module.createGetLocal(0, nativeSizeType)
], nativeSizeType);
stmts.push(module.createGetLocal(0, nativeSizeType));
// if not all branches are guaranteed to allocate, also append a conditional allocation
} else {
let parent = assert(instance.parent);
assert(parent.kind == ElementKind.CLASS);
stmt = module.createBlock(null, [
stmt,
module.createTeeLocal(0,
this.makeConditionalAllocate(<Class>parent, declaration.name)
)
], nativeSizeType);
stmts.push(module.createTeeLocal(0,
this.makeConditionalAllocate(<Class>parent, declaration.name)
));
}
}
@ -949,6 +938,11 @@ export class Compiler extends DiagnosticEmitter {
declaration.signature.returnType.range
);
}
stmt = !stmts.length
? module.createNop()
: stmts.length == 1
? stmts[0]
: module.createBlock(null, stmts, returnType.toNativeType());
}
this.currentFunction = previousFunction;
@ -1451,7 +1445,7 @@ export class Compiler extends DiagnosticEmitter {
case NodeKind.TYPEDECLARATION: {
// type declarations must be top-level because function bodies are evaluated when
// reachaable only.
if (this.currentFunction == this.startFunction) {
if (this.currentFunction == this.startFunctionInstance) {
return module.createNop();
}
// otherwise fall-through
@ -1468,16 +1462,23 @@ export class Compiler extends DiagnosticEmitter {
compileStatements(statements: Statement[]): ExpressionRef[] {
var numStatements = statements.length;
var stmts = new Array<ExpressionRef>(numStatements);
var count = 0;
stmts.length = 0;
var flow = this.currentFunction.flow;
for (let i = 0; i < numStatements; ++i) {
let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt;
if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
switch (getExpressionId(stmt)) {
case ExpressionId.Block: {
if (!getBlockName(stmt)) {
for (let j = 0, k = getBlockChildCount(stmt); j < k; ++j) stmts.push(getBlockChild(stmt, j));
break;
}
// fall-through
}
default: stmts.push(stmt);
case ExpressionId.Nop:
}
if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
}
stmts.length = count;
return stmts;
}
@ -1949,7 +1950,7 @@ export class Compiler extends DiagnosticEmitter {
// top-level variables and constants become globals
if (isKnownGlobal || (
currentFunction == this.startFunction &&
currentFunction == this.startFunctionInstance &&
statement.parent && statement.parent.kind == NodeKind.SOURCE
)) {
// NOTE that the above condition also covers top-level variables declared with 'let', even