// Copyright (c) 2017, 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. /// Defines the VM-specific translation of Dart source code to kernel binaries. library vm.kernel_front_end; import 'dart:async'; import 'package:front_end/src/base/processed_options.dart' show ProcessedOptions; import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; import 'package:front_end/src/fasta/fasta_codes.dart' as codes; import 'package:front_end/src/api_prototype/compiler_options.dart' show CompilerOptions, ProblemHandler; import 'package:front_end/src/api_prototype/kernel_generator.dart' show kernelForProgram; import 'package:front_end/src/api_prototype/compilation_message.dart' show Severity; import 'package:kernel/type_environment.dart' show TypeEnvironment; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/ast.dart' show Component, Constant, DartType, Field, FileUriNode, Procedure, StaticGet, TreeNode; import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/transformations/constants.dart' as constants; import 'package:kernel/vm/constants_native_effects.dart' as vm_constants; import 'bytecode/gen_bytecode.dart' show generateBytecode; import 'transformations/devirtualization.dart' as devirtualization show transformComponent; import 'transformations/mixin_deduplication.dart' as mixin_deduplication show transformComponent; import 'transformations/no_dynamic_invocations_annotator.dart' as no_dynamic_invocations_annotator show transformComponent; import 'transformations/type_flow/transformer.dart' as globalTypeFlow show transformComponent; /// Generates a kernel representation of the program whose main library is in /// the given [source]. Intended for whole program (non-modular) compilation. /// /// VM-specific replacement of [kernelForProgram]. /// Future compileToKernel(Uri source, CompilerOptions options, {bool aot: false, bool useGlobalTypeFlowAnalysis: false, List entryPoints, Map environmentDefines, bool genBytecode: false, bool dropAST: false, bool enableAsserts: false, bool enableConstantEvaluation: true}) async { // Replace error handler to detect if there are compilation errors. final errorDetector = new ErrorDetector(previousErrorHandler: options.onProblem); options.onProblem = errorDetector; final component = await kernelForProgram(source, options); // Run global transformations only if component is correct. if (aot && component != null) { await _runGlobalTransformations( source, options, component, options.strongMode, useGlobalTypeFlowAnalysis, entryPoints, environmentDefines, enableAsserts, enableConstantEvaluation, errorDetector); } // Restore error handler (in case 'options' are reused). options.onProblem = errorDetector.previousErrorHandler; if (genBytecode && component != null) { generateBytecode(component, strongMode: options.strongMode, dropAST: dropAST); } return component; } Future _runGlobalTransformations( Uri source, CompilerOptions compilerOptions, Component component, bool strongMode, bool useGlobalTypeFlowAnalysis, List entryPoints, Map environmentDefines, bool enableAsserts, bool enableConstantEvaluation, ErrorDetector errorDetector) async { if (strongMode) { if (errorDetector.hasCompilationErrors) return; final coreTypes = new CoreTypes(component); _patchVmConstants(coreTypes); // TODO(alexmarkov, dmitryas): Consider doing canonicalization of identical // mixin applications when creating mixin applications in frontend, // so all backends (and all transformation passes from the very beginning) // can benefit from mixin de-duplication. // At least, in addition to VM/AOT case we should run this transformation // when building a platform dill file for VM/JIT case. mixin_deduplication.transformComponent(component); if (enableConstantEvaluation) { await _performConstantEvaluation(source, compilerOptions, component, coreTypes, environmentDefines, strongMode, enableAsserts); if (errorDetector.hasCompilationErrors) return; } if (useGlobalTypeFlowAnalysis) { globalTypeFlow.transformComponent(coreTypes, component, entryPoints); } else { devirtualization.transformComponent(coreTypes, component); } no_dynamic_invocations_annotator.transformComponent(component); } } Future _performConstantEvaluation( Uri source, CompilerOptions compilerOptions, Component component, CoreTypes coreTypes, Map environmentDefines, bool strongMode, bool enableAsserts) async { final vmConstants = new vm_constants.VmConstantsBackend(environmentDefines, coreTypes); final processedOptions = new ProcessedOptions(compilerOptions, [source]); // Run within the context, so we have uri source tokens... await CompilerContext.runWithOptions(processedOptions, (CompilerContext context) async { // To make the fileUri/fileOffset -> line/column mapping, we need to // pre-fill the map. context.uriToSource.addAll(component.uriToSource); final hierarchy = new ClassHierarchy(component); final typeEnvironment = new TypeEnvironment(coreTypes, hierarchy, strongMode: strongMode); // TFA will remove constants fields which are unused (and respects the // vm/embedder entrypoints). constants.transformComponent(component, vmConstants, keepFields: true, strongMode: true, evaluateAnnotations: true, enableAsserts: enableAsserts, errorReporter: new ForwardConstantEvaluationErrors(context, typeEnvironment)); }); } void _patchVmConstants(CoreTypes coreTypes) { // Fix Endian.host to be a const field equal to Endial.little instead of // a final field. VM does not support big-endian architectures at the // moment. // Can't use normal patching process for this because CFE does not // support patching fields. // See http://dartbug.com/32836 for the background. final Field host = coreTypes.index.getMember('dart:typed_data', 'Endian', 'host'); host.isConst = true; host.initializer = new StaticGet( coreTypes.index.getMember('dart:typed_data', 'Endian', 'little')) ..parent = host; } class ErrorDetector { final ProblemHandler previousErrorHandler; bool hasCompilationErrors = false; ErrorDetector({this.previousErrorHandler}); void call(codes.FormattedMessage problem, Severity severity, List context) { if (severity == Severity.error) { hasCompilationErrors = true; } previousErrorHandler?.call(problem, severity, context); } } class ErrorPrinter { final ProblemHandler previousErrorHandler; final compilationMessages = >{}; ErrorPrinter({this.previousErrorHandler}); void call(codes.FormattedMessage problem, Severity severity, List context) { final sourceUri = problem.locatedMessage.uri; compilationMessages.putIfAbsent(sourceUri, () => []) ..add([problem, context]); previousErrorHandler?.call(problem, severity, context); } void printCompilationMessages(Uri baseUri) { final sortedUris = compilationMessages.keys.toList() ..sort((a, b) => '$a'.compareTo('$b')); for (final Uri sourceUri in sortedUris) { for (final List errorTuple in compilationMessages[sourceUri]) { final codes.FormattedMessage message = errorTuple.first; print(message.formatted); final List context = errorTuple.last; for (final codes.FormattedMessage message in context?.reversed) { print(message.formatted); } } } } } class ForwardConstantEvaluationErrors implements constants.ErrorReporter { final CompilerContext compilerContext; final TypeEnvironment typeEnvironment; ForwardConstantEvaluationErrors(this.compilerContext, this.typeEnvironment); duplicateKey(List context, TreeNode node, Constant key) { final message = codes.templateConstEvalDuplicateKey.withArguments(key); reportIt(context, message, node); } invalidDartType(List context, TreeNode node, Constant receiver, DartType expectedType) { final message = codes.templateConstEvalInvalidType.withArguments( receiver, expectedType, receiver.getType(typeEnvironment)); reportIt(context, message, node); } invalidBinaryOperandType( List context, TreeNode node, Constant receiver, String op, DartType expectedType, DartType actualType) { final message = codes.templateConstEvalInvalidBinaryOperandType .withArguments(op, receiver, expectedType, actualType); reportIt(context, message, node); } invalidMethodInvocation( List context, TreeNode node, Constant receiver, String op) { final message = codes.templateConstEvalInvalidMethodInvocation .withArguments(op, receiver); reportIt(context, message, node); } invalidStaticInvocation( List context, TreeNode node, Procedure target) { final message = codes.templateConstEvalInvalidStaticInvocation .withArguments(target.name.toString()); reportIt(context, message, node); } invalidStringInterpolationOperand( List context, TreeNode node, Constant constant) { final message = codes.templateConstEvalInvalidStringInterpolationOperand .withArguments(constant); reportIt(context, message, node); } nonConstLiteral(List context, TreeNode node, String klass) { final message = codes.templateConstEvalNonConstantLiteral.withArguments(klass); reportIt(context, message, node); } failedAssertion(List context, TreeNode node, String string) { final message = string == null ? codes.messageConstEvalFailedAssertion : codes.templateConstEvalFailedAssertionWithMessage .withArguments(string); reportIt(context, message, node); } reportIt(List context, codes.Message message, TreeNode node) { final Uri uri = getFileUri(node); final int fileOffset = getFileOffset(node); final contextMessages = []; for (final TreeNode node in context) { final Uri uri = getFileUri(node); final int fileOffset = getFileOffset(node); contextMessages.add(codes.messageConstEvalContext .withLocation(uri, fileOffset, codes.noLength)); } final locatedMessage = message.withLocation(uri, fileOffset, codes.noLength); compilerContext.options .report(locatedMessage, Severity.error, context: contextMessages); } getFileUri(TreeNode node) { while (node is! FileUriNode) { node = node.parent; } return (node as FileUriNode).fileUri; } getFileOffset(TreeNode node) { while (node.fileOffset == TreeNode.noOffset) { node = node.parent; } return node == null ? TreeNode.noOffset : node.fileOffset; } }