Flow improvements

Makes the internal API for working with flows more explicit in an attempt to avoid further issues. Also tackles remaining issues with 'continue' statements in 'for' loops.
This commit is contained in:
dcodeIO 2018-06-27 02:53:45 +02:00
parent 2e5a42d233
commit 0d64c9661a
7 changed files with 279 additions and 157 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1474,7 +1474,7 @@ export class Compiler extends DiagnosticEmitter {
let stmt = this.compileStatement(statements[i]); let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) { if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt; stmts[count++] = stmt;
if (flow.isAny(FlowFlags.TERMINATED)) break; if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
} }
} }
stmts.length = count; stmts.length = count;
@ -1483,10 +1483,9 @@ export class Compiler extends DiagnosticEmitter {
compileBlockStatement(statement: BlockStatement): ExpressionRef { compileBlockStatement(statement: BlockStatement): ExpressionRef {
var statements = statement.statements; var statements = statement.statements;
var parentFlow = this.currentFunction.flow;
// Not actually a branch, but can contain its own scoped variables. var flow = parentFlow.fork();
var blockFlow = this.currentFunction.flow.enterBranchOrScope(); this.currentFunction.flow = flow;
this.currentFunction.flow = blockFlow;
var stmts = this.compileStatements(statements); var stmts = this.compileStatements(statements);
var stmt = stmts.length == 0 var stmt = stmts.length == 0
@ -1495,10 +1494,8 @@ export class Compiler extends DiagnosticEmitter {
? stmts[0] ? stmts[0]
: this.module.createBlock(null, stmts,getExpressionType(stmts[stmts.length - 1])); : this.module.createBlock(null, stmts,getExpressionType(stmts[stmts.length - 1]));
// Switch back to the parent flow this.currentFunction.flow = flow.free();
var parentFlow = blockFlow.leaveBranchOrScope(); parentFlow.inherit(flow);
this.currentFunction.flow = parentFlow;
parentFlow.inherit(blockFlow);
return stmt; return stmt;
} }
@ -1553,7 +1550,8 @@ export class Compiler extends DiagnosticEmitter {
var module = this.module; var module = this.module;
var label = currentFunction.enterBreakContext(); var label = currentFunction.enterBreakContext();
var flow = currentFunction.flow.enterBranchOrScope(); var parentFlow = currentFunction.flow;
var flow = parentFlow.fork();
currentFunction.flow = flow; currentFunction.flow = flow;
var breakLabel = "break|" + label; var breakLabel = "break|" + label;
flow.breakLabel = breakLabel; flow.breakLabel = breakLabel;
@ -1565,22 +1563,30 @@ export class Compiler extends DiagnosticEmitter {
this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE, WrapMode.NONE), this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE, WrapMode.NONE),
this.currentType this.currentType
); );
// TODO: check if condition is always false and if so, omit it? // TODO: check if condition is always false and if so, omit it (just a block)
// Switch back to the parent flow // Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope(); currentFunction.flow = flow.free();
currentFunction.leaveBreakContext(); currentFunction.leaveBreakContext();
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
flow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
FlowFlags.CONTINUES |
FlowFlags.CONDITIONALLY_CONTINUES
);
parentFlow.inherit(flow);
return module.createBlock(breakLabel, [ return module.createBlock(breakLabel, [
module.createLoop(continueLabel, module.createLoop(continueLabel,
flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS) terminated
? body // skip trailing continue if unnecessary ? body // skip trailing continue if unnecessary
: module.createBlock(null, [ : module.createBlock(null, [
body, body,
module.createBreak(continueLabel, condExpr) module.createBreak(continueLabel, condExpr)
], NativeType.None) ], NativeType.None)
) )
], NativeType.None); ], terminated ? NativeType.Unreachable : NativeType.None);
} }
compileEmptyStatement(statement: EmptyStatement): ExpressionRef { compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
@ -1601,7 +1607,8 @@ export class Compiler extends DiagnosticEmitter {
// possibly declared in its initializer, and break context. // possibly declared in its initializer, and break context.
var currentFunction = this.currentFunction; var currentFunction = this.currentFunction;
var label = currentFunction.enterBreakContext(); var label = currentFunction.enterBreakContext();
var flow = currentFunction.flow.enterBranchOrScope(); var parentFlow = currentFunction.flow;
var flow = parentFlow.fork();
currentFunction.flow = flow; currentFunction.flow = flow;
var breakLabel = flow.breakLabel = "break|" + label; var breakLabel = flow.breakLabel = "break|" + label;
flow.breakLabel = breakLabel; flow.breakLabel = breakLabel;
@ -1615,7 +1622,7 @@ export class Compiler extends DiagnosticEmitter {
? this.compileStatement(<Statement>statement.initializer) ? this.compileStatement(<Statement>statement.initializer)
: 0; : 0;
var condExpr: ExpressionRef = 0; var condExpr: ExpressionRef = 0;
var alwaysTrue = true; var alwaysTrue = false;
if (statement.condition) { if (statement.condition) {
condExpr = this.makeIsTrueish( condExpr = this.makeIsTrueish(
this.compileExpressionRetainType(<Expression>statement.condition, Type.bool, WrapMode.NONE), this.compileExpressionRetainType(<Expression>statement.condition, Type.bool, WrapMode.NONE),
@ -1645,16 +1652,24 @@ export class Compiler extends DiagnosticEmitter {
var bodyExpr = this.compileStatement(statement.statement); var bodyExpr = this.compileStatement(statement.statement);
// Switch back to the parent flow // Switch back to the parent flow
var parentFlow = flow.leaveBranchOrScope(); currentFunction.flow = flow.free();
if (alwaysTrue) parentFlow.inherit(flow);
currentFunction.flow = parentFlow;
currentFunction.leaveBreakContext(); currentFunction.leaveBreakContext();
var usesContinue = flow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES);
flow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
FlowFlags.CONTINUES |
FlowFlags.CONDITIONALLY_CONTINUES
);
var terminated = alwaysTrue && flow.isAny(FlowFlags.ANY_TERMINATING);
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
var breakBlock = new Array<ExpressionRef>(); // outer 'break' block var breakBlock = new Array<ExpressionRef>(); // outer 'break' block
if (initExpr) breakBlock.push(initExpr); if (initExpr) breakBlock.push(initExpr);
var repeatBlock = new Array<ExpressionRef>(); // block repeating the loop var repeatBlock = new Array<ExpressionRef>(); // block repeating the loop
if (parentFlow.isAny(FlowFlags.CONTINUES | FlowFlags.CONDITIONALLY_CONTINUES)) { if (usesContinue) {
repeatBlock.push( repeatBlock.push(
module.createBlock(continueLabel, [ // inner 'continue' block module.createBlock(continueLabel, [ // inner 'continue' block
module.createBreak(breakLabel, module.createUnary(UnaryOp.EqzI32, condExpr)), module.createBreak(breakLabel, module.createUnary(UnaryOp.EqzI32, condExpr)),
@ -1678,16 +1693,13 @@ export class Compiler extends DiagnosticEmitter {
) )
); );
var expr = module.createBlock(breakLabel, breakBlock, NativeType.None); return module.createBlock(
breakLabel,
// If the loop is guaranteed to run and return, append a hint for Binaryen breakBlock,
if (flow.isAny(FlowFlags.RETURNS | FlowFlags.THROWS)) { terminated
expr = module.createBlock(null, [ ? NativeType.Unreachable
expr, : NativeType.None
module.createUnreachable() );
]);
}
return expr;
} }
compileIfStatement(statement: IfStatement): ExpressionRef { compileIfStatement(statement: IfStatement): ExpressionRef {
@ -1728,20 +1740,21 @@ export class Compiler extends DiagnosticEmitter {
} }
// Each arm initiates a branch // Each arm initiates a branch
var ifTrueFlow = currentFunction.flow.enterBranchOrScope(); var parentFlow = currentFunction.flow;
var ifTrueFlow = parentFlow.fork();
currentFunction.flow = ifTrueFlow; currentFunction.flow = ifTrueFlow;
var ifTrueExpr = this.compileStatement(ifTrue); var ifTrueExpr = this.compileStatement(ifTrue);
currentFunction.flow = ifTrueFlow.leaveBranchOrScope(); currentFunction.flow = ifTrueFlow.free();
var ifFalseFlow: Flow | null;
var ifFalseExpr: ExpressionRef = 0; var ifFalseExpr: ExpressionRef = 0;
if (ifFalse) { if (ifFalse) {
ifFalseFlow = currentFunction.flow.enterBranchOrScope(); let ifFalseFlow = parentFlow.fork();
currentFunction.flow = ifFalseFlow; currentFunction.flow = ifFalseFlow;
ifFalseExpr = this.compileStatement(ifFalse); ifFalseExpr = this.compileStatement(ifFalse);
let parentFlow = ifFalseFlow.leaveBranchOrScope(); currentFunction.flow = ifFalseFlow.free();
currentFunction.flow = parentFlow;
parentFlow.inheritMutual(ifTrueFlow, ifFalseFlow); parentFlow.inheritMutual(ifTrueFlow, ifFalseFlow);
} else {
parentFlow.inheritConditional(ifTrueFlow);
} }
return module.createIf(condExpr, ifTrueExpr, ifFalseExpr); return module.createIf(condExpr, ifTrueExpr, ifFalseExpr);
} }
@ -1800,6 +1813,7 @@ export class Compiler extends DiagnosticEmitter {
// Everything within a switch uses the same break context // Everything within a switch uses the same break context
var context = currentFunction.enterBreakContext(); var context = currentFunction.enterBreakContext();
var parentFlow = currentFunction.flow;
// introduce a local for evaluating the condition (exactly once) // introduce a local for evaluating the condition (exactly once)
var tempLocal = currentFunction.getTempLocal(Type.u32, false); var tempLocal = currentFunction.getTempLocal(Type.u32, false);
@ -1850,7 +1864,7 @@ export class Compiler extends DiagnosticEmitter {
let numStatements = statements.length; let numStatements = statements.length;
// Each switch case initiates a new branch // Each switch case initiates a new branch
let flow = currentFunction.flow.enterBranchOrScope(); let flow = parentFlow.fork();
currentFunction.flow = flow; currentFunction.flow = flow;
let breakLabel = "break|" + context; let breakLabel = "break|" + context;
flow.breakLabel = breakLabel; flow.breakLabel = breakLabel;
@ -1865,7 +1879,7 @@ export class Compiler extends DiagnosticEmitter {
let stmt = this.compileStatement(statements[j]); let stmt = this.compileStatement(statements[j]);
if (getExpressionId(stmt) != ExpressionId.Nop) { if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt; stmts[count++] = stmt;
if (flow.isAny(FlowFlags.TERMINATED)) { if (flow.isAny(FlowFlags.ANY_TERMINATING)) {
terminated = true; terminated = true;
break; break;
} }
@ -1880,18 +1894,21 @@ export class Compiler extends DiagnosticEmitter {
} }
// Switch back to the parent flow // Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope(false); flow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS
);
currentFunction.flow = flow.free();
currentBlock = module.createBlock(nextLabel, stmts, NativeType.None); // must be a labeled block currentBlock = module.createBlock(nextLabel, stmts, NativeType.None); // must be a labeled block
} }
currentFunction.leaveBreakContext(); currentFunction.leaveBreakContext();
// If the switch has a default (guaranteed to handle any value), propagate common flags // If the switch has a default (guaranteed to handle any value), propagate common flags
if (defaultIndex >= 0) { if (defaultIndex >= 0) {
let flow = currentFunction.flow; if (alwaysReturns) parentFlow.set(FlowFlags.RETURNS);
if (alwaysReturns) flow.set(FlowFlags.RETURNS); if (alwaysReturnsWrapped) parentFlow.set(FlowFlags.RETURNS_WRAPPED);
if (alwaysReturnsWrapped) flow.set(FlowFlags.RETURNS_WRAPPED); if (alwaysThrows) parentFlow.set(FlowFlags.THROWS);
if (alwaysThrows) flow.set(FlowFlags.THROWS); if (alwaysAllocates) parentFlow.set(FlowFlags.ALLOCATES);
if (alwaysAllocates) flow.set(FlowFlags.ALLOCATES);
} }
return currentBlock; return currentBlock;
} }
@ -2109,7 +2126,8 @@ export class Compiler extends DiagnosticEmitter {
// Statements initiate a new branch with its own break context // Statements initiate a new branch with its own break context
var currentFunction = this.currentFunction; var currentFunction = this.currentFunction;
var label = currentFunction.enterBreakContext(); var label = currentFunction.enterBreakContext();
var flow = currentFunction.flow.enterBranchOrScope(); var parentFlow = currentFunction.flow;
var flow = parentFlow.fork();
currentFunction.flow = flow; currentFunction.flow = flow;
var breakLabel = "break|" + label; var breakLabel = "break|" + label;
flow.breakLabel = breakLabel; flow.breakLabel = breakLabel;
@ -2117,17 +2135,26 @@ export class Compiler extends DiagnosticEmitter {
flow.continueLabel = continueLabel; flow.continueLabel = continueLabel;
var body = this.compileStatement(statement.statement); var body = this.compileStatement(statement.statement);
var alwaysReturns = false; // CONDITION_IS_ALWAYS_TRUE && flow.is(FlowFlags.RETURNS); var alwaysTrue = false; // TODO
// TODO: evaluate if condition is always true var alwaysReturns = alwaysTrue && flow.is(FlowFlags.RETURNS);
var terminated = flow.isAny(FlowFlags.ANY_TERMINATING);
// Switch back to the parent flow // Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope(); currentFunction.flow = flow.free();
currentFunction.leaveBreakContext(); currentFunction.leaveBreakContext();
flow.unset(
FlowFlags.BREAKS |
FlowFlags.CONDITIONALLY_BREAKS |
FlowFlags.CONTINUES |
FlowFlags.CONDITIONALLY_CONTINUES
);
if (alwaysTrue) parentFlow.inherit(flow);
else parentFlow.inheritConditional(flow);
var expr = module.createBlock(breakLabel, [ var expr = module.createBlock(breakLabel, [
module.createLoop(continueLabel, module.createLoop(continueLabel,
module.createIf(condExpr, module.createIf(condExpr,
flow.isAny(FlowFlags.CONTINUES | FlowFlags.BREAKS | FlowFlags.RETURNS) terminated
? body // skip trailing continue if unnecessary ? body // skip trailing continue if unnecessary
: module.createBlock(null, [ : module.createBlock(null, [
body, body,
@ -2135,15 +2162,7 @@ export class Compiler extends DiagnosticEmitter {
], NativeType.None) ], NativeType.None)
) )
) )
], NativeType.None); ], alwaysReturns ? NativeType.Unreachable : NativeType.None);
// If the loop is guaranteed to run and return, propagate that and append a hint
if (alwaysReturns) {
expr = module.createBlock(null, [
expr,
module.createUnreachable()
]);
}
return expr; return expr;
} }
@ -5282,7 +5301,7 @@ export class Compiler extends DiagnosticEmitter {
let stmt = this.compileStatement(statements[i]); let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) { if (getExpressionId(stmt) != ExpressionId.Nop) {
body.push(stmt); body.push(stmt);
if (flow.isAny(FlowFlags.TERMINATED)) break; if (flow.isAny(FlowFlags.ANY_TERMINATING)) break;
} }
} }
} else { } else {
@ -5304,7 +5323,7 @@ export class Compiler extends DiagnosticEmitter {
this.currentType = returnType; this.currentType = returnType;
// Check that all branches are terminated // Check that all branches are terminated
if (returnType != Type.void && !flow.isAny(FlowFlags.TERMINATED)) { if (returnType != Type.void && !flow.isAny(FlowFlags.ANY_TERMINATING)) {
this.error( this.error(
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value, DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
declaration.signature.returnType.range declaration.signature.returnType.range
@ -6482,6 +6501,7 @@ export class Compiler extends DiagnosticEmitter {
var ifThen = expression.ifThen; var ifThen = expression.ifThen;
var ifElse = expression.ifElse; var ifElse = expression.ifElse;
var currentFunction = this.currentFunction; var currentFunction = this.currentFunction;
var parentFlow = currentFunction.flow;
var condExpr = this.makeIsTrueish( var condExpr = this.makeIsTrueish(
this.compileExpressionRetainType(expression.condition, Type.bool, WrapMode.NONE), this.compileExpressionRetainType(expression.condition, Type.bool, WrapMode.NONE),
@ -6511,40 +6531,20 @@ export class Compiler extends DiagnosticEmitter {
} }
} }
var ifThenExpr: ExpressionRef; var ifThenFlow = parentFlow.fork();
var ifElseExpr: ExpressionRef; currentFunction.flow = ifThenFlow;
var ifThenType: Type; var ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
var ifElseType: Type; var ifThenType = this.currentType;
ifThenFlow.free();
// if part of a constructor, keep track of memory allocations var ifElseFlow = parentFlow.fork();
if (currentFunction.is(CommonFlags.CONSTRUCTOR)) { currentFunction.flow = ifElseFlow;
let flow = currentFunction.flow; var ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
var ifElseType = this.currentType;
currentFunction.flow = ifElseFlow.free();
flow = flow.enterBranchOrScope(); parentFlow.inheritMutual(ifThenFlow, ifElseFlow);
currentFunction.flow = flow;
ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
ifThenType = this.currentType;
let ifThenAllocates = flow.is(FlowFlags.ALLOCATES);
flow = flow.leaveBranchOrScope();
currentFunction.flow = flow;
flow = flow.enterBranchOrScope();
currentFunction.flow = flow;
ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
ifElseType = this.currentType;
let ifElseAllocates = flow.is(FlowFlags.ALLOCATES);
flow = flow.leaveBranchOrScope();
currentFunction.flow = flow;
if (ifThenAllocates && ifElseAllocates) flow.set(FlowFlags.ALLOCATES);
// otherwise simplify
} else {
ifThenExpr = this.compileExpressionRetainType(ifThen, contextualType, WrapMode.NONE);
ifThenType = this.currentType;
ifElseExpr = this.compileExpressionRetainType(ifElse, contextualType, WrapMode.NONE);
ifElseType = this.currentType;
}
var commonType = Type.commonCompatible(ifThenType, ifElseType, false); var commonType = Type.commonCompatible(ifThenType, ifElseType, false);
if (!commonType) { if (!commonType) {
this.error( this.error(

View File

@ -3715,37 +3715,63 @@ export const enum FlowFlags {
/** No specific conditions. */ /** No specific conditions. */
NONE = 0, NONE = 0,
// categorical
/** This branch always returns. */ /** This branch always returns. */
RETURNS = 1 << 0, RETURNS = 1 << 0,
/** This branch always returns a wrapped value. */
RETURNS_WRAPPED = 1 << 1,
/** This branch always throws. */ /** This branch always throws. */
THROWS = 1 << 1, THROWS = 1 << 2,
/** This branch always breaks. */ /** This branch always breaks. */
BREAKS = 1 << 2, BREAKS = 1 << 3,
/** This branch always continues. */ /** This branch always continues. */
CONTINUES = 1 << 3, CONTINUES = 1 << 4,
/** This branch always allocates. Constructors only. */ /** This branch always allocates. Constructors only. */
ALLOCATES = 1 << 4, ALLOCATES = 1 << 5,
// conditional
/** This branch conditionally returns in a child branch. */ /** This branch conditionally returns in a child branch. */
CONDITIONALLY_RETURNS = 1 << 5, CONDITIONALLY_RETURNS = 1 << 6,
/** This branch conditionally throws in a child branch. */ /** This branch conditionally throws in a child branch. */
CONDITIONALLY_THROWS = 1 << 6, CONDITIONALLY_THROWS = 1 << 7,
/** This branch conditionally breaks in a child branch. */ /** This branch conditionally breaks in a child branch. */
CONDITIONALLY_BREAKS = 1 << 7, CONDITIONALLY_BREAKS = 1 << 8,
/** This branch conditionally continues in a child branch. */ /** This branch conditionally continues in a child branch. */
CONDITIONALLY_CONTINUES = 1 << 8, CONDITIONALLY_CONTINUES = 1 << 9,
/** This branch conditionally allocates in a child branch. Constructors only. */ /** This branch conditionally allocates in a child branch. Constructors only. */
CONDITIONALLY_ALLOCATES = 1 << 9, CONDITIONALLY_ALLOCATES = 1 << 10,
// special
/** This branch is part of inlining a function. */ /** This branch is part of inlining a function. */
INLINE_CONTEXT = 1 << 10, INLINE_CONTEXT = 1 << 11,
/** This branch explicitly requests no bounds checking. */ /** This branch explicitly requests no bounds checking. */
UNCHECKED_CONTEXT = 1 << 11, UNCHECKED_CONTEXT = 1 << 12,
/** This branch returns a properly wrapped value. */
RETURNS_WRAPPED = 1 << 12,
/** This branch is terminated if any of these flags is set. */ // masks
TERMINATED = FlowFlags.RETURNS | FlowFlags.THROWS | FlowFlags.BREAKS | FlowFlags.CONTINUES
/** Any terminating flag. */
ANY_TERMINATING = FlowFlags.RETURNS
| FlowFlags.THROWS
| FlowFlags.BREAKS
| FlowFlags.CONTINUES,
/** Any categorical flag. */
ANY_CATEGORICAL = FlowFlags.RETURNS
| FlowFlags.RETURNS_WRAPPED
| FlowFlags.THROWS
| FlowFlags.BREAKS
| FlowFlags.CONTINUES
| FlowFlags.ALLOCATES,
/** Any conditional flag. */
ANY_CONDITIONAL = FlowFlags.CONDITIONALLY_RETURNS
| FlowFlags.CONDITIONALLY_THROWS
| FlowFlags.CONDITIONALLY_BREAKS
| FlowFlags.CONDITIONALLY_CONTINUES
| FlowFlags.CONDITIONALLY_ALLOCATES
} }
/** A control flow evaluator. */ /** A control flow evaluator. */
@ -3801,8 +3827,8 @@ export class Flow {
/** Unsets the specified flag or flags. */ /** Unsets the specified flag or flags. */
unset(flag: FlowFlags): void { this.flags &= ~flag; } unset(flag: FlowFlags): void { this.flags &= ~flag; }
/** Enters a new branch or scope and returns the new flow. */ /** Forks this flow to a child flow. */
enterBranchOrScope(): Flow { fork(): Flow {
var branch = new Flow(); var branch = new Flow();
branch.parent = this; branch.parent = this;
branch.flags = this.flags; branch.flags = this.flags;
@ -3817,12 +3843,10 @@ export class Flow {
return branch; return branch;
} }
/** Leaves the current branch or scope and returns the parent flow. */ /** Frees this flow's scoped variables. */
leaveBranchOrScope(propagate: bool = true): Flow { free(): Flow {
var parent = assert(this.parent); var parent = assert(this.parent);
if (this.scopedLocals) { // free block-scoped locals
// Free block-scoped locals
if (this.scopedLocals) {
for (let scopedLocal of this.scopedLocals.values()) { for (let scopedLocal of this.scopedLocals.values()) {
if (scopedLocal.is(CommonFlags.SCOPED)) { // otherwise an alias if (scopedLocal.is(CommonFlags.SCOPED)) { // otherwise an alias
this.currentFunction.freeTempLocal(scopedLocal); this.currentFunction.freeTempLocal(scopedLocal);
@ -3830,25 +3854,6 @@ export class Flow {
} }
this.scopedLocals = null; this.scopedLocals = null;
} }
// Propagate conditionaal flags to parent
if (propagate) {
if (this.is(FlowFlags.RETURNS)) {
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
}
if (this.is(FlowFlags.THROWS)) {
parent.set(FlowFlags.CONDITIONALLY_THROWS);
}
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
}
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
}
if (this.is(FlowFlags.ALLOCATES)) {
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
}
}
return parent; return parent;
} }
@ -3982,36 +3987,43 @@ export class Flow {
else this.wrappedLocals = map; else this.wrappedLocals = map;
} }
/** Inherits flags and local wrap states from the specified flow (e.g. on inner block). */ /** Inherits flags and local wrap states from the specified flow (e.g. blocks). */
inherit(other: Flow): void { inherit(other: Flow): void {
this.flags |= other.flags & ( this.flags |= other.flags & (FlowFlags.ANY_CATEGORICAL | FlowFlags.ANY_CONDITIONAL);
FlowFlags.RETURNS |
FlowFlags.RETURNS_WRAPPED |
FlowFlags.THROWS |
FlowFlags.BREAKS |
FlowFlags.CONTINUES |
FlowFlags.ALLOCATES
);
this.wrappedLocals = other.wrappedLocals; this.wrappedLocals = other.wrappedLocals;
this.wrappedLocalsExt = other.wrappedLocalsExt; // no need to slice because other flow is finished this.wrappedLocalsExt = other.wrappedLocalsExt; // no need to slice because other flow is finished
} }
/** Inherits mutual flags and local wrap states from the specified flows (e.g. on then/else branches). */ /** Inherits categorical flags as conditional flags from the specified flow (e.g. then without else). */
inheritConditional(other: Flow): void {
if (other.is(FlowFlags.RETURNS)) {
this.set(FlowFlags.CONDITIONALLY_RETURNS);
}
if (other.is(FlowFlags.THROWS)) {
this.set(FlowFlags.CONDITIONALLY_THROWS);
}
if (other.is(FlowFlags.BREAKS) && other.breakLabel == this.breakLabel) {
this.set(FlowFlags.CONDITIONALLY_BREAKS);
}
if (other.is(FlowFlags.CONTINUES) && other.continueLabel == this.continueLabel) {
this.set(FlowFlags.CONDITIONALLY_CONTINUES);
}
if (other.is(FlowFlags.ALLOCATES)) {
this.set(FlowFlags.CONDITIONALLY_ALLOCATES);
}
}
/** Inherits mutual flags and local wrap states from the specified flows (e.g. then with else). */
inheritMutual(left: Flow, right: Flow): void { inheritMutual(left: Flow, right: Flow): void {
// flags set in both arms // categorical flags set in both arms
this.flags |= left.flags & right.flags & ( this.flags |= left.flags & right.flags & FlowFlags.ANY_CATEGORICAL;
FlowFlags.RETURNS |
FlowFlags.RETURNS_WRAPPED | // conditional flags set in at least one arm
FlowFlags.THROWS | this.flags |= left.flags & FlowFlags.ANY_CONDITIONAL;
FlowFlags.BREAKS | this.flags |= right.flags & FlowFlags.ANY_CONDITIONAL;
FlowFlags.CONTINUES |
FlowFlags.ALLOCATES
);
// locals wrapped in both arms // locals wrapped in both arms
this.wrappedLocals = i64_and( this.wrappedLocals = i64_and(left.wrappedLocals, right.wrappedLocals);
left.wrappedLocals,
right.wrappedLocals
);
var leftExt = left.wrappedLocalsExt; var leftExt = left.wrappedLocalsExt;
var rightExt = right.wrappedLocalsExt; var rightExt = right.wrappedLocalsExt;
if (leftExt != null && rightExt != null) { if (leftExt != null && rightExt != null) {

View File

@ -9,6 +9,7 @@
(start $start) (start $start)
(func $start (; 1 ;) (type $v) (func $start (; 1 ;) (type $v)
(local $0 i32) (local $0 i32)
(local $1 i32)
(block $break|0 (block $break|0
(set_global $for/i (set_global $for/i
(i32.const 0) (i32.const 0)
@ -153,5 +154,53 @@
(unreachable) (unreachable)
) )
) )
(block $break|6
(set_local $0
(i32.const 0)
)
(loop $repeat|6
(br_if $break|6
(i32.ge_s
(get_local $0)
(i32.const 10)
)
)
(block $break|7
(set_local $1
(i32.const 0)
)
(loop $repeat|7
(block $continue|7
(br_if $break|7
(i32.ge_s
(get_local $1)
(i32.const 10)
)
)
(br_if $continue|7
(i32.eq
(get_local $0)
(get_local $1)
)
)
)
(set_local $1
(i32.add
(get_local $1)
(i32.const 1)
)
)
(br $repeat|7)
)
)
(set_local $0
(i32.add
(get_local $0)
(i32.const 1)
)
)
(br $repeat|6)
)
)
) )
) )

View File

@ -17,3 +17,9 @@ for (;;) if (--i == 0) break;
for (var k: i32 = 0; k < 10; ++k) continue; for (var k: i32 = 0; k < 10; ++k) continue;
assert(k == 10); assert(k == 10);
for (let i = 0; i < 10; i++) { // without continue block
for (let j = 0; j < 10; j++) { // with continue block
if (i === j) continue;
}
}

View File

@ -11,6 +11,8 @@
(func $start (; 1 ;) (type $v) (func $start (; 1 ;) (type $v)
(local $0 i32) (local $0 i32)
(local $1 i32) (local $1 i32)
(local $2 i32)
(local $3 i32)
(block $break|0 (block $break|0
(set_global $for/i (set_global $for/i
(i32.const 0) (i32.const 0)
@ -201,5 +203,58 @@
(unreachable) (unreachable)
) )
) )
(block $break|6
(set_local $2
(i32.const 0)
)
(loop $repeat|6
(br_if $break|6
(i32.eqz
(i32.lt_s
(get_local $2)
(i32.const 10)
)
)
)
(block $break|7
(set_local $3
(i32.const 0)
)
(loop $repeat|7
(block $continue|7
(br_if $break|7
(i32.eqz
(i32.lt_s
(get_local $3)
(i32.const 10)
)
)
)
(if
(i32.eq
(get_local $2)
(get_local $3)
)
(br $continue|7)
)
)
(set_local $3
(i32.add
(get_local $3)
(i32.const 1)
)
)
(br $repeat|7)
)
)
(set_local $2
(i32.add
(get_local $2)
(i32.const 1)
)
)
(br $repeat|6)
)
)
) )
) )