| // 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/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/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}) { |
| 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); |
| } |
| |
| class BytecodeGenerator extends RecursiveVisitor<Null> { |
| 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; |
| BytecodeAssembler asm; |
| ConstantPool cp; |
| LocalVariables locals; |
| ConstantEmitter constantEmitter; |
| ConstantEvaluator constantEvaluator; |
| Map<LabeledStatement, Label> labeledStatements; |
| Map<SwitchCase, Label> switchCases; |
| |
| 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'); |
| |
| 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; |
| } |
| |
| asm.emitPush(locals.thisVarIndex); |
| 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) { |
| final argDescIndex = cp.add(argDesc); |
| final icdataIndex = cp.add(new ConstantStaticICData(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<DartType> 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)); |
| } |
| |
| bool isGenericFunction(Member member) { |
| final function = member.function; |
| return function != null && function.typeParameters.isNotEmpty; |
| } |
| |
| void _genTypeArguments(List<DartType> 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<DartType> 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 from closures to up-level type arguments. |
| if ((enclosingMember.isInstanceMember || enclosingMember is Constructor) && |
| hasInstantiatorTypeArguments(enclosingClass)) { |
| asm.emitPush(locals.thisVarIndex); |
| final int cpIndex = |
| cp.add(new ConstantTypeArgumentsFieldOffset(enclosingClass)); |
| asm.emitLoadFieldTOS(cpIndex); |
| } else { |
| _genPushNull(); |
| } |
| } |
| |
| void _genPushFunctionTypeArguments() { |
| // TODO(alexmarkov): closures |
| if (isGenericFunction(enclosingMember)) { |
| asm.emitPush(locals.functionTypeArgsVarIndex); |
| } else { |
| _genPushNull(); |
| } |
| } |
| |
| /// 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 |
| } |
| |
| // Duplicates value on top of the stack using temporary variable |
| // corresponding to [node]. |
| void _genDupTOS(TreeNode node) { |
| // TODO(alexmarkov): Consider introducing Dup bytecode or keeping track of |
| // expression stack depth. |
| final int temp = locals.tempIndex(node); |
| asm.emitStoreLocal(temp); |
| asm.emitPush(temp); |
| } |
| |
| void start(Member node) { |
| enclosingMember = node; |
| enclosingClass = node.enclosingClass; |
| asm = new BytecodeAssembler(); |
| cp = new ConstantPool(); |
| locals = new LocalVariables(); |
| constantEmitter = new ConstantEmitter(cp); |
| // TODO(alexmarkov): improve caching in ConstantEvaluator and reuse it |
| constantEvaluator = new ConstantEvaluator(constantsBackend, typeEnvironment, |
| coreTypes, strongMode, /* enableAsserts = */ true) |
| ..env = new EvaluationEnvironment(); |
| labeledStatements = <LabeledStatement, Label>{}; |
| switchCases = <SwitchCase, Label>{}; |
| |
| node.accept(locals); |
| |
| if (locals.hasOptionalParameters) { |
| final function = node.function; |
| 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 |
| } |
| |
| int _getDefaultParamConstIndex(VariableDeclaration param) { |
| if (param.initializer == null) { |
| return cp.add(const ConstantNull()); |
| } |
| final constant = constantEvaluator.evaluate(param.initializer); |
| return constant.accept(constantEmitter); |
| } |
| |
| void _genJumpIfTrue(bool negated, Label dest) { |
| _genJumpIfFalse(!negated, dest); |
| } |
| |
| void end(Member node) { |
| enclosingMember = null; |
| enclosingClass = null; |
| metadata.mapping[node] = new BytecodeMetadata(asm.bytecode, cp); |
| if (isTraceEnabled) { |
| print('Generated bytecode for $node'); |
| } |
| } |
| |
| @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.tempIndex(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(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); |
| } else { |
| throw new UnsupportedOperationError( |
| 'Unsupported DirectPropertyGet with ${target.runtimeType} $target'); |
| } |
| } |
| |
| // @override |
| // visitDirectPropertySet(DirectPropertySet node) { |
| // } |
| // |
| // @override |
| // visitFunctionExpression(FunctionExpression node) { |
| // } |
| // |
| // @override |
| // visitInstantiation(Instantiation node) { |
| // } |
| // |
| // @override |
| // visitInvalidExpression(InvalidExpression node) { |
| // } |
| |
| @override |
| visitIsExpression(IsExpression node) { |
| node.operand.accept(this); |
| |
| // TODO(alexmarkov): generate _simpleInstanceOf if possible |
| |
| 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('_instanceOf', argDescIndex)); |
| asm.emitInstanceCall1(4, icdataIndex); |
| } |
| |
| @override |
| visitLet(Let node) { |
| node.variable.accept(this); |
| node.body.accept(this); |
| } |
| |
| @override |
| visitListLiteral(ListLiteral node) { |
| if (node.isConst) { |
| _genPushConstExpr(node); |
| return; |
| } |
| |
| _genTypeArguments([node.typeArgument]); |
| |
| _genDupTOS(node); |
| |
| // TODO(alexmarkov): gen more efficient code for empty array |
| _genPushInt(node.expressions.length); |
| asm.emitCreateArrayTOS(); |
| final int temp = locals.tempIndex(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.tempIndex(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.tempIndex(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.tempIndex(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) { |
| asm.emitPush(locals.thisVarIndex); |
| 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); |
| } 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) { |
| // } |
| |
| 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); |
| } |
| } else if (target is Procedure) { |
| if (target.isGetter) { |
| _genStaticCall(target, new ConstantArgDesc(0), 0); |
| } 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(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); |
| 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.tempIndex(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) { |
| // TODO(alexmarkov): access to captured this from closures. |
| asm.emitPush(locals.thisVarIndex); |
| } |
| |
| @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) { |
| if (node.variable.isConst) { |
| _genPushConstExpr(node.variable.initializer); |
| } else { |
| // TODO(alexmarkov): access to captured variables. |
| asm.emitPush(locals.varIndex(node.variable)); |
| } |
| } |
| |
| @override |
| visitVariableSet(VariableSet node) { |
| node.value.accept(this); |
| // TODO(alexmarkov): access to captured variables. |
| asm.emitStoreLocal(locals.varIndex(node.variable)); |
| } |
| |
| // @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) { |
| visitList(node.statements, this); |
| } |
| |
| @override |
| visitAssertBlock(AssertBlock node) { |
| // TODO(alexmarkov): support asserts |
| } |
| |
| @override |
| visitBreakStatement(BreakStatement node) { |
| // TODO(alexmarkov): execute all finally blocks on the way out. |
| final label = labeledStatements[node.target] ?? |
| (throw 'Target label ${node.target} was not registered for break $node'); |
| asm.emitJump(label); |
| } |
| |
| @override |
| visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| // TODO(alexmarkov): execute all finally blocks on the way out. |
| final label = switchCases[node.target] ?? |
| (throw 'Target label ${node.target} was not registered for continue-switch $node'); |
| asm.emitJump(label); |
| } |
| |
| @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.tempIndex(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); |
| |
| asm.emitPush(iteratorTemp); |
| asm.emitInstanceCall1(1, |
| cp.add(new ConstantICData(kCurrent, cp.add(new ConstantArgDesc(1))))); |
| asm.emitPopLocal(locals.varIndex(node.variable)); |
| |
| node.body.accept(this); |
| |
| asm.emitJump(join); |
| |
| asm.bind(done); |
| } |
| |
| @override |
| visitForStatement(ForStatement 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); |
| |
| for (var update in node.updates) { |
| update.accept(this); |
| asm.emitDrop1(); |
| } |
| |
| asm.emitJump(join); |
| |
| asm.bind(done); |
| } |
| |
| // @override |
| // visitFunctionDeclaration(FunctionDeclaration node) { |
| // } |
| |
| @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; |
| node.body.accept(this); |
| asm.bind(label); |
| labeledStatements[node] = null; |
| } |
| |
| @override |
| visitReturnStatement(ReturnStatement node) { |
| if (node.expression != null) { |
| node.expression.accept(this); |
| } else { |
| _genPushNull(); |
| } |
| asm.emitReturnTOS(); |
| } |
| |
| @override |
| visitSwitchStatement(SwitchStatement node) { |
| node.expression.accept(this); |
| |
| final int temp = locals.tempIndex(node); |
| asm.emitPopLocal(temp); |
| |
| final Label done = new Label(); |
| final List<Label> caseLabels = |
| new List<Label>.generate(node.cases.length, (_) => new Label()); |
| final equalsArgDesc = cp.add(new ConstantArgDesc(2)); |
| |
| Label defaultLabel = done; |
| for (int i = 0; i < node.cases.length; i++) { |
| final SwitchCase switchCase = node.cases[i]; |
| final Label caseLabel = caseLabels[i]; |
| switchCases[switchCase] = caseLabel; |
| |
| if (switchCase.isDefault) { |
| defaultLabel = caseLabel; |
| } else { |
| for (var expr in switchCase.expressions) { |
| asm.emitPush(temp); |
| _genPushConstExpr(expr); |
| asm.emitInstanceCall2( |
| 2, cp.add(new ConstantICData('==', equalsArgDesc))); |
| _genJumpIfTrue(/* negated = */ false, caseLabel); |
| } |
| } |
| } |
| |
| asm.emitJump(defaultLabel); |
| |
| for (int i = 0; i < node.cases.length; i++) { |
| final SwitchCase switchCase = node.cases[i]; |
| final Label caseLabel = caseLabels[i]; |
| |
| asm.bind(caseLabel); |
| switchCase.body.accept(this); |
| |
| // Front-end issues a compile-time error if there is a fallthrough |
| // between cases. Also, default case should be the last one. |
| } |
| |
| asm.bind(done); |
| node.cases.forEach(switchCases.remove); |
| } |
| |
| // @override |
| // visitTryCatch(TryCatch node) { |
| // } |
| // |
| // @override |
| // visitTryFinally(TryFinally node) { |
| // } |
| |
| @override |
| visitVariableDeclaration(VariableDeclaration node) { |
| if (node.isConst) { |
| final Constant constant = constantEvaluator.evaluate(node.initializer); |
| constantEvaluator.env.addVariableValue(node, constant); |
| } else { |
| if (node.initializer != null) { |
| node.initializer.accept(this); |
| } else { |
| _genPushNull(); |
| } |
| asm.emitPopLocal(locals.varIndex(node)); |
| } |
| } |
| |
| @override |
| visitWhileStatement(WhileStatement node) { |
| final Label done = new Label(); |
| final Label join = new Label(); |
| asm.bind(join); |
| |
| asm.emitCheckStack(); |
| |
| bool negated = _genCondition(node.condition); |
| _genJumpIfFalse(negated, done); |
| |
| node.body.accept(this); |
| |
| asm.emitJump(join); |
| |
| asm.bind(done); |
| } |
| |
| // @override |
| // visitYieldStatement(YieldStatement node) { |
| // } |
| |
| @override |
| visitFieldInitializer(FieldInitializer node) { |
| _genFieldInitializer(node.field, node.value); |
| } |
| |
| @override |
| visitRedirectingInitializer(RedirectingInitializer node) { |
| final args = node.arguments; |
| assert(args.types.isEmpty); |
| _genArguments(new ThisExpression(), args); |
| _genStaticCallWithArgs(node.target, args, hasReceiver: true); |
| asm.emitDrop1(); |
| } |
| |
| @override |
| visitSuperInitializer(SuperInitializer node) { |
| final args = node.arguments; |
| assert(args.types.isEmpty); |
| _genArguments(new ThisExpression(), args); |
| // Re-resolve target due to partial mixin resolution. |
| Member target; |
| for (var replacement in enclosingClass.superclass.constructors) { |
| if (node.target.name == replacement.name) { |
| target = replacement; |
| break; |
| } |
| } |
| assert(target != null); |
| _genStaticCallWithArgs(target, args, hasReceiver: true); |
| asm.emitDrop1(); |
| } |
| |
| // @override |
| // visitLocalInitializer(LocalInitializer node) { |
| // } |
| // |
| // @override |
| // visitAssertInitializer(AssertInitializer node) { |
| // } |
| // |
| // @override |
| // visitInvalidInitializer(InvalidInitializer node) {} |
| |
| @override |
| visitConstantExpression(ConstantExpression node) { |
| int cpIndex = node.constant.accept(constantEmitter); |
| asm.emitPushConstant(cpIndex); |
| } |
| } |
| |
| class ConstantEmitter extends ConstantVisitor<int> { |
| final ConstantPool cp; |
| |
| ConstantEmitter(this.cp); |
| |
| @override |
| int defaultConstant(Constant node) => throw new UnsupportedOperationError( |
| 'Unsupported constant node ${node.runtimeType}'); |
| |
| @override |
| int visitNullConstant(NullConstant node) => cp.add(const ConstantNull()); |
| |
| @override |
| int visitBoolConstant(BoolConstant node) => |
| cp.add(new ConstantBool(node.value)); |
| |
| @override |
| int visitIntConstant(IntConstant node) => cp.add(new ConstantInt(node.value)); |
| |
| @override |
| int visitDoubleConstant(DoubleConstant node) => |
| cp.add(new ConstantDouble(node.value)); |
| |
| @override |
| int visitStringConstant(StringConstant node) => |
| cp.add(new ConstantString(node.value)); |
| |
| @override |
| int visitListConstant(ListConstant node) => cp.add(new ConstantList( |
| node.typeArgument, |
| new List<int>.from(node.entries.map((Constant c) => c.accept(this))))); |
| |
| @override |
| int visitInstanceConstant(InstanceConstant node) => |
| cp.add(new ConstantInstance( |
| node.klass, |
| cp.add(new ConstantTypeArguments(node.typeArguments)), |
| node.fieldValues.map<Reference, int>( |
| (Reference fieldRef, Constant value) => |
| new MapEntry(fieldRef, value.accept(this))))); |
| |
| @override |
| int visitTearOffConstant(TearOffConstant node) => |
| cp.add(new ConstantTearOff(node.procedure)); |
| |
| // @override |
| // int visitTypeLiteralConstant(TypeLiteralConstant node) => defaultConstant(node); |
| } |
| |
| class UnsupportedOperationError { |
| final String message; |
| UnsupportedOperationError(this.message); |
| |
| @override |
| String toString() => message; |
| } |
| |
| class FindTypeParametersVisitor extends DartTypeVisitor<bool> { |
| bool visit(DartType type) => type.accept(this); |
| |
| @override |
| bool defaultDartType(DartType node) => |
| throw 'Unexpected type ${node.runtimeType} $node'; |
| |
| @override |
| bool visitInvalidType(InvalidType node) => false; |
| |
| @override |
| bool visitDynamicType(DynamicType node) => false; |
| |
| @override |
| bool visitVoidType(VoidType node) => false; |
| |
| @override |
| bool visitBottomType(BottomType node) => false; |
| |
| @override |
| bool visitVectorType(VectorType node) => false; |
| |
| @override |
| bool visitTypeParameterType(TypeParameterType node) => true; |
| |
| @override |
| bool visitInterfaceType(InterfaceType node) => |
| node.typeArguments.any((t) => t.accept(this)); |
| |
| @override |
| bool visitTypedefType(TypedefType node) => |
| node.typeArguments.any((t) => t.accept(this)); |
| |
| @override |
| bool visitFunctionType(FunctionType node) => |
| node.typeParameters.isNotEmpty || |
| node.positionalParameters.any((t) => t.accept(this)) || |
| node.namedParameters.any((p) => p.type.accept(this)); |
| } |