diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 58ee760..3815f65 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -105,6 +105,7 @@ import com.google.gwt.dev.js.JsSymbolResolver;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.JsVerboseNamer;
 import com.google.gwt.dev.js.SizeBreakdown;
+import com.google.gwt.dev.js.Stringer;
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.util.AbstractTextOutput;
@@ -349,6 +350,8 @@ public class JavaToJavaScriptCompiler {
         JsIEBlockSizeVisitor.exec(jsProgram);
       }
       JsBreakUpLargeVarStatements.exec(jsProgram, propertyOracles);
+      
+      Stringer.exec(jsProgram);
 
       // (12) Generate the final output text.
       String[] js = new String[jsProgram.getFragmentCount()];
diff --git a/dev/core/src/com/google/gwt/dev/js/Stringer.java b/dev/core/src/com/google/gwt/dev/js/Stringer.java
new file mode 100644
index 0000000..7abd94b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/Stringer.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBinaryOperator;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsInvocation;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameOf;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsNode;
+import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsReturn;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsThisRef;
+import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.TextOutput;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * PFM, baby.
+ */
+public class Stringer {
+
+  private class HasReturnCollector extends JsVisitor {
+
+    private final Stack<JsFunction> stack = new Stack<JsFunction>();
+
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      if (stack.pop() != x) {
+        throw new RuntimeException("Unexpected function popped");
+      }
+    }
+
+    @Override
+    public void endVisit(JsReturn x, JsContext<JsStatement> ctx) {
+      hasReturnStatement.add(stack.peek());
+    }
+
+    @Override
+    public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
+      hasThisReference.add(stack.peek());
+    }
+
+    @Override
+    public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
+      stack.push(x);
+      return true;
+    }
+  }
+
+  /**
+   * Determines which functions are referred to by name and not immediately
+   * invoked. This is intended to match polymorphic vtable setup code as well as
+   * GWT-derived Java methods being used in a functional manner.
+   */
+  private class ReferencedFunctionsCollector extends JsVisitor {
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      JsNode<?> staticRef = x.getName().getStaticRef();
+      if (staticRef instanceof JsFunction) {
+        referencedFunctions.add((JsFunction) staticRef);
+      }
+    }
+
+    /**
+     * If we're invoking a static function name, only descend into the arguments
+     * of the invocation.
+     */
+    @Override
+    public boolean visit(JsInvocation x, JsContext<JsExpression> ctx) {
+      if (x.getQualifier() instanceof JsNameRef) {
+        JsNameRef asRef = (JsNameRef) x.getQualifier();
+        JsNode<?> maybeFunction = asRef.getName().getStaticRef();
+        if (maybeFunction instanceof JsFunction) {
+          acceptList(x.getArguments());
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  private class Rewrite extends JsModVisitor {
+
+    // TODO : Has return statement, min size, stable ident check
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      if (x.getName() == null) {
+        // Can't do anything with an anonymous function
+        return;
+      } else if (x.getBody().getStatements().isEmpty()) {
+        // Or an empty function
+        return;
+      }
+
+      boolean isReferenced = referencedFunctions.contains(x);
+
+      TextOutput out = new DefaultTextOutput(true);
+      JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
+      v.accept(x.getBody());
+      String contents = out.toString();
+
+      SourceInfo sourceInfo = x.getSourceInfo().makeChild(Stringer.class,
+          "Lazy-eval string");
+
+      JsName compiledName = program.getObjectScope().declareName(
+          "$Stringer_compiledRef", "compiled");
+
+      // name = arguments.callee.compiled = Function('p1', 'p2', 'code')
+      JsBinaryOperation asg;
+      {
+        JsInvocation functionCall = new JsInvocation(sourceInfo);
+        functionCall.setQualifier(program.getRootScope().declareName("Function").makeRef(
+            sourceInfo));
+        for (JsParameter p : x.getParameters()) {
+          functionCall.getArguments().add(new JsNameOf(sourceInfo, p.getName()));
+        }
+        functionCall.getArguments().add(
+            program.getStringLiteral(sourceInfo, contents));
+
+        if (isReferenced) {
+          JsNameRef calleeRef = program.getRootScope().declareName("callee").makeRef(
+              sourceInfo);
+          calleeRef.setQualifier(program.getRootScope().declareName("arguments").makeRef(
+              sourceInfo));
+          JsNameRef compiledRef = compiledName.makeRef(sourceInfo);
+          compiledRef.setQualifier(calleeRef);
+
+          asg = new JsBinaryOperation(sourceInfo, JsBinaryOperator.ASG,
+              compiledRef, functionCall);
+          asg = new JsBinaryOperation(sourceInfo, JsBinaryOperator.ASG,
+              x.getName().makeRef(sourceInfo), asg);
+        } else {
+          asg = new JsBinaryOperation(sourceInfo, JsBinaryOperator.ASG,
+              x.getName().makeRef(sourceInfo), functionCall);
+        }
+
+        if (ADD_EVAL_LOG) {
+          // $wnd.console.log(name)
+          JsNameRef wnd = program.getRootScope().declareName("$wnd").makeRef(
+              sourceInfo);
+          JsNameRef console = program.getRootScope().declareName("console").makeRef(
+              sourceInfo);
+          JsNameRef log = program.getRootScope().declareName("log").makeRef(
+              sourceInfo);
+
+          console.setQualifier(wnd);
+          log.setQualifier(console);
+
+          JsInvocation logInvocation = new JsInvocation(sourceInfo);
+          logInvocation.setQualifier(log);
+          logInvocation.getArguments().add(
+              new JsNameOf(sourceInfo, x.getName()));
+
+          asg = new JsBinaryOperation(sourceInfo, JsBinaryOperator.COMMA,
+              logInvocation, asg);
+        }
+      }
+
+      JsExpression compiledOrEvaluated;
+      if (isReferenced) {
+        // arguments.callee.compiled || (log, asg))
+        JsNameRef calleeRef = program.getRootScope().declareName("callee").makeRef(
+            sourceInfo);
+        calleeRef.setQualifier(program.getRootScope().declareName("arguments").makeRef(
+            sourceInfo));
+        JsNameRef compiled = compiledName.makeRef(sourceInfo);
+        compiled.setQualifier(calleeRef);
+
+        compiledOrEvaluated = new JsBinaryOperation(sourceInfo,
+            JsBinaryOperator.OR, compiled, asg);
+      } else {
+        compiledOrEvaluated = asg;
+      }
+
+      JsInvocation invocation = new JsInvocation(sourceInfo);
+      {
+        if (hasThisReference.contains(x)) {
+          // (fnExpression).apply(this, arguments);
+          JsNameRef applyRef = new JsNameRef(sourceInfo,
+              program.getRootScope().declareName("apply"));
+          applyRef.setQualifier(compiledOrEvaluated);
+
+          invocation.setQualifier(applyRef);
+          invocation.getArguments().add(new JsThisRef(sourceInfo));
+          invocation.getArguments().add(
+              program.getRootScope().declareName("arguments").makeRef(
+                  sourceInfo));
+        } else {
+          // (fnExpression)(a,b,c)
+          invocation.setQualifier(compiledOrEvaluated);
+          for (JsParameter p : x.getParameters()) {
+            invocation.getArguments().add(p.getName().makeRef(sourceInfo));
+          }
+        }
+      }
+
+      // Reset function body
+      x.getBody().getStatements().clear();
+
+      if (hasReturnStatement.contains(x)) {
+        JsReturn ret = new JsReturn(sourceInfo, invocation);
+        x.getBody().getStatements().add(ret);
+      } else {
+        x.getBody().getStatements().add(invocation.makeStmt());
+      }
+    }
+  }
+
+  /**
+   * An internal debugging flag that causes a <code>window.log</code> call to be
+   * made when a given function is lazily evaluated.
+   */
+  private static final boolean ADD_EVAL_LOG = Boolean.getBoolean("gwt.jjs.logLazyEvals");
+
+  public static void exec(JsProgram program) {
+    new Stringer(program).execImpl();
+  }
+
+  private final Set<JsFunction> hasReturnStatement = new HashSet<JsFunction>();
+  private final Set<JsFunction> hasThisReference = new HashSet<JsFunction>();
+  private final Set<JsFunction> referencedFunctions = new HashSet<JsFunction>();
+  private final JsProgram program;
+
+  private Stringer(JsProgram program) {
+    this.program = program;
+  }
+
+  private void execImpl() {
+    new HasReturnCollector().accept(program);
+    new ReferencedFunctionsCollector().accept(program);
+    new Rewrite().accept(program);
+  }
+}
