// Copyright (c) 2018, 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 vm.bytecode.gen_bytecode; import 'package:kernel/ast.dart' hide MapEntry; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/clone.dart'; import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/library_index.dart' show LibraryIndex; import 'package:kernel/transformations/constants.dart' show ConstantEvaluator, ConstantsBackend, EvaluationEnvironment; import 'package:kernel/type_environment.dart' show TypeEnvironment; import 'package:kernel/vm/constants_native_effects.dart' show VmConstantsBackend; import 'package:vm/bytecode/assembler.dart'; import 'package:vm/bytecode/constant_pool.dart'; import 'package:vm/bytecode/dbc.dart'; import 'package:vm/bytecode/exceptions.dart'; import 'package:vm/bytecode/local_vars.dart' show LocalVariables; import 'package:vm/metadata/bytecode.dart'; /// Flag to toggle generation of bytecode in kernel files. const bool isKernelBytecodeEnabled = false; /// Flag to toggle generation of bytecode in platform kernel files. const bool isKernelBytecodeEnabledForPlatform = isKernelBytecodeEnabled; const bool isTraceEnabled = false; void generateBytecode(Component component, {bool strongMode: true, bool dropAST: false}) { final coreTypes = new CoreTypes(component); void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {} final hierarchy = new ClassHierarchy(component, onAmbiguousSupertypes: ignoreAmbiguousSupertypes); final typeEnvironment = new TypeEnvironment(coreTypes, hierarchy, strongMode: strongMode); final constantsBackend = new VmConstantsBackend(null, coreTypes); new BytecodeGenerator(component, coreTypes, hierarchy, typeEnvironment, constantsBackend, strongMode) .visitComponent(component); if (dropAST) { new DropAST().visitComponent(component); } } class BytecodeGenerator extends RecursiveVisitor { final Component component; final CoreTypes coreTypes; final ClassHierarchy hierarchy; final TypeEnvironment typeEnvironment; final ConstantsBackend constantsBackend; final bool strongMode; final BytecodeMetadataRepository metadata = new BytecodeMetadataRepository(); Class enclosingClass; Member enclosingMember; LocalVariables locals; ConstantEvaluator constantEvaluator; Map labeledStatements; Map switchCases; Map tryCatches; Map> finallyBlocks; Map contextLevels; List closures; ConstantPool cp; ConstantEmitter constantEmitter; BytecodeAssembler asm; List savedAssemblers; BytecodeGenerator(this.component, this.coreTypes, this.hierarchy, this.typeEnvironment, this.constantsBackend, this.strongMode) { component.addMetadataRepository(metadata); } @override visitComponent(Component node) => node.visitChildren(this); @override visitLibrary(Library node) { if (node.isExternal) { return; } visitList(node.classes, this); visitList(node.procedures, this); visitList(node.fields, this); } @override visitClass(Class node) { visitList(node.constructors, this); visitList(node.procedures, this); visitList(node.fields, this); } @override defaultMember(Member node) { if (node.isAbstract) { return; } try { if (node is Field) { if (node.isStatic && node.initializer != null) { start(node); if (node.isConst) { _genPushConstExpr(node.initializer); } else { node.initializer.accept(this); } _genReturnTOS(); end(node); } } else if ((node is Procedure && !node.isRedirectingFactoryConstructor) || (node is Constructor)) { start(node); if (node is Constructor) { _genConstructorInitializers(node); } node.function?.body?.accept(this); // TODO(alexmarkov): figure out when 'return null' should be generated. _genPushNull(); _genReturnTOS(); end(node); } } on UnsupportedOperationError catch (e) { if (isTraceEnabled) { print('Unable to generate bytecode for $node: $e'); } } } LibraryIndex _libraryIndex; LibraryIndex get libraryIndex => _libraryIndex ??= new LibraryIndex.coreLibraries(component); Procedure _listFromLiteral; Procedure get listFromLiteral => _listFromLiteral ??= libraryIndex.getMember('dart:core', 'List', '_fromLiteral'); Procedure _mapFromLiteral; Procedure get mapFromLiteral => _mapFromLiteral ??= libraryIndex.getMember('dart:core', 'Map', '_fromLiteral'); Procedure _interpolateSingle; Procedure get interpolateSingle => _interpolateSingle ??= libraryIndex.getMember('dart:core', '_StringBase', '_interpolateSingle'); Procedure _interpolate; Procedure get interpolate => _interpolate ??= libraryIndex.getMember('dart:core', '_StringBase', '_interpolate'); Class _closureClass; Class get closureClass => _closureClass ??= libraryIndex.getClass('dart:core', '_Closure'); Field _closureInstantiatorTypeArguments; Field get closureInstantiatorTypeArguments => _closureInstantiatorTypeArguments ??= libraryIndex.getMember( 'dart:core', '_Closure', '_instantiator_type_arguments'); Field _closureFunctionTypeArguments; Field get closureFunctionTypeArguments => _closureFunctionTypeArguments ??= libraryIndex.getMember( 'dart:core', '_Closure', '_function_type_arguments'); Field _closureFunction; Field get closureFunction => _closureFunction ??= libraryIndex.getMember('dart:core', '_Closure', '_function'); Field _closureContext; Field get closureContext => _closureContext ??= libraryIndex.getMember('dart:core', '_Closure', '_context'); Procedure _prependTypeArguments; Procedure get prependTypeArguments => _prependTypeArguments ??= libraryIndex.getTopLevelMember('dart:_internal', '_prependTypeArguments'); void _genConstructorInitializers(Constructor node) { bool isRedirecting = node.initializers.any((init) => init is RedirectingInitializer); if (!isRedirecting) { for (var field in node.enclosingClass.fields) { if (!field.isStatic && field.initializer != null && !node.initializers.any( (init) => init is FieldInitializer && init.field == field)) { _genFieldInitializer(field, field.initializer); } } } visitList(node.initializers, this); } void _genFieldInitializer(Field field, Expression initializer) { if (initializer is NullLiteral) { return; } _genPushReceiver(); initializer.accept(this); // TODO(alexmarkov): assignability check final int cpIndex = cp.add(new ConstantFieldOffset(field)); asm.emitStoreFieldTOS(cpIndex); } void _genArguments(Expression receiver, Arguments arguments) { if (arguments.types.isNotEmpty) { _genTypeArguments(arguments.types); } receiver?.accept(this); visitList(arguments.positional, this); arguments.named.forEach((NamedExpression ne) => ne.value.accept(this)); } void _genPushNull() { final cpIndex = cp.add(const ConstantNull()); asm.emitPushConstant(cpIndex); } void _genPushInt(int value) { int cpIndex = cp.add(new ConstantInt(value)); asm.emitPushConstant(cpIndex); } void _genPushConstExpr(Expression expr) { final constant = constantEvaluator.evaluate(expr); asm.emitPushConstant(constant.accept(constantEmitter)); } void _genReturnTOS() { asm.emitReturnTOS(); } void _genStaticCall(Member target, ConstantArgDesc argDesc, int totalArgCount, {bool isGet: false, bool isSet: false}) { assert(!isGet || !isSet); final argDescIndex = cp.add(argDesc); final kind = isGet ? InvocationKind.getter : (isSet ? InvocationKind.setter : InvocationKind.method); final icdataIndex = cp.add(new ConstantStaticICData(kind, target, argDescIndex)); asm.emitPushConstant(icdataIndex); asm.emitIndirectStaticCall(totalArgCount, argDescIndex); } void _genStaticCallWithArgs(Member target, Arguments args, {bool hasReceiver: false, bool alwaysPassTypeArgs: false}) { final ConstantArgDesc argDesc = new ConstantArgDesc.fromArguments(args, hasReceiver: hasReceiver); int totalArgCount = args.positional.length + args.named.length; if (hasReceiver) { totalArgCount++; } if (args.types.isNotEmpty || alwaysPassTypeArgs) { totalArgCount++; } _genStaticCall(target, argDesc, totalArgCount); } bool hasTypeParameters(List typeArgs) { final findTypeParams = new FindTypeParametersVisitor(); return typeArgs.any((t) => t.accept(findTypeParams)); } bool hasInstantiatorTypeArguments(Class c) { return c.typeParameters.isNotEmpty || (c.superclass != null && hasInstantiatorTypeArguments(c.superclass)); } void _genTypeArguments(List typeArgs, {Class instantiatingClass}) { int typeArgsCPIndex = cp.add(new ConstantTypeArguments(typeArgs)); if (instantiatingClass != null) { typeArgsCPIndex = cp.add(new ConstantTypeArgumentsForInstanceAllocation( instantiatingClass, typeArgsCPIndex)); } if (typeArgs.isEmpty || !hasTypeParameters(typeArgs)) { asm.emitPushConstant(typeArgsCPIndex); } else { // TODO(alexmarkov): try to reuse instantiator type arguments _genPushInstantiatorAndFunctionTypeArguments(typeArgs); asm.emitInstantiateTypeArgumentsTOS(1, typeArgsCPIndex); } } void _genPushInstantiatorAndFunctionTypeArguments(List types) { // TODO(alexmarkov): do not load instantiator type arguments / function type // arguments if they are not needed for these particular [types]. _genPushInstantiatorTypeArguments(); _genPushFunctionTypeArguments(); } void _genPushInstantiatorTypeArguments() { // TODO(alexmarkov): access to type arguments in factory constructors. if ((enclosingMember.isInstanceMember || enclosingMember is Constructor) && hasInstantiatorTypeArguments(enclosingClass)) { _genPushReceiver(); final int cpIndex = cp.add(new ConstantTypeArgumentsFieldOffset(enclosingClass)); asm.emitLoadFieldTOS(cpIndex); } else { _genPushNull(); } } void _genPushFunctionTypeArguments() { if (locals.hasTypeArgsVar) { asm.emitPush(locals.typeArgsVarIndexInFrame); } else { _genPushNull(); } } void _genPushContextForVariable(VariableDeclaration variable) { int depth = locals.currentContextLevel - locals.getContextLevelOfVar(variable); assert(depth >= 0); asm.emitPush(locals.contextVarIndexInFrame); if (depth > 0) { int cpIndex = cp.add(new ConstantContextOffset.parent()); for (; depth > 0; --depth) { asm.emitLoadFieldTOS(cpIndex); } } } void _genPushContextIfCaptured(VariableDeclaration variable) { if (locals.isCaptured(variable)) { _genPushContextForVariable(variable); } } void _genLoadVar(VariableDeclaration v) { if (locals.isCaptured(v)) { _genPushContextForVariable(v); final int cpIndex = cp.add( new ConstantContextOffset.variable(locals.getVarIndexInContext(v))); asm.emitLoadFieldTOS(cpIndex); } else { asm.emitPush(locals.getVarIndexInFrame(v)); } } void _genPushReceiver() { // TODO(alexmarkov): generate more efficient access to receiver // even if it is captured. _genLoadVar(locals.receiverVar); } // Stores value into variable. // If variable is captured, context should be pushed before value. void _genStoreVar(VariableDeclaration variable) { if (locals.isCaptured(variable)) { final int cpIndex = cp.add(new ConstantContextOffset.variable( locals.getVarIndexInContext(variable))); asm.emitStoreFieldTOS(cpIndex); } else { asm.emitPopLocal(locals.getVarIndexInFrame(variable)); } } /// Generates bool condition. Returns `true` if condition is negated. bool _genCondition(Node condition) { bool negated = false; if (condition is Not) { condition = (condition as Not).operand; negated = true; } condition.accept(this); // TODO(alexmarkov): bool check return negated; } void _genJumpIfFalse(bool negated, Label dest) { asm.emitPushConstant(cp.add(new ConstantBool(true))); if (negated) { asm.emitIfEqStrictTOS(); // if ((!condition) == true) ... } else { asm.emitIfNeStrictTOS(); // if (condition != true) ... } asm.emitJump(dest); // ... then jump dest } void _genJumpIfTrue(bool negated, Label dest) { _genJumpIfFalse(!negated, dest); } int _getDefaultParamConstIndex(VariableDeclaration param) { if (param.initializer == null) { return cp.add(const ConstantNull()); } final constant = constantEvaluator.evaluate(param.initializer); return constant.accept(constantEmitter); } // Duplicates value on top of the stack using temporary variable with // given index. void _genDupTOS(int tempIndexInFrame) { // TODO(alexmarkov): Consider introducing Dup bytecode or keeping track of // expression stack depth. asm.emitStoreLocal(tempIndexInFrame); asm.emitPush(tempIndexInFrame); } /// Generates is-test for the value at TOS. void _genInstanceOf(DartType type) { // TODO(alexmarkov): generate _simpleInstanceOf if possible if (hasTypeParameters([type])) { _genPushInstantiatorAndFunctionTypeArguments([type]); } else { _genPushNull(); // Instantiator type arguments. _genPushNull(); // Function type arguments. } asm.emitPushConstant(cp.add(new ConstantType(type))); final argDescIndex = cp.add(new ConstantArgDesc(4)); final icdataIndex = cp.add(new ConstantICData('_instanceOf', argDescIndex)); asm.emitInstanceCall1(4, icdataIndex); } void start(Member node) { enclosingClass = node.enclosingClass; enclosingMember = node; locals = new LocalVariables(node); // TODO(alexmarkov): improve caching in ConstantEvaluator and reuse it constantEvaluator = new ConstantEvaluator(constantsBackend, typeEnvironment, coreTypes, strongMode, /* enableAsserts = */ true) ..env = new EvaluationEnvironment(); labeledStatements = {}; switchCases = {}; tryCatches = {}; finallyBlocks = >{}; contextLevels = {}; closures = []; cp = new ConstantPool(); constantEmitter = new ConstantEmitter(cp); asm = new BytecodeAssembler(); savedAssemblers = []; locals.enterScope(node); _genPrologue(node, node.function); _genEqualsOperatorNullHandling(node); } // Generate additional code for 'operator ==' to handle nulls. void _genEqualsOperatorNullHandling(Member member) { if (member.name.name != '==' || locals.numParameters != 2 || member.enclosingClass != coreTypes.objectClass) { return; } Label done = new Label(); _genLoadVar(member.function.positionalParameters[0]); _genPushNull(); asm.emitIfNeStrictTOS(); asm.emitJump(done); asm.emitPushConstant(cp.add(new ConstantBool(false))); _genReturnTOS(); asm.bind(done); } void end(Member node) { metadata.mapping[node] = new BytecodeMetadata(cp, asm.bytecode, asm.exceptionsTable, closures); if (isTraceEnabled) { print('Generated bytecode for $node'); } enclosingClass = null; enclosingMember = null; locals = null; constantEvaluator = null; labeledStatements = null; switchCases = null; tryCatches = null; finallyBlocks = null; contextLevels = null; closures = null; cp = null; constantEmitter = null; asm = null; savedAssemblers = null; } void _genPrologue(Node node, FunctionNode function) { if (locals.hasOptionalParameters) { final int numOptionalPositional = function.positionalParameters.length - function.requiredParameterCount; final int numOptionalNamed = function.namedParameters.length; final int numFixed = locals.numParameters - (numOptionalPositional + numOptionalNamed); asm.emitEntryOptional(numFixed, numOptionalPositional, numOptionalNamed); if (numOptionalPositional != 0) { assert(numOptionalNamed == 0); for (int i = 0; i < numOptionalPositional; i++) { final param = function .positionalParameters[function.requiredParameterCount + i]; asm.emitLoadConstant(numFixed + i, _getDefaultParamConstIndex(param)); } } else { assert(numOptionalNamed != 0); for (int i = 0; i < numOptionalNamed; i++) { final param = function.namedParameters[i]; asm.emitLoadConstant( numFixed + i, cp.add(new ConstantString(param.name))); asm.emitLoadConstant(numFixed + i, _getDefaultParamConstIndex(param)); } } asm.emitFrame(locals.frameSize - locals.numParameters); } else { asm.emitEntry(locals.frameSize); } asm.emitCheckStack(); // TODO(alexmarkov): add type checks for parameters final bool isClosure = node is FunctionDeclaration || node is FunctionExpression; if (isClosure) { asm.emitPush(locals.closureVarIndexInFrame); asm.emitLoadFieldTOS(cp.add(new ConstantFieldOffset(closureContext))); asm.emitPopLocal(locals.contextVarIndexInFrame); } _allocateContextIfNeeded(); if (locals.hasCapturedParameters) { // Copy captured parameters to their respective locations in the context. if (locals.hasReceiver) { _copyParamIfCaptured(locals.receiverVar); } function.positionalParameters.forEach(_copyParamIfCaptured); function.namedParameters.forEach(_copyParamIfCaptured); } if (locals.hasTypeArgsVar && isClosure) { if (function.typeParameters.isNotEmpty) { final int numParentTypeArgs = locals.numParentTypeArguments; asm.emitPush(locals.typeArgsVarIndexInFrame); asm.emitPush(locals.closureVarIndexInFrame); asm.emitLoadFieldTOS( cp.add(new ConstantFieldOffset(closureFunctionTypeArguments))); asm.emitPushConstant(cp.add(new ConstantInt(numParentTypeArgs))); asm.emitPushConstant(cp.add(new ConstantInt( numParentTypeArgs + function.typeParameters.length))); _genStaticCall(prependTypeArguments, new ConstantArgDesc(4), 4); asm.emitPopLocal(locals.typeArgsVarIndexInFrame); } else { asm.emitPush(locals.closureVarIndexInFrame); asm.emitLoadFieldTOS( cp.add(new ConstantFieldOffset(closureFunctionTypeArguments))); asm.emitPopLocal(locals.typeArgsVarIndexInFrame); } } } void _copyParamIfCaptured(VariableDeclaration variable) { if (locals.isCaptured(variable)) { _genPushContextForVariable(variable); asm.emitPush(locals.getOriginalParamSlotIndex(variable)); _genStoreVar(variable); // TODO(alexmarkov): Do we need to store null at the original parameter // location? } } void _pushAssemblerState() { savedAssemblers.add(asm); asm = new BytecodeAssembler(); } void _popAssemblerState() { asm = savedAssemblers.removeLast(); } int _genClosureBytecode(TreeNode node, String name, FunctionNode function) { _pushAssemblerState(); locals.enterScope(node); final int closureFunctionIndex = cp.add(new ConstantClosureFunction( name, new CloneWithoutBody().visitFunctionNode(function))); _genPrologue(node, function); function.body.accept(this); // TODO(alexmarkov): figure out when 'return null' should be generated. _genPushNull(); _genReturnTOS(); cp.add(new ConstantEndClosureFunctionScope()); locals.leaveScope(); closures.add(new ClosureBytecode( closureFunctionIndex, asm.bytecode, asm.exceptionsTable)); _popAssemblerState(); return closureFunctionIndex; } void _genAllocateClosureInstance( TreeNode node, int closureFunctionIndex, FunctionNode function) { // TODO(alexmarkov): Consider adding a bytecode to allocate closure. assert(closureClass.typeParameters.isEmpty); asm.emitAllocate(cp.add(new ConstantClass(closureClass))); final int temp = locals.tempIndexInFrame(node); asm.emitStoreLocal(temp); // TODO(alexmarkov): We need to fill _instantiator_type_arguments field // only if function signature uses instantiator type arguments. asm.emitPush(temp); _genPushInstantiatorTypeArguments(); asm.emitStoreFieldTOS( cp.add(new ConstantFieldOffset(closureInstantiatorTypeArguments))); asm.emitPush(temp); _genPushFunctionTypeArguments(); asm.emitStoreFieldTOS( cp.add(new ConstantFieldOffset(closureFunctionTypeArguments))); // TODO(alexmarkov): How to put Object::empty_type_arguments() // to _delayed_type_arguments? asm.emitPush(temp); asm.emitPushConstant(closureFunctionIndex); asm.emitStoreFieldTOS(cp.add(new ConstantFieldOffset(closureFunction))); asm.emitPush(temp); asm.emitPush(locals.contextVarIndexInFrame); asm.emitStoreFieldTOS(cp.add(new ConstantFieldOffset(closureContext))); } void _genClosure(TreeNode node, String name, FunctionNode function) { final int closureFunctionIndex = _genClosureBytecode(node, name, function); _genAllocateClosureInstance(node, closureFunctionIndex, function); } void _allocateContextIfNeeded() { final int contextSize = locals.currentContextSize; if (contextSize > 0) { asm.emitAllocateContext(contextSize); _genDupTOS(locals.scratchVarIndexInFrame); asm.emitPush(locals.contextVarIndexInFrame); asm.emitStoreFieldTOS(cp.add(new ConstantContextOffset.parent())); asm.emitPopLocal(locals.contextVarIndexInFrame); } } void _enterScope(TreeNode node) { locals.enterScope(node); _allocateContextIfNeeded(); } void _leaveScope() { if (locals.currentContextSize > 0) { _genUnwindContext(locals.currentContextLevel - 1); } locals.leaveScope(); } void _genUnwindContext(int targetContextLevel) { int currentContextLevel = locals.currentContextLevel; assert(currentContextLevel >= targetContextLevel); while (currentContextLevel > targetContextLevel) { asm.emitPush(locals.contextVarIndexInFrame); asm.emitLoadFieldTOS(cp.add(new ConstantContextOffset.parent())); asm.emitPopLocal(locals.contextVarIndexInFrame); --currentContextLevel; } } /// Returns the list of try-finally blocks between [from] and [to], /// ordered from inner to outer. If [to] is null, returns all enclosing /// try-finally blocks up to the function boundary. List _getEnclosingTryFinallyBlocks(TreeNode from, TreeNode to) { List blocks = []; TreeNode node = from; for (;;) { if (node == to) { return blocks; } if (node == null || node is FunctionNode || node is Member) { if (to == null) { return blocks; } else { throw 'Unable to find node $to up from $from'; } } // Inspect parent as we only need try-finally blocks enclosing [node] // in the body, and not in the finally-block. final parent = node.parent; if (parent is TryFinally && parent.body == node) { blocks.add(parent); } node = parent; } } /// Generates non-local transfer from inner node [from] into the outer /// node, executing finally blocks on the way out. [to] can be null, /// in such case all enclosing finally blocks are executed. /// [continuation] is invoked to generate control transfer code following /// the last finally block. void _generateNonLocalControlTransfer( TreeNode from, TreeNode to, GenerateContinuation continuation) { List tryFinallyBlocks = _getEnclosingTryFinallyBlocks(from, to); // Add finally blocks to all try-finally from outer to inner. // The outermost finally block should generate continuation, each inner // finally block should proceed to a corresponding outer block. for (var tryFinally in tryFinallyBlocks.reversed) { final finallyBlock = new FinallyBlock(continuation); finallyBlocks[tryFinally].add(finallyBlock); final Label nextFinally = finallyBlock.entry; continuation = () { asm.emitJump(nextFinally); }; } // Generate jump to the innermost finally (or to the original // continuation if there are no try-finally blocks). continuation(); } @override defaultTreeNode(Node node) => throw new UnsupportedOperationError( 'Unsupported node ${node.runtimeType}'); @override visitAsExpression(AsExpression node) { node.operand.accept(this); if (node.type == const DynamicType()) { return; } if (node.isTypeError) { // TODO(alexmarkov): type checks return; } if (hasTypeParameters([node.type])) { _genPushInstantiatorAndFunctionTypeArguments([node.type]); } else { _genPushNull(); // Instantiator type arguments. _genPushNull(); // Function type arguments. } final typeIndex = cp.add(new ConstantType(node.type)); asm.emitPushConstant(typeIndex); final argDescIndex = cp.add(new ConstantArgDesc(4)); final icdataIndex = cp.add(new ConstantICData('_as', argDescIndex)); asm.emitInstanceCall1(4, icdataIndex); } @override visitBoolLiteral(BoolLiteral node) { final cpIndex = cp.add(new ConstantBool.fromLiteral(node)); asm.emitPushConstant(cpIndex); } @override visitIntLiteral(IntLiteral node) { final cpIndex = cp.add(new ConstantInt.fromLiteral(node)); asm.emitPushConstant(cpIndex); } @override visitDoubleLiteral(DoubleLiteral node) { final cpIndex = cp.add(new ConstantDouble.fromLiteral(node)); asm.emitPushConstant(cpIndex); } @override visitConditionalExpression(ConditionalExpression node) { final Label otherwisePart = new Label(); final Label done = new Label(); final int temp = locals.tempIndexInFrame(node); final bool negated = _genCondition(node.condition); _genJumpIfFalse(negated, otherwisePart); node.then.accept(this); asm.emitPopLocal(temp); asm.emitJump(done); asm.bind(otherwisePart); node.otherwise.accept(this); asm.emitPopLocal(temp); asm.bind(done); asm.emitPush(temp); } @override visitConstructorInvocation(ConstructorInvocation node) { if (node.isConst) { _genPushConstExpr(node); return; } final constructedClass = node.constructedType.classNode; final classIndex = cp.add(new ConstantClass(constructedClass)); if (hasInstantiatorTypeArguments(constructedClass)) { _genTypeArguments(node.arguments.types, instantiatingClass: constructedClass); asm.emitPushConstant(cp.add(new ConstantClass(constructedClass))); asm.emitAllocateT(); } else { assert(node.arguments.types.isEmpty); asm.emitAllocate(classIndex); } _genDupTOS(locals.tempIndexInFrame(node)); // Remove type arguments as they are only passed to instance allocation, // and not passed to a constructor. final args = new Arguments(node.arguments.positional, named: node.arguments.named); _genArguments(null, args); _genStaticCallWithArgs(node.target, args, hasReceiver: true); asm.emitDrop1(); } // @override // visitDirectMethodInvocation(DirectMethodInvocation node) { // } @override visitDirectPropertyGet(DirectPropertyGet node) { node.receiver.accept(this); final target = node.target; if (target is Field || (target is Procedure && target.isGetter)) { _genStaticCall(target, new ConstantArgDesc(1), 1, isGet: true); } else { throw new UnsupportedOperationError( 'Unsupported DirectPropertyGet with ${target.runtimeType} $target'); } } // @override // visitDirectPropertySet(DirectPropertySet node) { // } @override visitFunctionExpression(FunctionExpression node) { _genClosure(node, '', node.function); } // @override // visitInstantiation(Instantiation node) { // } // // @override // visitInvalidExpression(InvalidExpression node) { // } @override visitIsExpression(IsExpression node) { node.operand.accept(this); _genInstanceOf(node.type); } @override visitLet(Let node) { _enterScope(node); node.variable.accept(this); node.body.accept(this); _leaveScope(); } @override visitListLiteral(ListLiteral node) { if (node.isConst) { _genPushConstExpr(node); return; } _genTypeArguments([node.typeArgument]); _genDupTOS(locals.tempIndexInFrame(node)); // TODO(alexmarkov): gen more efficient code for empty array _genPushInt(node.expressions.length); asm.emitCreateArrayTOS(); final int temp = locals.tempIndexInFrame(node); asm.emitStoreLocal(temp); for (int i = 0; i < node.expressions.length; i++) { asm.emitPush(temp); _genPushInt(i); node.expressions[i].accept(this); // TODO(alexmarkov): assignable check asm.emitStoreIndexedTOS(); } _genStaticCall(listFromLiteral, new ConstantArgDesc(1, numTypeArgs: 1), 2); } @override visitLogicalExpression(LogicalExpression node) { assert(node.operator == '||' || node.operator == '&&'); final Label shortCircuit = new Label(); final Label done = new Label(); final int temp = locals.tempIndexInFrame(node); final isOR = (node.operator == '||'); bool negated = _genCondition(node.left); asm.emitPushConstant(cp.add(new ConstantBool(true))); if (negated != isOR) { // OR: if (condition == true) // AND: if ((!condition) == true) asm.emitIfEqStrictTOS(); } else { // OR: if ((!condition) != true) // AND: if (condition != true) asm.emitIfNeStrictTOS(); } asm.emitJump(shortCircuit); negated = _genCondition(node.right); if (negated) { asm.emitBooleanNegateTOS(); } asm.emitPopLocal(temp); asm.emitJump(done); asm.bind(shortCircuit); asm.emitPushConstant(cp.add(new ConstantBool(isOR))); asm.emitPopLocal(temp); asm.bind(done); asm.emitPush(temp); } @override visitMapLiteral(MapLiteral node) { if (node.isConst) { _genPushConstExpr(node); return; } _genTypeArguments([node.keyType, node.valueType]); if (node.entries.isEmpty) { asm.emitPushConstant( cp.add(new ConstantList(const DynamicType(), const []))); } else { _genTypeArguments([const DynamicType()]); _genPushInt(node.entries.length * 2); asm.emitCreateArrayTOS(); final int temp = locals.tempIndexInFrame(node); asm.emitStoreLocal(temp); for (int i = 0; i < node.entries.length; i++) { // key asm.emitPush(temp); _genPushInt(i * 2); node.entries[i].key.accept(this); asm.emitStoreIndexedTOS(); // value asm.emitPush(temp); _genPushInt(i * 2 + 1); node.entries[i].value.accept(this); asm.emitStoreIndexedTOS(); } } _genStaticCall(mapFromLiteral, new ConstantArgDesc(1, numTypeArgs: 1), 2); } @override visitMethodInvocation(MethodInvocation node) { final args = node.arguments; _genArguments(node.receiver, args); // TODO(alexmarkov): fast path smi ops final argDescIndex = cp.add(new ConstantArgDesc.fromArguments(args, hasReceiver: true)); final icdataIndex = cp.add(new ConstantICData(node.name.name, argDescIndex)); // TODO(alexmarkov): figure out when generate InstanceCall2 (2 checked arguments). asm.emitInstanceCall1( args.positional.length + args.named.length + 1, icdataIndex); } @override visitPropertyGet(PropertyGet node) { node.receiver.accept(this); final argDescIndex = cp.add(new ConstantArgDesc(1)); final icdataIndex = cp.add( new ConstantICData('$kGetterPrefix${node.name.name}', argDescIndex)); asm.emitInstanceCall1(1, icdataIndex); } @override visitPropertySet(PropertySet node) { final int temp = locals.tempIndexInFrame(node); node.receiver.accept(this); node.value.accept(this); asm.emitStoreLocal(temp); final argDescIndex = cp.add(new ConstantArgDesc(2)); final icdataIndex = cp.add( new ConstantICData('$kSetterPrefix${node.name.name}', argDescIndex)); asm.emitInstanceCall1(2, icdataIndex); asm.emitDrop1(); asm.emitPush(temp); } @override visitSuperMethodInvocation(SuperMethodInvocation node) { final args = node.arguments; _genArguments(new ThisExpression(), args); Member target = hierarchy.getDispatchTarget(enclosingClass.superclass, node.name); if (target == null) { throw new UnsupportedOperationError( 'Unsupported SuperMethodInvocation without target'); } if (target is Procedure && !target.isGetter) { _genStaticCallWithArgs(target, args); } else { throw new UnsupportedOperationError( 'Unsupported SuperMethodInvocation with target ${target.runtimeType} $target'); } } @override visitSuperPropertyGet(SuperPropertyGet node) { _genPushReceiver(); Member target = hierarchy.getDispatchTarget(enclosingClass.superclass, node.name); if (target == null) { throw new UnsupportedOperationError( 'Unsupported SuperPropertyGet without target'); } if (target is Field || (target is Procedure && target.isGetter)) { _genStaticCall(target, new ConstantArgDesc(1), 1, isGet: true); } else { throw new UnsupportedOperationError( 'Unsupported SuperPropertyGet with target ${target.runtimeType} $target'); } } // @override // visitSuperPropertySet(SuperPropertySet node) { // } @override visitNot(Not node) { bool negated = _genCondition(node.operand); if (!negated) { asm.emitBooleanNegateTOS(); } } @override visitNullLiteral(NullLiteral node) { final cpIndex = cp.add(const ConstantNull()); asm.emitPushConstant(cpIndex); } @override visitRethrow(Rethrow node) { TryCatch tryCatch; for (var parent = node.parent;; parent = parent.parent) { if (parent is Catch) { tryCatch = parent.parent as TryCatch; break; } if (parent == null || parent is FunctionNode) { throw 'Unable to find enclosing catch for $node'; } } tryCatches[tryCatch].needsStackTrace = true; _genRethrow(tryCatch); } bool _hasTrivialInitializer(Field field) => (field.initializer == null) || (field.initializer is StringLiteral) || (field.initializer is BoolLiteral) || (field.initializer is IntLiteral) || (field.initializer is NullLiteral); @override visitStaticGet(StaticGet node) { final target = node.target; if (target is Field) { if (target.isConst) { _genPushConstExpr(target.initializer); } else if (_hasTrivialInitializer(target)) { final fieldIndex = cp.add(new ConstantField(target)); asm.emitPushConstant( fieldIndex); // TODO(alexmarkov): do we really need this? asm.emitPushStatic(fieldIndex); } else { _genStaticCall(target, new ConstantArgDesc(0), 0, isGet: true); } } else if (target is Procedure) { if (target.isGetter) { _genStaticCall(target, new ConstantArgDesc(0), 0, isGet: true); } else { final tearOffIndex = cp.add(new ConstantTearOff(target)); asm.emitPushConstant(tearOffIndex); } } else { throw 'Unexpected target for StaticGet: ${target.runtimeType} $target'; } } @override visitStaticInvocation(StaticInvocation node) { final args = node.arguments; bool alwaysPassTypeArgs = false; if (node.target.isFactory && args.types.isEmpty) { // VM needs type arguments for every invocation of a factory constructor. // TODO(alexmarkov): Why? Clean this up. _genPushNull(); alwaysPassTypeArgs = true; } _genArguments(null, args); _genStaticCallWithArgs(node.target, args, alwaysPassTypeArgs: alwaysPassTypeArgs); } @override visitStaticSet(StaticSet node) { node.value.accept(this); _genDupTOS(locals.tempIndexInFrame(node)); final target = node.target; if (target is Field) { // TODO(alexmarkov): assignable check int cpIndex = cp.add(new ConstantField(target)); asm.emitStoreStaticTOS(cpIndex); } else { _genStaticCall(target, new ConstantArgDesc(1), 1, isSet: true); asm.emitDrop1(); } } @override visitStringConcatenation(StringConcatenation node) { if (node.expressions.length == 1) { node.expressions.single.accept(this); _genStaticCall(interpolateSingle, new ConstantArgDesc(1), 1); } else { _genPushNull(); _genPushInt(node.expressions.length); asm.emitCreateArrayTOS(); final int temp = locals.tempIndexInFrame(node); asm.emitStoreLocal(temp); for (int i = 0; i < node.expressions.length; i++) { asm.emitPush(temp); _genPushInt(i); node.expressions[i].accept(this); asm.emitStoreIndexedTOS(); } _genStaticCall(interpolate, new ConstantArgDesc(1), 1); } } @override visitStringLiteral(StringLiteral node) { final cpIndex = cp.add(new ConstantString.fromLiteral(node)); asm.emitPushConstant(cpIndex); } @override visitSymbolLiteral(SymbolLiteral node) { final cpIndex = cp.add(new ConstantSymbol.fromLiteral(node)); asm.emitPushConstant(cpIndex); } @override visitThisExpression(ThisExpression node) { _genPushReceiver(); } @override visitThrow(Throw node) { node.expression.accept(this); asm.emitThrow(0); } @override visitTypeLiteral(TypeLiteral node) { final DartType type = node.type; final int typeCPIndex = cp.add(new ConstantType(type)); if (!hasTypeParameters([type])) { asm.emitPushConstant(typeCPIndex); } else { _genPushInstantiatorAndFunctionTypeArguments([type]); asm.emitInstantiateType(typeCPIndex); } } @override visitVariableGet(VariableGet node) { final v = node.variable; if (v.isConst) { _genPushConstExpr(v.initializer); } else { _genLoadVar(v); } } @override visitVariableSet(VariableSet node) { final v = node.variable; if (locals.isCaptured(v)) { _genPushContextForVariable(v); node.value.accept(this); // Preserve value. final int temp = locals.tempIndexInFrame(node); asm.emitStoreLocal(temp); _genStoreVar(v); asm.emitPush(temp); } else { node.value.accept(this); asm.emitStoreLocal(locals.getVarIndexInFrame(v)); } } // @override // visitLoadLibrary(LoadLibrary node) { // } // // @override // visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { // } // // @override // visitVectorCreation(VectorCreation node) { // } // // @override // visitVectorGet(VectorGet node) { // } // // @override // visitVectorSet(VectorSet node) { // } // // @override // visitVectorCopy(VectorCopy node) { // } // // @override // visitClosureCreation(ClosureCreation node) { // } @override visitAssertStatement(AssertStatement node) { // TODO(alexmarkov): support asserts } @override visitBlock(Block node) { _enterScope(node); visitList(node.statements, this); _leaveScope(); } @override visitAssertBlock(AssertBlock node) { // TODO(alexmarkov): support asserts } @override visitBreakStatement(BreakStatement node) { final targetLabel = labeledStatements[node.target] ?? (throw 'Target label ${node.target} was not registered for break $node'); final targetContextLevel = contextLevels[node.target]; _generateNonLocalControlTransfer(node, node.target, () { _genUnwindContext(targetContextLevel); asm.emitJump(targetLabel); }); } @override visitContinueSwitchStatement(ContinueSwitchStatement node) { final targetLabel = switchCases[node.target] ?? (throw 'Target label ${node.target} was not registered for continue-switch $node'); final targetContextLevel = contextLevels[node.target.parent]; _generateNonLocalControlTransfer(node, node.target.parent, () { _genUnwindContext(targetContextLevel); asm.emitJump(targetLabel); }); } @override visitDoStatement(DoStatement node) { final Label join = new Label(); asm.bind(join); asm.emitCheckStack(); node.body.accept(this); // TODO(alexmarkov): do we need to break this critical edge in CFG? bool negated = _genCondition(node.condition); _genJumpIfTrue(negated, join); } @override visitEmptyStatement(EmptyStatement node) { // no-op } @override visitExpressionStatement(ExpressionStatement node) { node.expression.accept(this); asm.emitDrop1(); } @override visitForInStatement(ForInStatement node) { node.iterable.accept(this); const kIterator = 'iterator'; // Iterable.iterator const kMoveNext = 'moveNext'; // Iterator.moveNext const kCurrent = 'current'; // Iterator.current asm.emitInstanceCall1( 1, cp.add(new ConstantICData( '$kGetterPrefix$kIterator', cp.add(new ConstantArgDesc(1))))); final iteratorTemp = locals.tempIndexInFrame(node); asm.emitPopLocal(iteratorTemp); final Label done = new Label(); final Label join = new Label(); asm.bind(join); asm.emitCheckStack(); asm.emitPush(iteratorTemp); asm.emitInstanceCall1(1, cp.add(new ConstantICData(kMoveNext, cp.add(new ConstantArgDesc(1))))); _genJumpIfFalse(/* negated = */ false, done); _enterScope(node); _genPushContextIfCaptured(node.variable); asm.emitPush(iteratorTemp); asm.emitInstanceCall1( 1, cp.add(new ConstantICData( '$kGetterPrefix$kCurrent', cp.add(new ConstantArgDesc(1))))); _genStoreVar(node.variable); node.body.accept(this); _leaveScope(); asm.emitJump(join); asm.bind(done); } @override visitForStatement(ForStatement node) { _enterScope(node); visitList(node.variables, this); final Label done = new Label(); final Label join = new Label(); asm.bind(join); asm.emitCheckStack(); if (node.condition != null) { bool negated = _genCondition(node.condition); _genJumpIfFalse(negated, done); } node.body.accept(this); if (locals.currentContextSize > 0) { asm.emitPush(locals.contextVarIndexInFrame); asm.emitCloneContext(); asm.emitPopLocal(locals.contextVarIndexInFrame); } for (var update in node.updates) { update.accept(this); asm.emitDrop1(); } asm.emitJump(join); asm.bind(done); _leaveScope(); } @override visitFunctionDeclaration(FunctionDeclaration node) { _genPushContextIfCaptured(node.variable); _genClosure(node, node.variable.name, node.function); _genStoreVar(node.variable); } @override visitIfStatement(IfStatement node) { final Label otherwisePart = new Label(); final bool negated = _genCondition(node.condition); _genJumpIfFalse(negated, otherwisePart); node.then.accept(this); if (node.otherwise != null) { final Label done = new Label(); asm.emitJump(done); asm.bind(otherwisePart); node.otherwise.accept(this); asm.bind(done); } else { asm.bind(otherwisePart); } } @override visitLabeledStatement(LabeledStatement node) { final label = new Label(); labeledStatements[node] = label; contextLevels[node] = locals.currentContextLevel; node.body.accept(this); asm.bind(label); labeledStatements.remove(node); contextLevels.remove(node); } @override visitReturnStatement(ReturnStatement node) { if (node.expression != null) { node.expression.accept(this); } else { _genPushNull(); } // TODO(alexmarkov): Do we need to save return value // to a variable? _generateNonLocalControlTransfer(node, null, () { asm.emitReturnTOS(); }); } @override visitSwitchStatement(SwitchStatement node) { contextLevels[node] = locals.currentContextLevel; node.expression.accept(this); final int temp = locals.tempIndexInFrame(node); asm.emitPopLocal(temp); final Label done = new Label(); final List