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

Unified Diff: pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart

Issue 1201463003: dart2js cps: Merge JsIrBuilder and IrBuilder. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Rebase Created 5 years, 6 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 | « no previous file | pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart
diff --git a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart
index 8be9b21676f8a222743fbfb3fba93dc0357d7475..20a22af71ba0be30990ea56e5172145a3774abd6 100644
--- a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart
+++ b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart
@@ -422,6 +422,8 @@ abstract class IrBuilderMixin<N> {
/// Shared state between delimited IrBuilders within the same function.
class IrBuilderSharedState {
+ final GlobalProgramInformation program;
+
final BackendConstantEnvironment constants;
ConstantSystem get constantSystem => constants.constantSystem;
@@ -443,18 +445,22 @@ class IrBuilderSharedState {
/// a return is intercepted to give a place to generate the finally code.
JumpCollector returnCollector = null;
- ir.Parameter _thisParameter;
- ir.Parameter enclosingMethodThisParameter;
+ /// Parameter holding the internal value of 'this' passed to the function.
+ ///
+ /// For nested functions, this is *not* captured receiver, but the function
+ /// object itself.
+ ir.Parameter thisParameter;
+
+ /// If non-null, this refers to the receiver (`this`) in the enclosing method.
+ ir.Primitive enclosingThis;
final List<ir.Parameter> functionParameters = <ir.Parameter>[];
- IrBuilderSharedState(this.constants, this.currentElement);
+ /// Maps boxed locals to their location. These locals are not part of
+ /// the environment.
+ final Map<Local, ClosureLocation> boxedVariables = {};
- ir.Parameter get thisParameter => _thisParameter;
- void set thisParameter(ir.Parameter value) {
- assert(_thisParameter == null);
- _thisParameter = value;
- }
+ IrBuilderSharedState(this.program, this.constants, this.currentElement);
}
class ThisParameterLocal implements Local {
@@ -464,72 +470,24 @@ class ThisParameterLocal implements Local {
toString() => 'ThisParameterLocal($executableContext)';
}
-/// A factory for building the cps IR.
+/// The IR builder maintains an environment and an IR fragment.
///
-/// [DartIrBuilder] and [JsIrBuilder] implement nested functions and captured
-/// variables in different ways.
-abstract class IrBuilder {
- IrBuilder _makeInstance();
-
- void declareLocalVariable(LocalVariableElement element,
- {ir.Primitive initialValue});
-
- /// Called when entering a nested function with free variables.
- ///
- /// The free variables must subsequently be accessible using [buildLocalGet]
- /// and [buildLocalSet].
- void _enterClosureEnvironment(ClosureEnvironment env);
-
- /// Called when entering a function body or loop body.
- ///
- /// This is not called for for-loops, which instead use the methods
- /// [_enterForLoopInitializer], [_enterForLoopBody], and [_enterForLoopUpdate]
- /// due to their special scoping rules.
- ///
- /// The boxed variables declared in this scope must subsequently be available
- /// using [buildLocalGet], [buildLocalSet], etc.
- void _enterScope(ClosureScope scope);
-
- /// Called before building the initializer of a for-loop.
- ///
- /// The loop variables will subsequently be declared using
- /// [declareLocalVariable].
- void _enterForLoopInitializer(ClosureScope scope,
- List<LocalElement> loopVariables);
-
- /// Called before building the body of a for-loop.
- void _enterForLoopBody(ClosureScope scope,
- List<LocalElement> loopVariables);
-
- /// Called before building the update of a for-loop.
- void _enterForLoopUpdate(ClosureScope scope,
- List<LocalElement> loopVariables);
-
- /// Add the given function parameter to the IR, and bind it in the environment
- /// or put it in its box, if necessary.
- void _createFunctionParameter(Local parameterElement);
- void _createThisParameter();
-
- /// Reifies the value of [variable] on the current receiver object.
- ir.Primitive buildReifyTypeVariable(TypeVariableType variable);
-
- /// Creates an access to the receiver from the current (or enclosing) method.
- ///
- /// If inside a closure class, [buildThis] will redirect access through
- /// closure fields in order to access the receiver from the enclosing method.
- ir.Primitive buildThis();
-
- /// Creates a type test or type cast of [value] against [type].
- ir.Primitive buildTypeOperator(ir.Primitive value,
- DartType type,
- {bool isTypeTest});
-
- // TODO(johnniwinther): Make these field final and remove the default values
- // when [IrBuilder] is a property of [IrBuilderVisitor] instead of a mixin.
-
+/// The IR fragment is an expression with a hole in it. The hole represents
+/// the focus where new expressions can be added. The fragment is implemented
+/// by [_root] which is the root of the expression and [_current] which is the
+/// expression that immediately contains the hole. Not all expressions have a
+/// hole (e.g., invocations, which always occur in tail position, do not have a
+/// hole). Expressions with a hole have a plug method.
+///
+/// The environment maintains the reaching definition of each local variable,
+/// including some synthetic locals such as [TypeVariableLocal].
+///
+/// Internally, IR builders also maintains a [JumpCollector] stack and tracks
+/// which variables are currently boxed or held in a mutable local variable.
+class IrBuilder {
final List<ir.Parameter> _parameters = <ir.Parameter>[];
- IrBuilderSharedState state;
+ final IrBuilderSharedState state;
/// A map from variable indexes to their values.
///
@@ -543,6 +501,36 @@ abstract class IrBuilder {
/// side effects.
Map<Local, ir.MutableVariable> mutableVariables;
+ ir.Expression _root = null;
+ ir.Expression _current = null;
+
+ GlobalProgramInformation get program => state.program;
+
+ IrBuilder(GlobalProgramInformation program,
+ BackendConstantEnvironment constants,
+ ExecutableElement currentElement)
+ : state = new IrBuilderSharedState(program, constants, currentElement),
+ environment = new Environment.empty(),
+ mutableVariables = <Local, ir.MutableVariable>{};
+
+ IrBuilder._internal(this.state, this.environment, this.mutableVariables);
+
+ /// Construct a delimited visitor for visiting a subtree.
+ ///
+ /// Build a subterm that is not (yet) connected to the CPS term. The
+ /// delimited visitor has its own has its own context for building an IR
+ /// expression, so the built expression is not plugged into the parent's
+ /// context. It has its own compile-time environment mapping local
+ /// variables to their values. If an optional environment argument is
+ /// supplied, it is used as the builder's initial environment. Otherwise
+ /// the environment is initially a copy of the parent builder's environment.
+ IrBuilder makeDelimitedBuilder([Environment env = null]) {
+ return new IrBuilder._internal(
+ state,
+ env != null ? env : new Environment.from(environment),
+ mutableVariables);
+ }
+
/// True if [local] should currently be accessed from a [ir.MutableVariable].
bool isInMutableVariable(Local local) {
return mutableVariables.containsKey(local);
@@ -566,57 +554,6 @@ abstract class IrBuilder {
return mutableVariables[local];
}
- // The IR builder maintains a context, which is an expression with a hole in
- // it. The hole represents the focus where new expressions can be added.
- // The context is implemented by 'root' which is the root of the expression
- // and 'current' which is the expression that immediately contains the hole.
- // Not all expressions have a hole (e.g., invocations, which always occur in
- // tail position, do not have a hole). Expressions with a hole have a plug
- // method.
- //
- // Conceptually, visiting a statement takes a context as input and returns
- // either a new context or else an expression without a hole if all
- // control-flow paths through the statement have exited. An expression
- // without a hole is represented by a (root, current) pair where root is the
- // expression and current is null.
- //
- // Conceptually again, visiting an expression takes a context as input and
- // returns either a pair of a new context and a definition denoting
- // the expression's value, or else an expression without a hole if all
- // control-flow paths through the expression have exited.
- //
- // We do not pass contexts as arguments or return them. Rather we use the
- // current context (root, current) as the visitor state and mutate current.
- // Visiting a statement returns null; visiting an expression returns the
- // primitive denoting its value.
-
- ir.Expression _root = null;
- ir.Expression _current = null;
-
- /// Initialize a new top-level IR builder.
- void _init(BackendConstantEnvironment constants,
- ExecutableElement currentElement) {
- state = new IrBuilderSharedState(constants, currentElement);
- environment = new Environment.empty();
- mutableVariables = <Local, ir.MutableVariable>{};
- }
-
- /// Construct a delimited visitor for visiting a subtree.
- ///
- /// Build a subterm that is not (yet) connected to the CPS term. The
- /// delimited visitor has its own has its own context for building an IR
- /// expression, so the built expression is not plugged into the parent's
- /// context. It has its own compile-time environment mapping local
- /// variables to their values. If an optional environment argument is
- /// supplied, it is used as the builder's initial environment. Otherwise
- /// the environment is initially a copy of the parent builder's environment.
- IrBuilder makeDelimitedBuilder([Environment env = null]) {
- return _makeInstance()
- ..state = state
- ..environment = env != null ? env : new Environment.from(environment)
- ..mutableVariables = mutableVariables;
- }
-
bool get isOpen => _root == null || _current != null;
List<ir.Primitive> buildFunctionHeader(Iterable<Local> parameters,
@@ -630,7 +567,7 @@ abstract class IrBuilder {
}
/// Creates a parameter for [local] and adds it to the current environment.
- ir.Parameter createLocalParameter(Local local) {
+ ir.Parameter _createLocalParameter(Local local) {
ir.Parameter parameter = new ir.Parameter(local);
_parameters.add(parameter);
environment.extend(local, parameter);
@@ -858,15 +795,6 @@ abstract class IrBuilder {
return _buildInvokeSuper(method, selector, arguments);
}
- /// Create a read access of the [field] on the super class.
- ir.Primitive buildSuperFieldGet(FieldElement field) {
- // TODO(johnniwinther): This should have its own ir node.
- return _buildInvokeSuper(
- field,
- new Selector.getter(field.name, field.library),
- const <ir.Primitive>[]);
- }
-
/// Create a read access of the [method] on the super class, i.e. a
/// closurization of [method].
ir.Primitive buildSuperMethodGet(MethodElement method) {
@@ -886,16 +814,6 @@ abstract class IrBuilder {
const <ir.Primitive>[]);
}
- /// Create a write access to the [field] on the super class of with [value].
- ir.Primitive buildSuperFieldSet(Element field, ir.Primitive value) {
- // TODO(johnniwinther): This should have its own ir node.
- _buildInvokeSuper(
- field,
- new Selector.setter(field.name, field.library),
- <ir.Primitive>[value]);
- return value;
- }
-
/// Create an setter invocation of the [setter] on the super class with
/// [value].
ir.Primitive buildSuperSetterSet(MethodElement setter,
@@ -935,19 +853,6 @@ abstract class IrBuilder {
return _buildInvokeDynamic(receiver, selector, arguments);
}
- /// Create an if-null expression. This is equivalent to a conditional
- /// expression whose result is either [value] if [value] is not null, or
- /// `right` if [value] is null. Only when [value] is null, [buildRight] is
- /// evaluated to produce the `right` value.
- ir.Primitive buildIfNull(ir.Primitive value,
- ir.Primitive buildRight(IrBuilder builder));
-
- /// Create a conditional send. This is equivalent to a conditional expression
- /// that checks if [receiver] is null, if so, it returns null, otherwise it
- /// evaluates the [buildSend] expression.
- ir.Primitive buildIfNotNullSend(ir.Primitive receiver,
- ir.Primitive buildSend(IrBuilder builder));
-
/// Create a dynamic getter invocation on [receiver] where the getter name is
/// defined by [selector].
ir.Primitive buildDynamicGet(ir.Primitive receiver, Selector selector) {
@@ -967,16 +872,14 @@ abstract class IrBuilder {
/// Create a dynamic index set invocation on [receiver] with the provided
/// [index] and [value].
- ir.Primitive buildDynamicIndexSet(ir.Primitive receiver,
- ir.Primitive index,
- ir.Primitive value) {
+ ir.Primitive buildDynamicIndexSet(ir.Primitive receiver,
+ ir.Primitive index,
+ ir.Primitive value) {
_buildInvokeDynamic(
receiver, new Selector.indexSet(), <ir.Primitive>[index, value]);
return value;
}
- ir.Primitive _buildLocalGet(LocalElement element);
-
/// Create a read access of the [local] variable or parameter.
ir.Primitive buildLocalVariableGet(LocalElement local) {
// TODO(johnniwinther): Separate function access from variable access.
@@ -990,10 +893,6 @@ abstract class IrBuilder {
return _buildLocalGet(function);
}
- /// Create a write access to the [local] variable or parameter with the
- /// provided [value].
- ir.Primitive buildLocalVariableSet(LocalElement local, ir.Primitive value);
-
/// Create an invocation of the the [local] variable or parameter where
/// argument structure is defined by [callStructure] and the argument values
/// are defined by [arguments].
@@ -1088,16 +987,6 @@ abstract class IrBuilder {
return _buildInvokeStatic(element, selector, arguments, null);
}
- /// Create a constructor invocation of [element] on [type] where the
- /// constructor name and argument structure are defined by [callStructure] and
- /// the argument values are defined by [arguments].
- ir.Primitive buildConstructorInvocation(FunctionElement element,
- CallStructure callStructure,
- DartType type,
- List<ir.Primitive> arguments);
-
- ir.Primitive buildStringify(ir.Primitive argument);
-
/// Concatenate string values.
///
/// The arguments must be strings; usually a call to [buildStringify] is
@@ -2229,51 +2118,11 @@ abstract class IrBuilder {
return addPrimitive(new ir.ApplyBuiltinOperator(
ir.BuiltinOperator.Identical, <ir.Primitive>[x, y]));
}
-}
-
-/// State shared between JsIrBuilders within the same function.
-///
-/// Note that this is not shared between builders of nested functions.
-class JsIrBuilderSharedState {
- /// Maps boxed locals to their location. These locals are not part of
- /// the environment.
- final Map<Local, ClosureLocation> boxedVariables = {};
-
- /// If non-null, this refers to the receiver (`this`) in the enclosing method.
- ir.Primitive receiver;
-
- /// `true` when we are currently building expressions inside the initializer
- /// list of a constructor.
- bool inInitializers = false;
-}
-
-/// JS-specific subclass of [IrBuilder].
-///
-/// Inner functions are represented by a [ClosureClassElement], and captured
-/// variables are boxed as necessary using [CreateBox], [GetField], [SetField].
-class JsIrBuilder extends IrBuilder {
- final JsIrBuilderSharedState jsState;
- final GlobalProgramInformation program;
-
- IrBuilder _makeInstance() => new JsIrBuilder._blank(program, jsState);
- JsIrBuilder._blank(this.program, this.jsState);
-
- JsIrBuilder(this.program, BackendConstantEnvironment constants,
- ExecutableElement currentElement)
- : jsState = new JsIrBuilderSharedState() {
- _init(constants, currentElement);
- }
-
- void enterInitializers() {
- assert(jsState.inInitializers == false);
- jsState.inInitializers = true;
- }
-
- void leaveInitializers() {
- assert(jsState.inInitializers == true);
- jsState.inInitializers = false;
- }
+ /// Called when entering a nested function with free variables.
+ ///
+ /// The free variables must subsequently be accessible using [buildLocalGet]
+ /// and [buildLocalSet].
void _enterClosureEnvironment(ClosureEnvironment env) {
if (env == null) return;
@@ -2284,7 +2133,7 @@ class JsIrBuilder extends IrBuilder {
env.freeVariables.forEach((Local local, ClosureLocation location) {
if (location.isBox) {
// Boxed variables are loaded from their box on-demand.
- jsState.boxedVariables[local] = location;
+ state.boxedVariables[local] = location;
} else {
// Unboxed variables are loaded from the function object immediately.
// This includes BoxLocals which are themselves unboxed variables.
@@ -2296,7 +2145,7 @@ class JsIrBuilder extends IrBuilder {
// If the function captures a reference to the receiver from the
// enclosing method, remember which primitive refers to the receiver object.
if (env.thisLocal != null && env.freeVariables.containsKey(env.thisLocal)) {
- jsState.receiver = environment.lookup(env.thisLocal);
+ state.enclosingThis = environment.lookup(env.thisLocal);
}
// If the function has a self-reference, use the value of `this`.
@@ -2312,24 +2161,34 @@ class JsIrBuilder extends IrBuilder {
/// [declareLocalVariable], [buildLocalGet], and [buildLocalSet].
void enterScope(ClosureScope scope) => _enterScope(scope);
+ /// Called when entering a function body or loop body.
+ ///
+ /// This is not called for for-loops, which instead use the methods
+ /// [_enterForLoopInitializer], [_enterForLoopBody], and [_enterForLoopUpdate]
+ /// due to their special scoping rules.
+ ///
+ /// The boxed variables declared in this scope must subsequently be available
+ /// using [buildLocalGet], [buildLocalSet], etc.
void _enterScope(ClosureScope scope) {
if (scope == null) return;
ir.CreateBox boxPrim = addPrimitive(new ir.CreateBox());
environment.extend(scope.box, boxPrim);
boxPrim.useElementAsHint(scope.box);
scope.capturedVariables.forEach((Local local, ClosureLocation location) {
- assert(!jsState.boxedVariables.containsKey(local));
+ assert(!state.boxedVariables.containsKey(local));
if (location.isBox) {
- jsState.boxedVariables[local] = location;
+ state.boxedVariables[local] = location;
}
});
}
+ /// Add the given function parameter to the IR, and bind it in the environment
+ /// or put it in its box, if necessary.
void _createFunctionParameter(Local parameterElement) {
ir.Parameter parameter = new ir.Parameter(parameterElement);
_parameters.add(parameter);
state.functionParameters.add(parameter);
- ClosureLocation location = jsState.boxedVariables[parameterElement];
+ ClosureLocation location = state.boxedVariables[parameterElement];
if (location != null) {
add(new ir.SetField(environment.lookup(location.box),
location.field,
@@ -2340,6 +2199,7 @@ class JsIrBuilder extends IrBuilder {
}
void _createThisParameter() {
+ assert(state.thisParameter == null);
if (Elements.isStaticOrTopLevel(state.currentElement)) return;
if (state.currentElement.isLocal) return;
state.thisParameter =
@@ -2352,7 +2212,7 @@ class JsIrBuilder extends IrBuilder {
if (initialValue == null) {
initialValue = buildNullConstant();
}
- ClosureLocation location = jsState.boxedVariables[variableElement];
+ ClosureLocation location = state.boxedVariables[variableElement];
if (location != null) {
add(new ir.SetField(environment.lookup(location.box),
location.field,
@@ -2388,10 +2248,9 @@ class JsIrBuilder extends IrBuilder {
}
/// Create a read access of [local] variable or parameter.
- @override
ir.Primitive _buildLocalGet(LocalElement local) {
assert(isOpen);
- ClosureLocation location = jsState.boxedVariables[local];
+ ClosureLocation location = state.boxedVariables[local];
if (location != null) {
ir.Primitive result = new ir.GetField(environment.lookup(location.box),
location.field);
@@ -2406,10 +2265,9 @@ class JsIrBuilder extends IrBuilder {
/// Create a write access to [local] variable or parameter with the provided
/// [value].
- @override
ir.Primitive buildLocalVariableSet(LocalElement local, ir.Primitive value) {
assert(isOpen);
- ClosureLocation location = jsState.boxedVariables[local];
+ ClosureLocation location = state.boxedVariables[local];
if (location != null) {
add(new ir.SetField(environment.lookup(location.box),
location.field,
@@ -2423,6 +2281,10 @@ class JsIrBuilder extends IrBuilder {
return value;
}
+ /// Called before building the initializer of a for-loop.
+ ///
+ /// The loop variables will subsequently be declared using
+ /// [declareLocalVariable].
void _enterForLoopInitializer(ClosureScope scope,
List<LocalElement> loopVariables) {
if (scope == null) return;
@@ -2432,6 +2294,7 @@ class JsIrBuilder extends IrBuilder {
_enterScope(scope);
}
+ /// Called before building the body of a for-loop.
void _enterForLoopBody(ClosureScope scope,
List<LocalElement> loopVariables) {
if (scope == null) return;
@@ -2441,6 +2304,7 @@ class JsIrBuilder extends IrBuilder {
_enterScope(scope);
}
+ /// Called before building the update of a for-loop.
void _enterForLoopUpdate(ClosureScope scope,
List<LocalElement> loopVariables) {
if (scope == null) return;
@@ -2458,18 +2322,20 @@ class JsIrBuilder extends IrBuilder {
environment.update(scope.box, newBox);
}
+ /// Creates an access to the receiver from the current (or enclosing) method.
+ ///
+ /// If inside a closure class, [buildThis] will redirect access through
+ /// closure fields in order to access the receiver from the enclosing method.
ir.Primitive buildThis() {
- if (jsState.receiver != null) return jsState.receiver;
+ if (state.enclosingThis != null) return state.enclosingThis;
assert(state.thisParameter != null);
return state.thisParameter;
}
- @override
ir.Primitive buildSuperFieldGet(FieldElement target) {
return addPrimitive(new ir.GetField(buildThis(), target));
}
- @override
ir.Primitive buildSuperFieldSet(FieldElement target, ir.Primitive value) {
add(new ir.SetField(buildThis(), target, value));
return value;
@@ -2495,15 +2361,17 @@ class JsIrBuilder extends IrBuilder {
ClosureScope closureScope) {
_createThisParameter();
for (Local param in parameters) {
- ir.Parameter parameter = createLocalParameter(param);
+ ir.Parameter parameter = _createLocalParameter(param);
state.functionParameters.add(parameter);
}
if (closureScope != null) {
- jsState.boxedVariables.addAll(closureScope.capturedVariables);
+ state.boxedVariables.addAll(closureScope.capturedVariables);
}
}
- @override
+ /// Create a constructor invocation of [element] on [type] where the
+ /// constructor name and argument structure are defined by [callStructure] and
+ /// the argument values are defined by [arguments].
ir.Primitive buildConstructorInvocation(ConstructorElement element,
CallStructure callStructure,
DartType type,
@@ -2576,7 +2444,7 @@ class JsIrBuilder extends IrBuilder {
buildTypeExpression(binding));
}
- @override
+ /// Reifies the value of [variable] on the current receiver object.
ir.Primitive buildReifyTypeVariable(TypeVariableType variable) {
ir.Primitive typeArgument = buildTypeVariableAccess(variable);
return addPrimitive(new ir.ReifyRuntimeType(typeArgument));
@@ -2605,10 +2473,11 @@ class JsIrBuilder extends IrBuilder {
add(new ir.ForeignCode(codeTemplate, type, arguments, behavior,
dependency: dependency));
_current = null;
+ return null;
}
}
- @override
+ /// Creates a type test or type cast of [value] against [type].
ir.Primitive buildTypeOperator(ir.Primitive value,
DartType type,
{bool isTypeTest}) {
@@ -2656,14 +2525,19 @@ class JsIrBuilder extends IrBuilder {
}
}
- @override
+ /// Create an if-null expression. This is equivalent to a conditional
+ /// expression whose result is either [value] if [value] is not null, or
+ /// `right` if [value] is null. Only when [value] is null, [buildRight] is
+ /// evaluated to produce the `right` value.
ir.Primitive buildIfNull(ir.Primitive value,
ir.Primitive buildRight(IrBuilder builder)) {
ir.Primitive condition = _buildCheckNull(value);
return buildConditional(condition, buildRight, (_) => value);
}
- @override
+ /// Create a conditional send. This is equivalent to a conditional expression
+ /// that checks if [receiver] is null, if so, it returns null, otherwise it
+ /// evaluates the [buildSend] expression.
ir.Primitive buildIfNotNullSend(ir.Primitive receiver,
ir.Primitive buildSend(IrBuilder builder)) {
ir.Primitive condition = _buildCheckNull(receiver);
« no previous file with comments | « no previous file | pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698