dbab92e737
This reapplies commit 6631 plus a fix, so that captured fields in closures are mangled correctly. Original CL: https://chromiumcodereview.appspot.com//10091036 Review URL: https://chromiumcodereview.appspot.com//9969212 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@6679 260f80e4-7a28-3924-810f-c04153c831b5
434 lines
17 KiB
Dart
434 lines
17 KiB
Dart
// 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 ClosureFieldElement extends Element {
|
|
ClosureFieldElement(SourceString name, ClassElement enclosing)
|
|
: super(name, ElementKind.FIELD, enclosing);
|
|
|
|
bool isInstanceMember() => true;
|
|
bool isAssignable() => false;
|
|
|
|
String toString() => "ClosureFieldElement($name)";
|
|
}
|
|
|
|
class ClosureClassElement extends ClassElement {
|
|
ClosureClassElement(Compiler compiler, Element enclosingElement)
|
|
: super(compiler.closureClass.name, enclosingElement) {
|
|
isResolved = true;
|
|
compiler.closureClass.ensureResolved(compiler);
|
|
supertype = compiler.closureClass.computeType(compiler);
|
|
}
|
|
}
|
|
|
|
class BoxElement extends Element {
|
|
BoxElement(SourceString name, Element enclosingElement)
|
|
: super(name, ElementKind.VARIABLE, enclosingElement);
|
|
}
|
|
|
|
class ThisElement extends Element {
|
|
ThisElement(Element enclosing)
|
|
: super(const SourceString('this'), ElementKind.PARAMETER, enclosing);
|
|
|
|
bool isAssignable() => false;
|
|
}
|
|
|
|
// The box-element for a scope, and the captured variables that need to be
|
|
// stored in the box.
|
|
class ClosureScope {
|
|
Element boxElement;
|
|
Map<Element, Element> capturedVariableMapping;
|
|
// If the scope is attached to a [For] contains the variables that are
|
|
// declared in the initializer of the [For] and that need to be boxed.
|
|
// Otherwise contains the empty List.
|
|
List<Element> boxedLoopVariables;
|
|
|
|
ClosureScope(this.boxElement, this.capturedVariableMapping)
|
|
: boxedLoopVariables = const <Element>[];
|
|
|
|
bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty();
|
|
}
|
|
|
|
class ClosureData {
|
|
// The closure's element before any translation. Will be null for methods.
|
|
final FunctionElement closureElement;
|
|
// The closureClassElement will be null for methods that are not local
|
|
// closures.
|
|
final ClassElement closureClassElement;
|
|
// The callElement will be null for methods that are not local closures.
|
|
final FunctionElement callElement;
|
|
// The [thisElement] makes handling 'this' easier by treating it like any
|
|
// other argument. It is only set for instance-members.
|
|
final ThisElement thisElement;
|
|
|
|
// Maps free locals, arguments and function elements to their captured
|
|
// copies.
|
|
final Map<Element, Element> freeVariableMapping;
|
|
// Maps closure-fields to their captured elements. This is somehow the inverse
|
|
// mapping of [freeVariableMapping], but whereas [freeVariableMapping] does
|
|
// not deal with boxes, here we map instance-fields (which might represent
|
|
// boxes) to their boxElement.
|
|
final Map<Element, Element> capturedFieldMapping;
|
|
|
|
// Maps scopes ([Loop] and [FunctionExpression] nodes) to their
|
|
// [ClosureScope] which contains their box and the
|
|
// captured variables that are stored in the box.
|
|
// This map will be empty if the method/closure of this [ClosureData] does not
|
|
// contain any nested closure.
|
|
final Map<Node, ClosureScope> capturingScopes;
|
|
|
|
final Set<Element> usedVariablesInTry;
|
|
|
|
ClosureData(this.closureElement,
|
|
this.closureClassElement,
|
|
this.callElement,
|
|
this.thisElement)
|
|
: this.freeVariableMapping = new Map<Element, Element>(),
|
|
this.capturedFieldMapping = new Map<Element, Element>(),
|
|
this.capturingScopes = new Map<Node, ClosureScope>(),
|
|
this.usedVariablesInTry = new Set<Element>();
|
|
|
|
bool isClosure() => closureElement !== null;
|
|
}
|
|
|
|
class ClosureTranslator extends AbstractVisitor {
|
|
final Compiler compiler;
|
|
final TreeElements elements;
|
|
int closureFieldCounter = 0;
|
|
bool inTryCatchOrFinally = false;
|
|
final Map<Node, ClosureData> closureDataCache;
|
|
|
|
// Map of captured variables. Initially they will map to themselves. If
|
|
// a variable needs to be boxed then the scope declaring the variable
|
|
// will update this mapping.
|
|
Map<Element, Element> capturedVariableMapping;
|
|
// List of encountered closures.
|
|
List<FunctionExpression> closures;
|
|
|
|
// The variables that have been declared in the current scope.
|
|
List<Element> scopeVariables;
|
|
|
|
FunctionElement currentFunctionElement;
|
|
// The closureData of the currentFunctionElement.
|
|
ClosureData closureData;
|
|
|
|
bool insideClosure = false;
|
|
|
|
ClosureTranslator(Compiler compiler, this.elements)
|
|
: capturedVariableMapping = new Map<Element, Element>(),
|
|
closures = <FunctionExpression>[],
|
|
this.compiler = compiler,
|
|
this.closureDataCache = compiler.builder.closureDataCache;
|
|
|
|
ClosureData translate(Node node) {
|
|
// Closures have already been analyzed when visiting the surrounding
|
|
// method/function. This also shortcuts for bailout functions.
|
|
ClosureData cached = closureDataCache[node];
|
|
if (cached !== null) return cached;
|
|
|
|
visit(node);
|
|
// When variables need to be boxed their [capturedVariableMapping] is
|
|
// updated, but we delay updating the similar freeVariableMapping in the
|
|
// closure datas that capture these variables.
|
|
// The closures don't have their fields (in the closure class) set, either.
|
|
updateClosures();
|
|
|
|
return closureDataCache[node];
|
|
}
|
|
|
|
// This function runs through all of the existing closures and updates their
|
|
// free variables to the boxed value. It also adds the field-elements to the
|
|
// class representing the closure. At the same time it fills the
|
|
// [capturedFieldMapping].
|
|
void updateClosures() {
|
|
for (FunctionExpression closure in closures) {
|
|
// The captured variables that need to be stored in a field of the closure
|
|
// class.
|
|
Set<Element> fieldCaptures = new Set<Element>();
|
|
ClosureData data = closureDataCache[closure];
|
|
Map<Element, Element> freeVariableMapping = data.freeVariableMapping;
|
|
// We get a copy of the keys and iterate over it, to avoid modifications
|
|
// to the map while iterating over it.
|
|
freeVariableMapping.getKeys().forEach((Element fromElement) {
|
|
assert(fromElement == freeVariableMapping[fromElement]);
|
|
Element updatedElement = capturedVariableMapping[fromElement];
|
|
assert(updatedElement !== null);
|
|
if (fromElement == updatedElement) {
|
|
assert(freeVariableMapping[fromElement] == updatedElement);
|
|
assert(Elements.isLocal(updatedElement));
|
|
// The variable has not been boxed.
|
|
fieldCaptures.add(updatedElement);
|
|
} else {
|
|
// A boxed element.
|
|
freeVariableMapping[fromElement] = updatedElement;
|
|
Element boxElement = updatedElement.enclosingElement;
|
|
assert(boxElement.kind == ElementKind.VARIABLE);
|
|
fieldCaptures.add(boxElement);
|
|
}
|
|
});
|
|
ClassElement closureElement = data.closureClassElement;
|
|
assert(closureElement != null || fieldCaptures.isEmpty());
|
|
for (Element capturedElement in fieldCaptures) {
|
|
SourceString name;
|
|
if (capturedElement is BoxElement) {
|
|
// The name is already mangled.
|
|
name = capturedElement.name;
|
|
} else {
|
|
int id = closureFieldCounter++;
|
|
name = new SourceString("${capturedElement.name.slowToString()}_$id");
|
|
}
|
|
Element fieldElement = new ClosureFieldElement(name, closureElement);
|
|
closureElement.backendMembers =
|
|
closureElement.backendMembers.prepend(fieldElement);
|
|
data.capturedFieldMapping[fieldElement] = capturedElement;
|
|
freeVariableMapping[capturedElement] = fieldElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
void useLocal(Element element) {
|
|
// TODO(floitsch): replace this with a general solution.
|
|
Element functionElement = currentFunctionElement;
|
|
if (functionElement.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
|
|
ConstructorBodyElement body = functionElement;
|
|
functionElement = body.constructor;
|
|
}
|
|
// If the element is not declared in the current function and the element
|
|
// is not the closure itself we need to mark the element as free variable.
|
|
if (element.enclosingElement != functionElement &&
|
|
element != functionElement) {
|
|
assert(closureData.freeVariableMapping[element] == null ||
|
|
closureData.freeVariableMapping[element] == element);
|
|
closureData.freeVariableMapping[element] = element;
|
|
} else if (inTryCatchOrFinally) {
|
|
// Don't mark the this-element. This would complicate things in the
|
|
// builder.
|
|
if (element != closureData.thisElement) {
|
|
// TODO(ngeoffray): only do this if the variable is mutated.
|
|
closureData.usedVariablesInTry.add(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
void declareLocal(Element element) {
|
|
scopeVariables.add(element);
|
|
}
|
|
|
|
visit(Node node) => node.accept(this);
|
|
|
|
visitNode(Node node) => node.visitChildren(this);
|
|
|
|
visitVariableDefinitions(VariableDefinitions node) {
|
|
for (Link<Node> link = node.definitions.nodes;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
Node definition = link.head;
|
|
Element element = elements[definition];
|
|
assert(element !== null);
|
|
declareLocal(element);
|
|
}
|
|
// We still need to visit the right-hand sides of the init-assignments.
|
|
// Simply visit all children. We will visit the locals again and make them
|
|
// used, but that should not be a problem.
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitIdentifier(Identifier node) {
|
|
if (node.isThis()) {
|
|
useLocal(closureData.thisElement);
|
|
}
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitSend(Send node) {
|
|
Element element = elements[node];
|
|
if (Elements.isLocal(element)) {
|
|
useLocal(element);
|
|
} else if (node.receiver === null &&
|
|
Elements.isInstanceSend(node, elements)) {
|
|
useLocal(closureData.thisElement);
|
|
}
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
// If variables that are declared in the [node] scope are captured and need
|
|
// to be boxed create a box-element and update the [capturingScopes] in the
|
|
// current [closureData].
|
|
// The boxed variables are updated in the [capturedVariableMapping].
|
|
void attachCapturedScopeVariables(Node node) {
|
|
Element box = null;
|
|
Map<Element, Element> scopeMapping = new Map<Element, Element>();
|
|
for (Element element in scopeVariables) {
|
|
// No need to box non-assignable elements.
|
|
if (!element.isAssignable()) continue;
|
|
if (capturedVariableMapping.containsKey(element)) {
|
|
if (box == null) {
|
|
// TODO(floitsch): construct better box names.
|
|
SourceString boxName =
|
|
new SourceString("box_${closureFieldCounter++}");
|
|
box = new BoxElement(boxName, currentFunctionElement);
|
|
}
|
|
// TODO(floitsch): construct better boxed names.
|
|
String elementName = element.name.slowToString();
|
|
// We are currently using the name in an HForeign which could replace
|
|
// "$X" with something else.
|
|
String escaped = elementName.replaceAll("\$", "_");
|
|
SourceString boxedName =
|
|
new SourceString("${escaped}_${closureFieldCounter++}");
|
|
Element boxed = new Element(boxedName, ElementKind.FIELD, box);
|
|
scopeMapping[element] = boxed;
|
|
capturedVariableMapping[element] = boxed;
|
|
}
|
|
}
|
|
if (!scopeMapping.isEmpty()) {
|
|
ClosureScope scope = new ClosureScope(box, scopeMapping);
|
|
closureData.capturingScopes[node] = scope;
|
|
}
|
|
}
|
|
|
|
visitLoop(Loop node) {
|
|
List<Element> oldScopeVariables = scopeVariables;
|
|
scopeVariables = new List<Element>();
|
|
node.visitChildren(this);
|
|
attachCapturedScopeVariables(node);
|
|
scopeVariables = oldScopeVariables;
|
|
}
|
|
|
|
visitFor(For node) {
|
|
visitLoop(node);
|
|
// See if we have declared loop variables that need to be boxed.
|
|
if (node.initializer === null) return;
|
|
VariableDefinitions definitions = node.initializer.asVariableDefinitions();
|
|
if (definitions == null) return;
|
|
ClosureScope scopeData = closureData.capturingScopes[node];
|
|
if (scopeData === null) return;
|
|
List<Element> result = <Element>[];
|
|
for (Link<Node> link = definitions.definitions.nodes;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
Node definition = link.head;
|
|
Element element = elements[definition];
|
|
if (capturedVariableMapping.containsKey(element)) {
|
|
result.add(element);
|
|
};
|
|
}
|
|
scopeData.boxedLoopVariables = result;
|
|
}
|
|
|
|
ClosureData globalizeClosure(FunctionExpression node) {
|
|
FunctionElement element = elements[node];
|
|
ClassElement globalizedElement =
|
|
new ClosureClassElement(compiler, element.getCompilationUnit());
|
|
FunctionElement callElement =
|
|
new FunctionElement.from(Namer.CLOSURE_INVOCATION_NAME,
|
|
element,
|
|
globalizedElement);
|
|
globalizedElement.backendMembers =
|
|
const EmptyLink<Element>().prepend(callElement);
|
|
// The nested function's 'this' is the same as the one for the outer
|
|
// function. It could be [null] if we are inside a static method.
|
|
Element thisElement = closureData.thisElement;
|
|
return new ClosureData(element, globalizedElement,
|
|
callElement, thisElement);
|
|
}
|
|
|
|
visitFunctionExpression(FunctionExpression node) {
|
|
Element element = elements[node];
|
|
if (element.kind === ElementKind.PARAMETER) {
|
|
// TODO(ahe): This is a hack. This method should *not* call
|
|
// visitChildren.
|
|
return node.name.accept(this);
|
|
}
|
|
bool isClosure = (closureData !== null);
|
|
|
|
if (isClosure) closures.add(node);
|
|
|
|
bool oldInsideClosure = insideClosure;
|
|
FunctionElement oldFunctionElement = currentFunctionElement;
|
|
ClosureData oldClosureData = closureData;
|
|
List<Element> oldScopeVariables = scopeVariables;
|
|
|
|
|
|
insideClosure = isClosure;
|
|
currentFunctionElement = elements[node];
|
|
if (insideClosure) {
|
|
closureData = globalizeClosure(node);
|
|
} else {
|
|
Element thisElement = null;
|
|
// TODO(floitsch): we should not need to look for generative constructors.
|
|
// At the moment we store only one ClosureData for both the factory and
|
|
// the body.
|
|
if (element.isInstanceMember() ||
|
|
element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
|
|
// TODO(floitsch): currently all variables are considered to be
|
|
// declared in the GENERATIVE_CONSTRUCTOR. Including the 'this'.
|
|
Element thisEnclosingElement = element;
|
|
if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
|
|
ConstructorBodyElement body = element;
|
|
thisEnclosingElement = body.constructor;
|
|
}
|
|
thisElement = new ThisElement(thisEnclosingElement);
|
|
}
|
|
closureData = new ClosureData(null, null, null, thisElement);
|
|
}
|
|
scopeVariables = new List<Element>();
|
|
|
|
// We have to declare the implicit 'this' parameter.
|
|
if (!insideClosure && closureData.thisElement !== null) {
|
|
declareLocal(closureData.thisElement);
|
|
}
|
|
// If we are inside a named closure we have to declare ourselve. For
|
|
// simplicity we declare the local even if the closure does not have a name
|
|
// It will simply not be used.
|
|
if (insideClosure) {
|
|
declareLocal(element);
|
|
}
|
|
|
|
// TODO(ahe): This is problematic. The backend should not repeat
|
|
// the work of the resolver. It is the resolver's job to create
|
|
// parameters, etc. Other phases should only visit statements.
|
|
// TODO(floitsch): we avoid visiting the initializers on purpose so that we
|
|
// get an error-message later in the builder.
|
|
if (node.parameters !== null) node.parameters.accept(this);
|
|
if (node.body !== null) node.body.accept(this);
|
|
|
|
attachCapturedScopeVariables(node);
|
|
|
|
closureDataCache[node] = closureData;
|
|
|
|
ClosureData savedClosureData = closureData;
|
|
bool savedInsideClosure = insideClosure;
|
|
|
|
// Restore old values.
|
|
scopeVariables = oldScopeVariables;
|
|
insideClosure = oldInsideClosure;
|
|
closureData = oldClosureData;
|
|
currentFunctionElement = oldFunctionElement;
|
|
|
|
// Mark all free variables as captured and use them in the outer function.
|
|
List<Element> freeVariables =
|
|
savedClosureData.freeVariableMapping.getKeys();
|
|
assert(freeVariables.isEmpty() || savedInsideClosure);
|
|
for (Element freeElement in freeVariables) {
|
|
if (capturedVariableMapping[freeElement] != null &&
|
|
capturedVariableMapping[freeElement] != freeElement) {
|
|
compiler.internalError('In closure analyzer', node: node);
|
|
}
|
|
capturedVariableMapping[freeElement] = freeElement;
|
|
useLocal(freeElement);
|
|
}
|
|
}
|
|
|
|
visitFunctionDeclaration(FunctionDeclaration node) {
|
|
node.visitChildren(this);
|
|
declareLocal(elements[node]);
|
|
}
|
|
|
|
visitTryStatement(TryStatement node) {
|
|
// TODO(ngeoffray): implement finer grain state.
|
|
inTryCatchOrFinally = true;
|
|
node.visitChildren(this);
|
|
inTryCatchOrFinally = false;
|
|
}
|
|
}
|