Rework loop compilation / flows (#644)

This commit is contained in:
Daniel Wirtz
2019-06-11 12:52:16 +02:00
committed by GitHub
parent 420812f5b2
commit 40dac8269d
125 changed files with 52818 additions and 61256 deletions

View File

@ -38,11 +38,11 @@ import {
getBlockChildCount,
getBlockChild,
getBlockName,
needsExplicitUnreachable,
getLocalGetIndex,
FeatureFlags,
isLocalTee,
getLocalSetIndex
getLocalSetIndex,
FeatureFlags,
needsExplicitUnreachable
} from "./module";
import {
@ -1144,7 +1144,7 @@ export class Compiler extends DiagnosticEmitter {
);
if (!stmts) stmts = [ expr ];
else stmts.push(expr);
if (!flow.isAny(FlowFlags.ANY_TERMINATING)) { // TODO: detect if returning an autorelease local?
if (!flow.is(FlowFlags.TERMINATES)) { // TODO: detect if returning an autorelease local?
let indexBefore = stmts.length;
this.performAutoreleases(flow, stmts);
this.finishAutoreleases(flow, stmts);
@ -1159,7 +1159,7 @@ export class Compiler extends DiagnosticEmitter {
}
if (!canOverflow) flow.set(FlowFlags.RETURNS_WRAPPED);
if (nonNull) flow.set(FlowFlags.RETURNS_NONNULL);
flow.set(FlowFlags.RETURNS); // now is terminating
flow.set(FlowFlags.RETURNS | FlowFlags.TERMINATES);
}
}
@ -1169,7 +1169,7 @@ export class Compiler extends DiagnosticEmitter {
assert(instance.is(CommonFlags.INSTANCE));
let classInstance = assert(instance.parent); assert(classInstance.kind == ElementKind.CLASS);
if (!flow.isAny(FlowFlags.ANY_TERMINATING)) {
if (!flow.is(FlowFlags.TERMINATES)) {
let thisLocal = assert(flow.lookupLocal(CommonSymbols.this_));
// if `this` wasn't accessed before, allocate if necessary and initialize `this`
@ -1196,7 +1196,7 @@ export class Compiler extends DiagnosticEmitter {
this.performAutoreleases(flow, stmts); // `this` is excluded anyway
this.finishAutoreleases(flow, stmts);
stmts.push(module.local_get(thisLocal.index, this.options.nativeSizeType));
flow.set(FlowFlags.RETURNS | FlowFlags.RETURNS_NONNULL);
flow.set(FlowFlags.RETURNS | FlowFlags.RETURNS_NONNULL | FlowFlags.TERMINATES);
}
// check that super has been called if this is a derived class
@ -1207,8 +1207,8 @@ export class Compiler extends DiagnosticEmitter {
);
}
// if this is a normal function, make sure that all branches return
} else if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) {
// if this is a normal function, make sure that all branches terminate
} else if (returnType != Type.void && !flow.is(FlowFlags.TERMINATES)) {
this.error(
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
instance.prototype.signatureNode.returnType.range
@ -1283,7 +1283,7 @@ export class Compiler extends DiagnosticEmitter {
}
this.compileFunctionBody(instance, stmts);
if (!flow.isAny(FlowFlags.ANY_TERMINATING)) {
if (!flow.is(FlowFlags.TERMINATES)) {
this.performAutoreleases(flow, stmts);
this.finishAutoreleases(flow, stmts);
}
@ -1780,6 +1780,7 @@ export class Compiler extends DiagnosticEmitter {
stmts = new Array<ExpressionRef>(numStatements);
stmts.length = 0;
}
var module = this.module;
var flow = this.currentFlow;
for (let i = 0; i < numStatements; ++i) {
let stmt = this.compileStatement(statements[i],
@ -1798,8 +1799,8 @@ export class Compiler extends DiagnosticEmitter {
default: stmts.push(stmt);
case ExpressionId.Nop:
}
if (flow.isAny(FlowFlags.ANY_TERMINATING)) {
if (needsExplicitUnreachable(stmt)) stmts.push(this.module.unreachable());
if (flow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) {
if (needsExplicitUnreachable(stmt)) stmts.push(module.unreachable());
break;
}
}
@ -1816,7 +1817,7 @@ export class Compiler extends DiagnosticEmitter {
this.currentFlow = innerFlow;
var stmts = this.compileStatements(statements);
if (!innerFlow.isAny(FlowFlags.ANY_TERMINATING)) this.performAutoreleases(innerFlow, stmts);
if (!innerFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) this.performAutoreleases(innerFlow, stmts);
innerFlow.freeScopedLocals();
outerFlow.inherit(innerFlow); // TODO: only if not terminated?
this.currentFlow = outerFlow;
@ -1846,10 +1847,10 @@ export class Compiler extends DiagnosticEmitter {
}
var stmts = new Array<ExpressionRef>();
this.performAutoreleases(flow, stmts);
var current: Flow | null = flow.parent;
while (current && current.breakLabel === breakLabel) {
this.performAutoreleases(current, stmts, /* clearFlags */ false);
current = current.parent;
var parent = flow.parent;
while (parent !== null && parent.breakLabel == breakLabel) {
this.performAutoreleases(parent, stmts, /* clearFlags */ false);
parent = parent.parent;
}
flow.freeScopedLocals();
stmts.push(module.br(breakLabel));
@ -1880,7 +1881,7 @@ export class Compiler extends DiagnosticEmitter {
);
return module.unreachable();
}
flow.set(FlowFlags.CONTINUES);
flow.set(FlowFlags.CONTINUES | FlowFlags.TERMINATES);
var stmts = new Array<ExpressionRef>();
this.performAutoreleases(flow, stmts);
var current: Flow | null = flow.parent;
@ -1916,18 +1917,44 @@ export class Compiler extends DiagnosticEmitter {
this.compileStatement(statement.statement)
);
}
var condExpr = this.makeIsTrueish(
this.compileExpression(statement.condition, Type.i32),
this.currentType
var condExpr = module.precomputeExpression(
this.makeIsTrueish(
this.compileExpression(statement.condition, Type.i32),
this.currentType
)
);
// TODO: check if condition is always false and if so, omit it (just a block)
var alwaysFalse = false;
if (getExpressionId(condExpr) == ExpressionId.Const) {
assert(getExpressionType(condExpr) == NativeType.I32);
if (!getConstValueI32(condExpr)) alwaysFalse = true;
}
var terminates = innerFlow.is(FlowFlags.TERMINATES);
var continues = innerFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES);
var breaks = innerFlow.isAny(FlowFlags.BREAKS | FlowFlags.CONDITIONALLY_BREAKS);
// (block $break ;; (1) skip if no breaks
// (loop $continue ;; (2) skip if skipping (4) + no continues
// (...) ;; (3)
// (br_if cond $continue) ;; (4) skip if (3) does not fall through or always false
// )
// )
var fallsThrough = !terminates && !innerFlow.is(FlowFlags.BREAKS);
if (fallsThrough && !alwaysFalse) { // (4)
stmts.push(module.br(continueLabel, condExpr));
}
var expr = flatten(module, stmts, NativeType.None);
if (fallsThrough && !alwaysFalse || continues) { // (2)
expr = module.loop(continueLabel, expr);
}
if (breaks) { // (1)
expr = module.block(breakLabel, [ expr ]);
}
// Switch back to the parent flow
var terminated = innerFlow.isAny(FlowFlags.ANY_TERMINATING);
if (!terminated) this.performAutoreleases(innerFlow, stmts);
if (!terminates) this.performAutoreleases(innerFlow, stmts);
innerFlow.freeScopedLocals();
outerFlow.popBreakLabel();
this.currentFlow = outerFlow;
innerFlow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
@ -1935,19 +1962,8 @@ export class Compiler extends DiagnosticEmitter {
FlowFlags.CONDITIONALLY_CONTINUES
);
outerFlow.inherit(innerFlow);
var block: ExpressionRef[] = [
module.loop(continueLabel,
terminated
? flatten(module, stmts, NativeType.None) // skip trailing continue if unnecessary
: module.block(null, [
flatten(module, stmts, NativeType.None),
module.br(continueLabel, condExpr)
], NativeType.None)
)
];
if (terminated) block.push(module.unreachable());
return module.block(breakLabel, block);
this.currentFlow = outerFlow;
return expr;
}
compileEmptyStatement(
@ -1970,23 +1986,20 @@ export class Compiler extends DiagnosticEmitter {
statement: ForStatement,
contextualFlags: ContextualFlags
): ExpressionRef {
// A for statement initiates a new branch with its own scoped variables
// possibly declared in its initializer, and break context.
var module = this.module;
// Set up and use the inner flow
var outerFlow = this.currentFlow;
var label = outerFlow.pushBreakLabel();
var innerFlow = outerFlow.fork();
this.currentFlow = innerFlow;
var breakLabel = innerFlow.breakLabel = "break|" + label;
innerFlow.breakLabel = breakLabel;
var continueLabel = "continue|" + label;
innerFlow.continueLabel = continueLabel;
var repeatLabel = "repeat|" + label;
// Compile in correct order
var module = this.module;
var initExpr = statement.initializer
? this.compileStatement(<Statement>statement.initializer)
: 0;
// Compile the initializer
var initializer = statement.initializer;
var initExpr: ExpressionRef = 0;
if (initializer) initExpr = this.compileStatement(initializer);
// Compile the condition
var condExpr: ExpressionRef = 0;
var alwaysTrue = false;
if (statement.condition) {
@ -1996,40 +2009,97 @@ export class Compiler extends DiagnosticEmitter {
this.currentType
)
);
// check if the condition is always true
// Simplify if the condition is constant
if (getExpressionId(condExpr) == ExpressionId.Const) {
assert(getExpressionType(condExpr) == NativeType.I32);
if (getConstValueI32(condExpr) != 0) alwaysTrue = true;
// TODO: could skip compilation if the condition is always false here, but beware that the
// initializer could still declare new 'var's that are used later on.
if (getConstValueI32(condExpr) == /* false */ 0) {
let stmts = new Array<ExpressionRef>();
if (initExpr) stmts.push(initExpr);
this.performAutoreleases(innerFlow, stmts);
innerFlow.freeScopedLocals();
outerFlow.popBreakLabel();
this.currentFlow = outerFlow;
return flatten(module, stmts, NativeType.None);
}
alwaysTrue = true;
}
} else {
// omitted condition is always true
} else { // Omitted condition is always true
condExpr = module.i32(1);
alwaysTrue = true;
}
innerFlow.inheritNonnullIfTrue(condExpr);
var incrExpr = statement.incrementor
? this.compileExpression(<Expression>statement.incrementor, Type.void,
ContextualFlags.IMPLICIT | ContextualFlags.WILL_DROP
)
: 0;
// Compile incrementor
var incrementor = statement.incrementor;
var incrExpr: ExpressionRef = 0;
if (incrementor) incrExpr = this.compileExpression(incrementor, Type.void, ContextualFlags.IMPLICIT | ContextualFlags.WILL_DROP);
// Compile body (break: drop out, continue: fall through to incrementor, + loop)
var breakLabel = innerFlow.breakLabel = "break|" + label; innerFlow.breakLabel = breakLabel;
innerFlow.breakLabel = breakLabel;
var continueLabel = "continue|" + label;
innerFlow.continueLabel = continueLabel;
var loopLabel = "loop|" + label;
var bodyStatement = statement.statement;
var stmts = new Array<ExpressionRef>();
if (bodyStatement.kind == NodeKind.BLOCK) {
this.compileStatements((<BlockStatement>bodyStatement).statements, false, stmts);
} else {
stmts.push(
this.compileStatement(bodyStatement)
);
stmts.push(this.compileStatement(bodyStatement));
}
var terminates = innerFlow.is(FlowFlags.TERMINATES);
var continues = innerFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES);
var breaks = innerFlow.isAny(FlowFlags.BREAKS | FlowFlags.CONDITIONALLY_BREAKS);
// (block $break ;; (1) skip label (needed anyway) if skipping (4) + no breaks
// (initializer) ;; (2) [may be empty]
// (loop $loop ;; (3) skip if (6) does not fall through + no continues
// (br_if !cond $break) ;; (4) skip if always true
// (block $continue ;; (5) skip if no continues or nothing else than continue
// (...) ;; (6)
// )
// (incrementor) ;; (7) skip if skipping (3) [may be empty]
// (br $loop) ;; (8) skip if skipping (3)
// )
// )
var fallsThrough = !terminates && !innerFlow.is(FlowFlags.BREAKS);
var needsLabel = !alwaysTrue || breaks;
var loop = new Array<ExpressionRef>();
if (!alwaysTrue) { // (4)
loop.push(module.br(breakLabel, module.unary(UnaryOp.EqzI32, condExpr)));
}
if (continues) { // (5)
if (stmts.length > 1 || getExpressionId(stmts[0]) != ExpressionId.Break) { // otherwise lonely continue
loop.push(module.block(continueLabel, stmts));
}
} else {
for (let i = 0, k = stmts.length; i < k; ++i) loop.push(stmts[i]);
}
var expr: ExpressionRef;
if (fallsThrough || continues) { // (3)
if (incrExpr) loop.push(incrExpr); // (7)
this.performAutoreleases(innerFlow, loop);
loop.push(module.br(loopLabel)); // (8)
if (initExpr) { // (2)
expr = module.block(needsLabel ? breakLabel : null, [
initExpr,
module.loop(loopLabel, module.block(null, loop))
]);
} else {
expr = module.block(needsLabel ? breakLabel : null, [
module.loop(loopLabel, flatten(module, loop, NativeType.None))
]);
}
} else {
if (initExpr) loop.unshift(initExpr); // (2)
this.performAutoreleases(innerFlow, loop);
expr = module.block(needsLabel ? breakLabel : null, loop);
}
if (!innerFlow.isAny(FlowFlags.ANY_TERMINATING)) this.performAutoreleases(innerFlow, stmts);
// Switch back to the parent flow
innerFlow.freeScopedLocals();
outerFlow.popBreakLabel();
this.currentFlow = outerFlow;
var usesContinue = innerFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES);
innerFlow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
@ -2038,36 +2108,8 @@ export class Compiler extends DiagnosticEmitter {
);
if (alwaysTrue) outerFlow.inherit(innerFlow);
else outerFlow.inheritConditional(innerFlow);
var breakBlock = new Array<ExpressionRef>(); // outer 'break' block
if (initExpr) breakBlock.push(initExpr);
var repeatBlock = new Array<ExpressionRef>(); // block repeating the loop
if (usesContinue) {
stmts.unshift(
module.br(breakLabel, module.unary(UnaryOp.EqzI32, condExpr))
);
repeatBlock.push(
module.block(continueLabel, stmts, NativeType.None)
);
} else { // can omit the 'continue' block
repeatBlock.push(
module.br(breakLabel, module.unary(UnaryOp.EqzI32, condExpr))
);
for (let i = 0, k = stmts.length; i < k; ++i) {
repeatBlock.push(stmts[i]);
}
}
if (incrExpr) repeatBlock.push(incrExpr);
repeatBlock.push(
module.br(repeatLabel)
);
breakBlock.push(
module.loop(repeatLabel, module.block(null, repeatBlock, NativeType.None))
);
return module.block(breakLabel, breakBlock);
this.currentFlow = outerFlow;
return expr;
}
compileIfStatement(
@ -2109,7 +2151,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
ifTrueStmts.push(this.compileStatement(ifTrue));
}
if (!ifTrueFlow.isAny(FlowFlags.ANY_TERMINATING)) this.performAutoreleases(ifTrueFlow, ifTrueStmts);
if (!ifTrueFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) this.performAutoreleases(ifTrueFlow, ifTrueStmts);
ifTrueFlow.freeScopedLocals();
this.currentFlow = outerFlow;
@ -2123,7 +2165,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
ifFalseStmts.push(this.compileStatement(ifFalse));
}
if (!ifFalseFlow.isAny(FlowFlags.ANY_TERMINATING)) this.performAutoreleases(ifFalseFlow, ifFalseStmts);
if (!ifFalseFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) this.performAutoreleases(ifFalseFlow, ifFalseStmts);
ifFalseFlow.freeScopedLocals();
this.currentFlow = outerFlow;
outerFlow.inheritMutual(ifTrueFlow, ifFalseFlow);
@ -2133,9 +2175,7 @@ export class Compiler extends DiagnosticEmitter {
);
} else {
outerFlow.inheritConditional(ifTrueFlow);
if (ifTrueFlow.isAny(FlowFlags.ANY_TERMINATING)) {
outerFlow.inheritNonnullIfFalse(condExpr);
}
if (ifTrueFlow.is(FlowFlags.TERMINATES)) outerFlow.inheritNonnullIfFalse(condExpr);
return module.if(condExpr,
flatten(module, ifTrueStmts, NativeType.None)
);
@ -2157,7 +2197,7 @@ export class Compiler extends DiagnosticEmitter {
var returnType = flow.returnType;
// Remember that this flow returns
flow.set(FlowFlags.RETURNS);
flow.set(FlowFlags.RETURNS | FlowFlags.TERMINATES);
var valueExpression = statement.value;
if (valueExpression) {
@ -2294,10 +2334,8 @@ export class Compiler extends DiagnosticEmitter {
// nest blocks in order
var currentBlock = module.block("case0|" + context, breaks, NativeType.None);
var alwaysReturns = true;
var alwaysReturnsWrapped = true;
var alwaysThrows = true;
var alwaysAllocates = true;
var commonCategorical = FlowFlags.ANY_CATEGORICAL;
var commonConditional = 0;
for (let i = 0; i < numCases; ++i) {
let case_ = cases[i];
let statements = case_.statements;
@ -2314,27 +2352,25 @@ export class Compiler extends DiagnosticEmitter {
let stmts = new Array<ExpressionRef>(1 + numStatements);
stmts[0] = currentBlock;
let count = 1;
let terminated = false;
let terminates = false;
for (let j = 0; j < numStatements; ++j) {
let stmt = this.compileStatement(statements[j]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt;
if (innerFlow.isAny(FlowFlags.ANY_TERMINATING)) {
terminated = true;
break;
}
}
if (innerFlow.isAny(FlowFlags.TERMINATES | FlowFlags.BREAKS)) {
if (innerFlow.is(FlowFlags.TERMINATES)) terminates = true;
break;
}
}
stmts.length = count;
if (terminated || isLast) {
if (!innerFlow.is(FlowFlags.RETURNS)) alwaysReturns = false;
if (!innerFlow.is(FlowFlags.RETURNS_WRAPPED)) alwaysReturnsWrapped = false;
if (!innerFlow.is(FlowFlags.THROWS)) alwaysThrows = false;
if (!innerFlow.is(FlowFlags.ALLOCATES)) alwaysAllocates = false;
if (terminates || isLast || innerFlow.isAny(FlowFlags.BREAKS | FlowFlags.CONDITIONALLY_BREAKS)) {
commonCategorical &= innerFlow.flags;
}
commonConditional |= innerFlow.flags & FlowFlags.ANY_CONDITIONAL;
// Switch back to the parent flow
if (!innerFlow.isAny(FlowFlags.ANY_TERMINATING)) this.performAutoreleases(innerFlow, stmts);
if (!terminates) this.performAutoreleases(innerFlow, stmts);
innerFlow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS
@ -2346,12 +2382,9 @@ export class Compiler extends DiagnosticEmitter {
outerFlow.popBreakLabel();
// If the switch has a default (guaranteed to handle any value), propagate common flags
if (defaultIndex >= 0) {
if (alwaysReturns) outerFlow.set(FlowFlags.RETURNS);
if (alwaysReturnsWrapped) outerFlow.set(FlowFlags.RETURNS_WRAPPED);
if (alwaysThrows) outerFlow.set(FlowFlags.THROWS);
if (alwaysAllocates) outerFlow.set(FlowFlags.ALLOCATES);
}
if (defaultIndex >= 0) outerFlow.flags |= commonCategorical & ~FlowFlags.BREAKS;
outerFlow.flags |= commonConditional & ~FlowFlags.CONDITIONALLY_BREAKS;
// TODO: what about local states?
return currentBlock;
}
@ -2362,10 +2395,7 @@ export class Compiler extends DiagnosticEmitter {
var flow = this.currentFlow;
// Remember that this branch throws
flow.set(FlowFlags.THROWS);
// FIXME: without try-catch it is safe to assume RETURNS as well for now
flow.set(FlowFlags.RETURNS);
flow.set(FlowFlags.THROWS | FlowFlags.TERMINATES);
var stmts = new Array<ExpressionRef>();
this.finishAutoreleases(flow, stmts);
@ -2597,7 +2627,7 @@ export class Compiler extends DiagnosticEmitter {
var module = this.module;
var outerFlow = this.currentFlow;
// The condition does not yet initialize a branch
// Compile condition
var condExpr = module.precomputeExpression(
this.makeIsTrueish(
this.compileExpressionRetainType(statement.condition, Type.bool),
@ -2605,15 +2635,15 @@ export class Compiler extends DiagnosticEmitter {
)
);
// Try to eliminate unnecesssary loops if the condition is constant
if (
getExpressionId(condExpr) == ExpressionId.Const &&
getExpressionType(condExpr) == NativeType.I32
) {
if (!getConstValueI32(condExpr)) return module.nop();
// Simplify if the condition is constant
var alwaysTrue = false;
if (getExpressionId(condExpr) == ExpressionId.Const) {
assert(getExpressionType(condExpr) == NativeType.I32);
if (!getConstValueI32(condExpr)) return module.nop(); // simplify
alwaysTrue = true;
}
// Statements initiate a new branch with its own break context
// Compile body
var label = outerFlow.pushBreakLabel();
var innerFlow = outerFlow.fork();
this.currentFlow = innerFlow;
@ -2621,7 +2651,6 @@ export class Compiler extends DiagnosticEmitter {
innerFlow.breakLabel = breakLabel;
var continueLabel = "continue|" + label;
innerFlow.continueLabel = continueLabel;
innerFlow.inheritNonnullIfTrue(condExpr);
var stmts = new Array<ExpressionRef>();
if (statement.statement.kind == NodeKind.BLOCK) {
@ -2629,17 +2658,35 @@ export class Compiler extends DiagnosticEmitter {
} else {
stmts.push(this.compileStatement(statement.statement));
}
var alwaysTrue = false; // TODO
var terminated = innerFlow.isAny(FlowFlags.ANY_TERMINATING);
if (!terminated) {
var terminates = innerFlow.is(FlowFlags.TERMINATES);
// (block $break ;; (1) skip if skipping (3) + no breaks
// (loop $continue ;; (2) skip if skipping (5) + no continues
// (br_if !cond $break) ;; (3) skip if always true
// (...) ;; (4)
// (br $continue) ;; (5) skip if (4) does not fall through
// )
// )
var fallsThrough = !terminates && !innerFlow.is(FlowFlags.BREAKS);
if (fallsThrough) { // (5)
this.performAutoreleases(innerFlow, stmts);
stmts.push(module.br(continueLabel));
}
innerFlow.freeScopedLocals();
if (!alwaysTrue) { // (3)
stmts.unshift(module.br(breakLabel, module.unary(UnaryOp.EqzI32, condExpr)));
}
var expr = flatten(module, stmts, NativeType.None);
if (fallsThrough || innerFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES)) { // (2)
expr = module.loop(continueLabel, expr);
}
if (!alwaysTrue || innerFlow.isAny(FlowFlags.BREAKS | FlowFlags.CONDITIONALLY_BREAKS)) { // (1)
expr = module.block(breakLabel, [ expr ]);
}
// Switch back to the parent flow
innerFlow.freeScopedLocals();
outerFlow.popBreakLabel();
this.currentFlow = outerFlow;
innerFlow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
@ -2648,14 +2695,8 @@ export class Compiler extends DiagnosticEmitter {
);
if (alwaysTrue) outerFlow.inherit(innerFlow);
else outerFlow.inheritConditional(innerFlow);
return module.block(breakLabel, [
module.loop(continueLabel,
module.if(condExpr,
flatten(module, stmts, NativeType.None)
)
)
]);
this.currentFlow = outerFlow;
return expr;
}
// === Expressions ==============================================================================
@ -6282,7 +6323,7 @@ export class Compiler extends DiagnosticEmitter {
this.compileFunctionBody(instance, body);
// Free any new scoped locals and reset to the original flow
if (!flow.isAny(FlowFlags.ANY_TERMINATING)) {
if (!flow.is(FlowFlags.TERMINATES)) {
this.performAutoreleases(flow, body);
this.finishAutoreleases(flow, body);
}
@ -9028,7 +9069,16 @@ var mangleImportName_elementName: string;
export function flatten(module: Module, stmts: ExpressionRef[], type: NativeType): ExpressionRef {
var length = stmts.length;
if (length == 0) return module.nop(); // usually filtered out again
if (length == 1) return stmts[0];
if (length == 1) {
let single = stmts[0];
if (getExpressionType(single) == type) return single;
if (getExpressionId(single) == ExpressionId.Block) {
let count = getBlockChildCount(single);
let children = new Array<ExpressionRef>(count);
for (let i = 0; i < count; ++i) children[i] = getBlockChild(single, i);
return module.block(getBlockName(single), children, type);
}
}
return module.block(null, stmts,
type == NativeType.Auto
? getExpressionType(stmts[length - 1])

View File

@ -91,35 +91,33 @@ export const enum FlowFlags {
ALLOCATES = 1 << 6,
/** This flow calls super. Constructors only. */
CALLS_SUPER = 1 << 7,
/** This flow terminates (returns, throws or continues). */
TERMINATES = 1 << 8,
// conditional
/** This flow conditionally returns in a child flow. */
CONDITIONALLY_RETURNS = 1 << 8,
CONDITIONALLY_RETURNS = 1 << 9,
/** This flow conditionally throws in a child flow. */
CONDITIONALLY_THROWS = 1 << 9,
CONDITIONALLY_THROWS = 1 << 10,
/** This flow conditionally terminates in a child flow. */
CONDITIONALLY_TERMINATES = 1 << 11,
/** This flow conditionally breaks in a child flow. */
CONDITIONALLY_BREAKS = 1 << 10,
CONDITIONALLY_BREAKS = 1 << 12,
/** This flow conditionally continues in a child flow. */
CONDITIONALLY_CONTINUES = 1 << 11,
CONDITIONALLY_CONTINUES = 1 << 13,
/** This flow conditionally allocates in a child flow. Constructors only. */
CONDITIONALLY_ALLOCATES = 1 << 12,
CONDITIONALLY_ALLOCATES = 1 << 14,
// special
/** This is an inlining flow. */
INLINE_CONTEXT = 1 << 13,
INLINE_CONTEXT = 1 << 15,
/** This is a flow with explicitly disabled bounds checking. */
UNCHECKED_CONTEXT = 1 << 14,
UNCHECKED_CONTEXT = 1 << 16,
// masks
/** Any terminating flag. */
ANY_TERMINATING = FlowFlags.RETURNS
| FlowFlags.THROWS
| FlowFlags.BREAKS
| FlowFlags.CONTINUES,
/** Any categorical flag. */
ANY_CATEGORICAL = FlowFlags.RETURNS
| FlowFlags.RETURNS_WRAPPED
@ -128,7 +126,8 @@ export const enum FlowFlags {
| FlowFlags.BREAKS
| FlowFlags.CONTINUES
| FlowFlags.ALLOCATES
| FlowFlags.CALLS_SUPER,
| FlowFlags.CALLS_SUPER
| FlowFlags.TERMINATES,
/** Any conditional flag. */
ANY_CONDITIONAL = FlowFlags.CONDITIONALLY_RETURNS
@ -551,10 +550,14 @@ export class Flow {
// categorical flags set in both arms
this.set(left.flags & right.flags & FlowFlags.ANY_CATEGORICAL);
// conditional flags set in at least one arm
// conditional flags set in any arm
this.set(left.flags & FlowFlags.ANY_CONDITIONAL);
this.set(right.flags & FlowFlags.ANY_CONDITIONAL);
// categorical flags in either arm as conditional
this.inheritConditional(left);
this.inheritConditional(right);
// categorical local flags set in both arms / conditional local flags set in at least one arm
var leftLocalFlags = left.localFlags;
var numLeftLocalFlags = leftLocalFlags.length;

View File

@ -14,7 +14,11 @@ import "./i64";
import { Module } from "../../module";
Module.prototype.toText = function(this: Module) {
return binaryen.wrapModule(this.ref).emitStackIR();
// NOTE: Conversion to StackIR can yield conversion artifacts like sequences
// of unreachable statements not actually emitted by the compiler. Optimizing
// StackIR removes these again, but may also suppress useless code emitted by
// the compiler that's then no longer visible in tests. Both not ideal.
return binaryen.wrapModule(this.ref).emitStackIR(/* optimize-stack-ir */ true);
};
Module.prototype.toAsmjs = function(this: Module) {

View File

@ -1566,7 +1566,7 @@ export class Program extends DiagnosticEmitter {
this.error(
DiagnosticCode.Duplicate_identifier_0,
declaration.name.range, "default"
)
);
return;
}
exports.set("default", element);