Index: pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart |
diff --git a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart |
index 20a22af71ba0be30990ea56e5172145a3774abd6..b9ee61f3f627543eafab7554ac7d4263b40a5c26 100644 |
--- a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart |
+++ b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart |
@@ -1621,29 +1621,86 @@ class IrBuilder { |
} |
} |
- /// Creates a try-statement. |
+ /// Utility function to translate try/catch into the IR. |
/// |
- /// [tryInfo] provides information on local variables declared and boxed |
- /// within this try statement. |
+ /// The translation treats try/finally and try/catch/finally as if they |
+ /// were macro-expanded into try/catch. This utility function generates |
+ /// that try/catch. The function is parameterized over a list of variables |
+ /// that should be boxed on entry to the try, and over functions to emit |
+ /// code for entering the try, building the try body, leaving the try body, |
+ /// building the catch body, and leaving the entire try/catch. |
+ /// |
+ /// Please see the function's implementation for where these functions are |
+ /// called. |
+ void _helpBuildTryCatch(TryStatementInfo variables, |
+ void enterTry(IrBuilder builder), |
+ SubbuildFunction buildTryBlock, |
+ void leaveTry(IrBuilder builder), |
+ List<ir.Parameter> buildCatch(IrBuilder builder, JumpCollector join), |
+ void leaveTryCatch(IrBuilder builder, JumpCollector join, |
+ ir.Expression body)) { |
+ JumpCollector join = new ForwardJumpCollector(environment); |
+ IrBuilder tryCatchBuilder = makeDelimitedBuilder(); |
+ |
+ // Variables treated as mutable in a try are not mutable outside of it. |
+ // Work with a copy of the outer builder's mutable variables. |
+ tryCatchBuilder.mutableVariables = |
+ new Map<Local, ir.MutableVariable>.from(mutableVariables); |
+ for (LocalVariableElement variable in variables.boxedOnEntry) { |
+ assert(!tryCatchBuilder.isInMutableVariable(variable)); |
+ ir.Primitive value = tryCatchBuilder.buildLocalVariableGet(variable); |
+ tryCatchBuilder.makeMutableVariable(variable); |
+ tryCatchBuilder.declareLocalVariable(variable, initialValue: value); |
+ } |
+ |
+ IrBuilder tryBuilder = tryCatchBuilder.makeDelimitedBuilder(); |
+ enterTry(tryBuilder); |
+ buildTryBlock(tryBuilder); |
+ if (tryBuilder.isOpen) { |
+ join.enterTry(variables.boxedOnEntry); |
+ tryBuilder.jumpTo(join); |
+ join.leaveTry(); |
+ } |
+ leaveTry(tryBuilder); |
+ |
+ IrBuilder catchBuilder = tryCatchBuilder.makeDelimitedBuilder(); |
+ for (LocalVariableElement variable in variables.boxedOnEntry) { |
+ assert(catchBuilder.isInMutableVariable(variable)); |
+ ir.Primitive value = catchBuilder.buildLocalVariableGet(variable); |
+ // After this point, the variables that were boxed on entry to the try |
+ // are no longer treated as mutable. |
+ catchBuilder.removeMutableVariable(variable); |
+ catchBuilder.environment.update(variable, value); |
+ } |
+ |
+ List<ir.Parameter> catchParameters = buildCatch(catchBuilder, join); |
+ ir.Continuation catchContinuation = new ir.Continuation(catchParameters); |
+ catchContinuation.body = catchBuilder._root; |
+ tryCatchBuilder.add( |
+ new ir.LetHandler(catchContinuation, tryBuilder._root)); |
+ |
+ leaveTryCatch(this, join, tryCatchBuilder._root); |
+ } |
+ |
+ /// Translates a try/catch. |
+ /// |
+ /// [variables] provides information on local variables declared and boxed |
+ /// within the try body. |
/// [buildTryBlock] builds the try block. |
/// [catchClauseInfos] provides access to the catch type, exception variable, |
/// and stack trace variable, and a function for building the catch block. |
- void buildTry( |
- {TryStatementInfo tryStatementInfo, |
- SubbuildFunction buildTryBlock, |
- List<CatchClauseInfo> catchClauseInfos: const <CatchClauseInfo>[], |
- SubbuildFunction buildFinallyBlock, |
- ClosureClassMap closureClassMap}) { |
+ void buildTryCatch(TryStatementInfo variables, |
+ SubbuildFunction buildTryBlock, |
+ List<CatchClauseInfo> catchClauseInfos) { |
assert(isOpen); |
- |
// Catch handlers are in scope for their body. The CPS translation of |
- // [[try tryBlock catch (e) catchBlock; successor]] is: |
+ // [[try tryBlock catch (ex, st) catchBlock; successor]] is: |
// |
// let cont join(v0, v1, ...) = [[successor]] in |
// let mutable m0 = x0 in |
// let mutable m1 = x1 in |
// ... |
- // let handler catch_(e) = |
+ // let handler catch_(ex, st) = |
// let prim p0 = GetMutable(m0) in |
// let prim p1 = GetMutable(m1) in |
// ... |
@@ -1665,49 +1722,31 @@ class IrBuilder { |
// scope of the handler. The mutable bindings are dereferenced at the end |
// of the try block and at the beginning of the catch block, so the |
// variables are unboxed in the catch block and at the join point. |
- if (catchClauseInfos.isNotEmpty) { |
- JumpCollector join = new ForwardJumpCollector(environment); |
- IrBuilder tryCatchBuilder = makeDelimitedBuilder(); |
- |
- // Variables treated as mutable in a try are not mutable outside of it. |
- // Work with a copy of the outer builder's mutable variables. |
- tryCatchBuilder.mutableVariables = |
- new Map<Local, ir.MutableVariable>.from(mutableVariables); |
- for (LocalVariableElement variable in tryStatementInfo.boxedOnEntry) { |
- assert(!tryCatchBuilder.isInMutableVariable(variable)); |
- ir.Primitive value = tryCatchBuilder.buildLocalVariableGet(variable); |
- tryCatchBuilder.makeMutableVariable(variable); |
- tryCatchBuilder.declareLocalVariable(variable, initialValue: value); |
- } |
- |
- IrBuilder tryBuilder = tryCatchBuilder.makeDelimitedBuilder(); |
+ void enterTry(IrBuilder builder) { |
+ // On entry to try of try/catch, update the builder's state to reflect the |
+ // variables that have been boxed. |
void interceptJump(JumpCollector collector) { |
- collector.enterTry(tryStatementInfo.boxedOnEntry); |
+ collector.enterTry(variables.boxedOnEntry); |
} |
+ builder.state.breakCollectors.forEach(interceptJump); |
+ builder.state.continueCollectors.forEach(interceptJump); |
+ } |
+ |
+ void leaveTry(IrBuilder builder) { |
+ // On exit from try of try/catch, update the builder's state to reflect |
+ // the variables that are no longer boxed. |
void restoreJump(JumpCollector collector) { |
collector.leaveTry(); |
} |
- tryBuilder.state.breakCollectors.forEach(interceptJump); |
- tryBuilder.state.continueCollectors.forEach(interceptJump); |
- buildTryBlock(tryBuilder); |
- if (tryBuilder.isOpen) { |
- interceptJump(join); |
- tryBuilder.jumpTo(join); |
- restoreJump(join); |
- } |
- tryBuilder.state.breakCollectors.forEach(restoreJump); |
- tryBuilder.state.continueCollectors.forEach(restoreJump); |
- |
- IrBuilder catchBuilder = tryCatchBuilder.makeDelimitedBuilder(); |
- for (LocalVariableElement variable in tryStatementInfo.boxedOnEntry) { |
- assert(catchBuilder.isInMutableVariable(variable)); |
- ir.Primitive value = catchBuilder.buildLocalVariableGet(variable); |
- // After this point, the variables that were boxed on entry to the try |
- // are no longer treated as mutable. |
- catchBuilder.removeMutableVariable(variable); |
- catchBuilder.environment.update(variable, value); |
- } |
+ builder.state.breakCollectors.forEach(restoreJump); |
+ builder.state.continueCollectors.forEach(restoreJump); |
+ } |
+ |
+ List<ir.Parameter> buildCatch(IrBuilder builder, |
+ JumpCollector join) { |
+ // Translate the catch clauses. Multiple clauses are translated as if |
+ // they were explicitly cascaded if/else type tests. |
// Handlers are always translated as having both exception and stack trace |
// parameters. Multiple clauses do not have to use the same names for |
@@ -1729,136 +1768,140 @@ class IrBuilder { |
} |
} |
ir.Parameter traceParameter = new ir.Parameter(traceVariable); |
- // Expand multiple catch clauses into an explicit if/then/else. Iterate |
- // them in reverse so the current block becomes the next else block. |
- ir.Expression catchBody; |
- if (catchAll == null) { |
- catchBody = new ir.Rethrow(); |
- } else { |
- IrBuilder clauseBuilder = catchBuilder.makeDelimitedBuilder(); |
- clauseBuilder.declareLocalVariable(catchAll.exceptionVariable, |
- initialValue: exceptionParameter); |
- if (catchAll.stackTraceVariable != null) { |
- clauseBuilder.declareLocalVariable(catchAll.stackTraceVariable, |
- initialValue: traceParameter); |
- } |
- catchAll.buildCatchBlock(clauseBuilder); |
- if (clauseBuilder.isOpen) clauseBuilder.jumpTo(join); |
- catchBody = clauseBuilder._root; |
- } |
- for (CatchClauseInfo clause in catchClauseInfos.reversed) { |
- IrBuilder clauseBuilder = catchBuilder.makeDelimitedBuilder(); |
+ |
+ ir.Expression buildCatchClause(CatchClauseInfo clause) { |
+ IrBuilder clauseBuilder = builder.makeDelimitedBuilder(); |
clauseBuilder.declareLocalVariable(clause.exceptionVariable, |
- initialValue: exceptionParameter); |
+ initialValue: exceptionParameter); |
if (clause.stackTraceVariable != null) { |
clauseBuilder.declareLocalVariable(clause.stackTraceVariable, |
- initialValue: traceParameter); |
+ initialValue: traceParameter); |
} |
clause.buildCatchBlock(clauseBuilder); |
if (clauseBuilder.isOpen) clauseBuilder.jumpTo(join); |
+ return clauseBuilder._root; |
+ } |
+ |
+ // Expand multiple catch clauses into an explicit if/then/else. Iterate |
+ // them in reverse so the current block becomes the next else block. |
+ ir.Expression catchBody = (catchAll == null) |
+ ? new ir.Rethrow() |
+ : buildCatchClause(catchAll); |
+ for (CatchClauseInfo clause in catchClauseInfos.reversed) { |
ir.Continuation thenContinuation = new ir.Continuation([]); |
- thenContinuation.body = clauseBuilder._root; |
ir.Continuation elseContinuation = new ir.Continuation([]); |
+ thenContinuation.body = buildCatchClause(clause); |
elseContinuation.body = catchBody; |
// Build the type test guarding this clause. We can share the |
// environment with the nested builder because this part cannot mutate |
// it. |
- IrBuilder checkBuilder = catchBuilder.makeDelimitedBuilder(environment); |
+ IrBuilder checkBuilder = builder.makeDelimitedBuilder(environment); |
ir.Primitive typeMatches = |
checkBuilder.buildTypeOperator(exceptionParameter, |
- clause.type, |
- isTypeTest: true); |
+ clause.type, |
+ isTypeTest: true); |
checkBuilder.add(new ir.LetCont.many([thenContinuation, |
elseContinuation], |
- new ir.Branch(new ir.IsTrue(typeMatches), |
- thenContinuation, |
- elseContinuation))); |
+ new ir.Branch(new ir.IsTrue(typeMatches), |
+ thenContinuation, |
+ elseContinuation))); |
catchBody = checkBuilder._root; |
} |
+ builder.add(catchBody); |
- List<ir.Parameter> catchParameters = |
- <ir.Parameter>[exceptionParameter, traceParameter]; |
- ir.Continuation catchContinuation = new ir.Continuation(catchParameters); |
- catchBuilder.add(catchBody); |
- catchContinuation.body = catchBuilder._root; |
+ return <ir.Parameter>[exceptionParameter, traceParameter]; |
+ } |
- tryCatchBuilder.add( |
- new ir.LetHandler(catchContinuation, tryBuilder._root)); |
- add(new ir.LetCont(join.continuation, tryCatchBuilder._root)); |
- environment = join.environment; |
- } else { |
- // Try/finally. |
- JumpCollector join = new ForwardJumpCollector(environment); |
- IrBuilder tryFinallyBuilder = makeDelimitedBuilder(); |
- |
- tryFinallyBuilder.mutableVariables = |
- new Map<Local, ir.MutableVariable>.from(mutableVariables); |
- for (LocalVariableElement variable in tryStatementInfo.boxedOnEntry) { |
- assert(!tryFinallyBuilder.isInMutableVariable(variable)); |
- ir.Primitive value = tryFinallyBuilder.buildLocalVariableGet(variable); |
- tryFinallyBuilder.makeMutableVariable(variable); |
- tryFinallyBuilder.declareLocalVariable(variable, initialValue: value); |
- } |
+ void leaveTryCatch(IrBuilder builder, JumpCollector join, |
+ ir.Expression body) { |
+ // Add the binding for the join-point continuation and continue the |
+ // translation in its body. |
+ builder.add(new ir.LetCont(join.continuation, body)); |
+ builder.environment = join.environment; |
+ } |
- IrBuilder tryBuilder = tryFinallyBuilder.makeDelimitedBuilder(); |
+ _helpBuildTryCatch(variables, enterTry, buildTryBlock, leaveTry, |
+ buildCatch, leaveTryCatch); |
+ } |
+ /// Translates a try/finally. |
+ /// |
+ /// [variables] provides information on local variables declared and boxed |
+ /// within the try body. |
+ /// [buildTryBlock] builds the try block. |
+ /// [buildFinallyBlock] builds the finally block. |
+ void buildTryFinally(TryStatementInfo variables, |
+ SubbuildFunction buildTryBlock, |
+ SubbuildFunction buildFinallyBlock) { |
+ assert(isOpen); |
+ // Try/finally is implemented in terms of try/catch and by duplicating the |
+ // code for finally at all exits. The encoding is: |
+ // |
+ // try tryBlock finally finallyBlock |
+ // ==> |
+ // try tryBlock catch (ex, st) { finallyBlock; rethrow } finallyBlock |
+ // |
+ // Where in tryBlock, all of the break, continue, and return exits are |
+ // translated as jumps to continuations (bound outside the catch handler) |
+ // that include the finally code followed by a break, continue, or |
+ // return respectively. |
+ |
+ List<JumpCollector> savedBreaks, newBreaks, savedContinues, newContinues; |
+ JumpCollector savedReturn, newReturn; |
+ void enterTry(IrBuilder builder) { |
+ // On entry to the try of try/finally, update the builder's state to |
+ // relfect the variables that have been boxed. Then intercept all break, |
+ // continue, and return jumps out of the try so that they can go to |
+ // continuations that include the finally code. |
JumpCollector interceptJump(JumpCollector collector) { |
JumpCollector result = |
new ForwardJumpCollector(environment, target: collector.target); |
- result.enterTry(tryStatementInfo.boxedOnEntry); |
+ result.enterTry(variables.boxedOnEntry); |
return result; |
} |
- void restoreJump(JumpCollector collector) { |
- collector.leaveTry(); |
- } |
- List<JumpCollector> savedBreaks = tryBuilder.state.breakCollectors; |
- List<JumpCollector> savedContinues = tryBuilder.state.continueCollectors; |
- JumpCollector savedReturn = tryBuilder.state.returnCollector; |
+ savedBreaks = builder.state.breakCollectors; |
+ savedContinues = builder.state.continueCollectors; |
+ savedReturn = builder.state.returnCollector; |
- List<JumpCollector> newBreaks = tryBuilder.state.breakCollectors = |
+ builder.state.breakCollectors = newBreaks = |
savedBreaks.map(interceptJump).toList(); |
- List<JumpCollector> newContinues = tryBuilder.state.continueCollectors = |
+ builder.state.continueCollectors = newContinues = |
savedContinues.map(interceptJump).toList(); |
- JumpCollector newReturn = tryBuilder.state.returnCollector = |
- new ForwardJumpCollector(environment, hasExtraArgument: true); |
- newReturn.enterTry(tryStatementInfo.boxedOnEntry); |
- buildTryBlock(tryBuilder); |
- if (tryBuilder.isOpen) { |
- // To cover control falling off the end of the try block, the finally |
- // code is translated at the join point. This ensures that it is |
- // correctly outside the scope of the catch handler. |
- join.enterTry(tryStatementInfo.boxedOnEntry); |
- tryBuilder.jumpTo(join); |
- join.leaveTry(); |
+ builder.state.returnCollector = newReturn = |
+ new ForwardJumpCollector(environment, hasExtraArgument: true) |
+ ..enterTry(variables.boxedOnEntry); |
+ } |
+ |
+ void leaveTry(IrBuilder builder) { |
+ // On exit from the try of try/finally, update the builder's state to |
+ // reflect the variables that are no longer boxed and restore the |
+ // original, unintercepted break, continue, and return targets. |
+ void restoreJump(JumpCollector collector) { |
+ collector.leaveTry(); |
} |
newBreaks.forEach(restoreJump); |
newContinues.forEach(restoreJump); |
newReturn.leaveTry(); |
- tryBuilder.state.breakCollectors = savedBreaks; |
- tryBuilder.state.continueCollectors = savedContinues; |
- tryBuilder.state.returnCollector = savedReturn; |
- |
- IrBuilder catchBuilder = tryFinallyBuilder.makeDelimitedBuilder(); |
- for (LocalVariableElement variable in tryStatementInfo.boxedOnEntry) { |
- assert(catchBuilder.isInMutableVariable(variable)); |
- ir.Primitive value = catchBuilder.buildLocalVariableGet(variable); |
- catchBuilder.removeMutableVariable(variable); |
- catchBuilder.environment.update(variable, value); |
- } |
+ builder.state.breakCollectors = savedBreaks; |
+ builder.state.continueCollectors = savedContinues; |
+ builder.state.returnCollector = savedReturn; |
+ } |
- buildFinallyBlock(catchBuilder); |
- if (catchBuilder.isOpen) { |
- catchBuilder.add(new ir.Rethrow()); |
- catchBuilder._current = null; |
+ List<ir.Parameter> buildCatch(IrBuilder builder, |
+ JumpCollector join) { |
+ // The catch block of the try/catch used for try/finally is the finally |
+ // code followed by a rethrow. |
+ buildFinallyBlock(builder); |
+ if (builder.isOpen) { |
+ builder.add(new ir.Rethrow()); |
+ builder._current = null; |
} |
- List<ir.Parameter> catchParameters = |
- <ir.Parameter>[new ir.Parameter(null), new ir.Parameter(null)]; |
- ir.Continuation catchContinuation = new ir.Continuation(catchParameters); |
- catchContinuation.body = catchBuilder._root; |
- tryFinallyBuilder.add( |
- new ir.LetHandler(catchContinuation, tryBuilder._root)); |
+ return <ir.Parameter>[new ir.Parameter(null), new ir.Parameter(null)]; |
+ } |
+ void leaveTryCatch(IrBuilder builder, JumpCollector join, |
+ ir.Expression body) { |
// Build a list of continuations for jumps from the try block and |
// duplicate the finally code before jumping to the actual target. |
List<ir.Continuation> exits = <ir.Continuation>[join.continuation]; |
@@ -1885,10 +1928,13 @@ class IrBuilder { |
newReturn.continuation.body = builder._root; |
exits.add(newReturn.continuation); |
} |
- add(new ir.LetCont.many(exits, tryFinallyBuilder._root)); |
- environment = join.environment; |
- buildFinallyBlock(this); |
+ builder.add(new ir.LetCont.many(exits, body)); |
+ builder.environment = join.environment; |
+ buildFinallyBlock(builder); |
} |
+ |
+ _helpBuildTryCatch(variables, enterTry, buildTryBlock, leaveTry, |
+ buildCatch, leaveTryCatch); |
} |
/// Create a return statement `return value;` or `return;` if [value] is |