blob: 286d808580049b2fa174164309e485653ff79812 [file] [log] [blame]
// 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,
IntConstant,
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<Component> compileToKernel(Uri source, CompilerOptions options,
{bool aot: false,
bool useGlobalTypeFlowAnalysis: false,
List<String> entryPoints,
Map<String, String> 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 (!errorDetector.hasCompilationErrors && 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<String> entryPoints,
Map<String, String> 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<String, String> 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<codes.FormattedMessage> context) {
if (severity == Severity.error) {
hasCompilationErrors = true;
}
previousErrorHandler?.call(problem, severity, context);
}
}
class ErrorPrinter {
final ProblemHandler previousErrorHandler;
final compilationMessages = <Uri, List<List>>{};
ErrorPrinter({this.previousErrorHandler});
void call(codes.FormattedMessage problem, Severity severity,
List<codes.FormattedMessage> 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<TreeNode> context, TreeNode node, Constant key) {
final message = codes.templateConstEvalDuplicateKey.withArguments(key);
reportIt(context, message, node);
}
invalidDartType(List<TreeNode> context, TreeNode node, Constant receiver,
DartType expectedType) {
final message = codes.templateConstEvalInvalidType.withArguments(
receiver, expectedType, receiver.getType(typeEnvironment));
reportIt(context, message, node);
}
invalidBinaryOperandType(
List<TreeNode> 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<TreeNode> context, TreeNode node, Constant receiver, String op) {
final message = codes.templateConstEvalInvalidMethodInvocation
.withArguments(op, receiver);
reportIt(context, message, node);
}
invalidStaticInvocation(
List<TreeNode> context, TreeNode node, Procedure target) {
final message = codes.templateConstEvalInvalidStaticInvocation
.withArguments(target.name.toString());
reportIt(context, message, node);
}
invalidStringInterpolationOperand(
List<TreeNode> context, TreeNode node, Constant constant) {
final message = codes.templateConstEvalInvalidStringInterpolationOperand
.withArguments(constant);
reportIt(context, message, node);
}
zeroDivisor(
List<TreeNode> context, TreeNode node, IntConstant receiver, String op) {
final message = codes.templateConstEvalZeroDivisor
.withArguments(op, '${receiver.value}');
reportIt(context, message, node);
}
negativeShift(List<TreeNode> context, TreeNode node, IntConstant receiver,
String op, IntConstant argument) {
final message = codes.templateConstEvalNegativeShift
.withArguments(op, '${receiver.value}', '${argument.value}');
reportIt(context, message, node);
}
nonConstLiteral(List<TreeNode> context, TreeNode node, String klass) {
final message =
codes.templateConstEvalNonConstantLiteral.withArguments(klass);
reportIt(context, message, node);
}
failedAssertion(List<TreeNode> context, TreeNode node, String string) {
final message = string == null
? codes.messageConstEvalFailedAssertion
: codes.templateConstEvalFailedAssertionWithMessage
.withArguments(string);
reportIt(context, message, node);
}
nonConstantVariableGet(
List<TreeNode> context, TreeNode node, String variableName) {
final message = codes.templateConstEvalNonConstantVariableGet
.withArguments(variableName);
reportIt(context, message, node);
}
reportIt(List<TreeNode> context, codes.Message message, TreeNode node) {
final Uri uri = getFileUri(node);
final int fileOffset = getFileOffset(node);
final contextMessages = <codes.LocatedMessage>[];
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;
}
}
bool parseCommandLineDefines(
List<String> dFlags, Map<String, String> environmentDefines, String usage) {
for (final String dflag in dFlags) {
final equalsSignIndex = dflag.indexOf('=');
if (equalsSignIndex < 0) {
environmentDefines[dflag] = '';
} else if (equalsSignIndex > 0) {
final key = dflag.substring(0, equalsSignIndex);
final value = dflag.substring(equalsSignIndex + 1);
environmentDefines[key] = value;
} else {
print('The environment constant options must have a key (was: "$dflag")');
print(usage);
return false;
}
}
return true;
}