| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 library code_generator; | |
| 6 | |
| 7 import '../../closure.dart' show ClosureClassElement; | |
| 8 import '../../common/codegen.dart' show CodegenRegistry; | |
| 9 import '../../constants/values.dart'; | |
| 10 import '../../dart_types.dart'; | |
| 11 import '../../elements/elements.dart'; | |
| 12 import '../../io/source_information.dart' show SourceInformation; | |
| 13 import '../../js/js.dart' as js; | |
| 14 import '../../tree_ir/tree_ir_nodes.dart' as tree_ir; | |
| 15 import '../../tree_ir/tree_ir_nodes.dart' | |
| 16 show BuiltinMethod, BuiltinOperator, isCompoundableOperator; | |
| 17 import '../../types/types.dart' show TypeMask; | |
| 18 import '../../universe/call_structure.dart' show CallStructure; | |
| 19 import '../../universe/selector.dart' show Selector; | |
| 20 import '../../universe/use.dart' show DynamicUse, StaticUse, TypeUse; | |
| 21 import '../../util/maplet.dart'; | |
| 22 import 'glue.dart'; | |
| 23 | |
| 24 class CodegenBailout { | |
| 25 final tree_ir.Node node; | |
| 26 final String reason; | |
| 27 CodegenBailout(this.node, this.reason); | |
| 28 String get message { | |
| 29 return 'bailout${node != null ? " on $node" : ""}: $reason'; | |
| 30 } | |
| 31 } | |
| 32 | |
| 33 class CodeGenerator extends tree_ir.StatementVisitor | |
| 34 with tree_ir.ExpressionVisitor<js.Expression> { | |
| 35 final CodegenRegistry registry; | |
| 36 | |
| 37 final Glue glue; | |
| 38 | |
| 39 ExecutableElement currentFunction; | |
| 40 | |
| 41 /// Maps variables to their name. | |
| 42 Map<tree_ir.Variable, String> variableNames = <tree_ir.Variable, String>{}; | |
| 43 | |
| 44 /// Maps local constants to their name. | |
| 45 Maplet<VariableElement, String> constantNames = | |
| 46 new Maplet<VariableElement, String>(); | |
| 47 | |
| 48 /// Variable names that have already been used. Used to avoid name clashes. | |
| 49 Set<String> usedVariableNames = new Set<String>(); | |
| 50 | |
| 51 final tree_ir.FallthroughStack fallthrough = new tree_ir.FallthroughStack(); | |
| 52 | |
| 53 /// Stacks whose top element is the current target of an unlabeled break | |
| 54 /// or continue. For continues, this is the loop node itself. | |
| 55 final tree_ir.FallthroughStack shortBreak = new tree_ir.FallthroughStack(); | |
| 56 final tree_ir.FallthroughStack shortContinue = new tree_ir.FallthroughStack(); | |
| 57 | |
| 58 /// When the top element is true, [Unreachable] statements will be emitted | |
| 59 /// as [Return]s, otherwise they are emitted as empty because they are | |
| 60 /// followed by the end of the method. | |
| 61 /// | |
| 62 /// Note on why the [fallthrough] stack should not be used for this: | |
| 63 /// Ordinary statements may choose whether to use the [fallthrough] target, | |
| 64 /// and the choice to do so may disable an optimization in [visitIf]. | |
| 65 /// But omitting an unreachable 'return' should have lower priority than | |
| 66 /// the optimizations in [visitIf], so [visitIf] will instead tell the | |
| 67 /// [Unreachable] statements whether they may use fallthrough or not. | |
| 68 List<bool> emitUnreachableAsReturn = <bool>[false]; | |
| 69 | |
| 70 final Map<tree_ir.Label, String> labelNames = <tree_ir.Label, String>{}; | |
| 71 | |
| 72 List<js.Statement> accumulator = new List<js.Statement>(); | |
| 73 | |
| 74 CodeGenerator(this.glue, this.registry); | |
| 75 | |
| 76 /// Generates JavaScript code for the body of [function]. | |
| 77 js.Fun buildFunction(tree_ir.FunctionDefinition function) { | |
| 78 registerDefaultParameterValues(function.element); | |
| 79 currentFunction = function.element; | |
| 80 tree_ir.Statement statement = function.body; | |
| 81 while (statement != null) { | |
| 82 statement = visitStatement(statement); | |
| 83 } | |
| 84 | |
| 85 List<js.Parameter> parameters = new List<js.Parameter>(); | |
| 86 Set<tree_ir.Variable> parameterSet = new Set<tree_ir.Variable>(); | |
| 87 Set<String> declaredVariables = new Set<String>(); | |
| 88 | |
| 89 for (tree_ir.Variable parameter in function.parameters) { | |
| 90 String name = getVariableName(parameter); | |
| 91 parameters.add(new js.Parameter(name)); | |
| 92 parameterSet.add(parameter); | |
| 93 declaredVariables.add(name); | |
| 94 } | |
| 95 | |
| 96 List<js.VariableInitialization> jsVariables = <js.VariableInitialization>[]; | |
| 97 | |
| 98 // Declare variables with an initializer. Pull statements into the | |
| 99 // initializer until we find a statement that cannot be pulled in. | |
| 100 int accumulatorIndex = 0; | |
| 101 while (accumulatorIndex < accumulator.length) { | |
| 102 js.Node node = accumulator[accumulatorIndex]; | |
| 103 | |
| 104 // Check that node is an assignment to a local variable. | |
| 105 if (node is! js.ExpressionStatement) break; | |
| 106 js.ExpressionStatement stmt = node; | |
| 107 if (stmt.expression is! js.Assignment) break; | |
| 108 js.Assignment assign = stmt.expression; | |
| 109 if (assign.leftHandSide is! js.VariableUse) break; | |
| 110 if (assign.op != null) break; // Compound assignment. | |
| 111 js.VariableUse use = assign.leftHandSide; | |
| 112 | |
| 113 // Do not touch non-local variables. | |
| 114 if (!usedVariableNames.contains(use.name)) break; | |
| 115 | |
| 116 // We cannot declare a variable more than once. | |
| 117 if (!declaredVariables.add(use.name)) break; | |
| 118 | |
| 119 js.VariableInitialization jsVariable = new js.VariableInitialization( | |
| 120 new js.VariableDeclaration(use.name), assign.value); | |
| 121 jsVariables.add(jsVariable); | |
| 122 | |
| 123 ++accumulatorIndex; | |
| 124 } | |
| 125 | |
| 126 // If the last statement is a for loop with an initializer expression, try | |
| 127 // to pull that expression into an initializer as well. | |
| 128 pullFromForLoop: if (accumulatorIndex < accumulator.length && | |
| 129 accumulator[accumulatorIndex] is js.For) { | |
| 130 js.For forLoop = accumulator[accumulatorIndex]; | |
| 131 if (forLoop.init is! js.Assignment) break pullFromForLoop; | |
| 132 js.Assignment assign = forLoop.init; | |
| 133 if (assign.leftHandSide is! js.VariableUse) break pullFromForLoop; | |
| 134 if (assign.op != null) break pullFromForLoop; // Compound assignment. | |
| 135 js.VariableUse use = assign.leftHandSide; | |
| 136 | |
| 137 // Do not touch non-local variables. | |
| 138 if (!usedVariableNames.contains(use.name)) break pullFromForLoop; | |
| 139 | |
| 140 // We cannot declare a variable more than once. | |
| 141 if (!declaredVariables.add(use.name)) break pullFromForLoop; | |
| 142 | |
| 143 js.VariableInitialization jsVariable = new js.VariableInitialization( | |
| 144 new js.VariableDeclaration(use.name), assign.value); | |
| 145 jsVariables.add(jsVariable); | |
| 146 | |
| 147 // Remove the initializer from the for loop. | |
| 148 accumulator[accumulatorIndex] = | |
| 149 new js.For(null, forLoop.condition, forLoop.update, forLoop.body); | |
| 150 } | |
| 151 | |
| 152 // Discard the statements that were pulled in the initializer. | |
| 153 if (accumulatorIndex > 0) { | |
| 154 accumulator = accumulator.sublist(accumulatorIndex); | |
| 155 } | |
| 156 | |
| 157 // Declare remaining variables. | |
| 158 for (tree_ir.Variable variable in variableNames.keys) { | |
| 159 String name = getVariableName(variable); | |
| 160 if (declaredVariables.contains(name)) continue; | |
| 161 js.VariableInitialization jsVariable = | |
| 162 new js.VariableInitialization(new js.VariableDeclaration(name), null); | |
| 163 jsVariables.add(jsVariable); | |
| 164 } | |
| 165 | |
| 166 if (jsVariables.length > 0) { | |
| 167 // Would be nice to avoid inserting at the beginning of list. | |
| 168 accumulator.insert( | |
| 169 0, | |
| 170 new js.ExpressionStatement(new js.VariableDeclarationList(jsVariables) | |
| 171 .withSourceInformation(function.sourceInformation))); | |
| 172 } | |
| 173 return new js.Fun(parameters, new js.Block(accumulator)); | |
| 174 } | |
| 175 | |
| 176 @override | |
| 177 js.Expression visitExpression(tree_ir.Expression node) { | |
| 178 js.Expression result = node.accept(this); | |
| 179 if (result == null) { | |
| 180 glue.reportInternalError('$node did not produce code.'); | |
| 181 } | |
| 182 return result; | |
| 183 } | |
| 184 | |
| 185 /// Generates a name for the given variable. First trying with the name of | |
| 186 /// the [Variable.element] if it is non-null. | |
| 187 String getVariableName(tree_ir.Variable variable) { | |
| 188 // Functions are not nested in the JS backend. | |
| 189 assert(variable.host == currentFunction); | |
| 190 | |
| 191 // Get the name if we already have one. | |
| 192 String name = variableNames[variable]; | |
| 193 if (name != null) { | |
| 194 return name; | |
| 195 } | |
| 196 | |
| 197 // Synthesize a variable name that isn't used elsewhere. | |
| 198 String prefix = variable.element == null ? 'v' : variable.element.name; | |
| 199 int counter = 0; | |
| 200 name = glue.safeVariableName( | |
| 201 variable.element == null ? '$prefix$counter' : variable.element.name); | |
| 202 while (!usedVariableNames.add(name)) { | |
| 203 ++counter; | |
| 204 name = '$prefix$counter'; | |
| 205 } | |
| 206 variableNames[variable] = name; | |
| 207 | |
| 208 return name; | |
| 209 } | |
| 210 | |
| 211 List<js.Expression> visitExpressionList( | |
| 212 List<tree_ir.Expression> expressions) { | |
| 213 List<js.Expression> result = new List<js.Expression>(expressions.length); | |
| 214 for (int i = 0; i < expressions.length; ++i) { | |
| 215 result[i] = visitExpression(expressions[i]); | |
| 216 } | |
| 217 return result; | |
| 218 } | |
| 219 | |
| 220 giveup(tree_ir.Node node, | |
| 221 [String reason = 'unimplemented in CodeGenerator']) { | |
| 222 throw new CodegenBailout(node, reason); | |
| 223 } | |
| 224 | |
| 225 @override | |
| 226 js.Expression visitConditional(tree_ir.Conditional node) { | |
| 227 return new js.Conditional( | |
| 228 visitExpression(node.condition), | |
| 229 visitExpression(node.thenExpression), | |
| 230 visitExpression(node.elseExpression)); | |
| 231 } | |
| 232 | |
| 233 js.Expression buildConstant(ConstantValue constant, | |
| 234 {SourceInformation sourceInformation}) { | |
| 235 registry.registerCompileTimeConstant(constant); | |
| 236 return glue | |
| 237 .constantReference(constant) | |
| 238 .withSourceInformation(sourceInformation); | |
| 239 } | |
| 240 | |
| 241 @override | |
| 242 js.Expression visitConstant(tree_ir.Constant node) { | |
| 243 return buildConstant(node.value, sourceInformation: node.sourceInformation); | |
| 244 } | |
| 245 | |
| 246 js.Expression buildStaticInvoke(Element target, List<js.Expression> arguments, | |
| 247 {SourceInformation sourceInformation}) { | |
| 248 if (target.isConstructor) { | |
| 249 // TODO(johnniwinther): Avoid dependency on [isGenerativeConstructor] by | |
| 250 // using backend-specific [StatisUse] classes. | |
| 251 registry.registerStaticUse(new StaticUse.constructorInvoke( | |
| 252 target.declaration, new CallStructure.unnamed(arguments.length))); | |
| 253 } else { | |
| 254 registry.registerStaticUse(new StaticUse.staticInvoke( | |
| 255 target.declaration, new CallStructure.unnamed(arguments.length))); | |
| 256 } | |
| 257 js.Expression elementAccess = glue.staticFunctionAccess(target); | |
| 258 return new js.Call(elementAccess, arguments, | |
| 259 sourceInformation: sourceInformation); | |
| 260 } | |
| 261 | |
| 262 @override | |
| 263 js.Expression visitInvokeConstructor(tree_ir.InvokeConstructor node) { | |
| 264 if (node.constant != null) return giveup(node); | |
| 265 | |
| 266 registry.registerInstantiation(node.type); | |
| 267 FunctionElement target = node.target; | |
| 268 List<js.Expression> arguments = visitExpressionList(node.arguments); | |
| 269 return buildStaticInvoke(target, arguments, | |
| 270 sourceInformation: node.sourceInformation); | |
| 271 } | |
| 272 | |
| 273 void registerMethodInvoke(Selector selector, TypeMask receiverType) { | |
| 274 registry.registerDynamicUse(new DynamicUse(selector, receiverType)); | |
| 275 if (!selector.isGetter && !selector.isSetter) { | |
| 276 // TODO(sigurdm): We should find a better place to register the call. | |
| 277 Selector call = new Selector.callClosureFrom(selector); | |
| 278 registry.registerDynamicUse(new DynamicUse(call, null)); | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 @override | |
| 283 js.Expression visitInvokeMethod(tree_ir.InvokeMethod node) { | |
| 284 TypeMask mask = glue.extendMaskIfReachesAll(node.selector, node.mask); | |
| 285 registerMethodInvoke(node.selector, mask); | |
| 286 return js | |
| 287 .propertyCall( | |
| 288 visitExpression(node.receiver), | |
| 289 glue.invocationName(node.selector), | |
| 290 visitExpressionList(node.arguments)) | |
| 291 .withSourceInformation(node.sourceInformation); | |
| 292 } | |
| 293 | |
| 294 @override | |
| 295 js.Expression visitInvokeStatic(tree_ir.InvokeStatic node) { | |
| 296 FunctionElement target = node.target; | |
| 297 List<js.Expression> arguments = visitExpressionList(node.arguments); | |
| 298 return buildStaticInvoke(target, arguments, | |
| 299 sourceInformation: node.sourceInformation); | |
| 300 } | |
| 301 | |
| 302 @override | |
| 303 js.Expression visitInvokeMethodDirectly(tree_ir.InvokeMethodDirectly node) { | |
| 304 if (node.isTearOff) { | |
| 305 // If this is a tear-off, register the fact that a tear-off closure | |
| 306 // will be created, and that this tear-off must bypass ordinary | |
| 307 // dispatch to ensure the super method is invoked. | |
| 308 registry.registerStaticUse(new StaticUse.staticInvoke( | |
| 309 glue.closureFromTearOff, | |
| 310 new CallStructure.unnamed( | |
| 311 glue.closureFromTearOff.parameters.length))); | |
| 312 registry.registerStaticUse(new StaticUse.superTearOff(node.target)); | |
| 313 } | |
| 314 if (node.target is ConstructorBodyElement) { | |
| 315 registry.registerStaticUse(new StaticUse.constructorBodyInvoke( | |
| 316 node.target.declaration, | |
| 317 new CallStructure.unnamed(node.arguments.length))); | |
| 318 // A constructor body cannot be overriden or intercepted, so we can | |
| 319 // use the short form for this invocation. | |
| 320 return js.js('#.#(#)', [ | |
| 321 visitExpression(node.receiver), | |
| 322 glue.instanceMethodName(node.target), | |
| 323 visitExpressionList(node.arguments) | |
| 324 ]).withSourceInformation(node.sourceInformation); | |
| 325 } | |
| 326 registry.registerStaticUse(new StaticUse.superInvoke( | |
| 327 node.target.declaration, | |
| 328 new CallStructure.unnamed(node.arguments.length))); | |
| 329 return js.js('#.#.call(#, #)', [ | |
| 330 glue.prototypeAccess(node.target.enclosingClass), | |
| 331 glue.invocationName(node.selector), | |
| 332 visitExpression(node.receiver), | |
| 333 visitExpressionList(node.arguments) | |
| 334 ]).withSourceInformation(node.sourceInformation); | |
| 335 } | |
| 336 | |
| 337 @override | |
| 338 js.Expression visitOneShotInterceptor(tree_ir.OneShotInterceptor node) { | |
| 339 registerMethodInvoke(node.selector, node.mask); | |
| 340 registry.registerUseInterceptor(); | |
| 341 return js.js('#.#(#)', [ | |
| 342 glue.getInterceptorLibrary(), | |
| 343 glue.registerOneShotInterceptor(node.selector), | |
| 344 visitExpressionList(node.arguments) | |
| 345 ]).withSourceInformation(node.sourceInformation); | |
| 346 } | |
| 347 | |
| 348 @override | |
| 349 js.Expression visitLiteralList(tree_ir.LiteralList node) { | |
| 350 registry.registerInstantiatedClass(glue.listClass); | |
| 351 List<js.Expression> entries = visitExpressionList(node.values); | |
| 352 return new js.ArrayInitializer(entries); | |
| 353 } | |
| 354 | |
| 355 @override | |
| 356 js.Expression visitLogicalOperator(tree_ir.LogicalOperator node) { | |
| 357 return new js.Binary( | |
| 358 node.operator, visitExpression(node.left), visitExpression(node.right)); | |
| 359 } | |
| 360 | |
| 361 @override | |
| 362 js.Expression visitNot(tree_ir.Not node) { | |
| 363 return new js.Prefix("!", visitExpression(node.operand)); | |
| 364 } | |
| 365 | |
| 366 @override | |
| 367 js.Expression visitThis(tree_ir.This node) { | |
| 368 return new js.This(); | |
| 369 } | |
| 370 | |
| 371 /// Ensure that 'instanceof' checks may be performed against [class_]. | |
| 372 /// | |
| 373 /// Even if the class is never instantiated, a JS constructor must be emitted | |
| 374 /// so the 'instanceof' expression does not throw an exception at runtime. | |
| 375 bool tryRegisterInstanceofCheck(ClassElement class_) { | |
| 376 if (glue.classWorld.isInstantiated(class_)) { | |
| 377 // Ensure the class remains instantiated during backend tree-shaking. | |
| 378 // TODO(asgerf): We could have a more precise hook to inform the emitter | |
| 379 // that the JS constructor function is needed, without the class being | |
| 380 // instantiated. | |
| 381 registry.registerInstantiatedClass(class_); | |
| 382 return true; | |
| 383 } | |
| 384 // Will throw if the JS constructor is not emitted, so do not allow the | |
| 385 // instanceof check. This should only happen when certain optimization | |
| 386 // passes are disabled, as the type check itself is trivial. | |
| 387 return false; | |
| 388 } | |
| 389 | |
| 390 @override | |
| 391 js.Expression visitTypeOperator(tree_ir.TypeOperator node) { | |
| 392 js.Expression value = visitExpression(node.value); | |
| 393 List<js.Expression> typeArguments = visitExpressionList(node.typeArguments); | |
| 394 DartType type = node.type; | |
| 395 if (type is InterfaceType) { | |
| 396 registry.registerTypeUse(new TypeUse.isCheck(type)); | |
| 397 ClassElement clazz = type.element; | |
| 398 | |
| 399 if (glue.isStringClass(clazz)) { | |
| 400 if (node.isTypeTest) { | |
| 401 return js.js(r'typeof # === "string"', <js.Expression>[value]); | |
| 402 } | |
| 403 // TODO(sra): Implement fast cast via calling 'stringTypeCast'. | |
| 404 } else if (glue.isBoolClass(clazz)) { | |
| 405 if (node.isTypeTest) { | |
| 406 return js.js(r'typeof # === "boolean"', <js.Expression>[value]); | |
| 407 } | |
| 408 // TODO(sra): Implement fast cast via calling 'boolTypeCast'. | |
| 409 } else if (node.isTypeTest && | |
| 410 node.typeArguments.isEmpty && | |
| 411 glue.mayGenerateInstanceofCheck(type) && | |
| 412 tryRegisterInstanceofCheck(clazz)) { | |
| 413 return js.js('# instanceof #', [value, glue.constructorAccess(clazz)]); | |
| 414 } | |
| 415 | |
| 416 // The helper we use needs the JSArray class to exist, but for some | |
| 417 // reason the helper does not cause this dependency to be registered. | |
| 418 // TODO(asgerf): Most programs need List anyway, but we should fix this. | |
| 419 registry.registerInstantiatedClass(glue.listClass); | |
| 420 | |
| 421 // We use one of the two helpers: | |
| 422 // | |
| 423 // checkSubtype(value, $isT, typeArgs, $asT) | |
| 424 // subtypeCast(value, $isT, typeArgs, $asT) | |
| 425 // | |
| 426 // Any of the last two arguments may be null if there are no type | |
| 427 // arguments, and/or if no substitution is required. | |
| 428 Element function = | |
| 429 node.isTypeTest ? glue.getCheckSubtype() : glue.getSubtypeCast(); | |
| 430 | |
| 431 js.Expression isT = js.quoteName(glue.getTypeTestTag(type)); | |
| 432 | |
| 433 js.Expression typeArgumentArray = typeArguments.isNotEmpty | |
| 434 ? new js.ArrayInitializer(typeArguments) | |
| 435 : new js.LiteralNull(); | |
| 436 | |
| 437 js.Expression asT = glue.hasStrictSubtype(clazz) | |
| 438 ? js.quoteName(glue.getTypeSubstitutionTag(clazz)) | |
| 439 : new js.LiteralNull(); | |
| 440 | |
| 441 return buildStaticHelperInvocation( | |
| 442 function, <js.Expression>[value, isT, typeArgumentArray, asT]); | |
| 443 } else if (type is TypeVariableType || type is FunctionType) { | |
| 444 registry.registerTypeUse(new TypeUse.isCheck(type)); | |
| 445 | |
| 446 Element function = node.isTypeTest | |
| 447 ? glue.getCheckSubtypeOfRuntimeType() | |
| 448 : glue.getSubtypeOfRuntimeTypeCast(); | |
| 449 | |
| 450 // The only type argument is the type held in the type variable. | |
| 451 js.Expression typeValue = typeArguments.single; | |
| 452 | |
| 453 return buildStaticHelperInvocation( | |
| 454 function, <js.Expression>[value, typeValue]); | |
| 455 } | |
| 456 return giveup(node, 'type check unimplemented for $type.'); | |
| 457 } | |
| 458 | |
| 459 @override | |
| 460 js.Expression visitGetTypeTestProperty(tree_ir.GetTypeTestProperty node) { | |
| 461 js.Expression object = visitExpression(node.object); | |
| 462 DartType dartType = node.dartType; | |
| 463 assert(dartType.isInterfaceType); | |
| 464 registry.registerTypeUse(new TypeUse.isCheck(dartType)); | |
| 465 //glue.registerIsCheck(dartType, registry); | |
| 466 js.Expression property = glue.getTypeTestTag(dartType); | |
| 467 return js.js(r'#.#', [object, property]); | |
| 468 } | |
| 469 | |
| 470 @override | |
| 471 js.Expression visitVariableUse(tree_ir.VariableUse node) { | |
| 472 return buildVariableAccess(node.variable) | |
| 473 .withSourceInformation(node.sourceInformation); | |
| 474 } | |
| 475 | |
| 476 js.Expression buildVariableAccess(tree_ir.Variable variable) { | |
| 477 return new js.VariableUse(getVariableName(variable)); | |
| 478 } | |
| 479 | |
| 480 /// Returns the JS operator for the given built-in operator for use in a | |
| 481 /// compound assignment (not including the '=' sign). | |
| 482 String getAsCompoundOperator(BuiltinOperator operator) { | |
| 483 switch (operator) { | |
| 484 case BuiltinOperator.NumAdd: | |
| 485 case BuiltinOperator.StringConcatenate: | |
| 486 return '+'; | |
| 487 case BuiltinOperator.NumSubtract: | |
| 488 return '-'; | |
| 489 case BuiltinOperator.NumMultiply: | |
| 490 return '*'; | |
| 491 case BuiltinOperator.NumDivide: | |
| 492 return '/'; | |
| 493 case BuiltinOperator.NumRemainder: | |
| 494 return '%'; | |
| 495 default: | |
| 496 throw 'Not a compoundable operator: $operator'; | |
| 497 } | |
| 498 } | |
| 499 | |
| 500 bool isCompoundableBuiltin(tree_ir.Expression exp) { | |
| 501 return exp is tree_ir.ApplyBuiltinOperator && | |
| 502 exp.arguments.length == 2 && | |
| 503 isCompoundableOperator(exp.operator); | |
| 504 } | |
| 505 | |
| 506 bool isOneConstant(tree_ir.Expression exp) { | |
| 507 return exp is tree_ir.Constant && exp.value.isOne; | |
| 508 } | |
| 509 | |
| 510 js.Expression makeAssignment(js.Expression leftHand, tree_ir.Expression value, | |
| 511 {SourceInformation sourceInformation, BuiltinOperator compound}) { | |
| 512 if (isOneConstant(value)) { | |
| 513 if (compound == BuiltinOperator.NumAdd) { | |
| 514 return new js.Prefix('++', leftHand) | |
| 515 .withSourceInformation(sourceInformation); | |
| 516 } | |
| 517 if (compound == BuiltinOperator.NumSubtract) { | |
| 518 return new js.Prefix('--', leftHand) | |
| 519 .withSourceInformation(sourceInformation); | |
| 520 } | |
| 521 } | |
| 522 if (compound != null) { | |
| 523 return new js.Assignment.compound( | |
| 524 leftHand, getAsCompoundOperator(compound), visitExpression(value)) | |
| 525 .withSourceInformation(sourceInformation); | |
| 526 } | |
| 527 return new js.Assignment(leftHand, visitExpression(value)) | |
| 528 .withSourceInformation(sourceInformation); | |
| 529 } | |
| 530 | |
| 531 @override | |
| 532 js.Expression visitAssign(tree_ir.Assign node) { | |
| 533 js.Expression variable = buildVariableAccess(node.variable); | |
| 534 if (isCompoundableBuiltin(node.value)) { | |
| 535 tree_ir.ApplyBuiltinOperator rhs = node.value; | |
| 536 tree_ir.Expression left = rhs.arguments[0]; | |
| 537 tree_ir.Expression right = rhs.arguments[1]; | |
| 538 if (left is tree_ir.VariableUse && left.variable == node.variable) { | |
| 539 return makeAssignment(variable, right, | |
| 540 compound: rhs.operator, sourceInformation: node.sourceInformation); | |
| 541 } | |
| 542 } | |
| 543 return makeAssignment(variable, node.value, | |
| 544 sourceInformation: node.sourceInformation); | |
| 545 } | |
| 546 | |
| 547 @override | |
| 548 void visitContinue(tree_ir.Continue node) { | |
| 549 tree_ir.Statement next = fallthrough.target; | |
| 550 if (node.target.binding == next || | |
| 551 next is tree_ir.Continue && node.target == next.target) { | |
| 552 // Fall through to continue target or to equivalent continue. | |
| 553 fallthrough.use(); | |
| 554 } else if (node.target.binding == shortContinue.target) { | |
| 555 // The target is the immediately enclosing loop. | |
| 556 shortContinue.use(); | |
| 557 accumulator.add(new js.Continue(null)); | |
| 558 } else { | |
| 559 accumulator.add(new js.Continue(makeLabel(node.target))); | |
| 560 } | |
| 561 } | |
| 562 | |
| 563 /// True if [other] is the target of [node] or is a [Break] with the same | |
| 564 /// target. This means jumping to [other] is equivalent to executing [node]. | |
| 565 bool isEffectiveBreakTarget(tree_ir.Break node, tree_ir.Statement other) { | |
| 566 return node.target.binding.next == other || | |
| 567 other is tree_ir.Break && node.target == other.target; | |
| 568 } | |
| 569 | |
| 570 /// True if the given break is equivalent to an unlabeled continue. | |
| 571 bool isShortContinue(tree_ir.Break node) { | |
| 572 tree_ir.Statement next = node.target.binding.next; | |
| 573 return next is tree_ir.Continue && | |
| 574 next.target.binding == shortContinue.target; | |
| 575 } | |
| 576 | |
| 577 @override | |
| 578 void visitBreak(tree_ir.Break node) { | |
| 579 if (isEffectiveBreakTarget(node, fallthrough.target)) { | |
| 580 // Fall through to break target or to equivalent break. | |
| 581 fallthrough.use(); | |
| 582 } else if (isEffectiveBreakTarget(node, shortBreak.target)) { | |
| 583 // Unlabeled break to the break target or to an equivalent break. | |
| 584 shortBreak.use(); | |
| 585 accumulator.add(new js.Break(null)); | |
| 586 } else if (isShortContinue(node)) { | |
| 587 // An unlabeled continue is better than a labeled break. | |
| 588 shortContinue.use(); | |
| 589 accumulator.add(new js.Continue(null)); | |
| 590 } else { | |
| 591 accumulator.add(new js.Break(makeLabel(node.target))); | |
| 592 } | |
| 593 } | |
| 594 | |
| 595 @override | |
| 596 visitExpressionStatement(tree_ir.ExpressionStatement node) { | |
| 597 js.Expression exp = visitExpression(node.expression); | |
| 598 if (node.next is tree_ir.Unreachable && emitUnreachableAsReturn.last) { | |
| 599 // Emit as 'return exp' to assist local analysis in the VM. | |
| 600 SourceInformation sourceInformation = node.expression.sourceInformation; | |
| 601 accumulator | |
| 602 .add(new js.Return(exp).withSourceInformation(sourceInformation)); | |
| 603 return null; | |
| 604 } else { | |
| 605 accumulator.add(new js.ExpressionStatement(exp)); | |
| 606 return node.next; | |
| 607 } | |
| 608 } | |
| 609 | |
| 610 bool isNullReturn(tree_ir.Statement node) { | |
| 611 return node is tree_ir.Return && isNull(node.value); | |
| 612 } | |
| 613 | |
| 614 bool isEndOfMethod(tree_ir.Statement node) { | |
| 615 return isNullReturn(node) || | |
| 616 node is tree_ir.Break && isNullReturn(node.target.binding.next); | |
| 617 } | |
| 618 | |
| 619 @override | |
| 620 visitIf(tree_ir.If node) { | |
| 621 js.Expression condition = visitExpression(node.condition); | |
| 622 int usesBefore = fallthrough.useCount; | |
| 623 // Unless the 'else' part ends the method. make sure to terminate any | |
| 624 // uncompletable code paths in the 'then' part. | |
| 625 emitUnreachableAsReturn.add(!isEndOfMethod(node.elseStatement)); | |
| 626 js.Statement thenBody = buildBodyStatement(node.thenStatement); | |
| 627 emitUnreachableAsReturn.removeLast(); | |
| 628 bool thenHasFallthrough = (fallthrough.useCount > usesBefore); | |
| 629 if (thenHasFallthrough) { | |
| 630 js.Statement elseBody = buildBodyStatement(node.elseStatement); | |
| 631 accumulator.add(new js.If(condition, thenBody, elseBody) | |
| 632 .withSourceInformation(node.sourceInformation)); | |
| 633 return null; | |
| 634 } else { | |
| 635 // The 'then' body cannot complete normally, so emit a short 'if' | |
| 636 // and put the 'else' body after it. | |
| 637 accumulator.add(new js.If.noElse(condition, thenBody) | |
| 638 .withSourceInformation(node.sourceInformation)); | |
| 639 return node.elseStatement; | |
| 640 } | |
| 641 } | |
| 642 | |
| 643 @override | |
| 644 visitLabeledStatement(tree_ir.LabeledStatement node) { | |
| 645 fallthrough.push(node.next); | |
| 646 js.Statement body = buildBodyStatement(node.body); | |
| 647 fallthrough.pop(); | |
| 648 accumulator.add(insertLabel(node.label, body)); | |
| 649 return node.next; | |
| 650 } | |
| 651 | |
| 652 /// Creates a name for [label] if it does not already have one. | |
| 653 /// | |
| 654 /// This also marks the label as being used. | |
| 655 String makeLabel(tree_ir.Label label) { | |
| 656 return labelNames.putIfAbsent(label, () => 'L${labelNames.length}'); | |
| 657 } | |
| 658 | |
| 659 /// Wraps a node in a labeled statement unless the label is unused. | |
| 660 js.Statement insertLabel(tree_ir.Label label, js.Statement node) { | |
| 661 String name = labelNames[label]; | |
| 662 if (name == null) return node; // Label is unused. | |
| 663 return new js.LabeledStatement(name, node); | |
| 664 } | |
| 665 | |
| 666 /// Returns the current [accumulator] wrapped in a block if neccessary. | |
| 667 js.Statement _bodyAsStatement() { | |
| 668 if (accumulator.length == 0) { | |
| 669 return new js.EmptyStatement(); | |
| 670 } | |
| 671 if (accumulator.length == 1) { | |
| 672 return accumulator.single; | |
| 673 } | |
| 674 return new js.Block(accumulator); | |
| 675 } | |
| 676 | |
| 677 /// Builds a nested statement. | |
| 678 js.Statement buildBodyStatement(tree_ir.Statement statement) { | |
| 679 List<js.Statement> savedAccumulator = accumulator; | |
| 680 accumulator = <js.Statement>[]; | |
| 681 while (statement != null) { | |
| 682 statement = visitStatement(statement); | |
| 683 } | |
| 684 js.Statement result = _bodyAsStatement(); | |
| 685 accumulator = savedAccumulator; | |
| 686 return result; | |
| 687 } | |
| 688 | |
| 689 js.Block buildBodyBlock(tree_ir.Statement statement) { | |
| 690 List<js.Statement> savedAccumulator = accumulator; | |
| 691 accumulator = <js.Statement>[]; | |
| 692 while (statement != null) { | |
| 693 statement = visitStatement(statement); | |
| 694 } | |
| 695 js.Statement result = new js.Block(accumulator); | |
| 696 accumulator = savedAccumulator; | |
| 697 return result; | |
| 698 } | |
| 699 | |
| 700 js.Expression makeSequence(List<tree_ir.Expression> list) { | |
| 701 return list.map(visitExpression).reduce((x, y) => new js.Binary(',', x, y)); | |
| 702 } | |
| 703 | |
| 704 @override | |
| 705 visitFor(tree_ir.For node) { | |
| 706 js.Expression condition = visitExpression(node.condition); | |
| 707 shortBreak.push(node.next); | |
| 708 shortContinue.push(node); | |
| 709 fallthrough.push(node); | |
| 710 emitUnreachableAsReturn.add(true); | |
| 711 js.Statement body = buildBodyStatement(node.body); | |
| 712 emitUnreachableAsReturn.removeLast(); | |
| 713 fallthrough.pop(); | |
| 714 shortContinue.pop(); | |
| 715 shortBreak.pop(); | |
| 716 js.Statement loopNode; | |
| 717 if (node.updates.isEmpty) { | |
| 718 loopNode = new js.While(condition, body); | |
| 719 } else { | |
| 720 // Compile as a for loop. | |
| 721 js.Expression init; | |
| 722 if (accumulator.isNotEmpty && | |
| 723 accumulator.last is js.ExpressionStatement) { | |
| 724 // Take the preceding expression from the accumulator and use | |
| 725 // it as the initializer expression. | |
| 726 js.ExpressionStatement initStmt = accumulator.removeLast(); | |
| 727 init = initStmt.expression; | |
| 728 } | |
| 729 js.Expression update = makeSequence(node.updates); | |
| 730 loopNode = new js.For(init, condition, update, body); | |
| 731 } | |
| 732 accumulator.add(insertLabel(node.label, loopNode)); | |
| 733 return node.next; | |
| 734 } | |
| 735 | |
| 736 @override | |
| 737 void visitWhileTrue(tree_ir.WhileTrue node) { | |
| 738 // A short break in the while will jump to the current fallthrough target. | |
| 739 shortBreak.push(fallthrough.target); | |
| 740 shortContinue.push(node); | |
| 741 fallthrough.push(node); | |
| 742 emitUnreachableAsReturn.add(true); | |
| 743 js.Statement jsBody = buildBodyStatement(node.body); | |
| 744 emitUnreachableAsReturn.removeLast(); | |
| 745 fallthrough.pop(); | |
| 746 shortContinue.pop(); | |
| 747 if (shortBreak.useCount > 0) { | |
| 748 // Short breaks use the current fallthrough target. | |
| 749 fallthrough.use(); | |
| 750 } | |
| 751 shortBreak.pop(); | |
| 752 accumulator | |
| 753 .add(insertLabel(node.label, new js.For(null, null, null, jsBody))); | |
| 754 } | |
| 755 | |
| 756 bool isNull(tree_ir.Expression node) { | |
| 757 return node is tree_ir.Constant && node.value.isNull; | |
| 758 } | |
| 759 | |
| 760 @override | |
| 761 void visitReturn(tree_ir.Return node) { | |
| 762 if (isNull(node.value) && fallthrough.target == null) { | |
| 763 // Do nothing. Implicitly return JS undefined by falling over the end. | |
| 764 registry.registerCompileTimeConstant(new NullConstantValue()); | |
| 765 fallthrough.use(); | |
| 766 } else { | |
| 767 accumulator.add(new js.Return(visitExpression(node.value)) | |
| 768 .withSourceInformation(node.sourceInformation)); | |
| 769 } | |
| 770 } | |
| 771 | |
| 772 @override | |
| 773 void visitThrow(tree_ir.Throw node) { | |
| 774 accumulator.add(new js.Throw(visitExpression(node.value))); | |
| 775 } | |
| 776 | |
| 777 @override | |
| 778 void visitUnreachable(tree_ir.Unreachable node) { | |
| 779 if (emitUnreachableAsReturn.last) { | |
| 780 // Emit a return to assist local analysis in the VM. | |
| 781 accumulator.add(new js.Return()); | |
| 782 } | |
| 783 } | |
| 784 | |
| 785 @override | |
| 786 void visitTry(tree_ir.Try node) { | |
| 787 js.Block tryBlock = buildBodyBlock(node.tryBody); | |
| 788 tree_ir.Variable exceptionVariable = node.catchParameters.first; | |
| 789 js.VariableDeclaration exceptionParameter = | |
| 790 new js.VariableDeclaration(getVariableName(exceptionVariable)); | |
| 791 js.Block catchBlock = buildBodyBlock(node.catchBody); | |
| 792 js.Catch catchPart = new js.Catch(exceptionParameter, catchBlock); | |
| 793 accumulator.add(new js.Try(tryBlock, catchPart, null)); | |
| 794 } | |
| 795 | |
| 796 @override | |
| 797 js.Expression visitCreateBox(tree_ir.CreateBox node) { | |
| 798 return new js.ObjectInitializer(const <js.Property>[]); | |
| 799 } | |
| 800 | |
| 801 @override | |
| 802 js.Expression visitCreateInstance(tree_ir.CreateInstance node) { | |
| 803 ClassElement classElement = node.classElement; | |
| 804 // TODO(asgerf): To allow inlining of InvokeConstructor, CreateInstance must | |
| 805 // carry a DartType so we can register the instantiated type | |
| 806 // with its type arguments. Otherwise dataflow analysis is | |
| 807 // needed to reconstruct the instantiated type. | |
| 808 registry.registerInstantiation(classElement.rawType); | |
| 809 if (classElement is ClosureClassElement) { | |
| 810 registry.registerInstantiatedClosure(classElement.methodElement); | |
| 811 } | |
| 812 js.Expression instance = new js.New(glue.constructorAccess(classElement), | |
| 813 visitExpressionList(node.arguments)) | |
| 814 .withSourceInformation(node.sourceInformation); | |
| 815 | |
| 816 tree_ir.Expression typeInformation = node.typeInformation; | |
| 817 if (typeInformation != null) { | |
| 818 FunctionElement helper = glue.getAddRuntimeTypeInformation(); | |
| 819 js.Expression typeArguments = visitExpression(typeInformation); | |
| 820 return buildStaticHelperInvocation( | |
| 821 helper, <js.Expression>[instance, typeArguments], | |
| 822 sourceInformation: node.sourceInformation); | |
| 823 } else { | |
| 824 return instance; | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 @override | |
| 829 js.Expression visitCreateInvocationMirror( | |
| 830 tree_ir.CreateInvocationMirror node) { | |
| 831 js.Expression name = js.string(node.selector.name); | |
| 832 js.Expression internalName = | |
| 833 js.quoteName(glue.invocationName(node.selector)); | |
| 834 js.Expression kind = js.number(node.selector.invocationMirrorKind); | |
| 835 js.Expression arguments = | |
| 836 new js.ArrayInitializer(visitExpressionList(node.arguments)); | |
| 837 js.Expression argumentNames = new js.ArrayInitializer( | |
| 838 node.selector.namedArguments.map(js.string).toList(growable: false)); | |
| 839 return buildStaticHelperInvocation(glue.createInvocationMirrorMethod, | |
| 840 <js.Expression>[name, internalName, kind, arguments, argumentNames]); | |
| 841 } | |
| 842 | |
| 843 @override | |
| 844 js.Expression visitInterceptor(tree_ir.Interceptor node) { | |
| 845 registry.registerUseInterceptor(); | |
| 846 // Default to all intercepted classes if they have not been computed. | |
| 847 // This is to ensure we can run codegen without prior optimization passes. | |
| 848 Set<ClassElement> interceptedClasses = node.interceptedClasses.isEmpty | |
| 849 ? glue.interceptedClasses | |
| 850 : node.interceptedClasses; | |
| 851 registry.registerSpecializedGetInterceptor(interceptedClasses); | |
| 852 js.Name helperName = glue.getInterceptorName(interceptedClasses); | |
| 853 js.Expression globalHolder = glue.getInterceptorLibrary(); | |
| 854 return js.js('#.#(#)', [ | |
| 855 globalHolder, | |
| 856 helperName, | |
| 857 visitExpression(node.input) | |
| 858 ]).withSourceInformation(node.sourceInformation); | |
| 859 } | |
| 860 | |
| 861 @override | |
| 862 js.Expression visitGetField(tree_ir.GetField node) { | |
| 863 registry.registerStaticUse(new StaticUse.fieldGet(node.field)); | |
| 864 return new js.PropertyAccess(visitExpression(node.object), | |
| 865 glue.instanceFieldPropertyName(node.field)) | |
| 866 .withSourceInformation(node.sourceInformation); | |
| 867 } | |
| 868 | |
| 869 @override | |
| 870 js.Expression visitSetField(tree_ir.SetField node) { | |
| 871 registry.registerStaticUse(new StaticUse.fieldSet(node.field)); | |
| 872 js.PropertyAccess field = new js.PropertyAccess( | |
| 873 visitExpression(node.object), | |
| 874 glue.instanceFieldPropertyName(node.field)); | |
| 875 return makeAssignment(field, node.value, | |
| 876 compound: node.compound, sourceInformation: node.sourceInformation); | |
| 877 } | |
| 878 | |
| 879 @override | |
| 880 js.Expression visitGetStatic(tree_ir.GetStatic node) { | |
| 881 assert(node.element is FieldElement || node.element is FunctionElement); | |
| 882 if (node.element is FunctionElement) { | |
| 883 // Tear off a method. | |
| 884 registry.registerStaticUse( | |
| 885 new StaticUse.staticTearOff(node.element.declaration)); | |
| 886 return glue | |
| 887 .isolateStaticClosureAccess(node.element) | |
| 888 .withSourceInformation(node.sourceInformation); | |
| 889 } | |
| 890 if (node.useLazyGetter) { | |
| 891 // Read a lazily initialized field. | |
| 892 registry.registerStaticUse( | |
| 893 new StaticUse.staticInit(node.element.declaration)); | |
| 894 js.Expression getter = glue.isolateLazyInitializerAccess(node.element); | |
| 895 return new js.Call(getter, <js.Expression>[], | |
| 896 sourceInformation: node.sourceInformation); | |
| 897 } | |
| 898 // Read an eagerly initialized field. | |
| 899 registry | |
| 900 .registerStaticUse(new StaticUse.staticGet(node.element.declaration)); | |
| 901 return glue | |
| 902 .staticFieldAccess(node.element) | |
| 903 .withSourceInformation(node.sourceInformation); | |
| 904 } | |
| 905 | |
| 906 @override | |
| 907 js.Expression visitSetStatic(tree_ir.SetStatic node) { | |
| 908 assert(node.element is FieldElement); | |
| 909 registry | |
| 910 .registerStaticUse(new StaticUse.staticSet(node.element.declaration)); | |
| 911 js.Expression field = glue.staticFieldAccess(node.element); | |
| 912 return makeAssignment(field, node.value, | |
| 913 compound: node.compound, sourceInformation: node.sourceInformation); | |
| 914 } | |
| 915 | |
| 916 @override | |
| 917 js.Expression visitGetLength(tree_ir.GetLength node) { | |
| 918 return new js.PropertyAccess.field(visitExpression(node.object), 'length'); | |
| 919 } | |
| 920 | |
| 921 @override | |
| 922 js.Expression visitGetIndex(tree_ir.GetIndex node) { | |
| 923 return new js.PropertyAccess( | |
| 924 visitExpression(node.object), visitExpression(node.index)); | |
| 925 } | |
| 926 | |
| 927 @override | |
| 928 js.Expression visitSetIndex(tree_ir.SetIndex node) { | |
| 929 js.Expression index = new js.PropertyAccess( | |
| 930 visitExpression(node.object), visitExpression(node.index)); | |
| 931 return makeAssignment(index, node.value, compound: node.compound); | |
| 932 } | |
| 933 | |
| 934 js.Expression buildStaticHelperInvocation( | |
| 935 FunctionElement helper, List<js.Expression> arguments, | |
| 936 {SourceInformation sourceInformation}) { | |
| 937 registry.registerStaticUse(new StaticUse.staticInvoke( | |
| 938 helper, new CallStructure.unnamed(arguments.length))); | |
| 939 return buildStaticInvoke(helper, arguments, | |
| 940 sourceInformation: sourceInformation); | |
| 941 } | |
| 942 | |
| 943 @override | |
| 944 js.Expression visitReifyRuntimeType(tree_ir.ReifyRuntimeType node) { | |
| 945 js.Expression typeToString = buildStaticHelperInvocation( | |
| 946 glue.getRuntimeTypeToString(), [visitExpression(node.value)], | |
| 947 sourceInformation: node.sourceInformation); | |
| 948 return buildStaticHelperInvocation( | |
| 949 glue.getCreateRuntimeType(), [typeToString], | |
| 950 sourceInformation: node.sourceInformation); | |
| 951 } | |
| 952 | |
| 953 @override | |
| 954 js.Expression visitReadTypeVariable(tree_ir.ReadTypeVariable node) { | |
| 955 ClassElement context = node.variable.element.enclosingClass; | |
| 956 js.Expression index = js.number(glue.getTypeVariableIndex(node.variable)); | |
| 957 if (glue.needsSubstitutionForTypeVariableAccess(context)) { | |
| 958 js.Expression typeName = glue.getRuntimeTypeName(context); | |
| 959 return buildStaticHelperInvocation(glue.getRuntimeTypeArgument(), | |
| 960 [visitExpression(node.target), typeName, index], | |
| 961 sourceInformation: node.sourceInformation); | |
| 962 } else { | |
| 963 return buildStaticHelperInvocation( | |
| 964 glue.getTypeArgumentByIndex(), [visitExpression(node.target), index], | |
| 965 sourceInformation: node.sourceInformation); | |
| 966 } | |
| 967 } | |
| 968 | |
| 969 @override | |
| 970 js.Expression visitTypeExpression(tree_ir.TypeExpression node) { | |
| 971 List<js.Expression> arguments = visitExpressionList(node.arguments); | |
| 972 switch (node.kind) { | |
| 973 case tree_ir.TypeExpressionKind.COMPLETE: | |
| 974 return glue.generateTypeRepresentation( | |
| 975 node.dartType, arguments, registry); | |
| 976 case tree_ir.TypeExpressionKind.INSTANCE: | |
| 977 // We expect only flat types for the INSTANCE representation. | |
| 978 assert( | |
| 979 node.dartType == (node.dartType.element as ClassElement).thisType); | |
| 980 registry.registerInstantiatedClass(glue.listClass); | |
| 981 return new js.ArrayInitializer(arguments); | |
| 982 } | |
| 983 } | |
| 984 | |
| 985 js.Node handleForeignCode(tree_ir.ForeignCode node) { | |
| 986 if (node.dependency != null) { | |
| 987 // Dependency is only used if [node] calls a Dart function. Currently only | |
| 988 // through foreign function `RAW_DART_FUNCTION_REF`. | |
| 989 registry.registerStaticUse(new StaticUse.staticInvoke( | |
| 990 node.dependency, new CallStructure.unnamed(node.arguments.length))); | |
| 991 } | |
| 992 // TODO(sra,johnniwinther): Should this be in CodegenRegistry? | |
| 993 glue.registerNativeBehavior(node.nativeBehavior, node); | |
| 994 return node.codeTemplate | |
| 995 .instantiate(visitExpressionList(node.arguments)) | |
| 996 .withSourceInformation(node.sourceInformation); | |
| 997 } | |
| 998 | |
| 999 @override | |
| 1000 js.Expression visitForeignExpression(tree_ir.ForeignExpression node) { | |
| 1001 return handleForeignCode(node); | |
| 1002 } | |
| 1003 | |
| 1004 @override | |
| 1005 void visitForeignStatement(tree_ir.ForeignStatement node) { | |
| 1006 accumulator.add(handleForeignCode(node)); | |
| 1007 } | |
| 1008 | |
| 1009 @override | |
| 1010 visitYield(tree_ir.Yield node) { | |
| 1011 js.Expression value = visitExpression(node.input); | |
| 1012 accumulator.add(new js.DartYield(value, node.hasStar)); | |
| 1013 return node.next; | |
| 1014 } | |
| 1015 | |
| 1016 @override | |
| 1017 visitReceiverCheck(tree_ir.ReceiverCheck node) { | |
| 1018 js.Expression value = visitExpression(node.value); | |
| 1019 // TODO(sra): Try to use the selector even when [useSelector] is false. The | |
| 1020 // reason we use 'toString' is that it is always defined so avoids a slow | |
| 1021 // lookup (in V8) of an absent property. We could use the property for the | |
| 1022 // selector if we knew it was present. The property is present if the | |
| 1023 // associated method was not inlined away, or if there is a noSuchMethod | |
| 1024 // hook for that selector. We don't know these things here, but the decision | |
| 1025 // could be deferred by creating a deferred property that was resolved after | |
| 1026 // codegen. | |
| 1027 js.Expression access = node.useSelector | |
| 1028 ? js.js('#.#', [value, glue.invocationName(node.selector)]) | |
| 1029 : js.js('#.toString', [value]); | |
| 1030 if (node.useInvoke) { | |
| 1031 access = new js.Call(access, []); | |
| 1032 } | |
| 1033 if (node.condition != null) { | |
| 1034 js.Expression condition = visitExpression(node.condition); | |
| 1035 js.Statement body = isNullReturn(node.next) | |
| 1036 ? new js.ExpressionStatement(access) | |
| 1037 : new js.Return(access); | |
| 1038 accumulator.add(new js.If.noElse(condition, body)); | |
| 1039 } else { | |
| 1040 accumulator.add(new js.ExpressionStatement(access)); | |
| 1041 } | |
| 1042 return node.next; | |
| 1043 } | |
| 1044 | |
| 1045 @override | |
| 1046 js.Expression visitApplyBuiltinOperator(tree_ir.ApplyBuiltinOperator node) { | |
| 1047 List<js.Expression> args = visitExpressionList(node.arguments); | |
| 1048 | |
| 1049 js.Expression createExpression() { | |
| 1050 switch (node.operator) { | |
| 1051 case BuiltinOperator.NumAdd: | |
| 1052 return new js.Binary('+', args[0], args[1]); | |
| 1053 case BuiltinOperator.NumSubtract: | |
| 1054 return new js.Binary('-', args[0], args[1]); | |
| 1055 case BuiltinOperator.NumMultiply: | |
| 1056 return new js.Binary('*', args[0], args[1]); | |
| 1057 case BuiltinOperator.NumDivide: | |
| 1058 return new js.Binary('/', args[0], args[1]); | |
| 1059 case BuiltinOperator.NumRemainder: | |
| 1060 return new js.Binary('%', args[0], args[1]); | |
| 1061 case BuiltinOperator.NumTruncatingDivideToSigned32: | |
| 1062 return js.js('(# / #) | 0', args); | |
| 1063 case BuiltinOperator.NumAnd: | |
| 1064 return normalizeBitOp(js.js('# & #', args), node); | |
| 1065 case BuiltinOperator.NumOr: | |
| 1066 return normalizeBitOp(js.js('# | #', args), node); | |
| 1067 case BuiltinOperator.NumXor: | |
| 1068 return normalizeBitOp(js.js('# ^ #', args), node); | |
| 1069 case BuiltinOperator.NumLt: | |
| 1070 return new js.Binary('<', args[0], args[1]); | |
| 1071 case BuiltinOperator.NumLe: | |
| 1072 return new js.Binary('<=', args[0], args[1]); | |
| 1073 case BuiltinOperator.NumGt: | |
| 1074 return new js.Binary('>', args[0], args[1]); | |
| 1075 case BuiltinOperator.NumGe: | |
| 1076 return new js.Binary('>=', args[0], args[1]); | |
| 1077 case BuiltinOperator.NumShl: | |
| 1078 return normalizeBitOp(js.js('# << #', args), node); | |
| 1079 case BuiltinOperator.NumShr: | |
| 1080 // No normalization required since output is always uint32. | |
| 1081 return js.js('# >>> #', args); | |
| 1082 case BuiltinOperator.NumBitNot: | |
| 1083 return js.js('(~#) >>> 0', args); | |
| 1084 case BuiltinOperator.NumNegate: | |
| 1085 return js.js('-#', args); | |
| 1086 case BuiltinOperator.StringConcatenate: | |
| 1087 if (args.isEmpty) return js.string(''); | |
| 1088 return args.reduce((e1, e2) => new js.Binary('+', e1, e2)); | |
| 1089 case BuiltinOperator.CharCodeAt: | |
| 1090 return js.js('#.charCodeAt(#)', args); | |
| 1091 case BuiltinOperator.Identical: | |
| 1092 registry.registerStaticUse(new StaticUse.staticInvoke( | |
| 1093 glue.identicalFunction, new CallStructure.unnamed(args.length))); | |
| 1094 return buildStaticHelperInvocation(glue.identicalFunction, args); | |
| 1095 case BuiltinOperator.StrictEq: | |
| 1096 return new js.Binary('===', args[0], args[1]); | |
| 1097 case BuiltinOperator.StrictNeq: | |
| 1098 return new js.Binary('!==', args[0], args[1]); | |
| 1099 case BuiltinOperator.LooseEq: | |
| 1100 return new js.Binary('==', args[0], args[1]); | |
| 1101 case BuiltinOperator.LooseNeq: | |
| 1102 return new js.Binary('!=', args[0], args[1]); | |
| 1103 case BuiltinOperator.IsFalsy: | |
| 1104 return new js.Prefix('!', args[0]); | |
| 1105 case BuiltinOperator.IsNumber: | |
| 1106 return js.js('typeof # === "number"', args); | |
| 1107 case BuiltinOperator.IsNotNumber: | |
| 1108 return js.js('typeof # !== "number"', args); | |
| 1109 case BuiltinOperator.IsFloor: | |
| 1110 return js.js('Math.floor(#) === #', args); | |
| 1111 case BuiltinOperator.IsInteger: | |
| 1112 return js.js('typeof # === "number" && Math.floor(#) === #', args); | |
| 1113 case BuiltinOperator.IsNotInteger: | |
| 1114 return js.js('typeof # !== "number" || Math.floor(#) !== #', args); | |
| 1115 case BuiltinOperator.IsUnsigned32BitInteger: | |
| 1116 return js.js('# >>> 0 === #', args); | |
| 1117 case BuiltinOperator.IsNotUnsigned32BitInteger: | |
| 1118 return js.js('# >>> 0 !== #', args); | |
| 1119 case BuiltinOperator.IsFixedLengthJSArray: | |
| 1120 // TODO(sra): Remove boolify (i.e. !!). | |
| 1121 return js.js(r'!!#.fixed$length', args); | |
| 1122 case BuiltinOperator.IsExtendableJSArray: | |
| 1123 return js.js(r'!#.fixed$length', args); | |
| 1124 case BuiltinOperator.IsModifiableJSArray: | |
| 1125 return js.js(r'!#.immutable$list', args); | |
| 1126 case BuiltinOperator.IsUnmodifiableJSArray: | |
| 1127 // TODO(sra): Remove boolify (i.e. !!). | |
| 1128 return js.js(r'!!#.immutable$list', args); | |
| 1129 } | |
| 1130 } | |
| 1131 | |
| 1132 return createExpression().withSourceInformation(node.sourceInformation); | |
| 1133 } | |
| 1134 | |
| 1135 /// Add a uint32 normalization `op >>> 0` to [op] if it is not in 31-bit | |
| 1136 /// range. | |
| 1137 js.Expression normalizeBitOp( | |
| 1138 js.Expression op, tree_ir.ApplyBuiltinOperator node) { | |
| 1139 const MAX_UINT31 = 0x7fffffff; | |
| 1140 const MAX_UINT32 = 0xffffffff; | |
| 1141 | |
| 1142 int constantValue(tree_ir.Expression e) { | |
| 1143 if (e is tree_ir.Constant) { | |
| 1144 ConstantValue value = e.value; | |
| 1145 if (!value.isInt) return null; | |
| 1146 IntConstantValue intConstant = value; | |
| 1147 if (intConstant.primitiveValue < 0) return null; | |
| 1148 if (intConstant.primitiveValue > MAX_UINT32) return null; | |
| 1149 return intConstant.primitiveValue; | |
| 1150 } | |
| 1151 return null; | |
| 1152 } | |
| 1153 | |
| 1154 /// Returns a value of the form 0b0001xxxx to represent the highest bit set | |
| 1155 /// in the result. This represents the range [0, 0b00011111], up to 32 | |
| 1156 /// bits. `null` represents a result possibly outside the uint32 range. | |
| 1157 int maxBitOf(tree_ir.Expression e) { | |
| 1158 if (e is tree_ir.Constant) { | |
| 1159 return constantValue(e); | |
| 1160 } | |
| 1161 if (e is tree_ir.ApplyBuiltinOperator) { | |
| 1162 if (e.operator == BuiltinOperator.NumAnd) { | |
| 1163 int left = maxBitOf(e.arguments[0]); | |
| 1164 int right = maxBitOf(e.arguments[1]); | |
| 1165 if (left == null && right == null) return MAX_UINT32; | |
| 1166 if (left == null) return right; | |
| 1167 if (right == null) return left; | |
| 1168 return (left < right) ? left : right; | |
| 1169 } | |
| 1170 if (e.operator == BuiltinOperator.NumOr || | |
| 1171 e.operator == BuiltinOperator.NumXor) { | |
| 1172 int left = maxBitOf(e.arguments[0]); | |
| 1173 int right = maxBitOf(e.arguments[1]); | |
| 1174 if (left == null || right == null) return MAX_UINT32; | |
| 1175 return left | right; | |
| 1176 } | |
| 1177 if (e.operator == BuiltinOperator.NumShr) { | |
| 1178 int right = constantValue(e.arguments[1]); | |
| 1179 // NumShr is JavaScript '>>>' so always generates a uint32 result. | |
| 1180 if (right == null || right <= 0 || right > 31) return MAX_UINT32; | |
| 1181 int left = maxBitOf(e.arguments[0]); | |
| 1182 if (left == null) return MAX_UINT32; | |
| 1183 return left >> right; | |
| 1184 } | |
| 1185 if (e.operator == BuiltinOperator.NumShl) { | |
| 1186 int right = constantValue(e.arguments[1]); | |
| 1187 if (right == null || right <= 0 || right > 31) return MAX_UINT32; | |
| 1188 int left = maxBitOf(e.arguments[0]); | |
| 1189 if (left == null) return MAX_UINT32; | |
| 1190 if (left.bitLength + right > 31) return MAX_UINT32; | |
| 1191 return left << right; | |
| 1192 } | |
| 1193 } | |
| 1194 return null; | |
| 1195 } | |
| 1196 | |
| 1197 int maxBit = maxBitOf(node); | |
| 1198 if (maxBit != null && maxBit <= MAX_UINT31) return op; | |
| 1199 return js.js('# >>> 0', [op]); | |
| 1200 } | |
| 1201 | |
| 1202 @override | |
| 1203 js.Expression visitApplyBuiltinMethod(tree_ir.ApplyBuiltinMethod node) { | |
| 1204 js.Expression receiver = visitExpression(node.receiver); | |
| 1205 List<js.Expression> args = visitExpressionList(node.arguments); | |
| 1206 switch (node.method) { | |
| 1207 case BuiltinMethod.Push: | |
| 1208 return js.js('#.push(#)', [receiver, args]); | |
| 1209 | |
| 1210 case BuiltinMethod.Pop: | |
| 1211 return js.js('#.pop()', [receiver]); | |
| 1212 | |
| 1213 case BuiltinMethod.SetLength: | |
| 1214 return js.js('#.length = #', [receiver, args[0]]); | |
| 1215 } | |
| 1216 } | |
| 1217 | |
| 1218 @override | |
| 1219 js.Expression visitAwait(tree_ir.Await node) { | |
| 1220 return new js.Await(visitExpression(node.input)); | |
| 1221 } | |
| 1222 | |
| 1223 /// Ensures that parameter defaults will be emitted. | |
| 1224 /// | |
| 1225 /// Ideally, this should be done when generating the relevant stub methods, | |
| 1226 /// since those are the ones that actually reference the constants, but those | |
| 1227 /// are created by the emitter when it is too late to register new constants. | |
| 1228 /// | |
| 1229 /// For non-static methods, we have no way of knowing if the defaults are | |
| 1230 /// actually used, so we conservatively register them all. | |
| 1231 void registerDefaultParameterValues(ExecutableElement element) { | |
| 1232 if (element is! FunctionElement) return; | |
| 1233 FunctionElement function = element; | |
| 1234 if (function.isStatic) return; // Defaults are inlined at call sites. | |
| 1235 function.functionSignature.forEachOptionalParameter((param) { | |
| 1236 ConstantValue constant = glue.getDefaultParameterValue(param); | |
| 1237 registry.registerCompileTimeConstant(constant); | |
| 1238 }); | |
| 1239 } | |
| 1240 } | |
| OLD | NEW |