Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Side by Side Diff: frog/leg/ssa/builder.dart

Issue 9632018: Switch-implementation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 class Interceptors { 5 class Interceptors {
6 Compiler compiler; 6 Compiler compiler;
7 Interceptors(Compiler this.compiler); 7 Interceptors(Compiler this.compiler);
8 8
9 SourceString mapOperatorToMethodName(Operator op) { 9 SourceString mapOperatorToMethodName(Operator op) {
10 String name = op.source.stringValue; 10 String name = op.source.stringValue;
(...skipping 602 matching lines...) Expand 10 before | Expand all | Expand 10 after
613 final HBreak breakInstruction; 613 final HBreak breakInstruction;
614 final LocalsHandler locals; 614 final LocalsHandler locals;
615 BreakHandlerEntry(this.breakInstruction, this.locals); 615 BreakHandlerEntry(this.breakInstruction, this.locals);
616 } 616 }
617 617
618 interface BreakHandler default BreakHandlerImpl { 618 interface BreakHandler default BreakHandlerImpl {
619 BreakHandler(SsaBuilder builder, StatementElement target); 619 BreakHandler(SsaBuilder builder, StatementElement target);
620 void addBreak(HBreak breakInstruction); 620 void addBreak(HBreak breakInstruction);
621 void forEachBreak(Function action); 621 void forEachBreak(Function action);
622 void close(); 622 void close();
623 List<SourceString> labels(); 623 List<LabelElement> labels();
624 } 624 }
625 625
626 // Inert break handler used to avoid null checks when a loop isn't 626 // Inert break handler used to avoid null checks when a loop isn't
627 // used as the target of a break, and therefore doesn't need a break 627 // used as the target of a break, and therefore doesn't need a break
628 // handler associated with it. 628 // handler associated with it.
629 class NullBreakHandler implements BreakHandler { 629 class NullBreakHandler implements BreakHandler {
630 const NullBreakHandler(); 630 const NullBreakHandler();
631 void addBreak(HBreak breakInstruction) { unreachable(); } 631 void addBreak(HBreak breakInstruction) { unreachable(); }
632 void forEachBreak(Function ignored) { } 632 void forEachBreak(Function ignored) { }
633 void close() { } 633 void close() { }
634 List<SourceString> labels() => const <SourceString>[]; 634 List<LabelElement> labels() => const <LabelElement>[];
635 } 635 }
636 636
637 // Records breaks until a target block is available. 637 // Records breaks until a target block is available.
638 // Breaks are always forward jumps. 638 // Breaks are always forward jumps.
639 class BreakHandlerImpl implements BreakHandler { 639 class BreakHandlerImpl implements BreakHandler {
640 final BreakHandler previous; 640 final BreakHandler previous;
641 final SsaBuilder builder; 641 final SsaBuilder builder;
642 final StatementElement target; 642 final StatementElement target;
643 final List<BreakHandlerEntry> breaks; 643 final List<BreakHandlerEntry> breaks;
644 BreakHandlerImpl(SsaBuilder builder, this.target) 644 BreakHandlerImpl(SsaBuilder builder, this.target)
(...skipping 16 matching lines...) Expand all
661 } 661 }
662 } 662 }
663 663
664 void close() { 664 void close() {
665 assert(builder.currentBreakHandler === this); 665 assert(builder.currentBreakHandler === this);
666 // The mapping from StatementElement to BreakHandler is no longer needed. 666 // The mapping from StatementElement to BreakHandler is no longer needed.
667 builder.breakTargets.remove(target); 667 builder.breakTargets.remove(target);
668 builder.currentBreakHandler = previous; 668 builder.currentBreakHandler = previous;
669 } 669 }
670 670
671 List<SourceString> labels() { 671 List<LabelElement> labels() {
672 List<SourceString> result = null; 672 List<LabelElement> result = null;
673 for (LabelElement element in target.labels) { 673 for (LabelElement element in target.labels) {
674 if (element.isBreakTarget) { 674 if (element.isBreakTarget) {
675 if (result === null) result = <SourceString>[]; 675 if (result === null) result = <LabelElement>[];
676 result.add(element.label.source); 676 result.add(element);
677 } 677 }
678 } 678 }
679 return (result === null) ? const <SourceString>[] : result; 679 return (result === null) ? const <LabelElement>[] : result;
680 } 680 }
681 } 681 }
682 682
683 class SsaBuilder implements Visitor { 683 class SsaBuilder implements Visitor {
684 final Compiler compiler; 684 final Compiler compiler;
685 TreeElements elements; 685 TreeElements elements;
686 final Interceptors interceptors; 686 final Interceptors interceptors;
687 final WorkItem work; 687 final WorkItem work;
688 bool methodInterceptionEnabled; 688 bool methodInterceptionEnabled;
689 HGraph graph; 689 HGraph graph;
690 LocalsHandler localsHandler; 690 LocalsHandler localsHandler;
691 HInstruction rethrowableException; 691 HInstruction rethrowableException;
692 692
693 Map<StatementElement, BreakHandler> breakTargets; 693 Map<StatementElement, BreakHandler> breakTargets;
694 694
695 // We build the Ssa graph by simulating a stack machine. 695 // We build the Ssa graph by simulating a stack machine.
696 List<HInstruction> stack; 696 List<HInstruction> stack;
697 697
698 // The current block to add instructions to. Might be null, if we are 698 // The current block to add instructions to. Might be null, if we are
699 // visiting dead code. 699 // visiting dead code.
700 HBasicBlock current; 700 HBasicBlock current;
701 // The most recently opened block. Has the same value as [current] while 701 // The most recently opened block. Has the same value as [current] while
702 // the block is open, but unlike [current], it isn't cleared when the current 702 // the block is open, but unlike [current], it isn't cleared when the current
703 // block is closed. 703 // block is closed.
704 HBasicBlock lastOpenedBlock; 704 HBasicBlock lastOpenedBlock;
705 705
706 // Linked list of active break-handlers. Will be removed in the order 706 // Linked list of active break-handlers. Will be removed in the order
707 // they are added. 707 // they are added.
708 BreakHandler currentBreakHandler = const NullBreakHandler(); 708 BreakHandler currentBreakHandler = const NullBreakHandler();
709 // The break handler to use for an upcoming loop statement (temporarily set
710 // if a labeled statement is labeling a loop).
711 BreakHandler loopBreakHandler = null;
712 709
713 SsaBuilder(Compiler compiler, WorkItem work) 710 SsaBuilder(Compiler compiler, WorkItem work)
714 : this.compiler = compiler, 711 : this.compiler = compiler,
715 this.work = work, 712 this.work = work,
716 interceptors = compiler.builder.interceptors, 713 interceptors = compiler.builder.interceptors,
717 methodInterceptionEnabled = true, 714 methodInterceptionEnabled = true,
718 elements = work.resolutionTree, 715 elements = work.resolutionTree,
719 graph = new HGraph(), 716 graph = new HGraph(),
720 stack = new List<HInstruction>(), 717 stack = new List<HInstruction>(),
721 breakTargets = new Map<StatementElement, BreakHandler>() { 718 breakTargets = new Map<StatementElement, BreakHandler>() {
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after
1000 } 997 }
1001 998
1002 /** 999 /**
1003 * Creates a new loop-header block. The previous [current] block 1000 * Creates a new loop-header block. The previous [current] block
1004 * is closed with an [HGoto] and replaced by the newly created block. 1001 * is closed with an [HGoto] and replaced by the newly created block.
1005 * Also notifies the locals handler that we're entering a loop. 1002 * Also notifies the locals handler that we're entering a loop.
1006 */ 1003 */
1007 BreakHandler beginLoopHeader(Node node) { 1004 BreakHandler beginLoopHeader(Node node) {
1008 assert(!isAborted()); 1005 assert(!isAborted());
1009 HBasicBlock previousBlock = close(new HGoto()); 1006 HBasicBlock previousBlock = close(new HGoto());
1010 BreakHandler breakHandler = getLoopBreakHandler(node); 1007 BreakHandler breakHandler = getBreakHandler(node);
1011 HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(breakHandler.labels()); 1008 HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(breakHandler.labels());
1012 previousBlock.addSuccessor(loopEntry); 1009 previousBlock.addSuccessor(loopEntry);
1013 open(loopEntry); 1010 open(loopEntry);
1014 1011
1015 localsHandler.beginLoopHeader(node, loopEntry); 1012 localsHandler.beginLoopHeader(node, loopEntry);
1016 return breakHandler; 1013 return breakHandler;
1017 } 1014 }
1018 1015
1019 /** 1016 /**
1020 * Ends the loop: 1017 * Ends the loop:
(...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after
1322 assert(op.token.kind !== PLUS_TOKEN); 1319 assert(op.token.kind !== PLUS_TOKEN);
1323 HInstruction operand = pop(); 1320 HInstruction operand = pop();
1324 // See if we can constant-fold right away. This avoids rewrites later on. 1321 // See if we can constant-fold right away. This avoids rewrites later on.
1325 if (operand is HConstant) { 1322 if (operand is HConstant) {
1326 HConstant typedOperand = operand; 1323 HConstant typedOperand = operand;
1327 Constant constant = typedOperand.constant; 1324 Constant constant = typedOperand.constant;
1328 Constant folded = constant.unaryFold(op.source.stringValue); 1325 Constant folded = constant.unaryFold(op.source.stringValue);
1329 if (folded !== null) { 1326 if (folded !== null) {
1330 stack.add(graph.addConstant(folded)); 1327 stack.add(graph.addConstant(folded));
1331 return; 1328 return;
1332 } 1329 }
1333 } 1330 }
1334 HInstruction target = 1331 HInstruction target =
1335 new HStatic(interceptors.getPrefixOperatorInterceptor(op)); 1332 new HStatic(interceptors.getPrefixOperatorInterceptor(op));
1336 add(target); 1333 add(target);
1337 switch (op.source.stringValue) { 1334 switch (op.source.stringValue) {
1338 case "-": push(new HNegate(target, operand)); break; 1335 case "-": push(new HNegate(target, operand)); break;
1339 case "~": push(new HBitNot(target, operand)); break; 1336 case "~": push(new HBitNot(target, operand)); break;
1340 default: unreachable(); 1337 default: unreachable();
1341 } 1338 }
1342 } 1339 }
(...skipping 294 matching lines...) Expand 10 before | Expand all | Expand 10 after
1637 SourceString name = selector.namedArguments[i]; 1634 SourceString name = selector.namedArguments[i];
1638 if (name == parameter.name) { 1635 if (name == parameter.name) {
1639 foundIndex = i; 1636 foundIndex = i;
1640 break; 1637 break;
1641 } 1638 }
1642 } 1639 }
1643 if (foundIndex != -1) { 1640 if (foundIndex != -1) {
1644 list.add(namedArguments[foundIndex]); 1641 list.add(namedArguments[foundIndex]);
1645 } else { 1642 } else {
1646 Constant constant = compiler.compileVariable(parameter); 1643 Constant constant = compiler.compileVariable(parameter);
1647 list.add(graph.addConstant(constant)); 1644 list.add(graph.addConstant(constant));
1648 } 1645 }
1649 } 1646 }
1650 } 1647 }
1651 } 1648 }
1652 1649
1653 void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) { 1650 void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) {
1654 for (; !link.isEmpty(); link = link.tail) { 1651 for (; !link.isEmpty(); link = link.tail) {
1655 visit(link.head); 1652 visit(link.head);
1656 list.add(pop()); 1653 list.add(pop());
1657 } 1654 }
(...skipping 446 matching lines...) Expand 10 before | Expand all | Expand 10 after
2104 work.allowSpeculativeOptimization = false; 2101 work.allowSpeculativeOptimization = false;
2105 assert(!isAborted()); 2102 assert(!isAborted());
2106 StatementElement target = elements[node]; 2103 StatementElement target = elements[node];
2107 assert(target !== null); 2104 assert(target !== null);
2108 BreakHandler handler = breakTargets[target]; 2105 BreakHandler handler = breakTargets[target];
2109 assert(handler !== null); 2106 assert(handler !== null);
2110 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); 2107 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
2111 HBreak breakInstruction; 2108 HBreak breakInstruction;
2112 if (node.target === null) { 2109 if (node.target === null) {
2113 breakInstruction = new HBreak(); 2110 breakInstruction = new HBreak();
2114 } else { 2111 } else {
2115 breakInstruction = new HBreak(node.target.source); 2112 LabelElement label = elements[node.target];
2113 breakInstruction = new HBreak(label);
2116 } 2114 }
2117 close(breakInstruction); 2115 close(breakInstruction);
2118 handler.addBreak(breakInstruction, savedLocals); 2116 handler.addBreak(breakInstruction, savedLocals);
2119 } 2117 }
2120 2118
2121 visitContinueStatement(ContinueStatement node) { 2119 visitContinueStatement(ContinueStatement node) {
2122 // TODO(lrn): Replace this with a real implementation of continue. 2120 // TODO(lrn): Replace this with a real implementation of continue.
2123 compiler.reportWarning(node, 'continue not implemented'); 2121 compiler.reportWarning(node, 'continue not implemented');
2124 generateUnimplemented('continue not implemented'); 2122 generateUnimplemented('continue not implemented');
2125 } 2123 }
2126 2124
2127 BreakHandler getLoopBreakHandler(Node node) { 2125 BreakHandler getBreakHandler(Node node) {
2128 StatementElement element = elements[node]; 2126 StatementElement element = elements[node];
2129 BreakHandler handler; 2127 if (element === null) return const NullBreakHandler();
2130 if (loopBreakHandler === null) { 2128 return new BreakHandler(this, element);
2131 if (element === null) return const NullBreakHandler();
2132 handler = new BreakHandler(this, element);
2133 } else {
2134 handler = loopBreakHandler;
2135 loopBreakHandler = null;
2136 if (element === null) return handler;
2137 }
2138 return handler;
2139 } 2129 }
2140 2130
2141 visitForInStatement(ForInStatement node) { 2131 visitForInStatement(ForInStatement node) {
2142 // Generate a structure equivalent to: 2132 // Generate a structure equivalent to:
2143 // Iterator<E> $iter = <iterable>.iterator() 2133 // Iterator<E> $iter = <iterable>.iterator()
2144 // while ($iter.hasNext()) { 2134 // while ($iter.hasNext()) {
2145 // E <declaredIdentifier> = $iter.next(); 2135 // E <declaredIdentifier> = $iter.next();
2146 // <body> 2136 // <body>
2147 // } 2137 // }
2148 localsHandler.startLoop(node); 2138 localsHandler.startLoop(node);
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
2290 visit(node.value); 2280 visit(node.value);
2291 visit(node.key); 2281 visit(node.key);
2292 } 2282 }
2293 2283
2294 visitNamedArgument(NamedArgument node) { 2284 visitNamedArgument(NamedArgument node) {
2295 visit(node.expression); 2285 visit(node.expression);
2296 } 2286 }
2297 2287
2298 visitSwitchStatement(SwitchStatement node) { 2288 visitSwitchStatement(SwitchStatement node) {
2299 work.allowSpeculativeOptimization = false; 2289 work.allowSpeculativeOptimization = false;
2290 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
2300 visit(node.expression); 2291 visit(node.expression);
2301 HInstruction expression = pop(); 2292 HInstruction expression = pop();
2293 if (node.cases.isEmpty()) {
2294 return;
2295 }
2302 Link<Node> cases = node.cases.nodes; 2296 Link<Node> cases = node.cases.nodes;
2303 int count = 0; 2297 Element equalsHelper = interceptors.getEqualsInterceptor();
2304 handleThen() { 2298 HInstruction target = new HStatic(equalsHelper);
2305 if (cases.head.statements.nodes.isEmpty()) { 2299 add(target);
2306 compiler.unimplemented('fall-through', node: cases.head); 2300
2301 BreakHandler breakHandler = getBreakHandler(node);
2302
2303 void buildCompare(Expression caseExpression) {
2304 visit(caseExpression);
2305 push(new HEquals(target, pop(), expression));
2306 }
2307 buildSwitchCases(cases, buildCompare);
2308
2309 // Create merge block for break targets.
2310 HBasicBlock joinBlock = new HBasicBlock();
2311 List<LocalsHandler> caseLocals = <LocalsHandler>[];
2312 breakHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
2313 instruction.block.addSuccessor(joinBlock);
2314 caseLocals.add(locals);
2315 });
2316 if (caseLocals.length == 0) return;
2317 if (!isAborted()) {
2318 caseLocals.add(localsHandler);
2319 goto(current, joinBlock);
2320 }
2321 graph.addBlock(joinBlock);
2322 open(joinBlock);
2323 if (caseLocals.length == 1) {
2324 localsHandler = caseLocals[0];
2325 } else {
2326 localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
2327 }
2328 }
2329
2330
2331 // Recursively build an if/else structure to match the cases.
2332 buildSwitchCases(Link<Node> cases, Function buildCompare) {
2333 SwitchCase node = cases.head;
2334 // TODO(lrn): Handle labels and continues.
2335
2336 void visitStatementsAndAbort() {
2337 visit(node.statements);
2338 if (!isAborted()) {
2339 compiler.cancel('Missing break at end of switch case', node: node);
2340 HBreak breakInstruction = new HBreak();
2341 close(breakInstruction);
2342 currentBreakHandler.addBreak(breakInstruction, localsHandler);
2307 } 2343 }
2308 visit(cases.head.statements);
2309 cases = cases.tail;
2310 }
2311 handleElse() {
2312 if (cases.isEmpty()) return;
2313 if (cases.head.asDefaultCase() !== null) {
2314 stack.add(graph.addConstantBool(true));
2315 if (!cases.tail.isEmpty()) {
2316 compiler.unimplemented('default case not last', node: cases.head);
2317 }
2318 } else {
2319 SwitchCase switchCase = cases.head;
2320 visit(switchCase.expression);
2321 HInstruction caseExpression = pop();
2322 Element equalsHelper = interceptors.getEqualsInterceptor();
2323 HInstruction target = new HStatic(equalsHelper);
2324 add(target);
2325 push(new HEquals(target, caseExpression, expression));
2326 }
2327 handleIf(handleThen, handleElse);
2328 } 2344 }
2329 2345
2330 localsHandler.startLoop(node); 2346 Link<Node> expressions = node.expressions.nodes;
2331 BreakHandler breakHandler = beginLoopHeader(node); 2347 if (expressions.isEmpty()) {
2332 HBasicBlock loopEntryBlock = current; 2348 // Default case with no expressions.
2333 localsHandler.enterLoopBody(node); 2349 if (!node.isDefaultCase) {
2334 2350 compiler.internalError("Case with no expression and not default");
2335 handleElse(); 2351 }
2336 2352 visitStatementsAndAbort();
2337 if (isAborted()) { 2353 return;
2338 compiler.unimplemented("SsaBuilder for loop with aborting body",
2339 node: node);
2340 } 2354 }
2341 2355
2342 HBasicBlock bodyExitBlock = close(new HGoto()); 2356 // Recursively build the test conditions.
2343 HBasicBlock conditionBlock = addNewBlock(); 2357 HInstruction buildTests(Link<Node> expressions, HInstruction left) {
2344 bodyExitBlock.addSuccessor(conditionBlock); 2358 // previous is a boolean instruction.
floitsch 2012/03/08 16:35:14 P
Lasse Reichstein Nielsen 2012/03/08 18:35:40 Whoops, 'previous'->'left'.
2345 open(conditionBlock); 2359 if (expressions.isEmpty()) return left;
2346 stack.add(graph.addConstantBool(false)); 2360 push(new HNot(left));
2347 2361
2348 conditionBlock = close(new HLoopBranch(popBoolified(), 2362 HIf branch = new HIf(pop(), false);
2349 HLoopBranch.DO_WHILE_LOOP)); 2363 HBasicBlock leftBlock = close(branch);
2364 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
2350 2365
2351 conditionBlock.addSuccessor(loopEntryBlock); // The back-edge. 2366 HBasicBlock rightBlock = addNewBlock();
2352 loopEntryBlock.postProcessLoopHeader(); 2367 leftBlock.addSuccessor(rightBlock);
2368 open(rightBlock);
2353 2369
2354 endLoop(loopEntryBlock, conditionBlock, breakHandler); 2370 buildCompare(expressions.head);
2371 HInstruction right = buildTests(expressions.tail, popBoolified());
2372
2373 SubGraph rightGraph = new SubGraph(rightBlock, current);
2374
2375 rightBlock = close(new HGoto());
2376 HBasicBlock joinBlock = addNewBlock();
2377 leftBlock.addSuccessor(joinBlock);
2378 rightBlock.addSuccessor(joinBlock);
2379 open(joinBlock);
2380
2381 branch.blockInformation =
2382 new HIfBlockInformation(branch, rightGraph, null, joinBlock);
2383
2384 localsHandler.mergeWith(savedLocals, joinBlock);
2385 HPhi result = new HPhi.manyInputs(null, [left, right]);
2386 joinBlock.addPhi(result);
2387 return result;
2388 }
2389
2390 buildCompare(expressions.head);
2391 HInstruction result = buildTests(expressions.tail, popBoolified());
2392
2393 if (node.isDefaultCase) {
2394 // Don't actually use the condition result.
2395 visitStatementsAndAbort();
2396 } else {
2397 stack.add(result);
2398 handleIf(() { visitStatementsAndAbort(); },
2399 cases.tail.isEmpty() ?
2400 null :
2401 () { buildSwitchCases(cases.tail, buildCompare); });
2402 }
2403 }
2404
2405 visitSwitchCase(SwitchCase node) {
2406 unreachable();
2355 } 2407 }
2356 2408
2357 visitTryStatement(TryStatement node) { 2409 visitTryStatement(TryStatement node) {
2358 work.allowSpeculativeOptimization = false; 2410 work.allowSpeculativeOptimization = false;
2359 assert(!work.isBailoutVersion()); 2411 assert(!work.isBailoutVersion());
2360 HBasicBlock enterBlock = graph.addNewBlock(); 2412 HBasicBlock enterBlock = graph.addNewBlock();
2361 close(new HGoto()).addSuccessor(enterBlock); 2413 close(new HGoto()).addSuccessor(enterBlock);
2362 open(enterBlock); 2414 open(enterBlock);
2363 HTry tryInstruction = new HTry(); 2415 HTry tryInstruction = new HTry();
2364 List<HBasicBlock> blocks = <HBasicBlock>[]; 2416 List<HBasicBlock> blocks = <HBasicBlock>[];
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
2476 // Normally, we would call [close] here. However, then we hit 2528 // Normally, we would call [close] here. However, then we hit
2477 // another unimplemented feature: aborting loop body. Simply 2529 // another unimplemented feature: aborting loop body. Simply
2478 // calling [add] does not work as it asserts that the instruction 2530 // calling [add] does not work as it asserts that the instruction
2479 // isn't a control flow instruction. So we inline parts of [add]. 2531 // isn't a control flow instruction. So we inline parts of [add].
2480 current.addAfter(current.last, new HThrow(message)); 2532 current.addAfter(current.last, new HThrow(message));
2481 if (isExpression) { 2533 if (isExpression) {
2482 stack.add(graph.addConstantNull()); 2534 stack.add(graph.addConstantNull());
2483 } 2535 }
2484 } 2536 }
2485 } 2537 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698