blob: cf51d9c135fb7be19bdad3a04e403b2701bb4e8c [file] [log] [blame]
// Copyright (c) 2012, 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.
class WorkItem {
final Element element;
TreeElements resolutionTree;
Function run;
bool allowSpeculativeOptimization = true;
List<HTypeGuard> guards = const <HTypeGuard>[];
WorkItem.toCompile(this.element) : resolutionTree = null {
run = this.compile;
}
WorkItem.toCodegen(this.element, this.resolutionTree) {
run = this.codegen;
}
bool isAnalyzed() => resolutionTree != null;
int hashCode() => element.hashCode();
String compile(Compiler compiler) {
return compiler.compile(this);
}
String codegen(Compiler compiler) {
return compiler.codegen(this);
}
}
class Compiler implements DiagnosticListener {
Queue<WorkItem> worklist;
Universe universe;
String assembledCode;
Namer namer;
Types types;
final String currentDirectory;
final Tracer tracer;
CompilerTask measuredTask;
Element _currentElement;
LibraryElement coreLibrary;
LibraryElement coreImplLibrary;
LibraryElement isolateLibrary;
LibraryElement jsHelperLibrary;
LibraryElement mainApp;
ClassElement objectClass;
ClassElement closureClass;
ClassElement dynamicClass;
ClassElement boolClass;
ClassElement numClass;
ClassElement intClass;
ClassElement doubleClass;
ClassElement stringClass;
ClassElement functionClass;
ClassElement nullClass;
ClassElement listClass;
Element get currentElement() => _currentElement;
withCurrentElement(Element element, f()) {
Element old = currentElement;
_currentElement = element;
try {
return f();
} finally {
_currentElement = old;
}
}
List<CompilerTask> tasks;
ScannerTask scanner;
DietParserTask dietParser;
ParserTask parser;
TreeValidatorTask validator;
ResolverTask resolver;
TypeCheckerTask checker;
SsaBuilderTask builder;
SsaOptimizerTask optimizer;
SsaCodeGeneratorTask generator;
CodeEmitterTask emitter;
ConstantHandler constantHandler;
EnqueueTask enqueuer;
static final SourceString MAIN = const SourceString('main');
static final SourceString NO_SUCH_METHOD = const SourceString('noSuchMethod');
static final SourceString NO_SUCH_METHOD_EXCEPTION =
const SourceString('NoSuchMethodException');
static final SourceString START_ROOT_ISOLATE =
const SourceString('startRootIsolate');
bool enabledNoSuchMethod = false;
bool workListIsClosed = false;
Stopwatch codegenProgress;
Compiler.withCurrentDirectory(String this.currentDirectory,
[this.tracer = const Tracer()])
: types = new Types(),
universe = new Universe(),
worklist = new Queue<WorkItem>(),
codegenProgress = new Stopwatch.start() {
namer = new Namer(this);
constantHandler = new ConstantHandler(this);
scanner = new ScannerTask(this);
dietParser = new DietParserTask(this);
parser = new ParserTask(this);
validator = new TreeValidatorTask(this);
resolver = new ResolverTask(this);
checker = new TypeCheckerTask(this);
builder = new SsaBuilderTask(this);
optimizer = new SsaOptimizerTask(this);
generator = new SsaCodeGeneratorTask(this);
emitter = new CodeEmitterTask(this);
enqueuer = new EnqueueTask(this);
tasks = [scanner, dietParser, parser, resolver, checker,
builder, optimizer, generator,
emitter, constantHandler, enqueuer];
}
void ensure(bool condition) {
if (!condition) cancel('failed assertion in leg');
}
void unimplemented(String methodName,
[Node node, Token token, HInstruction instruction,
Element element]) {
internalError("$methodName not implemented",
node, token, instruction, element);
}
void internalError(String message,
[Node node, Token token, HInstruction instruction,
Element element]) {
cancel("${red('internal error:')} $message",
node, token, instruction, element);
}
void internalErrorOnElement(Element element, String message) {
withCurrentElement(element, () {
internalError(message, element: element);
});
}
void cancel([String reason, Node node, Token token,
HInstruction instruction, Element element]) {
SourceSpan span = const SourceSpan(null, null, null);
if (node !== null) {
span = spanFromNode(node);
} else if (token !== null) {
span = spanFromTokens(token, token);
} else if (instruction !== null) {
span = spanFromElement(currentElement);
} else if (element !== null) {
span = spanFromElement(element);
}
reportDiagnostic(span, red(reason), true);
throw new CompilerCancelledException(reason);
}
void log(message) {
reportDiagnostic(null, message, false);
}
void enqueue(WorkItem work) {
if (workListIsClosed) {
internalErrorOnElement(work.element, "work list is closed");
}
worklist.add(work);
}
bool run(Uri uri) {
try {
runCompiler(uri);
} catch (CompilerCancelledException exception) {
log(exception.toString());
log('compilation failed');
return false;
}
tracer.close();
log('compilation succeeded');
return true;
}
void enableNoSuchMethod(Element element) {
if (enabledNoSuchMethod) return;
if (element.enclosingElement == objectClass) return;
enabledNoSuchMethod = true;
enqueuer.registerInvocation(NO_SUCH_METHOD, new Invocation(2));
}
void enableIsolateSupport(LibraryElement element) {
isolateLibrary = element;
addToWorkList(element.find(START_ROOT_ISOLATE));
}
bool hasIsolateSupport() => isolateLibrary !== null;
void onLibraryLoaded(LibraryElement library, Uri uri) {
if (uri.toString() == 'dart:isolate') {
enableIsolateSupport(library);
}
if (dynamicClass !== null) {
// When loading the built-in libraries, dynamicClass is null. We
// take advantage of this as core and coreimpl import js_helper
// and see Dynamic this way.
withCurrentElement(dynamicClass, () {
library.define(dynamicClass, this);
});
}
}
abstract LibraryElement scanBuiltinLibrary(String filename);
void initializeSpecialClasses() {
objectClass = coreLibrary.find(const SourceString('Object'));
boolClass = coreLibrary.find(const SourceString('bool'));
numClass = coreLibrary.find(const SourceString('num'));
intClass = coreLibrary.find(const SourceString('int'));
doubleClass = coreLibrary.find(const SourceString('double'));
stringClass = coreLibrary.find(const SourceString('String'));
functionClass = coreLibrary.find(const SourceString('Function'));
listClass = coreLibrary.find(const SourceString('List'));
closureClass = jsHelperLibrary.find(const SourceString('Closure'));
dynamicClass = jsHelperLibrary.find(const SourceString('Dynamic'));
nullClass = jsHelperLibrary.find(const SourceString('Null'));
}
void scanBuiltinLibraries() {
coreImplLibrary = scanBuiltinLibrary('coreimpl.dart');
jsHelperLibrary = scanBuiltinLibrary('js_helper.dart');
coreLibrary = scanBuiltinLibrary('core.dart');
// Since coreLibrary import the libraries "coreimpl", and
// "js_helper", coreLibrary is null when they are being built. So
// we add the implicit import of coreLibrary now. This can be
// cleaned up when we have proper support for "dart:core" and
// don't need to access it through the field "coreLibrary".
// TODO(ahe): Clean this up as described above.
scanner.importLibrary(coreImplLibrary, coreLibrary, null);
scanner.importLibrary(jsHelperLibrary, coreLibrary, null);
addForeignFunctions(jsHelperLibrary);
universe.libraries['dart:core'] = coreLibrary;
universe.libraries['dart:coreimpl'] = coreImplLibrary;
initializeSpecialClasses();
}
/** Define the JS helper functions in the given library. */
void addForeignFunctions(LibraryElement library) {
library.define(new ForeignElement(
const SourceString('JS'), library), this);
library.define(new ForeignElement(
const SourceString('UNINTERCEPTED'), library), this);
library.define(new ForeignElement(
const SourceString('JS_HAS_EQUALS'), library), this);
library.define(new ForeignElement(
const SourceString('JS_CURRENT_ISOLATE'), library), this);
library.define(new ForeignElement(
const SourceString('JS_CALL_IN_ISOLATE'), library), this);
library.define(new ForeignElement(
const SourceString('DART_CLOSURE_TO_JS'), library), this);
}
void runCompiler(Uri uri) {
scanBuiltinLibraries();
mainApp = scanner.loadLibrary(uri, null);
final Element mainMethod = mainApp.find(MAIN);
if (mainMethod === null) {
withCurrentElement(mainApp, () => cancel('Could not find $MAIN'));
} else {
withCurrentElement(mainMethod, () {
if (!mainMethod.isFunction()) {
cancel('main is not a function', element: mainMethod);
}
FunctionParameters parameters = mainMethod.computeParameters(this);
if (parameters.parameterCount > 0) {
cancel('main cannot have parameters', element: mainMethod);
}
});
}
native.processNativeClasses(this, universe.libraries.getValues());
enqueue(new WorkItem.toCompile(mainMethod));
codegenProgress.reset();
while (!worklist.isEmpty()) {
WorkItem work = worklist.removeLast();
withCurrentElement(work.element, () => (work.run)(this));
}
workListIsClosed = true;
assert(enqueuer.checkNoEnqueuedInvokedInstanceMethods());
enqueuer.registerFieldClosureInvocations();
emitter.assembleProgram();
if (!worklist.isEmpty()) {
internalErrorOnElement(worklist.first().element,
"work list is not empty");
}
}
TreeElements analyzeElement(Element element) {
assert(parser !== null);
Node tree = parser.parse(element);
validator.validate(tree);
TreeElements elements = resolver.resolve(element);
checker.check(tree, elements);
return elements;
}
TreeElements analyze(WorkItem work) {
work.resolutionTree = analyzeElement(work.element);
return work.resolutionTree;
}
String codegen(WorkItem work) {
if (codegenProgress.elapsedInMs() > 500) {
// TODO(ahe): Add structured diagnostics to the compiler API and
// use it to separate this from the --verbose option.
log('compiled ${universe.generatedCode.length} methods');
codegenProgress.reset();
}
if (work.element.kind.category == ElementCategory.VARIABLE) {
constantHandler.compileWorkItem(work);
return null;
} else {
HGraph graph = builder.build(work);
optimizer.optimize(work, graph);
if (work.allowSpeculativeOptimization
&& optimizer.trySpeculativeOptimizations(work, graph)) {
String code = generator.generateBailoutMethod(work, graph);
universe.addBailoutCode(work, code);
optimizer.prepareForSpeculativeOptimizations(work, graph);
optimizer.optimize(work, graph);
code = generator.generateMethod(work, graph);
universe.addGeneratedCode(work, code);
return code;
} else {
String code = generator.generateMethod(work, graph);
universe.addGeneratedCode(work, code);
return code;
}
}
}
String compile(WorkItem work) {
String code = universe.generatedCode[work.element];
if (code !== null) return code;
analyze(work);
return codegen(work);
}
void addToWorkList(Element element) {
if (workListIsClosed) {
internalErrorOnElement(element, "work list is closed");
}
if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR) {
registerInstantiatedClass(element.enclosingElement);
}
worklist.add(new WorkItem.toCompile(element));
}
void registerStaticUse(Element element) {
addToWorkList(element);
}
void registerGetOfStaticFunction(FunctionElement element) {
registerStaticUse(element);
universe.staticFunctionsNeedingGetter.add(element);
}
void registerDynamicInvocation(SourceString methodName, Selector selector) {
assert(selector !== null);
enqueuer.registerInvocation(methodName, selector);
}
void registerDynamicGetter(SourceString methodName) {
enqueuer.registerGetter(methodName);
}
void registerDynamicSetter(SourceString methodName) {
enqueuer.registerSetter(methodName);
}
void registerInstantiatedClass(ClassElement element) {
universe.instantiatedClasses.add(element);
enqueuer.onRegisterInstantiatedClass(element);
}
// TODO(ngeoffray): This should get a type.
void registerIsCheck(Element element) {
universe.isChecks.add(element);
}
Type resolveType(ClassElement element) {
return withCurrentElement(element, () => resolver.resolveType(element));
}
FunctionParameters resolveSignature(FunctionElement element) {
return withCurrentElement(element,
() => resolver.resolveSignature(element));
}
Constant compileVariable(VariableElement element) {
return withCurrentElement(element, () {
return constantHandler.compileVariable(element);
});
}
reportWarning(Node node, var message) {
if (message is ResolutionWarning) {
// TODO(ahe): Don't supress this warning when we support type variables.
if (message.message.kind === MessageKind.CANNOT_RESOLVE_TYPE) return;
} else if (message is TypeWarning) {
// TODO(ahe): Don't supress these warning when the type checker
// is more complete.
if (message.message.kind === MessageKind.NOT_ASSIGNABLE) return;
if (message.message.kind === MessageKind.MISSING_RETURN) return;
if (message.message.kind === MessageKind.ADDITIONAL_ARGUMENT) return;
if (message.message.kind === MessageKind.METHOD_NOT_FOUND) return;
}
SourceSpan span = spanFromNode(node);
reportDiagnostic(span, "${magenta('warning:')} $message", false);
}
reportError(Node node, var message) {
SourceSpan span = spanFromNode(node);
reportDiagnostic(span, "${red('error:')} $message", true);
throw new CompilerCancelledException(message.toString());
}
abstract void reportDiagnostic(SourceSpan span, String message, bool fatal);
SourceSpan spanFromTokens(Token begin, Token end) {
if (begin === null || end === null) {
// TODO(ahe): We can almost always do better. Often it is only
// end that is null. Otherwise, we probably know the current
// URI.
throw 'cannot find tokens to produce error message';
}
final startOffset = begin.charOffset;
// TODO(ahe): Compute proper end offset in token. Right now we use
// the position of the next token. We want to preserve the
// invariant that endOffset > startOffset, but for EOF the
// charoffset of the next token may be [startOffset]. This can
// also happen for synthetized tokens that are produced during
// error handling.
final endOffset =
Math.max((end.next !== null) ? end.next.charOffset : 0, startOffset + 1);
assert(endOffset > startOffset);
Uri uri = currentElement.getCompilationUnit().script.uri;
return new SourceSpan(uri, startOffset, endOffset);
}
SourceSpan spanFromNode(Node node) {
return spanFromTokens(node.getBeginToken(), node.getEndToken());
}
SourceSpan spanFromElement(Element element) {
if (element.position() === null) {
// Sometimes, the backend fakes up elements that have no
// position. So we use the enclosing element instead. It is
// not a good error location, but cancel really is "internal
// error" or "not implemented yet", so the vicinity is good
// enough for now.
element = element.enclosingElement;
// TODO(ahe): I plan to overhaul this infrastructure anyways.
}
if (element === null) {
element = currentElement;
}
Token position = element.position();
if (position === null) {
// TODO(ahe): Find the enclosing library.
return const SourceSpan(null, null, null);
}
return spanFromTokens(position, position);
}
Script readScript(Uri uri, [ScriptTag node]) {
unimplemented('Compiler.readScript');
}
String get legDirectory() {
unimplemented('Compiler.legDirectory');
}
Element findHelper(SourceString name) => jsHelperLibrary.find(name);
bool get isMockCompilation() => false;
}
class CompilerTask {
final Compiler compiler;
final Stopwatch watch;
CompilerTask(this.compiler) : watch = new Stopwatch();
String get name() => 'Unknown task';
int get timing() => watch.elapsedInMs();
measure(Function action) {
// TODO(kasperl): Do we have to worry about exceptions here?
CompilerTask previous = compiler.measuredTask;
compiler.measuredTask = this;
if (previous !== null) previous.watch.stop();
watch.start();
var result = action();
watch.stop();
if (previous !== null) previous.watch.start();
compiler.measuredTask = previous;
return result;
}
}
class CompilerCancelledException implements Exception {
final String reason;
CompilerCancelledException(this.reason);
String toString() {
String banner = 'compiler cancelled';
return (reason !== null) ? '$banner: $reason' : '$banner';
}
}
interface Tracer default LTracer {
const Tracer();
final bool enabled;
void traceCompilation(String methodName);
void traceGraph(String name, var graph);
void close();
}
// TODO(ahe): Remove when the VM supports implicit interfaces.
class LTracer implements Tracer {
const LTracer();
final bool enabled = false;
void traceCompilation(String methodName) {
}
void traceGraph(String name, var graph) {
}
void close() {
}
}
class SourceSpan {
final Uri uri;
final int begin;
final int end;
const SourceSpan(this.uri, this.begin, this.end);
}