Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(607)

Unified Diff: frog/gen.dart

Issue 9107067: Work in progress: changes to interpretation (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: work in progress Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « frog/frog_options.dart ('k') | frog/lang.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: frog/gen.dart
diff --git a/frog/gen.dart b/frog/gen.dart
index 029c1a600d342812e4c0c82147c8fda705bb46e3..16098f59caf86f350177608201683aa66db77931 100644
--- a/frog/gen.dart
+++ b/frog/gen.dart
@@ -56,8 +56,8 @@ class WorldGenerator {
// Generate callbacks from JS to isolate code if needed
if (corejs.useWrap0 || corejs.useWrap1) {
- genMethod(world.coreimpl.types['IsolateContext'].getMember('eval'));
- genMethod(world.coreimpl.types['EventLoop'].getMember('run'));
+ invokeMethod(world.coreimpl.types['IsolateContext'].getMember('eval'));
+ invokeMethod(world.coreimpl.types['EventLoop'].getMember('run'));
}
corejs.useIsolates = true;
@@ -102,11 +102,11 @@ class WorldGenerator {
type.factories.forEach((f) => members.add(f));
for (var member in members) {
if (member is PropertyMember) {
- if (member.getter != null) genMethod(member.getter);
- if (member.setter != null) genMethod(member.setter);
+ if (member.getter != null) invokeMethod(member.getter);
+ if (member.setter != null) invokeMethod(member.setter);
}
- if (member is MethodMember) genMethod(member);
+ if (member is MethodMember) invokeMethod(member);
}
}
@@ -226,10 +226,16 @@ class WorldGenerator {
}
}
- genMethod(Member meth, [MethodGenerator enclosingMethod=null]) {
- if (!meth.isGenerated && !meth.isAbstract && meth.definition != null) {
- new MethodGenerator(meth, enclosingMethod).run();
+ Type invokeMethod(Member method, [Value target, Arguments args]) {
+ if (method.isAbstract || method.definition == null) {
+ return method.returnType;
}
+
+ var s = method.specializer;
+ if (s == null) {
+ s = new MethodSpecializer(method);
+ }
+ return s.run(target, args);
}
String _prototypeOf(Type type, String name) {
@@ -373,8 +379,7 @@ class WorldGenerator {
}
} else {
Member standardConstructor = type.constructors[''];
- if (standardConstructor == null ||
- standardConstructor.generator == null) {
+ if (standardConstructor == null || !standardConstructor.isUsed) {
if (!type.isNative) {
writer.writeln('function ${type.jsname}() {}');
}
@@ -383,7 +388,7 @@ class WorldGenerator {
}
for (var c in type.constructors.getValues()) {
- if (c.generator != null && c != standardConstructor) {
+ if (c.isUsed && c != standardConstructor) {
c.generator.writeDefinition(writer, null);
}
}
@@ -459,16 +464,8 @@ class WorldGenerator {
* This predicate determines when we need to define lib_Float32Array.
*/
_typeNeedsHolderForStaticMethods(Type type) {
- for (var member in type.members.getValues()) {
- if (member.isMethod) {
- if (member.isConstructor || member.isStatic) {
- if (member.isGenerated) {
- return true;
- }
- }
- }
- }
- return false;
+ return type.members.getValues().some(
+ (m) => m.isUsed && m.isMethod && (m.isConstructor || m.isStatic));
}
/**
@@ -558,7 +555,7 @@ function $inheritsMembers(child, parent) {
}
_writeMethod(Member m) {
- if (m.generator != null) {
+ if (m.isUsed) {
m.generator.writeDefinition(writer, null);
} else if (m is MethodMember && m.isNative
&& m._providePropertySyntax && !m._provideFieldSyntax) {
@@ -745,13 +742,112 @@ function $inheritsMembers(child, parent) {
/**
- * A naive code generator for Dart.
+ * When inferring types, this will support potentially creating multiple
+ * copies of a method with different signatures. We don't implement that yet,
+ * though. Right now it's just a way of guarding against recursive invokes
+ * and tracking [option.inferTypes] specific state for a method.
*/
+// TODO(jmesserly): better name for this and MethodGenerator? (Perhaps
+// this should be named MethodGenerator and the other one should be
+// MethodInterpreter or something like that.)
+class MethodSpecializer {
+ final Member method;
+ final MethodGenerator enclosingMethod;
+
+ // These are used to track what inferred types are assumed by the generated
+ // code. Eventually we may want to use this as a key and generate multiple
+ // typed copies of a method. However that needs good heuristics to avoid
+ // making the code too big.
+ Value _thisObject;
+ List<VariableValue> _arguments;
+ int _evalCount = 0;
+
+ MethodGenerator generator;
+
+ MethodSpecializer(this.method, [this.enclosingMethod]) {
+ assert(method.specializer === null);
+ // TODO(jmesserly): I don't like side effects from constructors, but
+ // otherwise we rely on our callers to maintain this invariant which isn't
+ // great either.
+ method.specializer = this;
+ reset();
+ }
+
+ reset() {
+ _thisObject = new VariableValue(method.declaringType, 'this', null);
+ _arguments = method.parameters.map(
+ (p) => new VariableValue(p.type, p.name, p.span));
+ }
+
+ // TODO(jmesserly): Should this be renamed invoke?
+ // Also our caller has already taken care of named/missing args, but that's
+ // probably not what we want. Instead that could be handled here, and we
+ // could return a Value that performs the method call. (In other words,
+ // absorb some of MethodMember.invoke's current implementation.)
+ Type run([Value target, Arguments args]) {
+ method.markUsed();
+
+ // If this is the first time we've seen this call, or the argument types are
+ // now different, we'll need to interpret the method body.
+ bool shouldEval = _evalCount == 0;
+ bool infer = options.inferTypes && _evalCount <= options.maxInferenceCalls;
+
+ // Unless type inference is on, ignore passed in target & arg types.
+ if (infer) {
+ // TODO(jmesserly): should unify the target type too!
+ List<Value> argValues;
+ if (args == null) {
+ // TODO(jmesserly): we should always be passing in arguments
+ argValues = method.parameters.map(
+ (p) => new Value(p.type, p.name, p.span));
+ } else {
+ argValues = args.values;
+ }
+
+ for (int i = 0; i < _arguments.length; i++) {
+ var orig = _arguments[i].value;
+ var updated = Value.union(orig, argValues[i]);
+ if (updated !== orig) {
+ _arguments[i] = _arguments[i].replaceValue(updated);
+ shouldEval = true;
+ }
+ }
+ }
+
+ if (shouldEval) {
+ _evalCount++;
+
+ if (infer && _evalCount > options.maxInferenceCalls) {
+ // If we've tried too many times and haven't converged, rerun without
+ // inference.
+ world.info('inference failed to converge for method "${method.name}"',
+ method.span);
+
+ // Discard inferred things so we do a fresh run.
+ reset();
+ }
+
+ // Note: this might replace a generator from higher up on our call stack,
+ // if we're calling this method recursively (which is rather common, e.g.
+ // toString methods are potentially recursive). That's okay: the later
+ // generator will have collected more type info, so let it win.
+ // (Eventually we'll have some specialization, and key these off of the
+ // input types.)
+ generator = new MethodGenerator(method, enclosingMethod);
+ generator.evalBody(_thisObject, new Arguments(null, _arguments));
+ }
+
+ return generator._inferResult();
+ }
+}
+
+
+/** A code generator and abstract interpreter for Dart function/method. */
class MethodGenerator implements TreeVisitor {
- Member method;
+ final Member method;
+ final MethodGenerator enclosingMethod;
CodeWriter writer;
BlockScope _scope;
- MethodGenerator enclosingMethod;
bool needsThis;
List<String> _paramCode;
@@ -769,9 +865,11 @@ class MethodGenerator implements TreeVisitor {
*/
Set<String> captures;
+ /** The inferred result type. */
+ Value _inferredResult;
CounterLog counters;
- MethodGenerator(this.method, this.enclosingMethod)
+ MethodGenerator(this.method, [this.enclosingMethod])
: writer = new CodeWriter(), needsThis = false {
if (enclosingMethod != null) {
_scope = new BlockScope(this, enclosingMethod._scope, method.definition);
@@ -784,6 +882,10 @@ class MethodGenerator implements TreeVisitor {
counters = world.counters;
}
+ // TODO(jmesserly): do we need anything else here? (e.g. do we need to replace
+ // the scope with an empty scope so variables don't leak?)
+ Value evalExpression(Expression expr) => expr.visit(this);
+
Library get library() => method.library;
// TODO(jimhug): Where does this really belong?
@@ -833,43 +935,48 @@ class MethodGenerator implements TreeVisitor {
*/
}
- run() {
- if (method.isGenerated) return;
-
- // This avoids any attempts to infer across recursion.
- method.isGenerated = true;
- method.generator = this;
-
- // Create most generic possible call for this method.
- var thisObject;
- if (method.isConstructor) {
- thisObject = new ObjectValue(false, method.declaringType, method.span);
- thisObject.initFields();
- } else {
- thisObject = new Value(method.declaringType, 'this', null);
- }
- var values = [];
- for (var p in method.parameters) {
- values.add(new Value(p.type, p.name, null));
+ Type _inferResult() {
+ if (method.isNative || !options.inferTypes) {
+ // If it's a native, use the return type.
+ var t = method.returnType;
+ if (t.isBool && (method.library.isCore || method.library.isCoreImpl)) {
+ // We trust our core libraries not to return null from bools.
+ // I hope this trust is well placed!
+ t = world.nonNullBool;
+ }
+ return t;
}
- var args = new Arguments(null, values);
- evalBody(thisObject, args);
+ if (_inferredResult == null) {
+ // Handle the implicit 'return null;'
+ // TODO(jmesserly): this needs to be added in more cases, e.g. if there
+ // is a "return" statement in one control flow path but not another
+ // path.
+ _inferredResult = Value.fromNull(null);
+ }
+ return _inferredResult.type;
+ }
- if (method.definition.nativeBody != null) {
+ _evalNativeBody() {
+ // TODO: the .dynamic here is annoying
+ var nativeBody = method.definition.dynamic.nativeBody;
+ if (nativeBody != null) {
// Throw away the code--it was just used for tree shaking purposes.
- writer = new CodeWriter();
- if (method.definition.nativeBody == '') {
- method.generator = null;
+ if (nativeBody == '') {
+ // Set this to null so we don't try to write code.
+ writer = null;
} else {
_paramCode = map(method.parameters, (p) => p.name);
- writer.write(method.definition.nativeBody);
+ writer = new CodeWriter();
+ writer.write(nativeBody);
}
}
}
-
writeDefinition(CodeWriter defWriter, LambdaExpression lambda/*=null*/) {
+ // No need to write anything for natives with no native body.
+ if (writer == null) return;
+
// To implement block scope: capture any variables we need to.
var paramCode = _paramCode;
var names = null;
@@ -1015,25 +1122,31 @@ class MethodGenerator implements TreeVisitor {
return newObject.setField(field, value, duringInit: true);
}
- evalBody(Value newObject, Arguments args) {
+ void evalBody(thisObject, Arguments args) {
+ if (method.isConstructor && thisObject is! ObjectValue) {
+ // TODO(jmesserly): fix how contructors interact...
+ thisObject = new ObjectValue(false, method.declaringType, method.span);
+ thisObject.initFields();
+ }
+
bool fieldsSet = false;
- if (method.isNative && method.isConstructor && newObject is ObjectValue) {
- newObject.dynamic.seenNativeInitializer = true;
+ if (method.isNative && method.isConstructor) {
+ thisObject.dynamic.seenNativeInitializer = true;
}
// Collects parameters for writing signature in the future.
_paramCode = [];
for (int i = 0; i < method.parameters.length; i++) {
var p = method.parameters[i];
Value currentArg = null;
- // TODO(jimhug): bareCount is O(N)
if (i < args.bareCount) {
currentArg = args.values[i];
} else {
- // Handle named or missing arguments
+ // TODO(jmesserly): right now we're evaluating named/missing args on
+ // the call site side. That should probably move into here.
currentArg = args.getValue(p.name);
if (currentArg === null) {
// Ensure default value for param has been generated
- p.genValue(method, method.generator);
+ p.genValue(method, this);
currentArg = p.value;
}
}
@@ -1041,18 +1154,19 @@ class MethodGenerator implements TreeVisitor {
if (p.isInitializer) {
_paramCode.add(p.name);
fieldsSet = true;
- _initField(newObject, p.name, currentArg, p.definition.span);
+ _initField(thisObject, p.name, currentArg, p.definition.span);
} else {
var paramValue = _scope.declareParameter(p);
_paramCode.add(paramValue.code);
- if (newObject != null && newObject.isConst) {
+ if (thisObject != null && thisObject.isConst) {
_scope.assign(p.name, currentArg.convertTo(this, p.type));
}
}
}
var initializerCall = null;
- final declaredInitializers = method.definition.initializers;
+ var methodDef = method.definition;
+ final declaredInitializers = methodDef.initializers;
if (declaredInitializers != null) {
for (var init in declaredInitializers) {
if (init is CallExpression) {
@@ -1073,7 +1187,7 @@ class MethodGenerator implements TreeVisitor {
// context, so "this." is not in scope
var initValue = visitValue(init.y);
fieldsSet = true;
- _initField(newObject, left.name.name, initValue, left.span);
+ _initField(thisObject, left.name.name, initValue, left.span);
} else {
world.error('invalid initializer', init.span);
}
@@ -1090,8 +1204,8 @@ class MethodGenerator implements TreeVisitor {
}
}
- if (method.isConstructor && newObject is ObjectValue) {
- var fields = newObject.dynamic.fields;
+ if (method.isConstructor && thisObject is ObjectValue) {
+ var fields = thisObject.dynamic.fields;
for (var field in fields.getKeys()) {
var value = fields[field];
if (value !== null) {
@@ -1102,25 +1216,25 @@ class MethodGenerator implements TreeVisitor {
// TODO(jimhug): Doing this call last does not match spec.
if (initializerCall != null) {
- evalInitializerCall(newObject, initializerCall, fieldsSet);
+ evalInitializerCall(thisObject, initializerCall, fieldsSet);
}
- if (method.isConstructor && newObject !== null && newObject.isConst) {
- newObject.validateInitialized(method.span);
+ if (method.isConstructor && thisObject !== null && thisObject.isConst) {
+ thisObject.validateInitialized(method.span);
} else if (method.isConstructor) {
- var fields = newObject.dynamic.fields;
+ var fields = thisObject.dynamic.fields;
for (var field in fields.getKeys()) {
var value = fields[field];
if (value === null && field.isFinal &&
field.declaringType == method.declaringType &&
- !newObject.dynamic.seenNativeInitializer) {
+ !thisObject.dynamic.seenNativeInitializer) {
world.error('uninitialized final field "${field.name}"',
field.span, method.span);
}
}
}
- var body = method.definition.body;
+ var body = methodDef.body;
if (body === null) {
// TODO(jimhug): Move check into resolve on method.
@@ -1131,6 +1245,8 @@ class MethodGenerator implements TreeVisitor {
} else {
visitStatementsInBlock(body);
}
+
+ _evalNativeBody();
}
evalInitializerCall(ObjectValue newObject, CallExpression node,
@@ -1178,7 +1294,7 @@ class MethodGenerator implements TreeVisitor {
var newArgs = _makeArgs(node.arguments);
// ???? wacky stuff ????
- world.gen.genMethod(m);
+ world.gen.invokeMethod(m);
m._evalConstConstructor(newObject, newArgs);
@@ -1413,7 +1529,7 @@ class MethodGenerator implements TreeVisitor {
var funcValue = _scope.create(meth.name, meth.functionType,
method.definition.span, isFinal:true);
- world.gen.genMethod(meth, this);
+ new MethodSpecializer(meth, this).run();
meth.generator.writeDefinition(writer, null);
return false;
}
@@ -1425,13 +1541,15 @@ class MethodGenerator implements TreeVisitor {
bool visitReturnStatement(ReturnStatement node) {
if (node.value == null) {
// This is essentially "return null".
- // It can't issue a warning because every type is nullable.
+ // TODO(jmesserly): this should be a warning in non-void methods
writer.writeln('return;');
+ _inferredResult = Value.union(_inferredResult, Value.fromNull(null));
} else {
if (method.isConstructor) {
world.error('return of value not allowed from constructor', node.span);
}
var value = visitTypedValue(node.value, method.returnType);
+ _inferredResult = Value.union(_inferredResult, value);
writer.writeln('return ${value.code};');
}
return true;
@@ -1511,9 +1629,8 @@ class MethodGenerator implements TreeVisitor {
var exit1 = node.trueBranch.visit(this);
if (node.falseBranch != null) {
writer.write('else ');
- if (node.falseBranch.visit(this) && exit1) {
- return true;
- }
+ var exit2 = node.falseBranch.visit(this);
+ return exit1 && exit2;
}
return false;
}
@@ -1887,17 +2004,24 @@ class MethodGenerator implements TreeVisitor {
var name = (node.func.name != null) ? node.func.name.name : '';
MethodMember meth = _makeLambdaMethod(name, node.func);
- final lambdaGen = new MethodGenerator(meth, this);
if (name != '') {
- // Note: we don't want to put this in our enclosing scope because the
- // name shouldn't be visible except inside the lambda. We also don't want
- // to put the name directly in the lambda's scope because parameters are
- // allowed to shadow it. So we create an extra scope for it to go into.
- lambdaGen._scope.create(name, meth.functionType, meth.definition.span,
+ // Note: create a block scope for the lambda's name to go into. This keeps
+ // it from leaking into our current scope, and also allows the lambda's
+ // parameters to shadow it.
+ _pushBlock(node);
+ _scope.create(name, meth.functionType, meth.definition.span,
isFinal:true);
- lambdaGen._pushBlock(node);
}
- lambdaGen.run();
+
+ // TODO(jmesserly): if we wanted to, we could track this as a function
+ // value, and let it be invoked (or not) by whoever uses it.
+ // (Since the number of lambdas is bounded in the program it may be
+ // viable to track them all individually as Values.)
+ new MethodSpecializer(meth, this).run();
+
+ if (name != '') {
+ _popBlock(node);
+ }
var w = new CodeWriter();
meth.generator.writeDefinition(w, node);
« no previous file with comments | « frog/frog_options.dart ('k') | frog/lang.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698