| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library code_generator; |
| |
| import 'glue.dart'; |
| |
| import '../../closure.dart' show |
| ClosureClassElement; |
| import '../../common/codegen.dart' show |
| CodegenRegistry; |
| import '../../constants/values.dart'; |
| import '../../dart_types.dart'; |
| import '../../elements/elements.dart'; |
| import '../../io/source_information.dart' show |
| SourceInformation; |
| import '../../js/js.dart' as js; |
| import '../../tree_ir/tree_ir_nodes.dart' as tree_ir; |
| import '../../tree_ir/tree_ir_nodes.dart' show |
| BuiltinMethod, |
| BuiltinOperator, |
| isCompoundableOperator; |
| import '../../types/types.dart' show |
| TypeMask; |
| import '../../universe/call_structure.dart' show |
| CallStructure; |
| import '../../universe/selector.dart' show |
| Selector; |
| import '../../universe/use.dart' show |
| DynamicUse, |
| StaticUse, |
| TypeUse; |
| import '../../util/maplet.dart'; |
| |
| class CodegenBailout { |
| final tree_ir.Node node; |
| final String reason; |
| CodegenBailout(this.node, this.reason); |
| String get message { |
| return 'bailout${node != null ? " on $node" : ""}: $reason'; |
| } |
| } |
| |
| class CodeGenerator extends tree_ir.StatementVisitor |
| with tree_ir.ExpressionVisitor<js.Expression> { |
| final CodegenRegistry registry; |
| |
| final Glue glue; |
| |
| ExecutableElement currentFunction; |
| |
| /// Maps variables to their name. |
| Map<tree_ir.Variable, String> variableNames = <tree_ir.Variable, String>{}; |
| |
| /// Maps local constants to their name. |
| Maplet<VariableElement, String> constantNames = |
| new Maplet<VariableElement, String>(); |
| |
| /// Variable names that have already been used. Used to avoid name clashes. |
| Set<String> usedVariableNames = new Set<String>(); |
| |
| final tree_ir.FallthroughStack fallthrough = new tree_ir.FallthroughStack(); |
| |
| /// Stacks whose top element is the current target of an unlabeled break |
| /// or continue. For continues, this is the loop node itself. |
| final tree_ir.FallthroughStack shortBreak = new tree_ir.FallthroughStack(); |
| final tree_ir.FallthroughStack shortContinue = |
| new tree_ir.FallthroughStack(); |
| |
| /// When the top element is true, [Unreachable] statements will be emitted |
| /// as [Return]s, otherwise they are emitted as empty because they are |
| /// followed by the end of the method. |
| /// |
| /// Note on why the [fallthrough] stack should not be used for this: |
| /// Ordinary statements may choose whether to use the [fallthrough] target, |
| /// and the choice to do so may disable an optimization in [visitIf]. |
| /// But omitting an unreachable 'return' should have lower priority than |
| /// the optimizations in [visitIf], so [visitIf] will instead tell the |
| /// [Unreachable] statements whether they may use fallthrough or not. |
| List<bool> emitUnreachableAsReturn = <bool>[false]; |
| |
| final Map<tree_ir.Label, String> labelNames = <tree_ir.Label, String>{}; |
| |
| List<js.Statement> accumulator = new List<js.Statement>(); |
| |
| CodeGenerator(this.glue, this.registry); |
| |
| /// Generates JavaScript code for the body of [function]. |
| js.Fun buildFunction(tree_ir.FunctionDefinition function) { |
| registerDefaultParameterValues(function.element); |
| currentFunction = function.element; |
| tree_ir.Statement statement = function.body; |
| while (statement != null) { |
| statement = visitStatement(statement); |
| } |
| |
| List<js.Parameter> parameters = new List<js.Parameter>(); |
| Set<tree_ir.Variable> parameterSet = new Set<tree_ir.Variable>(); |
| Set<String> declaredVariables = new Set<String>(); |
| |
| for (tree_ir.Variable parameter in function.parameters) { |
| String name = getVariableName(parameter); |
| parameters.add(new js.Parameter(name)); |
| parameterSet.add(parameter); |
| declaredVariables.add(name); |
| } |
| |
| List<js.VariableInitialization> jsVariables = <js.VariableInitialization>[]; |
| |
| // Declare variables with an initializer. Pull statements into the |
| // initializer until we find a statement that cannot be pulled in. |
| int accumulatorIndex = 0; |
| while (accumulatorIndex < accumulator.length) { |
| js.Node node = accumulator[accumulatorIndex]; |
| |
| // Check that node is an assignment to a local variable. |
| if (node is! js.ExpressionStatement) break; |
| js.ExpressionStatement stmt = node; |
| if (stmt.expression is! js.Assignment) break; |
| js.Assignment assign = stmt.expression; |
| if (assign.leftHandSide is! js.VariableUse) break; |
| if (assign.op != null) break; // Compound assignment. |
| js.VariableUse use = assign.leftHandSide; |
| |
| // Do not touch non-local variables. |
| if (!usedVariableNames.contains(use.name)) break; |
| |
| // We cannot declare a variable more than once. |
| if (!declaredVariables.add(use.name)) break; |
| |
| js.VariableInitialization jsVariable = new js.VariableInitialization( |
| new js.VariableDeclaration(use.name), |
| assign.value); |
| jsVariables.add(jsVariable); |
| |
| ++accumulatorIndex; |
| } |
| |
| // If the last statement is a for loop with an initializer expression, try |
| // to pull that expression into an initializer as well. |
| pullFromForLoop: |
| if (accumulatorIndex < accumulator.length && |
| accumulator[accumulatorIndex] is js.For) { |
| js.For forLoop = accumulator[accumulatorIndex]; |
| if (forLoop.init is! js.Assignment) break pullFromForLoop; |
| js.Assignment assign = forLoop.init; |
| if (assign.leftHandSide is! js.VariableUse) break pullFromForLoop; |
| if (assign.op != null) break pullFromForLoop; // Compound assignment. |
| js.VariableUse use = assign.leftHandSide; |
| |
| // Do not touch non-local variables. |
| if (!usedVariableNames.contains(use.name)) break pullFromForLoop; |
| |
| // We cannot declare a variable more than once. |
| if (!declaredVariables.add(use.name)) break pullFromForLoop; |
| |
| js.VariableInitialization jsVariable = new js.VariableInitialization( |
| new js.VariableDeclaration(use.name), |
| assign.value); |
| jsVariables.add(jsVariable); |
| |
| // Remove the initializer from the for loop. |
| accumulator[accumulatorIndex] = |
| new js.For(null, forLoop.condition, forLoop.update, forLoop.body); |
| } |
| |
| // Discard the statements that were pulled in the initializer. |
| if (accumulatorIndex > 0) { |
| accumulator = accumulator.sublist(accumulatorIndex); |
| } |
| |
| // Declare remaining variables. |
| for (tree_ir.Variable variable in variableNames.keys) { |
| String name = getVariableName(variable); |
| if (declaredVariables.contains(name)) continue; |
| js.VariableInitialization jsVariable = new js.VariableInitialization( |
| new js.VariableDeclaration(name), |
| null); |
| jsVariables.add(jsVariable); |
| } |
| |
| if (jsVariables.length > 0) { |
| // Would be nice to avoid inserting at the beginning of list. |
| accumulator.insert(0, new js.ExpressionStatement( |
| new js.VariableDeclarationList(jsVariables) |
| .withSourceInformation(function.sourceInformation))); |
| } |
| return new js.Fun(parameters, new js.Block(accumulator)); |
| } |
| |
| @override |
| js.Expression visitExpression(tree_ir.Expression node) { |
| js.Expression result = node.accept(this); |
| if (result == null) { |
| glue.reportInternalError('$node did not produce code.'); |
| } |
| return result; |
| } |
| |
| /// Generates a name for the given variable. First trying with the name of |
| /// the [Variable.element] if it is non-null. |
| String getVariableName(tree_ir.Variable variable) { |
| // Functions are not nested in the JS backend. |
| assert(variable.host == currentFunction); |
| |
| // Get the name if we already have one. |
| String name = variableNames[variable]; |
| if (name != null) { |
| return name; |
| } |
| |
| // Synthesize a variable name that isn't used elsewhere. |
| String prefix = variable.element == null ? 'v' : variable.element.name; |
| int counter = 0; |
| name = glue.safeVariableName(variable.element == null |
| ? '$prefix$counter' |
| : variable.element.name); |
| while (!usedVariableNames.add(name)) { |
| ++counter; |
| name = '$prefix$counter'; |
| } |
| variableNames[variable] = name; |
| |
| return name; |
| } |
| |
| List<js.Expression> visitExpressionList( |
| List<tree_ir.Expression> expressions) { |
| List<js.Expression> result = new List<js.Expression>(expressions.length); |
| for (int i = 0; i < expressions.length; ++i) { |
| result[i] = visitExpression(expressions[i]); |
| } |
| return result; |
| } |
| |
| giveup(tree_ir.Node node, |
| [String reason = 'unimplemented in CodeGenerator']) { |
| throw new CodegenBailout(node, reason); |
| } |
| |
| @override |
| js.Expression visitConditional(tree_ir.Conditional node) { |
| return new js.Conditional( |
| visitExpression(node.condition), |
| visitExpression(node.thenExpression), |
| visitExpression(node.elseExpression)); |
| } |
| |
| js.Expression buildConstant(ConstantValue constant, |
| {SourceInformation sourceInformation}) { |
| registry.registerCompileTimeConstant(constant); |
| return glue.constantReference(constant) |
| .withSourceInformation(sourceInformation); |
| } |
| |
| @override |
| js.Expression visitConstant(tree_ir.Constant node) { |
| return buildConstant( |
| node.value, |
| sourceInformation: node.sourceInformation); |
| } |
| |
| js.Expression buildStaticInvoke(Element target, |
| List<js.Expression> arguments, |
| {SourceInformation sourceInformation}) { |
| if (target.isConstructor) { |
| // TODO(johnniwinther): Avoid dependency on [isGenerativeConstructor] by |
| // using backend-specific [StatisUse] classes. |
| registry.registerStaticUse( |
| new StaticUse.constructorInvoke(target.declaration, |
| new CallStructure.unnamed(arguments.length))); |
| } else { |
| registry.registerStaticUse( |
| new StaticUse.staticInvoke(target.declaration, |
| new CallStructure.unnamed(arguments.length))); |
| } |
| js.Expression elementAccess = glue.staticFunctionAccess(target); |
| return new js.Call(elementAccess, arguments, |
| sourceInformation: sourceInformation); |
| } |
| |
| @override |
| js.Expression visitInvokeConstructor(tree_ir.InvokeConstructor node) { |
| if (node.constant != null) return giveup(node); |
| |
| registry.registerInstantiation(node.type); |
| FunctionElement target = node.target; |
| List<js.Expression> arguments = visitExpressionList(node.arguments); |
| return buildStaticInvoke( |
| target, |
| arguments, |
| sourceInformation: node.sourceInformation); |
| } |
| |
| void registerMethodInvoke(Selector selector, TypeMask receiverType) { |
| registry.registerDynamicUse(new DynamicUse(selector, receiverType)); |
| if (!selector.isGetter && !selector.isSetter) { |
| // TODO(sigurdm): We should find a better place to register the call. |
| Selector call = new Selector.callClosureFrom(selector); |
| registry.registerDynamicUse(new DynamicUse(call, null)); |
| } |
| } |
| |
| @override |
| js.Expression visitInvokeMethod(tree_ir.InvokeMethod node) { |
| TypeMask mask = glue.extendMaskIfReachesAll(node.selector, node.mask); |
| registerMethodInvoke(node.selector, mask); |
| return js.propertyCall(visitExpression(node.receiver), |
| glue.invocationName(node.selector), |
| visitExpressionList(node.arguments)) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitInvokeStatic(tree_ir.InvokeStatic node) { |
| FunctionElement target = node.target; |
| List<js.Expression> arguments = visitExpressionList(node.arguments); |
| return buildStaticInvoke(target, arguments, |
| sourceInformation: node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitInvokeMethodDirectly(tree_ir.InvokeMethodDirectly node) { |
| if (node.isTearOff) { |
| // If this is a tear-off, register the fact that a tear-off closure |
| // will be created, and that this tear-off must bypass ordinary |
| // dispatch to ensure the super method is invoked. |
| registry.registerStaticUse(new StaticUse.staticInvoke( |
| glue.closureFromTearOff, new CallStructure.unnamed( |
| glue.closureFromTearOff.parameters.length))); |
| registry.registerStaticUse(new StaticUse.superTearOff(node.target)); |
| } |
| if (node.target is ConstructorBodyElement) { |
| registry.registerStaticUse( |
| new StaticUse.constructorBodyInvoke( |
| node.target.declaration, |
| new CallStructure.unnamed(node.arguments.length))); |
| // A constructor body cannot be overriden or intercepted, so we can |
| // use the short form for this invocation. |
| return js.js('#.#(#)', |
| [visitExpression(node.receiver), |
| glue.instanceMethodName(node.target), |
| visitExpressionList(node.arguments)]) |
| .withSourceInformation(node.sourceInformation); |
| } |
| registry.registerStaticUse( |
| new StaticUse.superInvoke( |
| node.target.declaration, |
| new CallStructure.unnamed(node.arguments.length))); |
| return js.js('#.#.call(#, #)', |
| [glue.prototypeAccess(node.target.enclosingClass), |
| glue.invocationName(node.selector), |
| visitExpression(node.receiver), |
| visitExpressionList(node.arguments)]) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitOneShotInterceptor(tree_ir.OneShotInterceptor node) { |
| registerMethodInvoke(node.selector, node.mask); |
| registry.registerUseInterceptor(); |
| return js.js('#.#(#)', |
| [glue.getInterceptorLibrary(), |
| glue.registerOneShotInterceptor(node.selector), |
| visitExpressionList(node.arguments)]) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitLiteralList(tree_ir.LiteralList node) { |
| registry.registerInstantiatedClass(glue.listClass); |
| List<js.Expression> entries = visitExpressionList(node.values); |
| return new js.ArrayInitializer(entries); |
| } |
| |
| @override |
| js.Expression visitLogicalOperator(tree_ir.LogicalOperator node) { |
| return new js.Binary( |
| node.operator, |
| visitExpression(node.left), |
| visitExpression(node.right)); |
| } |
| |
| @override |
| js.Expression visitNot(tree_ir.Not node) { |
| return new js.Prefix("!", visitExpression(node.operand)); |
| } |
| |
| @override |
| js.Expression visitThis(tree_ir.This node) { |
| return new js.This(); |
| } |
| |
| /// Ensure that 'instanceof' checks may be performed against [class_]. |
| /// |
| /// Even if the class is never instantiated, a JS constructor must be emitted |
| /// so the 'instanceof' expression does not throw an exception at runtime. |
| bool tryRegisterInstanceofCheck(ClassElement class_) { |
| if (glue.classWorld.isInstantiated(class_)) { |
| // Ensure the class remains instantiated during backend tree-shaking. |
| // TODO(asgerf): We could have a more precise hook to inform the emitter |
| // that the JS constructor function is needed, without the class being |
| // instantiated. |
| registry.registerInstantiatedClass(class_); |
| return true; |
| } |
| // Will throw if the JS constructor is not emitted, so do not allow the |
| // instanceof check. This should only happen when certain optimization |
| // passes are disabled, as the type check itself is trivial. |
| return false; |
| } |
| |
| @override |
| js.Expression visitTypeOperator(tree_ir.TypeOperator node) { |
| js.Expression value = visitExpression(node.value); |
| List<js.Expression> typeArguments = visitExpressionList(node.typeArguments); |
| DartType type = node.type; |
| if (type is InterfaceType) { |
| registry.registerTypeUse(new TypeUse.isCheck(type)); |
| ClassElement clazz = type.element; |
| |
| if (glue.isStringClass(clazz)) { |
| if (node.isTypeTest) { |
| return js.js(r'typeof # === "string"', <js.Expression>[value]); |
| } |
| // TODO(sra): Implement fast cast via calling 'stringTypeCast'. |
| } else if (glue.isBoolClass(clazz)) { |
| if (node.isTypeTest) { |
| return js.js(r'typeof # === "boolean"', <js.Expression>[value]); |
| } |
| // TODO(sra): Implement fast cast via calling 'boolTypeCast'. |
| } else if (node.isTypeTest && |
| node.typeArguments.isEmpty && |
| glue.mayGenerateInstanceofCheck(type) && |
| tryRegisterInstanceofCheck(clazz)) { |
| return js.js('# instanceof #', [value, glue.constructorAccess(clazz)]); |
| } |
| |
| // The helper we use needs the JSArray class to exist, but for some |
| // reason the helper does not cause this dependency to be registered. |
| // TODO(asgerf): Most programs need List anyway, but we should fix this. |
| registry.registerInstantiatedClass(glue.listClass); |
| |
| // We use one of the two helpers: |
| // |
| // checkSubtype(value, $isT, typeArgs, $asT) |
| // subtypeCast(value, $isT, typeArgs, $asT) |
| // |
| // Any of the last two arguments may be null if there are no type |
| // arguments, and/or if no substitution is required. |
| Element function = node.isTypeTest |
| ? glue.getCheckSubtype() |
| : glue.getSubtypeCast(); |
| |
| js.Expression isT = js.quoteName(glue.getTypeTestTag(type)); |
| |
| js.Expression typeArgumentArray = typeArguments.isNotEmpty |
| ? new js.ArrayInitializer(typeArguments) |
| : new js.LiteralNull(); |
| |
| js.Expression asT = glue.hasStrictSubtype(clazz) |
| ? js.quoteName(glue.getTypeSubstitutionTag(clazz)) |
| : new js.LiteralNull(); |
| |
| return buildStaticHelperInvocation( |
| function, |
| <js.Expression>[value, isT, typeArgumentArray, asT]); |
| } else if (type is TypeVariableType || type is FunctionType) { |
| registry.registerTypeUse(new TypeUse.isCheck(type)); |
| |
| Element function = node.isTypeTest |
| ? glue.getCheckSubtypeOfRuntimeType() |
| : glue.getSubtypeOfRuntimeTypeCast(); |
| |
| // The only type argument is the type held in the type variable. |
| js.Expression typeValue = typeArguments.single; |
| |
| return buildStaticHelperInvocation( |
| function, |
| <js.Expression>[value, typeValue]); |
| } |
| return giveup(node, 'type check unimplemented for $type.'); |
| } |
| |
| @override |
| js.Expression visitGetTypeTestProperty(tree_ir.GetTypeTestProperty node) { |
| js.Expression object = visitExpression(node.object); |
| DartType dartType = node.dartType; |
| assert(dartType.isInterfaceType); |
| registry.registerTypeUse(new TypeUse.isCheck(dartType)); |
| //glue.registerIsCheck(dartType, registry); |
| js.Expression property = glue.getTypeTestTag(dartType); |
| return js.js(r'#.#', [object, property]); |
| } |
| |
| @override |
| js.Expression visitVariableUse(tree_ir.VariableUse node) { |
| return buildVariableAccess(node.variable) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| js.Expression buildVariableAccess(tree_ir.Variable variable) { |
| return new js.VariableUse(getVariableName(variable)); |
| } |
| |
| /// Returns the JS operator for the given built-in operator for use in a |
| /// compound assignment (not including the '=' sign). |
| String getAsCompoundOperator(BuiltinOperator operator) { |
| switch (operator) { |
| case BuiltinOperator.NumAdd: |
| case BuiltinOperator.StringConcatenate: |
| return '+'; |
| case BuiltinOperator.NumSubtract: |
| return '-'; |
| case BuiltinOperator.NumMultiply: |
| return '*'; |
| case BuiltinOperator.NumDivide: |
| return '/'; |
| case BuiltinOperator.NumRemainder: |
| return '%'; |
| default: |
| throw 'Not a compoundable operator: $operator'; |
| } |
| } |
| |
| bool isCompoundableBuiltin(tree_ir.Expression exp) { |
| return exp is tree_ir.ApplyBuiltinOperator && |
| exp.arguments.length == 2 && |
| isCompoundableOperator(exp.operator); |
| } |
| |
| bool isOneConstant(tree_ir.Expression exp) { |
| return exp is tree_ir.Constant && exp.value.isOne; |
| } |
| |
| js.Expression makeAssignment( |
| js.Expression leftHand, |
| tree_ir.Expression value, |
| {SourceInformation sourceInformation, |
| BuiltinOperator compound}) { |
| if (isOneConstant(value)) { |
| if (compound == BuiltinOperator.NumAdd) { |
| return new js.Prefix('++', leftHand) |
| .withSourceInformation(sourceInformation); |
| } |
| if (compound == BuiltinOperator.NumSubtract) { |
| return new js.Prefix('--', leftHand) |
| .withSourceInformation(sourceInformation); |
| } |
| } |
| if (compound != null) { |
| return new js.Assignment.compound(leftHand, |
| getAsCompoundOperator(compound), visitExpression(value)) |
| .withSourceInformation(sourceInformation); |
| } |
| return new js.Assignment(leftHand, visitExpression(value)) |
| .withSourceInformation(sourceInformation); |
| } |
| |
| @override |
| js.Expression visitAssign(tree_ir.Assign node) { |
| js.Expression variable = buildVariableAccess(node.variable); |
| if (isCompoundableBuiltin(node.value)) { |
| tree_ir.ApplyBuiltinOperator rhs = node.value; |
| tree_ir.Expression left = rhs.arguments[0]; |
| tree_ir.Expression right = rhs.arguments[1]; |
| if (left is tree_ir.VariableUse && left.variable == node.variable) { |
| return makeAssignment(variable, right, compound: rhs.operator, |
| sourceInformation: node.sourceInformation); |
| } |
| } |
| return makeAssignment( |
| variable, node.value, sourceInformation: node.sourceInformation); |
| } |
| |
| @override |
| void visitContinue(tree_ir.Continue node) { |
| tree_ir.Statement next = fallthrough.target; |
| if (node.target.binding == next || |
| next is tree_ir.Continue && node.target == next.target) { |
| // Fall through to continue target or to equivalent continue. |
| fallthrough.use(); |
| } else if (node.target.binding == shortContinue.target) { |
| // The target is the immediately enclosing loop. |
| shortContinue.use(); |
| accumulator.add(new js.Continue(null)); |
| } else { |
| accumulator.add(new js.Continue(makeLabel(node.target))); |
| } |
| } |
| |
| /// True if [other] is the target of [node] or is a [Break] with the same |
| /// target. This means jumping to [other] is equivalent to executing [node]. |
| bool isEffectiveBreakTarget(tree_ir.Break node, tree_ir.Statement other) { |
| return node.target.binding.next == other || |
| other is tree_ir.Break && node.target == other.target; |
| } |
| |
| /// True if the given break is equivalent to an unlabeled continue. |
| bool isShortContinue(tree_ir.Break node) { |
| tree_ir.Statement next = node.target.binding.next; |
| return next is tree_ir.Continue && |
| next.target.binding == shortContinue.target; |
| } |
| |
| @override |
| void visitBreak(tree_ir.Break node) { |
| if (isEffectiveBreakTarget(node, fallthrough.target)) { |
| // Fall through to break target or to equivalent break. |
| fallthrough.use(); |
| } else if (isEffectiveBreakTarget(node, shortBreak.target)) { |
| // Unlabeled break to the break target or to an equivalent break. |
| shortBreak.use(); |
| accumulator.add(new js.Break(null)); |
| } else if (isShortContinue(node)) { |
| // An unlabeled continue is better than a labeled break. |
| shortContinue.use(); |
| accumulator.add(new js.Continue(null)); |
| } else { |
| accumulator.add(new js.Break(makeLabel(node.target))); |
| } |
| } |
| |
| @override |
| visitExpressionStatement(tree_ir.ExpressionStatement node) { |
| js.Expression exp = visitExpression(node.expression); |
| if (node.next is tree_ir.Unreachable && emitUnreachableAsReturn.last) { |
| // Emit as 'return exp' to assist local analysis in the VM. |
| SourceInformation sourceInformation = node.expression.sourceInformation; |
| accumulator.add( |
| new js.Return(exp).withSourceInformation(sourceInformation)); |
| return null; |
| } else { |
| accumulator.add(new js.ExpressionStatement(exp)); |
| return node.next; |
| } |
| } |
| |
| bool isNullReturn(tree_ir.Statement node) { |
| return node is tree_ir.Return && isNull(node.value); |
| } |
| |
| bool isEndOfMethod(tree_ir.Statement node) { |
| return isNullReturn(node) || |
| node is tree_ir.Break && isNullReturn(node.target.binding.next); |
| } |
| |
| @override |
| visitIf(tree_ir.If node) { |
| js.Expression condition = visitExpression(node.condition); |
| int usesBefore = fallthrough.useCount; |
| // Unless the 'else' part ends the method. make sure to terminate any |
| // uncompletable code paths in the 'then' part. |
| emitUnreachableAsReturn.add(!isEndOfMethod(node.elseStatement)); |
| js.Statement thenBody = buildBodyStatement(node.thenStatement); |
| emitUnreachableAsReturn.removeLast(); |
| bool thenHasFallthrough = (fallthrough.useCount > usesBefore); |
| if (thenHasFallthrough) { |
| js.Statement elseBody = buildBodyStatement(node.elseStatement); |
| accumulator.add(new js.If(condition, thenBody, elseBody) |
| .withSourceInformation(node.sourceInformation)); |
| return null; |
| } else { |
| // The 'then' body cannot complete normally, so emit a short 'if' |
| // and put the 'else' body after it. |
| accumulator.add(new js.If.noElse(condition, thenBody) |
| .withSourceInformation(node.sourceInformation)); |
| return node.elseStatement; |
| } |
| } |
| |
| @override |
| visitLabeledStatement(tree_ir.LabeledStatement node) { |
| fallthrough.push(node.next); |
| js.Statement body = buildBodyStatement(node.body); |
| fallthrough.pop(); |
| accumulator.add(insertLabel(node.label, body)); |
| return node.next; |
| } |
| |
| /// Creates a name for [label] if it does not already have one. |
| /// |
| /// This also marks the label as being used. |
| String makeLabel(tree_ir.Label label) { |
| return labelNames.putIfAbsent(label, () => 'L${labelNames.length}'); |
| } |
| |
| /// Wraps a node in a labeled statement unless the label is unused. |
| js.Statement insertLabel(tree_ir.Label label, js.Statement node) { |
| String name = labelNames[label]; |
| if (name == null) return node; // Label is unused. |
| return new js.LabeledStatement(name, node); |
| } |
| |
| /// Returns the current [accumulator] wrapped in a block if neccessary. |
| js.Statement _bodyAsStatement() { |
| if (accumulator.length == 0) { |
| return new js.EmptyStatement(); |
| } |
| if (accumulator.length == 1) { |
| return accumulator.single; |
| } |
| return new js.Block(accumulator); |
| } |
| |
| /// Builds a nested statement. |
| js.Statement buildBodyStatement(tree_ir.Statement statement) { |
| List<js.Statement> savedAccumulator = accumulator; |
| accumulator = <js.Statement>[]; |
| while (statement != null) { |
| statement = visitStatement(statement); |
| } |
| js.Statement result = _bodyAsStatement(); |
| accumulator = savedAccumulator; |
| return result; |
| } |
| |
| js.Block buildBodyBlock(tree_ir.Statement statement) { |
| List<js.Statement> savedAccumulator = accumulator; |
| accumulator = <js.Statement>[]; |
| while (statement != null) { |
| statement = visitStatement(statement); |
| } |
| js.Statement result = new js.Block(accumulator); |
| accumulator = savedAccumulator; |
| return result; |
| } |
| |
| js.Expression makeSequence(List<tree_ir.Expression> list) { |
| return list.map(visitExpression).reduce((x,y) => new js.Binary(',', x, y)); |
| } |
| |
| @override |
| visitFor(tree_ir.For node) { |
| js.Expression condition = visitExpression(node.condition); |
| shortBreak.push(node.next); |
| shortContinue.push(node); |
| fallthrough.push(node); |
| emitUnreachableAsReturn.add(true); |
| js.Statement body = buildBodyStatement(node.body); |
| emitUnreachableAsReturn.removeLast(); |
| fallthrough.pop(); |
| shortContinue.pop(); |
| shortBreak.pop(); |
| js.Statement loopNode; |
| if (node.updates.isEmpty) { |
| loopNode = new js.While(condition, body); |
| } else { // Compile as a for loop. |
| js.Expression init; |
| if (accumulator.isNotEmpty && |
| accumulator.last is js.ExpressionStatement) { |
| // Take the preceding expression from the accumulator and use |
| // it as the initializer expression. |
| js.ExpressionStatement initStmt = accumulator.removeLast(); |
| init = initStmt.expression; |
| } |
| js.Expression update = makeSequence(node.updates); |
| loopNode = new js.For(init, condition, update, body); |
| } |
| accumulator.add(insertLabel(node.label, loopNode)); |
| return node.next; |
| } |
| |
| @override |
| void visitWhileTrue(tree_ir.WhileTrue node) { |
| // A short break in the while will jump to the current fallthrough target. |
| shortBreak.push(fallthrough.target); |
| shortContinue.push(node); |
| fallthrough.push(node); |
| emitUnreachableAsReturn.add(true); |
| js.Statement jsBody = buildBodyStatement(node.body); |
| emitUnreachableAsReturn.removeLast(); |
| fallthrough.pop(); |
| shortContinue.pop(); |
| if (shortBreak.useCount > 0) { |
| // Short breaks use the current fallthrough target. |
| fallthrough.use(); |
| } |
| shortBreak.pop(); |
| accumulator.add( |
| insertLabel(node.label, new js.For(null, null, null, jsBody))); |
| } |
| |
| bool isNull(tree_ir.Expression node) { |
| return node is tree_ir.Constant && node.value.isNull; |
| } |
| |
| @override |
| void visitReturn(tree_ir.Return node) { |
| if (isNull(node.value) && fallthrough.target == null) { |
| // Do nothing. Implicitly return JS undefined by falling over the end. |
| registry.registerCompileTimeConstant(new NullConstantValue()); |
| fallthrough.use(); |
| } else { |
| accumulator.add(new js.Return(visitExpression(node.value)) |
| .withSourceInformation(node.sourceInformation)); |
| } |
| } |
| |
| @override |
| void visitThrow(tree_ir.Throw node) { |
| accumulator.add(new js.Throw(visitExpression(node.value))); |
| } |
| |
| @override |
| void visitUnreachable(tree_ir.Unreachable node) { |
| if (emitUnreachableAsReturn.last) { |
| // Emit a return to assist local analysis in the VM. |
| accumulator.add(new js.Return()); |
| } |
| } |
| |
| @override |
| void visitTry(tree_ir.Try node) { |
| js.Block tryBlock = buildBodyBlock(node.tryBody); |
| tree_ir.Variable exceptionVariable = node.catchParameters.first; |
| js.VariableDeclaration exceptionParameter = |
| new js.VariableDeclaration(getVariableName(exceptionVariable)); |
| js.Block catchBlock = buildBodyBlock(node.catchBody); |
| js.Catch catchPart = new js.Catch(exceptionParameter, catchBlock); |
| accumulator.add(new js.Try(tryBlock, catchPart, null)); |
| } |
| |
| @override |
| js.Expression visitCreateBox(tree_ir.CreateBox node) { |
| return new js.ObjectInitializer(const <js.Property>[]); |
| } |
| |
| @override |
| js.Expression visitCreateInstance(tree_ir.CreateInstance node) { |
| ClassElement classElement = node.classElement; |
| // TODO(asgerf): To allow inlining of InvokeConstructor, CreateInstance must |
| // carry a DartType so we can register the instantiated type |
| // with its type arguments. Otherwise dataflow analysis is |
| // needed to reconstruct the instantiated type. |
| registry.registerInstantiation(classElement.rawType); |
| if (classElement is ClosureClassElement) { |
| registry.registerInstantiatedClosure(classElement.methodElement); |
| } |
| js.Expression instance = new js.New( |
| glue.constructorAccess(classElement), |
| visitExpressionList(node.arguments)) |
| .withSourceInformation(node.sourceInformation); |
| |
| tree_ir.Expression typeInformation = node.typeInformation; |
| if (typeInformation != null) { |
| FunctionElement helper = glue.getAddRuntimeTypeInformation(); |
| js.Expression typeArguments = visitExpression(typeInformation); |
| return buildStaticHelperInvocation(helper, |
| <js.Expression>[instance, typeArguments], |
| sourceInformation: node.sourceInformation); |
| } else { |
| return instance; |
| } |
| } |
| |
| @override |
| js.Expression visitCreateInvocationMirror( |
| tree_ir.CreateInvocationMirror node) { |
| js.Expression name = js.string(node.selector.name); |
| js.Expression internalName = |
| js.quoteName(glue.invocationName(node.selector)); |
| js.Expression kind = js.number(node.selector.invocationMirrorKind); |
| js.Expression arguments = new js.ArrayInitializer( |
| visitExpressionList(node.arguments)); |
| js.Expression argumentNames = new js.ArrayInitializer( |
| node.selector.namedArguments.map(js.string).toList(growable: false)); |
| return buildStaticHelperInvocation(glue.createInvocationMirrorMethod, |
| <js.Expression>[name, internalName, kind, arguments, argumentNames]); |
| } |
| |
| @override |
| js.Expression visitInterceptor(tree_ir.Interceptor node) { |
| registry.registerUseInterceptor(); |
| // Default to all intercepted classes if they have not been computed. |
| // This is to ensure we can run codegen without prior optimization passes. |
| Set<ClassElement> interceptedClasses = node.interceptedClasses.isEmpty |
| ? glue.interceptedClasses |
| : node.interceptedClasses; |
| registry.registerSpecializedGetInterceptor(interceptedClasses); |
| js.Name helperName = glue.getInterceptorName(interceptedClasses); |
| js.Expression globalHolder = glue.getInterceptorLibrary(); |
| return js.js('#.#(#)', |
| [globalHolder, helperName, visitExpression(node.input)]) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitGetField(tree_ir.GetField node) { |
| registry.registerStaticUse(new StaticUse.fieldGet(node.field)); |
| return new js.PropertyAccess( |
| visitExpression(node.object), |
| glue.instanceFieldPropertyName(node.field)) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitSetField(tree_ir.SetField node) { |
| registry.registerStaticUse(new StaticUse.fieldSet(node.field)); |
| js.PropertyAccess field = |
| new js.PropertyAccess( |
| visitExpression(node.object), |
| glue.instanceFieldPropertyName(node.field)); |
| return makeAssignment(field, node.value, compound: node.compound, |
| sourceInformation: node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitGetStatic(tree_ir.GetStatic node) { |
| assert(node.element is FieldElement || node.element is FunctionElement); |
| if (node.element is FunctionElement) { |
| // Tear off a method. |
| registry.registerStaticUse( |
| new StaticUse.staticTearOff(node.element.declaration)); |
| return glue.isolateStaticClosureAccess(node.element) |
| .withSourceInformation(node.sourceInformation); |
| } |
| if (node.useLazyGetter) { |
| // Read a lazily initialized field. |
| registry.registerStaticUse( |
| new StaticUse.staticInit(node.element.declaration)); |
| js.Expression getter = glue.isolateLazyInitializerAccess(node.element); |
| return new js.Call(getter, <js.Expression>[], |
| sourceInformation: node.sourceInformation); |
| } |
| // Read an eagerly initialized field. |
| registry.registerStaticUse( |
| new StaticUse.staticGet(node.element.declaration)); |
| return glue.staticFieldAccess(node.element) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitSetStatic(tree_ir.SetStatic node) { |
| assert(node.element is FieldElement); |
| registry.registerStaticUse( |
| new StaticUse.staticSet(node.element.declaration)); |
| js.Expression field = glue.staticFieldAccess(node.element); |
| return makeAssignment(field, node.value, compound: node.compound, |
| sourceInformation: node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitGetLength(tree_ir.GetLength node) { |
| return new js.PropertyAccess.field(visitExpression(node.object), 'length'); |
| } |
| |
| @override |
| js.Expression visitGetIndex(tree_ir.GetIndex node) { |
| return new js.PropertyAccess( |
| visitExpression(node.object), |
| visitExpression(node.index)); |
| } |
| |
| @override |
| js.Expression visitSetIndex(tree_ir.SetIndex node) { |
| js.Expression index = new js.PropertyAccess( |
| visitExpression(node.object), visitExpression(node.index)); |
| return makeAssignment(index, node.value, compound: node.compound); |
| } |
| |
| js.Expression buildStaticHelperInvocation( |
| FunctionElement helper, |
| List<js.Expression> arguments, |
| {SourceInformation sourceInformation}) { |
| registry.registerStaticUse(new StaticUse.staticInvoke( |
| helper, new CallStructure.unnamed(arguments.length))); |
| return buildStaticInvoke( |
| helper, arguments, sourceInformation: sourceInformation); |
| } |
| |
| @override |
| js.Expression visitReifyRuntimeType(tree_ir.ReifyRuntimeType node) { |
| js.Expression typeToString = buildStaticHelperInvocation( |
| glue.getRuntimeTypeToString(), |
| [visitExpression(node.value)], |
| sourceInformation: node.sourceInformation); |
| return buildStaticHelperInvocation( |
| glue.getCreateRuntimeType(), |
| [typeToString], |
| sourceInformation: node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitReadTypeVariable(tree_ir.ReadTypeVariable node) { |
| ClassElement context = node.variable.element.enclosingClass; |
| js.Expression index = js.number(glue.getTypeVariableIndex(node.variable)); |
| if (glue.needsSubstitutionForTypeVariableAccess(context)) { |
| js.Expression typeName = glue.getRuntimeTypeName(context); |
| return buildStaticHelperInvocation( |
| glue.getRuntimeTypeArgument(), |
| [visitExpression(node.target), typeName, index], |
| sourceInformation: node.sourceInformation); |
| } else { |
| return buildStaticHelperInvocation( |
| glue.getTypeArgumentByIndex(), |
| [visitExpression(node.target), index], |
| sourceInformation: node.sourceInformation); |
| } |
| } |
| |
| @override |
| js.Expression visitTypeExpression(tree_ir.TypeExpression node) { |
| List<js.Expression> arguments = visitExpressionList(node.arguments); |
| switch (node.kind) { |
| case tree_ir.TypeExpressionKind.COMPLETE: |
| return glue.generateTypeRepresentation( |
| node.dartType, arguments, registry); |
| case tree_ir.TypeExpressionKind.INSTANCE: |
| // We expect only flat types for the INSTANCE representation. |
| assert(node.dartType == |
| (node.dartType.element as ClassElement).thisType); |
| registry.registerInstantiatedClass(glue.listClass); |
| return new js.ArrayInitializer(arguments); |
| } |
| } |
| |
| js.Node handleForeignCode(tree_ir.ForeignCode node) { |
| if (node.dependency != null) { |
| // Dependency is only used if [node] calls a Dart function. Currently only |
| // through foreign function `RAW_DART_FUNCTION_REF`. |
| registry.registerStaticUse( |
| new StaticUse.staticInvoke( |
| node.dependency, |
| new CallStructure.unnamed(node.arguments.length))); |
| } |
| // TODO(sra,johnniwinther): Should this be in CodegenRegistry? |
| glue.registerNativeBehavior(node.nativeBehavior, node); |
| return node.codeTemplate.instantiate(visitExpressionList(node.arguments)) |
| .withSourceInformation(node.sourceInformation); |
| } |
| |
| @override |
| js.Expression visitForeignExpression(tree_ir.ForeignExpression node) { |
| return handleForeignCode(node); |
| } |
| |
| @override |
| void visitForeignStatement(tree_ir.ForeignStatement node) { |
| accumulator.add(handleForeignCode(node)); |
| } |
| |
| @override |
| visitYield(tree_ir.Yield node) { |
| js.Expression value = visitExpression(node.input); |
| accumulator.add(new js.DartYield(value, node.hasStar)); |
| return node.next; |
| } |
| |
| @override |
| visitReceiverCheck(tree_ir.ReceiverCheck node) { |
| js.Expression value = visitExpression(node.value); |
| // TODO(sra): Try to use the selector even when [useSelector] is false. The |
| // reason we use 'toString' is that it is always defined so avoids a slow |
| // lookup (in V8) of an absent property. We could use the property for the |
| // selector if we knew it was present. The property is present if the |
| // associated method was not inlined away, or if there is a noSuchMethod |
| // hook for that selector. We don't know these things here, but the decision |
| // could be deferred by creating a deferred property that was resolved after |
| // codegen. |
| js.Expression access = node.useSelector |
| ? js.js('#.#', [value, glue.invocationName(node.selector)]) |
| : js.js('#.toString', [value]); |
| if (node.useInvoke) { |
| access = new js.Call(access, []); |
| } |
| if (node.condition != null) { |
| js.Expression condition = visitExpression(node.condition); |
| js.Statement body = isNullReturn(node.next) |
| ? new js.ExpressionStatement(access) |
| : new js.Return(access); |
| accumulator.add(new js.If.noElse(condition, body)); |
| } else { |
| accumulator.add(new js.ExpressionStatement(access)); |
| } |
| return node.next; |
| } |
| |
| @override |
| js.Expression visitApplyBuiltinOperator(tree_ir.ApplyBuiltinOperator node) { |
| List<js.Expression> args = visitExpressionList(node.arguments); |
| |
| js.Expression createExpression() { |
| switch (node.operator) { |
| case BuiltinOperator.NumAdd: |
| return new js.Binary('+', args[0], args[1]); |
| case BuiltinOperator.NumSubtract: |
| return new js.Binary('-', args[0], args[1]); |
| case BuiltinOperator.NumMultiply: |
| return new js.Binary('*', args[0], args[1]); |
| case BuiltinOperator.NumDivide: |
| return new js.Binary('/', args[0], args[1]); |
| case BuiltinOperator.NumRemainder: |
| return new js.Binary('%', args[0], args[1]); |
| case BuiltinOperator.NumTruncatingDivideToSigned32: |
| return js.js('(# / #) | 0', args); |
| case BuiltinOperator.NumAnd: |
| return normalizeBitOp(js.js('# & #', args), node); |
| case BuiltinOperator.NumOr: |
| return normalizeBitOp(js.js('# | #', args), node); |
| case BuiltinOperator.NumXor: |
| return normalizeBitOp(js.js('# ^ #', args), node); |
| case BuiltinOperator.NumLt: |
| return new js.Binary('<', args[0], args[1]); |
| case BuiltinOperator.NumLe: |
| return new js.Binary('<=', args[0], args[1]); |
| case BuiltinOperator.NumGt: |
| return new js.Binary('>', args[0], args[1]); |
| case BuiltinOperator.NumGe: |
| return new js.Binary('>=', args[0], args[1]); |
| case BuiltinOperator.NumShl: |
| return normalizeBitOp(js.js('# << #', args), node); |
| case BuiltinOperator.NumShr: |
| // No normalization required since output is always uint32. |
| return js.js('# >>> #', args); |
| case BuiltinOperator.NumBitNot: |
| return js.js('(~#) >>> 0', args); |
| case BuiltinOperator.NumNegate: |
| return js.js('-#', args); |
| case BuiltinOperator.StringConcatenate: |
| if (args.isEmpty) return js.string(''); |
| return args.reduce((e1,e2) => new js.Binary('+', e1, e2)); |
| case BuiltinOperator.CharCodeAt: |
| return js.js('#.charCodeAt(#)', args); |
| case BuiltinOperator.Identical: |
| registry.registerStaticUse(new StaticUse.staticInvoke( |
| glue.identicalFunction, new CallStructure.unnamed(args.length))); |
| return buildStaticHelperInvocation(glue.identicalFunction, args); |
| case BuiltinOperator.StrictEq: |
| return new js.Binary('===', args[0], args[1]); |
| case BuiltinOperator.StrictNeq: |
| return new js.Binary('!==', args[0], args[1]); |
| case BuiltinOperator.LooseEq: |
| return new js.Binary('==', args[0], args[1]); |
| case BuiltinOperator.LooseNeq: |
| return new js.Binary('!=', args[0], args[1]); |
| case BuiltinOperator.IsFalsy: |
| return new js.Prefix('!', args[0]); |
| case BuiltinOperator.IsNumber: |
| return js.js('typeof # === "number"', args); |
| case BuiltinOperator.IsNotNumber: |
| return js.js('typeof # !== "number"', args); |
| case BuiltinOperator.IsFloor: |
| return js.js('Math.floor(#) === #', args); |
| case BuiltinOperator.IsInteger: |
| return js.js('typeof # === "number" && Math.floor(#) === #', args); |
| case BuiltinOperator.IsNotInteger: |
| return js.js('typeof # !== "number" || Math.floor(#) !== #', args); |
| case BuiltinOperator.IsUnsigned32BitInteger: |
| return js.js('# >>> 0 === #', args); |
| case BuiltinOperator.IsNotUnsigned32BitInteger: |
| return js.js('# >>> 0 !== #', args); |
| case BuiltinOperator.IsFixedLengthJSArray: |
| // TODO(sra): Remove boolify (i.e. !!). |
| return js.js(r'!!#.fixed$length', args); |
| case BuiltinOperator.IsExtendableJSArray: |
| return js.js(r'!#.fixed$length', args); |
| case BuiltinOperator.IsModifiableJSArray: |
| return js.js(r'!#.immutable$list', args); |
| case BuiltinOperator.IsUnmodifiableJSArray: |
| // TODO(sra): Remove boolify (i.e. !!). |
| return js.js(r'!!#.immutable$list', args); |
| } |
| } |
| |
| return createExpression().withSourceInformation(node.sourceInformation); |
| } |
| |
| /// Add a uint32 normalization `op >>> 0` to [op] if it is not in 31-bit |
| /// range. |
| js.Expression normalizeBitOp(js.Expression op, |
| tree_ir.ApplyBuiltinOperator node) { |
| const MAX_UINT31 = 0x7fffffff; |
| const MAX_UINT32 = 0xffffffff; |
| |
| int constantValue(tree_ir.Expression e) { |
| if (e is tree_ir.Constant) { |
| ConstantValue value = e.value; |
| if (!value.isInt) return null; |
| IntConstantValue intConstant = value; |
| if (intConstant.primitiveValue < 0) return null; |
| if (intConstant.primitiveValue > MAX_UINT32) return null; |
| return intConstant.primitiveValue; |
| } |
| return null; |
| } |
| |
| /// Returns a value of the form 0b0001xxxx to represent the highest bit set |
| /// in the result. This represents the range [0, 0b00011111], up to 32 |
| /// bits. `null` represents a result possibly outside the uint32 range. |
| int maxBitOf(tree_ir.Expression e) { |
| if (e is tree_ir.Constant) { |
| return constantValue(e); |
| } |
| if (e is tree_ir.ApplyBuiltinOperator) { |
| if (e.operator == BuiltinOperator.NumAnd) { |
| int left = maxBitOf(e.arguments[0]); |
| int right = maxBitOf(e.arguments[1]); |
| if (left == null && right == null) return MAX_UINT32; |
| if (left == null) return right; |
| if (right == null) return left; |
| return (left < right) ? left : right; |
| } |
| if (e.operator == BuiltinOperator.NumOr || |
| e.operator == BuiltinOperator.NumXor) { |
| int left = maxBitOf(e.arguments[0]); |
| int right = maxBitOf(e.arguments[1]); |
| if (left == null || right == null) return MAX_UINT32; |
| return left | right; |
| } |
| if (e.operator == BuiltinOperator.NumShr) { |
| int right = constantValue(e.arguments[1]); |
| // NumShr is JavaScript '>>>' so always generates a uint32 result. |
| if (right == null || right <= 0 || right > 31) return MAX_UINT32; |
| int left = maxBitOf(e.arguments[0]); |
| if (left == null) return MAX_UINT32; |
| return left >> right; |
| } |
| if (e.operator == BuiltinOperator.NumShl) { |
| int right = constantValue(e.arguments[1]); |
| if (right == null || right <= 0 || right > 31) return MAX_UINT32; |
| int left = maxBitOf(e.arguments[0]); |
| if (left == null) return MAX_UINT32; |
| if (left.bitLength + right > 31) return MAX_UINT32; |
| return left << right; |
| } |
| } |
| return null; |
| } |
| |
| int maxBit = maxBitOf(node); |
| if (maxBit != null && maxBit <= MAX_UINT31) return op; |
| return js.js('# >>> 0', [op]); |
| } |
| |
| @override |
| js.Expression visitApplyBuiltinMethod(tree_ir.ApplyBuiltinMethod node) { |
| js.Expression receiver = visitExpression(node.receiver); |
| List<js.Expression> args = visitExpressionList(node.arguments); |
| switch (node.method) { |
| case BuiltinMethod.Push: |
| return js.js('#.push(#)', [receiver, args]); |
| |
| case BuiltinMethod.Pop: |
| return js.js('#.pop()', [receiver]); |
| |
| case BuiltinMethod.SetLength: |
| return js.js('#.length = #', [receiver, args[0]]); |
| } |
| } |
| |
| @override |
| js.Expression visitAwait(tree_ir.Await node) { |
| return new js.Await(visitExpression(node.input)); |
| } |
| |
| /// Ensures that parameter defaults will be emitted. |
| /// |
| /// Ideally, this should be done when generating the relevant stub methods, |
| /// since those are the ones that actually reference the constants, but those |
| /// are created by the emitter when it is too late to register new constants. |
| /// |
| /// For non-static methods, we have no way of knowing if the defaults are |
| /// actually used, so we conservatively register them all. |
| void registerDefaultParameterValues(ExecutableElement element) { |
| if (element is! FunctionElement) return; |
| FunctionElement function = element; |
| if (function.isStatic) return; // Defaults are inlined at call sites. |
| function.functionSignature.forEachOptionalParameter((param) { |
| ConstantValue constant = glue.getDefaultParameterValue(param); |
| registry.registerCompileTimeConstant(constant); |
| }); |
| } |
| } |