Build all synthetic lambda code in CF.

Bug: 169305577
Change-Id: I48c7753f90e9a2ba5e10121cb4b862b02a073aff
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 50083f2..141484c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -412,7 +412,9 @@
       Origin origin,
       boolean shouldApplyCodeRewritings) {
     if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
-      instructions.removeIf(CfInstruction::isFrame);
+      ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
+      copy.removeIf(CfInstruction::isFrame);
+      setInstructions(copy);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index b283f96..d7b3d0a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -1,129 +1,47 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 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.
-
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 
 // Source code representing synthesized accessor method.
-final class AccessorMethodSourceCode extends SynthesizedLambdaSourceCode {
 
-  AccessorMethodSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(
-        lambda, lambda.target.callTarget, callerPosition, null /* no receiver for static method */);
-    // We should never need an accessor for interface methods since
-    // they are supposed to be public.
-    assert !descriptor().implHandle.type.isInvokeInterface();
-    assert checkSignatures();
-  }
+public class AccessorMethodSourceCode {
 
-  private boolean checkSignatures() {
-    DexMethodHandle implHandle = descriptor().implHandle;
-    assert implHandle != null;
-
-    DexType[] accessorParams = proto.parameters.values;
-    DexMethod implMethod = implHandle.asMethod();
-    DexProto implProto = implMethod.proto;
-    DexType[] implParams = implProto.parameters.values;
-
-    int index = 0;
-    if (implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect()) {
-      assert accessorParams[index] == descriptor().getImplReceiverType();
-      index++;
-    }
-
-    for (DexType implParam : implParams) {
-      assert accessorParams[index] == implParam;
-      index++;
-    }
-    assert index == accessorParams.length;
-
-    assert delegatingToConstructor()
-        ? this.proto.returnType == implMethod.holder
-        : this.proto.returnType == implProto.returnType;
-    return true;
-  }
-
-  // Are we delegating to a constructor?
-  private boolean delegatingToConstructor() {
-    return descriptor().implHandle.type.isInvokeConstructor();
-  }
-
-  private Invoke.Type inferInvokeType() {
-    switch (descriptor().implHandle.type) {
+  public static CfCode build(LambdaClass lambda, DexMethod accessor) {
+    DexMethod target = lambda.descriptor.implHandle.asMethod();
+    ForwardMethodBuilder forwardMethodBuilder =
+        ForwardMethodBuilder.builder(lambda.appView.dexItemFactory()).setStaticSource(accessor);
+    switch (lambda.descriptor.implHandle.type) {
       case INVOKE_INSTANCE:
-        return Invoke.Type.VIRTUAL;
+        {
+          forwardMethodBuilder.setVirtualTarget(target, false);
+          break;
+        }
       case INVOKE_STATIC:
-        return Invoke.Type.STATIC;
+        {
+          forwardMethodBuilder.setStaticTarget(target, false);
+          break;
+        }
       case INVOKE_DIRECT:
+        {
+          forwardMethodBuilder.setDirectTarget(target, false);
+          break;
+        }
       case INVOKE_CONSTRUCTOR:
-        return Invoke.Type.DIRECT;
+        {
+          forwardMethodBuilder.setConstructorTarget(target, lambda.appView.dexItemFactory());
+          break;
+        }
       case INVOKE_INTERFACE:
         throw new Unreachable("Accessor for an interface method?");
       default:
         throw new Unreachable();
     }
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    DexMethod implMethod = descriptor().implHandle.asMethod();
-    DexType[] accessorParams = proto.parameters.values;
-
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
-
-    // If we are delegating to a constructor, we need to create the instance
-    // first. This instance will be the first argument to the call.
-    if (delegatingToConstructor()) {
-      int instance = nextRegister(ValueType.OBJECT);
-      add(builder -> builder.addNewInstance(instance, implMethod.holder));
-      argValueTypes.add(ValueType.OBJECT);
-      argRegisters.add(instance);
-    }
-
-    for (int i = 0; i < accessorParams.length; i++) {
-      DexType param = accessorParams[i];
-      argValueTypes.add(ValueType.fromDexType(param));
-      argRegisters.add(getParamRegister(i));
-    }
-
-    // Method call to the original impl-method.
-    // Mirroring assert in constructor, we never need accessors to interfaces.
-    assert !descriptor().implHandle.type.isInvokeInterface();
-    add(
-        builder ->
-            builder.addInvoke(
-                inferInvokeType(),
-                implMethod,
-                implMethod.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    // Does the method have return value?
-    if (proto.returnType == factory().voidType) {
-      add(IRBuilder::addReturn);
-    } else if (delegatingToConstructor()) {
-      // Return newly created instance
-      add(builder -> builder.addReturn(argRegisters.get(0)));
-    } else {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int tempValue = nextRegister(valueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      add(builder -> builder.addReturn(tempValue));
-    }
+    return forwardMethodBuilder.build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 6dc372e..99bad27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -1397,7 +1398,7 @@
 
   private interface TemplateMethodFactory {
 
-    Code create(InternalOptions options, DexMethod method);
+    CfCode create(InternalOptions options, DexMethod method);
   }
 
   private interface MethodInvokeRewriter {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 4d5c7be..01fb238 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -22,10 +22,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.google.common.base.Predicates;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -111,12 +109,13 @@
       DexProgramClass clazz,
       List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
       List<DexEncodedMethod> covariantReturnTypeMethods) {
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (methodHasCovariantReturnTypeAnnotation(method)) {
-        methodsWithCovariantReturnTypeAnnotation.add(method);
-        buildCovariantReturnTypeMethodsForMethod(clazz, method, covariantReturnTypeMethods);
-      }
-    }
+    clazz.forEachProgramVirtualMethod(
+        method -> {
+          if (methodHasCovariantReturnTypeAnnotation(method.getDefinition())) {
+            methodsWithCovariantReturnTypeAnnotation.add(method.getDefinition());
+            buildCovariantReturnTypeMethodsForMethod(method, covariantReturnTypeMethods);
+          }
+        });
   }
 
   private boolean methodHasCovariantReturnTypeAnnotation(DexEncodedMethod method) {
@@ -132,13 +131,11 @@
   // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
   // methods to the list covariantReturnTypeMethods.
   private void buildCovariantReturnTypeMethodsForMethod(
-      DexProgramClass clazz,
-      DexEncodedMethod method,
-      List<DexEncodedMethod> covariantReturnTypeMethods) {
-    assert methodHasCovariantReturnTypeAnnotation(method);
-    for (DexType covariantReturnType : getCovariantReturnTypes(clazz, method)) {
+      ProgramMethod method, List<DexEncodedMethod> covariantReturnTypeMethods) {
+    assert methodHasCovariantReturnTypeAnnotation(method.getDefinition());
+    for (DexType covariantReturnType : getCovariantReturnTypes(method)) {
       DexEncodedMethod covariantReturnTypeMethod =
-          buildCovariantReturnTypeMethod(clazz, method, covariantReturnType);
+          buildCovariantReturnTypeMethod(method, covariantReturnType);
       covariantReturnTypeMethods.add(covariantReturnTypeMethod);
     }
   }
@@ -149,32 +146,35 @@
   //
   // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
   private DexEncodedMethod buildCovariantReturnTypeMethod(
-      DexProgramClass clazz, DexEncodedMethod method, DexType covariantReturnType) {
+      ProgramMethod method, DexType covariantReturnType) {
+    DexProgramClass methodHolder = method.getHolder();
+    DexMethod methodReference = method.getReference();
+    DexEncodedMethod methodDefinition = method.getDefinition();
     DexProto newProto =
         factory.createProto(
-            covariantReturnType, method.method.proto.parameters, method.method.proto.shorty);
-    MethodAccessFlags newAccessFlags = method.accessFlags.copy();
+            covariantReturnType, methodReference.proto.parameters, methodReference.proto.shorty);
+    MethodAccessFlags newAccessFlags = methodDefinition.accessFlags.copy();
     newAccessFlags.setBridge();
     newAccessFlags.setSynthetic();
-    DexMethod newMethod = factory.createMethod(method.holder(), newProto, method.method.name);
-    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-        ForwardMethodSourceCode.builder(newMethod);
-    forwardSourceCodeBuilder
-        .setReceiver(clazz.type)
-        .setTargetReceiver(method.holder())
-        .setTarget(method.method)
-        .setInvokeType(Invoke.Type.VIRTUAL)
-        .setCastResult();
+    DexMethod newMethod =
+        factory.createMethod(methodHolder.getType(), newProto, methodReference.getName());
+    ForwardMethodBuilder forwardMethodBuilder =
+        ForwardMethodBuilder.builder(factory)
+            .setNonStaticSource(newMethod)
+            .setVirtualTarget(methodReference, methodHolder.isInterface())
+            .setCastResult();
     DexEncodedMethod newVirtualMethod =
         new DexEncodedMethod(
             newMethod,
             newAccessFlags,
-            method.annotations().keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)),
-            method.parameterAnnotationsList.keepIf(Predicates.alwaysTrue()),
-            new SynthesizedCode(forwardSourceCodeBuilder::build),
+            methodDefinition
+                .annotations()
+                .keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)),
+            methodDefinition.parameterAnnotationsList.keepIf(Predicates.alwaysTrue()),
+            forwardMethodBuilder.build(),
             true);
-    // Optimize to generate DexCode instead of SynthesizedCode.
-    ProgramMethod programMethod = new ProgramMethod(clazz, newVirtualMethod);
+    // Optimize to generate DexCode instead of CfCode.
+    ProgramMethod programMethod = new ProgramMethod(methodHolder, newVirtualMethod);
     converter.optimizeSynthesizedMethod(programMethod);
     return newVirtualMethod;
   }
@@ -187,12 +187,15 @@
   //   @Override
   //   public Foo foo() { ... return new SubOfSubOfFoo(); }
   // then this method returns the set { SubOfFoo, SubOfSubOfFoo }.
-  private Set<DexType> getCovariantReturnTypes(DexClass clazz, DexEncodedMethod method) {
+  private Set<DexType> getCovariantReturnTypes(ProgramMethod method) {
     Set<DexType> covariantReturnTypes = new HashSet<>();
-    for (DexAnnotation annotation : method.annotations().annotations) {
+    for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
       if (isCovariantReturnTypeAnnotation(annotation.annotation)) {
         getCovariantReturnTypesFromAnnotation(
-            clazz, method, annotation.annotation, covariantReturnTypes);
+            method.getHolder(),
+            method.getDefinition(),
+            annotation.annotation,
+            covariantReturnTypes);
       }
     }
     return covariantReturnTypes;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 67d389f..069f4b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -32,8 +32,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.BiMap;
@@ -260,15 +259,13 @@
 
       DexMethod origMethod = direct.method;
       DexMethod newMethod = rewriter.staticAsMethodOfDispatchClass(origMethod);
-      ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-          ForwardMethodSourceCode.builder(newMethod);
       // Create a forwarding method to the library static interface method. The method is added
       // to the dispatch class, however, the targeted method is still on the interface, so the
       // interface bit should be set to true.
-      forwardSourceCodeBuilder
-          .setTarget(origMethod)
-          .setInvokeType(Type.STATIC)
-          .setIsInterface(true);
+      ForwardMethodBuilder forwardMethodBuilder =
+          ForwardMethodBuilder.builder(appView.dexItemFactory())
+              .setStaticSource(newMethod)
+              .setStaticTarget(origMethod, true);
       DexEncodedMethod newEncodedMethod =
           new DexEncodedMethod(
               newMethod,
@@ -276,7 +273,7 @@
                   Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(forwardSourceCodeBuilder::build),
+              forwardMethodBuilder.build(),
               true);
       newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
       dispatchMethods.add(newEncodedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
deleted file mode 100644
index a733540..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2020, the R8 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.
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-public class LambdaAccessorMethodWithSynthesizedCode extends LambdaSynthesizedCode {
-
-  public LambdaAccessorMethodWithSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new AccessorMethodSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      DexMethodHandle handle = lambda.descriptor.implHandle;
-      DexMethod target = handle.asMethod();
-      switch (handle.type) {
-        case INVOKE_STATIC:
-          registry.registerInvokeStatic(target);
-          break;
-        case INVOKE_INSTANCE:
-          registry.registerInvokeVirtual(target);
-          break;
-        case INVOKE_CONSTRUCTOR:
-        case INVOKE_DIRECT:
-          registry.registerInvokeDirect(target);
-          break;
-        default:
-          throw new Unreachable("Unexpected handle type: " + handle.type);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
index f0eb914..ddcdc6e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
@@ -4,71 +4,21 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 
 // Source code representing synthesized lambda bridge method.
-final class LambdaBridgeMethodSourceCode extends SynthesizedLambdaSourceCode {
 
-  private final DexMethod mainMethod;
+final class LambdaBridgeMethodSourceCode {
 
-  LambdaBridgeMethodSourceCode(
-      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod, Position callerPosition) {
-    super(lambda, bridgeMethod, callerPosition);
-    this.mainMethod = mainMethod;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    DexType[] currentParams = proto.parameters.values;
-    DexType[] enforcedParams = descriptor().enforcedProto.parameters.values;
-
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
-
-    // Always add a receiver representing 'this' of the lambda class.
-    argValueTypes.add(ValueType.OBJECT);
-    argRegisters.add(getReceiverRegister());
-
-    // Prepare arguments.
-    for (int i = 0; i < currentParams.length; i++) {
-      DexType expectedParamType = enforcedParams[i];
-      argValueTypes.add(ValueType.fromDexType(expectedParamType));
-      argRegisters.add(enforceParameterType(
-          getParamRegister(i), currentParams[i], expectedParamType));
-    }
-
-    // Method call to the main functional interface method.
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.VIRTUAL,
-                this.mainMethod,
-                this.mainMethod.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    // Does the method have return value?
-    if (proto.returnType == factory().voidType) {
-      add(IRBuilder::addReturn);
-    } else {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int tempValue = nextRegister(valueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      // We lack precise sub-type information, but there should not be a need to cast to object.
-      if (proto.returnType != mainMethod.proto.returnType
-          && proto.returnType != factory().objectType) {
-        add(builder -> builder.addCheckCast(tempValue, proto.returnType));
-      }
-      add(builder -> builder.addReturn(tempValue));
-    }
+  public static CfCode build(
+      LambdaClass lambdaClass, DexMethod bridgeMethod, DexMethod mainMethod) {
+    return ForwardMethodBuilder.builder(lambdaClass.appView.dexItemFactory())
+        .setNonStaticSource(bridgeMethod)
+        .setVirtualTarget(mainMethod, false)
+        .setCastArguments(lambdaClass.appView.appInfoForDesugaring())
+        .setCastResult()
+        .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
deleted file mode 100644
index 6e109dd..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaBridgeMethodSynthesizedCode extends LambdaSynthesizedCode {
-
-  private final DexMethod mainMethod;
-  private final DexMethod bridgeMethod;
-
-  LambdaBridgeMethodSynthesizedCode(
-      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod) {
-    super(lambda);
-    this.mainMethod = mainMethod;
-    this.bridgeMethod = bridgeMethod;
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition ->
-        new LambdaBridgeMethodSourceCode(lambda, mainMethod, bridgeMethod, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerInvokeVirtual(mainMethod);
-
-      DexType bridgeMethodReturnType = bridgeMethod.proto.returnType;
-      if (!bridgeMethodReturnType.isVoidType()
-          && bridgeMethodReturnType != mainMethod.proto.returnType
-          && bridgeMethodReturnType != dexItemFactory().objectType) {
-        registry.registerCheckCast(bridgeMethodReturnType);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index cc6fec7..73b78aa2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -234,7 +234,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new LambdaMainMethodSynthesizedCode(this, mainMethod),
+            LambdaMainMethodSourceCode.build(this, mainMethod),
             true);
 
     // Synthesize bridge methods.
@@ -252,7 +252,7 @@
                   false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaBridgeMethodSynthesizedCode(this, mainMethod, bridgeMethod),
+              LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
               true);
     }
     return methods;
@@ -273,7 +273,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new LambdaConstructorSynthesizedCode(this),
+            LambdaConstructorSourceCode.build(this),
             true);
 
     // Class constructor for stateless lambda classes.
@@ -285,7 +285,7 @@
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaClassConstructorSynthesizedCode(this),
+              LambdaClassConstructorSourceCode.build(this),
               true);
     }
     return methods;
@@ -748,13 +748,14 @@
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
               false);
+
       DexEncodedMethod accessorEncodedMethod =
           new DexEncodedMethod(
               callTarget,
               accessorFlags,
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaAccessorMethodWithSynthesizedCode(LambdaClass.this),
+              AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
               true);
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index 6b85039..fd7a8b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -4,40 +4,34 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.graph.CfCode;
 import com.google.common.collect.ImmutableList;
+import org.objectweb.asm.Opcodes;
 
 // Source code representing synthesized lambda class constructor.
 // Used for stateless lambdas to instantiate singleton instance.
-final class LambdaClassConstructorSourceCode extends SynthesizedLambdaSourceCode {
+final class LambdaClassConstructorSourceCode {
 
-  LambdaClassConstructorSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(lambda, lambda.classConstructor, callerPosition, null /* Class initializer is static */);
-    assert lambda.lambdaField != null;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    // Create and initialize an instance.
-    int instance = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addNewInstance(instance, lambda.type));
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.DIRECT,
-                lambda.constructor,
-                lambda.constructor.proto,
-                ImmutableList.of(ValueType.OBJECT),
-                ImmutableList.of(instance),
-                false /* isInterface */));
-
-    // Assign to a field.
-    add(builder -> builder.addStaticPut(instance, lambda.lambdaField));
-
-    // Final return.
-    add(IRBuilder::addReturn);
+  public static CfCode build(LambdaClass lambda) {
+    int maxStack = 2;
+    int maxLocals = 0;
+    return new CfCode(
+        lambda.type,
+        maxStack,
+        maxLocals,
+        ImmutableList.of(
+            new CfNew(lambda.type),
+            new CfStackInstruction(Opcode.Dup),
+            new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
+            new CfFieldInstruction(Opcodes.PUTSTATIC, lambda.lambdaField, lambda.lambdaField),
+            new CfReturnVoid()),
+        ImmutableList.of(),
+        ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
deleted file mode 100644
index 2dbac91..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaClassConstructorSynthesizedCode extends LambdaSynthesizedCode {
-
-  LambdaClassConstructorSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaClassConstructorSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerNewInstance(lambda.type);
-      registry.registerInvokeDirect(lambda.constructor);
-      registry.registerStaticFieldWrite(lambda.lambdaField);
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index a8161d4..65ead77 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -4,70 +4,56 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.Collections;
-
 // Source code representing synthesized lambda constructor.
-final class LambdaConstructorSourceCode extends SynthesizedLambdaSourceCode {
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import org.objectweb.asm.Opcodes;
 
-  LambdaConstructorSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(lambda, lambda.constructor, callerPosition);
-  }
+final class LambdaConstructorSourceCode {
 
-  @Override
-  protected void prepareInstructions() {
+  public static CfCode build(LambdaClass lambda) {
+    int maxStack = 1;
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    Builder<CfInstruction> instructions = ImmutableList.builder();
     // Super constructor call (always java.lang.Object.<init>()).
-    DexMethod objectInitMethod = factory().objectMembers.constructor;
-    add(
-        builder -> {
-          assert builder.getReceiverValue() != null;
-          builder.addInvoke(
-              Invoke.Type.DIRECT,
-              objectInitMethod,
-              objectInitMethod.proto,
-              Collections.singletonList(builder.getReceiverValue()),
-              false /* isInterface */);
-        });
-
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(
+        new CfInvoke(
+            Opcodes.INVOKESPECIAL,
+            lambda.appView.dexItemFactory().objectMembers.constructor,
+            false));
     // Assign capture fields.
-    DexType[] capturedTypes = captures();
-    int capturedValues = capturedTypes.length;
-    if (capturedValues > 0) {
-      for (int i = 0; i < capturedValues; i++) {
-        DexField field = lambda.getCaptureField(i);
-        int idx = i;
-        add(builder -> builder.addInstancePut(getParamRegister(idx), getReceiverRegister(), field));
-      }
+    DexType[] capturedTypes = lambda.descriptor.captures.values;
+    int maxLocals = 1;
+    for (int i = 0; i < capturedTypes.length; i++) {
+      DexField field = lambda.getCaptureField(i);
+      assert field.type == capturedTypes[i];
+      ValueType type = ValueType.fromDexType(field.type);
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfLoad(type, maxLocals));
+      instructions.add(new CfFieldInstruction(Opcodes.PUTFIELD, field, field));
+      maxLocals += type.requiredRegisters();
+      maxStack += type.requiredRegisters();
     }
-
     // Final return.
-    add(IRBuilder::addReturn);
-  }
-
-  @Override
-  public int hashCode() {
-    // We want all zero-parameter constructor source code instances to
-    // be treated as equal, since it only has one call to super constructor,
-    // which is always java.lang.Object.<init>().
-    return captures().length == 0
-        ? System.identityHashCode(factory().objectMembers.constructor)
-        : super.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof LambdaConstructorSourceCode)) {
-      return false;
-    }
-    if (captures().length == 0) {
-      // See comment in hashCode().
-      return ((LambdaConstructorSourceCode) obj).captures().length == 0;
-    }
-    return super.equals(obj);
+    instructions.add(new CfReturnVoid());
+    return new CfCode(
+        lambda.constructor.holder,
+        maxStack,
+        maxLocals,
+        instructions.build(),
+        tryCatchRanges,
+        localVariables);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
deleted file mode 100644
index 1809d69..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaConstructorSynthesizedCode extends LambdaSynthesizedCode {
-
-  LambdaConstructorSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaConstructorSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerInvokeDirect(dexItemFactory().objectMembers.constructor);
-      DexType[] capturedTypes = captures();
-      for (int i = 0; i < capturedTypes.length; i++) {
-        registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index ac55e68..30ea01c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -4,7 +4,21 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNumberConversion;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -12,27 +26,25 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.desugar.LambdaClass.InvalidLambdaImplTarget;
-import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 // Source code representing synthesized lambda main method
-final class LambdaMainMethodSourceCode extends SynthesizedLambdaSourceCode {
+final class LambdaMainMethodSourceCode {
 
-  LambdaMainMethodSourceCode(LambdaClass lambda, DexMethod mainMethod, Position callerPosition) {
-    super(lambda, mainMethod, callerPosition);
-  }
-
-  private boolean checkSignatures(
-      DexType[] captures, DexType[] enforcedParams, DexType enforcedReturnType,
-      List<DexType> implReceiverAndArgs, DexType implReturnType) {
+  private static boolean checkSignatures(
+      DexType[] captures,
+      DexType[] enforcedParams,
+      DexType enforcedReturnType,
+      List<DexType> implReceiverAndArgs,
+      DexType implReturnType,
+      DexItemFactory factory) {
     List<DexType> capturesAndParams = new ArrayList<>();
     capturesAndParams.addAll(Lists.newArrayList(captures));
     capturesAndParams.addAll(Lists.newArrayList(enforcedParams));
@@ -43,23 +55,19 @@
     }
 
     for (int i = 0; i < size; i++) {
-      if (!isSameOrAdaptableTo(capturesAndParams.get(i), implReceiverAndArgs.get(i))) {
+      if (!isSameOrAdaptableTo(capturesAndParams.get(i), implReceiverAndArgs.get(i), factory)) {
         assert false;
       }
     }
 
     if (!enforcedReturnType.isVoidType()
-        && !isSameOrAdaptableTo(implReturnType, enforcedReturnType)) {
+        && !isSameOrAdaptableTo(implReturnType, enforcedReturnType, factory)) {
       assert false;
     }
     return true;
   }
 
-  private DexType getPrimitiveFromBoxed(DexType boxedPrimitive) {
-    return factory().getPrimitiveFromBoxed(boxedPrimitive);
-  }
-
-  private DexType getBoxedForPrimitiveType(DexType primitive) {
+  private static DexType getBoxedForPrimitiveType(DexType primitive, DexItemFactory factory) {
     switch (primitive.descriptor.content[0]) {
       case 'Z':  // byte
       case 'B':  // byte
@@ -69,19 +77,18 @@
       case 'J':  // long
       case 'F':  // float
       case 'D':  // double
-        return factory().getBoxedForPrimitiveType(primitive);
+        return factory.getBoxedForPrimitiveType(primitive);
       default:
         throw new Unreachable("Invalid primitive type descriptor: " + primitive);
     }
   }
 
   // Checks if the types are the same OR type `a` is adaptable to type `b`.
-  private boolean isSameOrAdaptableTo(DexType a, DexType b) {
+  private static boolean isSameOrAdaptableTo(DexType a, DexType b, DexItemFactory factory) {
     if (a == b) {
       return true;
     }
 
-    DexItemFactory factory = factory();
     if (a.isArrayType()) {
       // Arrays are only adaptable to java.lang.Object or other arrays, note that we
       // don't check element type inheritance in the second case since we assume the
@@ -100,7 +107,7 @@
       }
 
       // `a` is primitive and `b` is a supertype of the boxed type `a`.
-      DexType boxedPrimitiveType = getBoxedForPrimitiveType(a);
+      DexType boxedPrimitiveType = getBoxedForPrimitiveType(a, factory);
       if (b == boxedPrimitiveType ||
           b == factory.objectType ||
           b == factory.serializableType ||
@@ -120,7 +127,7 @@
       }
       // `a` is a boxed type for `a*` which can be
       // widened to primitive type `b`.
-      DexType unboxedA = getPrimitiveFromBoxed(a);
+      DexType unboxedA = factory.getPrimitiveFromBoxed(a);
       return unboxedA != null &&
           isSameOrAdaptableTo(unboxedA.descriptor.content[0], b.descriptor.content[0]);
     }
@@ -135,7 +142,7 @@
 
   // For two primitive types `a` is adjustable to `b` iff `a` is the same as `b`
   // or can be converted to `b` via a primitive widening conversion.
-  private boolean isSameOrAdaptableTo(byte from, byte to) {
+  private static boolean isSameOrAdaptableTo(byte from, byte to) {
     if (from == to) {
       return true;
     }
@@ -159,33 +166,31 @@
     }
   }
 
-  @Override
-  protected void prepareInstructions() {
-    DexType[] capturedTypes = captures();
-    DexType[] erasedParams = descriptor().erasedProto.parameters.values;
-    DexType erasedReturnType = descriptor().erasedProto.returnType;
-    DexType[] enforcedParams = descriptor().enforcedProto.parameters.values;
-    DexType enforcedReturnType = descriptor().enforcedProto.returnType;
-
+  public static CfCode build(LambdaClass lambda, DexMethod mainMethod) {
+    DexItemFactory factory = lambda.appView.dexItemFactory();
     LambdaClass.Target target = lambda.target;
     if (target instanceof InvalidLambdaImplTarget) {
-      add(
-          builder -> {
-            InvalidLambdaImplTarget invalidTarget = (InvalidLambdaImplTarget) target;
-            ExceptionThrowingSourceCode.build(builder, invalidTarget.exceptionType);
-          });
-      return;
+      InvalidLambdaImplTarget invalidTarget = (InvalidLambdaImplTarget) target;
+      DexType exceptionType = invalidTarget.exceptionType;
+      return buildThrowingCode(mainMethod, exceptionType, factory);
     }
 
     DexMethod methodToCall = target.callTarget;
+    DexType[] capturedTypes = lambda.descriptor.captures.values;
+    DexType[] erasedParams = lambda.descriptor.erasedProto.parameters.values;
+    DexType erasedReturnType = lambda.descriptor.erasedProto.returnType;
+    DexType[] enforcedParams = lambda.descriptor.enforcedProto.parameters.values;
+    DexType enforcedReturnType = lambda.descriptor.enforcedProto.returnType;
 
     // Only constructor call should use direct invoke type since super
     // and private methods require accessor methods.
     boolean constructorTarget = target.invokeType == Invoke.Type.DIRECT;
-    assert !constructorTarget || methodToCall.name == factory().constructorMethodName;
+    assert !constructorTarget || methodToCall.name == factory.constructorMethodName;
 
+    boolean targetWithReceiver =
+        target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE;
     List<DexType> implReceiverAndArgs = new ArrayList<>();
-    if (target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE) {
+    if (targetWithReceiver) {
       implReceiverAndArgs.add(methodToCall.holder);
     }
     implReceiverAndArgs.addAll(Lists.newArrayList(methodToCall.proto.parameters.values));
@@ -195,87 +200,129 @@
         || target.invokeType == Invoke.Type.VIRTUAL
         || target.invokeType == Invoke.Type.DIRECT
         || target.invokeType == Invoke.Type.INTERFACE;
-    assert checkSignatures(capturedTypes, enforcedParams,
-        enforcedReturnType, implReceiverAndArgs,
-        constructorTarget ? target.callTarget.holder : implReturnType);
+    assert checkSignatures(
+        capturedTypes,
+        enforcedParams,
+        enforcedReturnType,
+        implReceiverAndArgs,
+        constructorTarget ? target.callTarget.holder : implReturnType,
+        factory);
 
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
+    int maxStack = 0;
+    Builder<CfInstruction> instructions = ImmutableList.builder();
 
     // If the target is a constructor, we need to create the instance first.
-    // This instance will be the first argument to the call.
+    // This instance will be the first argument to the call and the dup will be on stack at return.
     if (constructorTarget) {
-      int instance = nextRegister(ValueType.OBJECT);
-      add(builder -> builder.addNewInstance(instance, methodToCall.holder));
-      argValueTypes.add(ValueType.OBJECT);
-      argRegisters.add(instance);
+      instructions.add(new CfNew(methodToCall.holder));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      maxStack += 2;
     }
 
     // Load captures if needed.
     int capturedValues = capturedTypes.length;
     for (int i = 0; i < capturedValues; i++) {
-      ValueType valueType = ValueType.fromDexType(capturedTypes[i]);
-      int register = nextRegister(valueType);
-
-      argValueTypes.add(valueType);
-      argRegisters.add(register);
-
-      // Read field into tmp local.
       DexField field = lambda.getCaptureField(i);
-      add(builder -> builder.addInstanceGet(register, getReceiverRegister(), field));
+      ValueType valueType = ValueType.fromDexType(field.type);
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      maxStack += valueType.requiredRegisters();
     }
 
     // Prepare arguments.
+    int maxLocals = 1; // Local 0 is the lambda/receiver.
     for (int i = 0; i < erasedParams.length; i++) {
+      ValueType valueType = ValueType.fromDexType(mainMethod.getParameters().values[i]);
+      instructions.add(new CfLoad(valueType, maxLocals));
+      maxLocals += valueType.requiredRegisters();
       DexType expectedParamType = implReceiverAndArgs.get(i + capturedValues);
-      argValueTypes.add(ValueType.fromDexType(expectedParamType));
-      argRegisters.add(prepareParameterValue(
-          getParamRegister(i), erasedParams[i], enforcedParams[i], expectedParamType));
+      maxStack =
+          Math.max(
+              maxStack,
+              prepareParameterValue(
+                  erasedParams[i], enforcedParams[i], expectedParamType, instructions, factory));
     }
 
-    // Method call to the method implementing lambda or method-ref.
-    add(
-        builder ->
-            builder.addInvoke(
-                target.invokeType,
-                methodToCall,
-                methodToCall.proto,
-                argValueTypes,
-                argRegisters,
-                target.isInterface()));
+    instructions.add(
+        new CfInvoke(target.invokeType.getCfOpcode(), methodToCall, target.isInterface()));
+    DexType methodToCallReturnType = methodToCall.getReturnType();
+    if (!methodToCallReturnType.isVoidType()) {
+      maxStack =
+          Math.max(maxStack, ValueType.fromDexType(methodToCallReturnType).requiredRegisters());
+    }
 
-    // Does the method have return value?
     if (enforcedReturnType.isVoidType()) {
-      add(IRBuilder::addReturn);
-    } else if (constructorTarget) {
-      // Return newly created instance
-      int instanceRegister = argRegisters.get(0);
-      int adjustedValue = prepareReturnValue(instanceRegister,
-          erasedReturnType, enforcedReturnType, methodToCall.holder);
-      add(builder -> builder.addReturn(adjustedValue));
+      if (!methodToCallReturnType.isVoidType()) {
+        instructions.add(
+            new CfStackInstruction(methodToCallReturnType.isWideType() ? Opcode.Pop2 : Opcode.Pop));
+      }
+      instructions.add(new CfReturnVoid());
     } else {
-      ValueType implValueType = ValueType.fromDexType(implReturnType);
-      int tempValue = nextRegister(implValueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      int adjustedValue = prepareReturnValue(tempValue,
-          erasedReturnType, enforcedReturnType, methodToCall.proto.returnType);
-      add(builder -> builder.addReturn(adjustedValue));
+      // Either the new instance or the called-method result is on top of stack.
+      assert constructorTarget || !methodToCallReturnType.isVoidType();
+      maxStack =
+          Math.max(
+              maxStack,
+              prepareReturnValue(
+                  erasedReturnType,
+                  enforcedReturnType,
+                  constructorTarget ? methodToCall.holder : methodToCallReturnType,
+                  instructions,
+                  factory));
+      instructions.add(new CfReturn(ValueType.fromDexType(enforcedReturnType)));
     }
+
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    CfCode code =
+        new CfCode(
+            mainMethod.holder,
+            maxStack,
+            maxLocals,
+            instructions.build(),
+            tryCatchRanges,
+            localVariables);
+    return code;
+  }
+
+  private static CfCode buildThrowingCode(
+      DexMethod method, DexType exceptionType, DexItemFactory factory) {
+    DexProto initProto = factory.createProto(factory.voidType);
+    DexMethod initMethod =
+        factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
+    int maxStack = 2;
+    int maxLocals = 1;
+    for (DexType param : method.proto.parameters.values) {
+      maxLocals += ValueType.fromDexType(param).requiredRegisters();
+    }
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    return new CfCode(
+        method.holder,
+        maxStack,
+        maxLocals,
+        ImmutableList.of(
+            new CfNew(exceptionType),
+            new CfStackInstruction(Opcode.Dup),
+            new CfInvoke(Opcodes.INVOKESPECIAL, initMethod, false),
+            new CfThrow()),
+        tryCatchRanges,
+        localVariables);
   }
 
   // Adds necessary casts and transformations to adjust the value
   // returned by impl-method to expected return type of the method.
-  private int prepareReturnValue(int register,
-      DexType erasedType, DexType enforcedType, DexType actualType) {
-    // `actualType` must be adjusted to `enforcedType` first.
-    register = adjustType(register, actualType, enforcedType, true);
-
+  private static int prepareReturnValue(
+      DexType erasedType,
+      DexType enforcedType,
+      DexType actualType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     // `erasedType` and `enforcedType` may only differ when they both
     // are class types and `erasedType` is a base type of `enforcedType`,
     // so no transformation is actually needed.
-    assert LambdaDescriptor.isSameOrDerived(factory(), enforcedType, erasedType);
-    return register;
+    assert LambdaDescriptor.isSameOrDerived(factory, enforcedType, erasedType);
+    return adjustType(actualType, enforcedType, true, instructions, factory);
   }
 
   // Adds necessary casts and transformations to adjust parameter
@@ -285,16 +332,50 @@
   // be converted to enforced parameter type (`enforcedType`), which,
   // in its turn, may need to be adjusted to the parameter type of
   // the impl-method (`expectedType`).
-  private int prepareParameterValue(int register,
-      DexType erasedType, DexType enforcedType, DexType expectedType) {
-    register = enforceParameterType(register, erasedType, enforcedType);
-    register = adjustType(register, enforcedType, expectedType, false);
-    return register;
+  private static int prepareParameterValue(
+      DexType erasedType,
+      DexType enforcedType,
+      DexType expectedType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    enforceParameterType(erasedType, enforcedType, instructions, factory);
+    return adjustType(enforcedType, expectedType, false, instructions, factory);
   }
 
-  private int adjustType(int register, DexType fromType, DexType toType, boolean returnType) {
+  private static void enforceParameterType(
+      DexType paramType,
+      DexType enforcedType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    // `paramType` must be either same as `enforcedType` or both must be class
+    // types and `enforcedType` must be a subclass of `paramType` in which case
+    // a cast need to be inserted.
+    if (paramType != enforcedType) {
+      assert LambdaDescriptor.isSameOrDerived(factory, enforcedType, paramType);
+      instructions.add(new CfCheckCast(enforcedType));
+    }
+  }
+
+  private static int adjustType(
+      DexType fromType,
+      DexType toType,
+      boolean returnType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    internalAdjustType(fromType, toType, returnType, instructions, factory);
+    return Math.max(
+        ValueType.fromDexType(fromType).requiredRegisters(),
+        ValueType.fromDexType(toType).requiredRegisters());
+  }
+
+  private static void internalAdjustType(
+      DexType fromType,
+      DexType toType,
+      boolean returnType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     if (fromType == toType) {
-      return register;
+      return;
     }
 
     boolean fromTypePrimitive = fromType.isPrimitiveType();
@@ -302,7 +383,8 @@
 
     // If both are primitive they must be convertible via primitive widening conversion.
     if (fromTypePrimitive && toTypePrimitive) {
-      return addPrimitiveWideningConversion(register, fromType, toType);
+      addPrimitiveWideningConversion(fromType, toType, instructions);
+      return;
     }
 
     // If the first one is a boxed primitive type and the second one is a primitive
@@ -310,62 +392,64 @@
     // widening conversion.
     if (toTypePrimitive) {
       DexType boxedType = fromType;
-      if (boxedType == factory().objectType) {
+      if (boxedType == factory.objectType) {
         // We are in situation when from(=java.lang.Object) is being adjusted to a
         // primitive type, in which case we assume it is of proper box type.
-        boxedType = getBoxedForPrimitiveType(toType);
-        register = castToBoxedType(register, boxedType);
+        boxedType = getBoxedForPrimitiveType(toType, factory);
+        instructions.add(new CfCheckCast(boxedType));
       }
-      DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(boxedType);
+      DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(boxedType);
       if (fromTypeAsPrimitive != null) {
-        int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, boxedType);
-        return addPrimitiveWideningConversion(unboxedRegister, fromTypeAsPrimitive, toType);
+        addPrimitiveUnboxing(fromTypeAsPrimitive, boxedType, instructions, factory);
+        addPrimitiveWideningConversion(fromTypeAsPrimitive, toType, instructions);
+        return;
       }
     }
 
     // If the first one is a primitive type and the second one is a boxed
     // type for this primitive type, just box the value.
     if (fromTypePrimitive) {
-      DexType boxedFromType = getBoxedForPrimitiveType(fromType);
-      if (toType == boxedFromType ||
-          toType == factory().objectType ||
-          toType == factory().serializableType ||
-          toType == factory().comparableType ||
-          (boxedFromType != factory().booleanType &&
-              boxedFromType != factory().charType &&
-              toType == factory().boxedNumberType)) {
-        return addPrimitiveBoxing(register, fromType, boxedFromType);
+      DexType boxedFromType = getBoxedForPrimitiveType(fromType, factory);
+      if (toType == boxedFromType
+          || toType == factory.objectType
+          || toType == factory.serializableType
+          || toType == factory.comparableType
+          || (boxedFromType != factory.booleanType
+              && boxedFromType != factory.charType
+              && toType == factory.boxedNumberType)) {
+        addPrimitiveBoxing(fromType, boxedFromType, instructions, factory);
+        return;
       }
     }
 
-    if (fromType.isArrayType() && (toType == factory().objectType || toType.isArrayType())) {
+    if (fromType.isArrayType() && (toType == factory.objectType || toType.isArrayType())) {
       // If `fromType` is an array and `toType` is java.lang.Object, no cast is needed.
       // If both `fromType` and `toType` are arrays, no cast is needed since we assume
       // the input code is verifiable.
-      return register;
+      return;
     }
 
     if ((fromType.isClassType() && toType.isClassType())
-        || (fromType == factory().objectType && toType.isArrayType())) {
-      if (returnType) {
+        || (fromType == factory.objectType && toType.isArrayType())) {
+      if (returnType && toType != factory.objectType) {
         // For return type adjustment in case `fromType` and `toType` are both reference types,
         // `fromType` does NOT have to be deriving from `toType` and we need to add a cast.
         // NOTE: we don't check `toType` for being actually a supertype, since we
         // might not have full classpath with inheritance information to do that.
-        int finalRegister = register;
-        add(builder -> builder.addCheckCast(finalRegister, toType));
+        instructions.add(new CfCheckCast(toType));
       }
-      return register;
+      return;
     }
 
     throw new Unreachable("Unexpected type adjustment from "
         + fromType.toSourceString() + " to " + toType);
   }
 
-  private int addPrimitiveWideningConversion(int register, DexType fromType, DexType toType) {
+  private static void addPrimitiveWideningConversion(
+      DexType fromType, DexType toType, Builder<CfInstruction> instructions) {
     assert fromType.isPrimitiveType() && toType.isPrimitiveType();
     if (fromType == toType) {
-      return register;
+      return;
     }
 
     NumericType from = NumericType.fromDexType(fromType);
@@ -379,14 +463,13 @@
           if (from != NumericType.BYTE) {
             break; // Only BYTE can be converted to SHORT via widening conversion.
           }
-          int result = nextRegister(ValueType.INT);
-          add(builder -> builder.addConversion(to, NumericType.INT, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(NumericType.INT, to));
+            return;
         }
 
         case INT:
           if (from == NumericType.BYTE || from == NumericType.CHAR || from == NumericType.SHORT) {
-            return register; // No actual conversion is needed.
+            return;
           }
           break;
 
@@ -394,27 +477,24 @@
           if (from == NumericType.FLOAT || from == NumericType.DOUBLE) {
             break; // Not a widening conversion.
           }
-          int result = nextRegister(ValueType.LONG);
-          add(builder -> builder.addConversion(to, NumericType.INT, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(NumericType.INT, to));
+            return;
         }
 
         case FLOAT: {
           if (from == NumericType.DOUBLE) {
             break; // Not a widening conversion.
           }
-          int result = nextRegister(ValueType.FLOAT);
           NumericType type = (from == NumericType.LONG) ? NumericType.LONG : NumericType.INT;
-          add(builder -> builder.addConversion(to, type, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(type, to));
+            return;
         }
 
         case DOUBLE: {
-          int result = nextRegister(ValueType.DOUBLE);
           NumericType type = (from == NumericType.FLOAT || from == NumericType.LONG)
               ? from : NumericType.INT;
-          add(builder -> builder.addConversion(to, type, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(type, to));
+            return;
         }
         default:
           // exception is thrown below
@@ -426,8 +506,7 @@
         "converted to " + toType.toSourceString() + " via primitive widening conversion.");
   }
 
-  private DexMethod getUnboxMethod(byte primitive, DexType boxType) {
-    DexItemFactory factory = factory();
+  private static DexMethod getUnboxMethod(byte primitive, DexType boxType, DexItemFactory factory) {
     DexProto proto;
     switch (primitive) {
       case 'Z':  // byte
@@ -459,53 +538,23 @@
     }
   }
 
-  private int addPrimitiveUnboxing(int register, DexType primitiveType, DexType boxType) {
-    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType);
-
-    List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT);
-    List<Integer> argRegisters = Collections.singletonList(register);
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.VIRTUAL,
-                method,
-                method.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    ValueType valueType = ValueType.fromDexType(primitiveType);
-    int result = nextRegister(valueType);
-    add(builder -> builder.addMoveResult(result));
-    return result;
+  private static void addPrimitiveUnboxing(
+      DexType primitiveType,
+      DexType boxType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType, factory);
+    instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
   }
 
-  private int castToBoxedType(int register, DexType boxType) {
-    add(builder -> builder.addCheckCast(register, boxType));
-    return register;
-  }
-
-  private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) {
+  private static void addPrimitiveBoxing(
+      DexType primitiveType,
+      DexType boxType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     // Generate factory method fo boxing.
-    DexItemFactory factory = factory();
     DexProto proto = factory.createProto(boxType, primitiveType);
     DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
-
-    ValueType valueType = ValueType.fromDexType(primitiveType);
-    List<ValueType> argValueTypes = ImmutableList.of(valueType);
-    List<Integer> argRegisters = Collections.singletonList(register);
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.STATIC,
-                method,
-                method.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    int result = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addMoveResult(result));
-    return result;
+    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
deleted file mode 100644
index 7c24b63..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import java.util.function.Consumer;
-
-public class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
-
-  private final DexMethod mainMethod;
-
-  LambdaMainMethodSynthesizedCode(LambdaClass lambda, DexMethod mainMethod) {
-    super(lambda);
-    this.mainMethod = mainMethod;
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaMainMethodSourceCode(lambda, mainMethod, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      LambdaClass.Target target = lambda.target;
-      assert target.invokeType == Invoke.Type.STATIC
-          || target.invokeType == Invoke.Type.VIRTUAL
-          || target.invokeType == Invoke.Type.DIRECT
-          || target.invokeType == Invoke.Type.INTERFACE;
-
-      boolean constructorTarget = target.invokeType == Type.DIRECT;
-      if (constructorTarget) {
-        registry.registerNewInstance(target.callTarget.holder);
-      }
-
-      DexType[] capturedTypes = captures();
-      for (int i = 0; i < capturedTypes.length; i++) {
-        registry.registerInstanceFieldRead(lambda.getCaptureField(i));
-      }
-
-      switch (target.invokeType) {
-        case DIRECT:
-          registry.registerInvokeDirect(target.callTarget);
-          break;
-        case INTERFACE:
-          registry.registerInvokeInterface(target.callTarget);
-          break;
-        case STATIC:
-          registry.registerInvokeStatic(target.callTarget);
-          break;
-        case VIRTUAL:
-          registry.registerInvokeVirtual(target.callTarget);
-          break;
-        default:
-          throw new Unreachable();
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
deleted file mode 100644
index 5643288..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import java.util.function.Consumer;
-
-abstract class LambdaSynthesizedCode extends SynthesizedCode {
-
-  final LambdaClass lambda;
-
-  LambdaSynthesizedCode(LambdaClass lambda) {
-    super(null);
-    this.lambda = lambda;
-  }
-
-  final DexItemFactory dexItemFactory() {
-    return lambda.appView.dexItemFactory();
-  }
-
-  final LambdaDescriptor descriptor() {
-    return lambda.descriptor;
-  }
-
-  final DexType[] captures() {
-    DexTypeList captures = descriptor().captures;
-    assert captures != null;
-    return captures.values;
-  }
-
-  @Override
-  public abstract SourceCodeProvider getSourceCodeProvider();
-
-  @Override
-  public abstract Consumer<UseRegistry> getRegistryCallback();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
deleted file mode 100644
index fc12713..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2017, the R8 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.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-
-// Represents source code of synthesized lambda class methods.
-abstract class SynthesizedLambdaSourceCode extends SyntheticSourceCode {
-
-  final DexMethod currentMethod;
-  final LambdaClass lambda;
-
-  SynthesizedLambdaSourceCode(
-      LambdaClass lambda, DexMethod currentMethod, Position callerPosition, DexType receiver) {
-    super(receiver, currentMethod, callerPosition);
-    this.lambda = lambda;
-    this.currentMethod = currentMethod;
-  }
-
-  SynthesizedLambdaSourceCode(
-      LambdaClass lambda, DexMethod currentMethod, Position callerPosition) {
-    this(lambda, currentMethod, callerPosition, lambda.type);
-  }
-
-  final LambdaDescriptor descriptor() {
-    return lambda.descriptor;
-  }
-
-  final DexType[] captures() {
-    DexTypeList captures = descriptor().captures;
-    assert captures != null;
-    return captures.values;
-  }
-
-  final DexItemFactory factory() {
-    return lambda.appView.dexItemFactory();
-  }
-
-  final int enforceParameterType(int register, DexType paramType, DexType enforcedType) {
-    // `paramType` must be either same as `enforcedType` or both must be class
-    // types and `enforcedType` must be a subclass of `paramType` in which case
-    // a cast need to be inserted.
-    if (paramType != enforcedType) {
-      assert LambdaDescriptor.isSameOrDerived(factory(), enforcedType, paramType);
-      add(builder -> builder.addCheckCast(register, enforcedType));
-    }
-    return register;
-  }
-
-  @Override
-  public String toString() {
-    return currentMethod.toSourceString();
-  }
-}
-
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
new file mode 100644
index 0000000..8d8ca9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -0,0 +1,243 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import org.objectweb.asm.Opcodes;
+
+public class ForwardMethodBuilder {
+
+  public static ForwardMethodBuilder builder(DexItemFactory factory) {
+    return new ForwardMethodBuilder(factory);
+  }
+
+  private enum InvokeType {
+    STATIC,
+    VIRTUAL,
+    SPECIAL
+  }
+
+  private final DexItemFactory factory;
+
+  private DexMethod sourceMethod = null;
+  private DexMethod targetMethod = null;
+
+  private boolean staticSource = false;
+
+  private InvokeType invokeType = null;
+  private Boolean isInterface = null;
+  private boolean castResult = false;
+  private boolean isConstructorDelegate = false;
+  private AppInfoWithClassHierarchy appInfoForCastArguments = null;
+
+  private ForwardMethodBuilder(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
+    sourceMethod = method;
+    staticSource = false;
+    return this;
+  }
+
+  public ForwardMethodBuilder setStaticSource(DexMethod method) {
+    sourceMethod = method;
+    staticSource = true;
+    return this;
+  }
+
+  public ForwardMethodBuilder setStaticTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.STATIC;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setVirtualTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.VIRTUAL;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setDirectTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.SPECIAL;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setCastResult() {
+    castResult = true;
+    return this;
+  }
+
+  public ForwardMethodBuilder setCastArguments(AppInfoWithClassHierarchy appInfo) {
+    appInfoForCastArguments = appInfo;
+    return this;
+  }
+
+  public ForwardMethodBuilder setConstructorTarget(DexMethod method, DexItemFactory factory) {
+    assert method.isInstanceInitializer(factory);
+    targetMethod = method;
+    isConstructorDelegate = true;
+    invokeType = InvokeType.SPECIAL;
+    isInterface = false;
+    return this;
+  }
+
+  public CfCode build() {
+    assert validate();
+    int maxStack = 0;
+    int maxLocals = 0;
+    Builder<CfInstruction> instructions = ImmutableList.builder();
+    if (isConstructorDelegate) {
+      // A constructor delegate allocates a new instance of the type.
+      // It is dup'ed on the stack so it is ready to return after the invoke call.
+      assert isStaticSource();
+      assert invokeType == InvokeType.SPECIAL;
+      instructions.add(new CfNew(targetMethod.getHolderType()));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      maxStack += 2;
+    } else if (!isStaticSource()) {
+      // If source is not static, load the receiver.
+      instructions.add(new CfLoad(ValueType.OBJECT, maxLocals));
+      maybeInsertArgumentCast(-1, sourceMethod.holder, instructions);
+      maxStack += 1;
+      maxLocals += 1;
+    }
+    DexType[] sourceParameters = getSourceParameters();
+    for (int i = 0; i < sourceParameters.length; i++) {
+      DexType parameter = sourceParameters[i];
+      ValueType parameterType = ValueType.fromDexType(parameter);
+      instructions.add(new CfLoad(parameterType, maxLocals));
+      maxLocals += parameterType.requiredRegisters();
+      maybeInsertArgumentCast(i, parameter, instructions);
+    }
+    instructions.add(new CfInvoke(getInvokeOpcode(), targetMethod, isInterface));
+    if (isSourceReturnVoid()) {
+      assert !isConstructorDelegate;
+      instructions.add(new CfReturnVoid());
+    } else {
+      if (!isConstructorDelegate && sourceMethod.getReturnType() != targetMethod.getReturnType()) {
+        assert castResult;
+        if (sourceMethod.getReturnType() != factory.objectType) {
+          instructions.add(new CfCheckCast(sourceMethod.getReturnType()));
+        }
+      }
+      instructions.add(new CfReturn(getSourceReturnType()));
+    }
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    return new CfCode(
+        sourceMethod.holder,
+        maxStack,
+        maxLocals,
+        instructions.build(),
+        tryCatchRanges,
+        localVariables);
+  }
+
+  private void maybeInsertArgumentCast(
+      int argumentIndex, DexType sourceArgumentType, Builder<CfInstruction> instructions) {
+    if (appInfoForCastArguments == null) {
+      return;
+    }
+    // Shift argument index if mapping between static and non-static.
+    if (isStaticSource() != isStaticTarget()) {
+      argumentIndex += isStaticSource() ? -1 : 1;
+    }
+    // Argument -1 is the receiver.
+    DexType targetArgumentType =
+        argumentIndex == -1
+            ? targetMethod.holder
+            : targetMethod.getParameters().values[argumentIndex];
+    if (sourceArgumentType != targetArgumentType
+        && targetArgumentType != appInfoForCastArguments.dexItemFactory().objectType) {
+      assert appInfoForCastArguments.isSubtype(targetArgumentType, sourceArgumentType);
+      instructions.add(new CfCheckCast(targetArgumentType));
+    }
+  }
+
+  private int getInvokeOpcode() {
+    switch (invokeType) {
+      case STATIC:
+        return Opcodes.INVOKESTATIC;
+      case VIRTUAL:
+        return Opcodes.INVOKEVIRTUAL;
+      case SPECIAL:
+        return Opcodes.INVOKESPECIAL;
+    }
+    throw new Unreachable("Unexpected invoke type: " + invokeType);
+  }
+
+  private DexType[] getSourceParameters() {
+    return sourceMethod.getParameters().values;
+  }
+
+  private boolean isSourceReturnVoid() {
+    return sourceMethod.getReturnType().isVoidType();
+  }
+
+  private ValueType getSourceReturnType() {
+    assert !isSourceReturnVoid();
+    return ValueType.fromDexType(sourceMethod.getReturnType());
+  }
+
+  private boolean isStaticSource() {
+    return staticSource;
+  }
+
+  private boolean isStaticTarget() {
+    return invokeType == InvokeType.STATIC;
+  }
+
+  private int sourceArguments() {
+    return sourceMethod.getParameters().size() + (isStaticSource() ? 0 : 1);
+  }
+
+  private int targetArguments() {
+    // A constructor delegate will allocate the instance so that is subtracted from args.
+    return targetMethod.getParameters().size()
+        + (isStaticTarget() || isConstructorDelegate ? 0 : 1);
+  }
+
+  private boolean validate() {
+    assert sourceMethod != null;
+    assert targetMethod != null;
+    assert invokeType != null;
+    assert isInterface != null;
+    assert sourceArguments() == targetArguments();
+    if (isConstructorDelegate) {
+      assert isStaticSource();
+      assert !sourceMethod.getReturnType().isVoidType();
+      assert targetMethod.getReturnType().isVoidType();
+      assert invokeType == InvokeType.SPECIAL;
+    } else if (castResult) {
+      assert ValueType.fromDexType(sourceMethod.getReturnType())
+          == ValueType.fromDexType(targetMethod.getReturnType());
+    } else {
+      assert sourceMethod.getReturnType() == targetMethod.getReturnType();
+    }
+    return true;
+  }
+}