Support translation between LIR and IR.
This adds support for just the parts needed to translate hello world.
Bug: b/225838009
Change-Id: I4833c3d2d793e274bc3978441be31f9033b3adb6
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index edd3468..76e9d15 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
import java.util.Set;
/**
@@ -174,4 +175,9 @@
return this;
}
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value> builder) {
+ builder.addArgument(index, knownToBeBoolean);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index ca71070..afb3203 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.lightir.LIRBuilder;
import java.io.UTFDataFormatException;
public class ConstString extends ConstInstruction {
@@ -179,4 +180,9 @@
assert getOutType().equals(expectedType);
return true;
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value> builder) {
+ builder.addConstString(value);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 7cb6f50..55d96c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.lightir.LIRBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
@@ -1556,6 +1557,10 @@
return false;
}
+ public void buildLIR(LIRBuilder<Value> builder) {
+ throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
+ }
+
public static class SideEffectAssumption {
public static final SideEffectAssumption NONE = new SideEffectAssumption();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index fed5f58..f44d128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.List;
@@ -175,4 +176,9 @@
return this;
}
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value> builder) {
+ builder.addInvokeVirtual(getInvokedMethod(), arguments());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 752b30c..935e9df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
public class Return extends JumpInstruction {
@@ -150,4 +151,13 @@
return this;
}
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value> builder) {
+ if (hasReturnValue()) {
+ builder.addReturn(returnValue());
+ } else {
+ builder.addReturnVoid();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index cdec1d9..19b9b27 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
@@ -290,4 +291,9 @@
return this;
}
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value> builder) {
+ builder.addStaticGet(getField());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
index a4a4bdf..5c51959 100644
--- a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.lightir;
+import it.unimi.dsi.fastutil.bytes.ByteIterator;
+
/** Simple utilities for byte encodings. */
public class ByteUtils {
@@ -36,6 +38,15 @@
writer.put(truncateToU1(value));
}
+ public static int readEncodedInt(ByteIterator iterator) {
+ assert 4 == intEncodingSize(0);
+ int value = ensureU1(iterator.nextByte()) << 24;
+ value |= ensureU1(iterator.nextByte()) << 16;
+ value |= ensureU1(iterator.nextByte()) << 8;
+ value |= ensureU1(iterator.nextByte());
+ return value;
+ }
+
public static boolean isU2(int value) {
return (value >= 0) && (value <= 0xFFFF);
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIR2IRBuilder.java
new file mode 100644
index 0000000..76f1040
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRBuilder.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2022, 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.lightir;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class LIR2IRBuilder {
+
+ private final AppView<?> appView;
+
+ public LIR2IRBuilder(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ public IRCode translate(ProgramMethod method, LIRCode lirCode) {
+ Parser parser = new Parser(lirCode, appView);
+ parser.parseArguments(method);
+ lirCode.forEach(view -> view.accept(parser));
+ return parser.getIRCode(method);
+ }
+
+ /**
+ * When building IR the structured LIR parser is used to obtain the decoded operand indexes. The
+ * below parser subclass handles translation of indexes to SSA values.
+ */
+ private static class Parser extends LIRParsedInstructionCallback {
+
+ private final AppView<?> appView;
+ private final LIRCode code;
+ private final NumberGenerator valueNumberGenerator = new NumberGenerator();
+ private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
+
+ private final Value[] values;
+ private final LinkedList<BasicBlock> blocks = new LinkedList<>();
+
+ private BasicBlock currentBlock;
+ private int nextInstructionIndex = 0;
+
+ public Parser(LIRCode code, AppView<?> appView) {
+ super(code);
+ this.appView = appView;
+ this.code = code;
+ this.values = new Value[code.getArgumentCount() + code.getInstructionCount()];
+ }
+
+ public void parseArguments(ProgramMethod method) {
+ currentBlock = new BasicBlock();
+ currentBlock.setNumber(basicBlockNumberGenerator.next());
+ boolean hasReceiverArgument = !method.getDefinition().isStatic();
+ assert code.getArgumentCount()
+ == method.getParameters().size() + (hasReceiverArgument ? 1 : 0);
+ if (hasReceiverArgument) {
+ addThisArgument(method.getHolderType());
+ }
+ method.getParameters().forEach(this::addArgument);
+ }
+
+ public IRCode getIRCode(ProgramMethod method) {
+ // TODO(b/225838009): Support control flow.
+ currentBlock.setFilled();
+ blocks.add(currentBlock);
+ return new IRCode(
+ appView.options(),
+ method,
+ Position.syntheticNone(),
+ blocks,
+ valueNumberGenerator,
+ basicBlockNumberGenerator,
+ code.getMetadata(),
+ method.getOrigin(),
+ new MutableMethodConversionOptions(appView.options()));
+ }
+
+ public Value getSsaValue(int index) {
+ Value value = values[index];
+ if (value == null) {
+ value = new Value(valueNumberGenerator.next(), TypeElement.getBottom(), null);
+ values[index] = value;
+ }
+ return value;
+ }
+
+ public List<Value> getSsaValues(IntList indices) {
+ List<Value> arguments = new ArrayList<>(indices.size());
+ for (int i = 0; i < indices.size(); i++) {
+ arguments.add(getSsaValue(indices.getInt(i)));
+ }
+ return arguments;
+ }
+
+ public int peekNextInstructionIndex() {
+ return nextInstructionIndex;
+ }
+
+ public Value getOutValueForNextInstruction(TypeElement type) {
+ // TODO(b/225838009): Support debug locals.
+ DebugLocalInfo localInfo = null;
+ int index = peekNextInstructionIndex();
+ Value value = values[index];
+ if (value == null) {
+ value = new Value(valueNumberGenerator.next(), type, localInfo);
+ values[index] = value;
+ } else {
+ value.setType(type);
+ if (localInfo != null) {
+ value.setLocalInfo(localInfo);
+ }
+ }
+ return value;
+ }
+
+ private void addInstruction(Instruction instruction) {
+ // TODO(b/225838009): Add correct position info.
+ instruction.setPosition(Position.syntheticNone());
+ currentBlock.getInstructions().add(instruction);
+ instruction.setBlock(currentBlock);
+ ++nextInstructionIndex;
+ }
+
+ private void addThisArgument(DexType type) {
+ Argument argument = addArgument(type);
+ argument.outValue().markAsThis();
+ }
+
+ private Argument addArgument(DexType type) {
+ Argument instruction =
+ new Argument(
+ getOutValueForNextInstruction(type.toTypeElement(appView)),
+ peekNextInstructionIndex(),
+ type.isBooleanType());
+ addInstruction(instruction);
+ return instruction;
+ }
+
+ @Override
+ public void onConstNull() {
+ Value dest = getOutValueForNextInstruction(TypeElement.getNull());
+ addInstruction(new ConstNumber(dest, 0));
+ }
+
+ @Override
+ public void onConstString(DexString string) {
+ Value dest = getOutValueForNextInstruction(TypeElement.stringClassType(appView));
+ addInstruction(new ConstString(dest, string));
+ }
+
+ @Override
+ public void onInvokeVirtual(DexMethod target, IntList arguments) {
+ // TODO(b/225838009): Maintain is-interface bit.
+ Value dest = getInvokeInstructionOutputValue(target);
+ List<Value> ssaArgumentValues = getSsaValues(arguments);
+ InvokeVirtual instruction = new InvokeVirtual(target, dest, ssaArgumentValues);
+ addInstruction(instruction);
+ }
+
+ private Value getInvokeInstructionOutputValue(DexMethod target) {
+ return target.getReturnType().isVoidType()
+ ? null
+ : getOutValueForNextInstruction(target.getReturnType().toTypeElement(appView));
+ }
+
+ @Override
+ public void onStaticGet(DexField field) {
+ Value dest = getOutValueForNextInstruction(field.getTypeElement(appView));
+ addInstruction(new StaticGet(dest, field));
+ }
+
+ @Override
+ public void onReturnVoid() {
+ addInstruction(new Return());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
deleted file mode 100644
index a08c036..0000000
--- a/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2022, 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.lightir;
-
-public interface LIRBasicInstructionCallback {
-
- /**
- * Most basic callback for interpreting LIR.
- *
- * @param opcode The opcode of the instruction (See {@code LIROpcodes} for values).
- * @param operandsOffsetInBytes The offset into the byte stream at which the instruction's payload
- * starts.
- * @param operandsSizeInBytes The total size of the instruction's payload (excluding the opcode
- * itself an any payload size encoding).
- */
- void onInstruction(int opcode, int operandsOffsetInBytes, int operandsSizeInBytes);
-}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index 7ced9ef..06e642b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -3,18 +3,38 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.lightir;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRMetadata;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.List;
-public class LIRBuilder {
+/**
+ * Builder for constructing LIR code from IR.
+ *
+ * @param <V> Type of SSA values. This is abstract to ensure that value internals are not used in
+ * building.
+ */
+public class LIRBuilder<V> {
+
+ public interface ValueIndexGetter<V> {
+ int getValueIndex(V value);
+ }
private final ByteArrayWriter byteWriter = new ByteArrayWriter();
private final LIRWriter writer = new LIRWriter(byteWriter);
private final Reference2IntMap<DexItem> constants;
+ private final ValueIndexGetter<V> valueIndexGetter;
+ private int argumentCount = 0;
+ private int instructionCount = 0;
+ private IRMetadata metadata = null;
- public LIRBuilder() {
+ public LIRBuilder(ValueIndexGetter<V> valueIndexGetter) {
constants = new Reference2IntOpenHashMap<>();
+ this.valueIndexGetter = valueIndexGetter;
}
private int getConstantIndex(DexItem item) {
@@ -23,17 +43,59 @@
return oldIndex != null ? oldIndex : nextIndex;
}
- public LIRBuilder addNop() {
+ private int constantIndexSize(DexItem item) {
+ return 4;
+ }
+
+ private void writeConstantIndex(DexItem item) {
+ int index = getConstantIndex(item);
+ ByteUtils.writeEncodedInt(index, writer::writeOperand);
+ }
+
+ private int getValueIndex(V value) {
+ return valueIndexGetter.getValueIndex(value);
+ }
+
+ private int valueIndexSize(int index) {
+ return ByteUtils.intEncodingSize(index);
+ }
+
+ private void writeValueIndex(int index) {
+ ByteUtils.writeEncodedInt(index, writer::writeOperand);
+ }
+
+ public LIRBuilder<V> setMetadata(IRMetadata metadata) {
+ this.metadata = metadata;
+ return this;
+ }
+
+ public LIRBuilder<V> writeConstantReferencingInstruction(int opcode, DexItem item) {
+ writer.writeInstruction(opcode, constantIndexSize(item));
+ writeConstantIndex(item);
+ return this;
+ }
+
+ public LIRBuilder<V> addArgument(int index, boolean knownToBeBoolean) {
+ // Arguments are implicitly given by method descriptor and not an actual instruction.
+ assert argumentCount == index;
+ argumentCount++;
+ return this;
+ }
+
+ public LIRBuilder<V> addNop() {
+ instructionCount++;
writer.writeOneByteInstruction(LIROpcodes.NOP);
return this;
}
- public LIRBuilder addConstNull() {
+ public LIRBuilder<V> addConstNull() {
+ instructionCount++;
writer.writeOneByteInstruction(LIROpcodes.ACONST_NULL);
return this;
}
- public LIRBuilder addConstInt(int value) {
+ public LIRBuilder<V> addConstInt(int value) {
+ instructionCount++;
if (0 <= value && value <= 5) {
writer.writeOneByteInstruction(LIROpcodes.ICONST_0 + value);
} else {
@@ -43,10 +105,50 @@
return this;
}
+ public LIRBuilder<V> addConstString(DexString string) {
+ instructionCount++;
+ return writeConstantReferencingInstruction(LIROpcodes.LDC, string);
+ }
+
+ public LIRBuilder<V> addStaticGet(DexField field) {
+ instructionCount++;
+ return writeConstantReferencingInstruction(LIROpcodes.GETSTATIC, field);
+ }
+
+ public LIRBuilder<V> addInvokeVirtual(DexMethod method, List<V> arguments) {
+ instructionCount++;
+ int argumentOprandSize = constantIndexSize(method);
+ int[] argumentIndexes = new int[arguments.size()];
+ int i = 0;
+ for (V argument : arguments) {
+ int argumentIndex = getValueIndex(argument);
+ argumentIndexes[i++] = argumentIndex;
+ argumentOprandSize += valueIndexSize(argumentIndex);
+ }
+ writer.writeInstruction(LIROpcodes.INVOKEVIRTUAL, argumentOprandSize);
+ writeConstantIndex(method);
+ for (int argumentIndex : argumentIndexes) {
+ writeValueIndex(argumentIndex);
+ }
+ return this;
+ }
+
+ public LIRBuilder<V> addReturn(V value) {
+ return this;
+ }
+
+ public LIRBuilder<V> addReturnVoid() {
+ instructionCount++;
+ writer.writeInstruction(LIROpcodes.RETURN, 0);
+ return this;
+ }
+
public LIRCode build() {
+ assert metadata != null;
int constantsCount = constants.size();
DexItem[] constantTable = new DexItem[constantsCount];
constants.forEach((item, index) -> constantTable[index] = item);
- return new LIRCode(constantTable, byteWriter.toByteArray());
+ return new LIRCode(
+ metadata, constantTable, argumentCount, byteWriter.toByteArray(), instructionCount);
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
index c91dbd9..d20c4d6 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -4,24 +4,93 @@
package com.android.tools.r8.lightir;
import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
public class LIRCode implements Iterable<LIRInstructionView> {
+ private final IRMetadata metadata;
+
+ /** Constant pool of items. */
private final DexItem[] constants;
+
+ /** Full number of arguments (including receiver for non-static methods). */
+ private final int argumentCount;
+
+ /** Byte encoding of the instructions (including phis). */
private final byte[] instructions;
- public static LIRBuilder builder() {
- return new LIRBuilder();
+ /** Cached value for the number of logical instructions (including phis). */
+ private final int instructionCount;
+
+ public static <V> LIRBuilder<V> builder(ValueIndexGetter<V> valueIndexGetter) {
+ return new LIRBuilder<V>(valueIndexGetter);
}
// Should be constructed using LIRBuilder.
- LIRCode(DexItem[] constants, byte[] instructions) {
+ LIRCode(
+ IRMetadata metadata,
+ DexItem[] constants,
+ int argumentCount,
+ byte[] instructions,
+ int instructionCount) {
+ this.metadata = metadata;
this.constants = constants;
+ this.argumentCount = argumentCount;
this.instructions = instructions;
+ this.instructionCount = instructionCount;
+ }
+
+ public int getArgumentCount() {
+ return argumentCount;
+ }
+
+ public byte[] getInstructionBytes() {
+ return instructions;
+ }
+
+ public int getInstructionCount() {
+ return instructionCount;
+ }
+
+ public IRMetadata getMetadata() {
+ return metadata;
+ }
+
+ public DexItem getConstantItem(int index) {
+ return constants[index];
}
@Override
public LIRIterator iterator() {
return new LIRIterator(new ByteArrayIterator(instructions));
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("LIRCode{");
+ builder.append("constants:");
+ StringUtils.append(builder, Arrays.asList(constants));
+ builder
+ .append(", arguments:")
+ .append(argumentCount)
+ .append(", instructions(size:")
+ .append(instructions.length)
+ .append("):{");
+ int index = 0;
+ for (LIRInstructionView view : this) {
+ builder
+ .append(LIROpcodes.toString(view.getOpcode()))
+ .append("(size:")
+ .append(1 + view.getRemainingOperandSizeInBytes())
+ .append(")");
+ if (index++ < instructionCount) {
+ builder.append(",");
+ }
+ }
+ builder.append("}}");
+ return builder.toString();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
new file mode 100644
index 0000000..42a0955
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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.lightir;
+
+/** Convenience interface to accept a LIR instruction view. */
+public interface LIRInstructionCallback {
+
+ void onInstructionView(LIRInstructionView view);
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
index 59051e3..f42abdb 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
@@ -12,5 +12,21 @@
*/
public interface LIRInstructionView {
- void accept(LIRBasicInstructionCallback eventCallback);
+ /** Convenience method to forward control to a callback. */
+ void accept(LIRInstructionCallback eventCallback);
+
+ /** The opcode of the instruction (See {@code LIROpcodes} for values). */
+ int getOpcode();
+
+ /** The remaining size of the instruction's payload. */
+ int getRemainingOperandSizeInBytes();
+
+ /** True if the instruction has any operands that have not yet been parsed. */
+ boolean hasMoreOperands();
+
+ /** Get the next operand as a constant-pool index. */
+ int getNextConstantOperand();
+
+ /** Get the next operand as an SSA value index. */
+ int getNextValueOperand();
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
index e05d521..9659aa5 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
@@ -18,35 +18,69 @@
private int currentByteIndex = 0;
private int currentOpcode = -1;
- private int currentOperandSize = 0;
+ private int endOfCurrentInstruction = 0;
public LIRIterator(ByteIterator iterator) {
this.iterator = iterator;
}
+ private void skipRemainingOperands() {
+ if (hasMoreOperands()) {
+ skip(getRemainingOperandSizeInBytes());
+ }
+ }
+
@Override
public boolean hasNext() {
+ skipRemainingOperands();
return iterator.hasNext();
}
@Override
public LIRInstructionView next() {
+ skipRemainingOperands();
currentOpcode = u1();
if (LIROpcodes.isOneByteInstruction(currentOpcode)) {
- currentOperandSize = 0;
+ endOfCurrentInstruction = currentByteIndex;
} else {
// Any instruction that is not a single byte has a two-byte header. The second byte is the
// size of the variable width operand payload.
- currentOperandSize = u1();
- skip(currentOperandSize);
+ int operandSize = u1();
+ endOfCurrentInstruction = currentByteIndex + operandSize;
}
return this;
}
@Override
- public void accept(LIRBasicInstructionCallback eventCallback) {
- int operandsOffset = currentByteIndex - currentOperandSize;
- eventCallback.onInstruction(currentOpcode, operandsOffset, currentOperandSize);
+ public void accept(LIRInstructionCallback eventCallback) {
+ eventCallback.onInstructionView(this);
+ }
+
+ @Override
+ public int getOpcode() {
+ return currentOpcode;
+ }
+
+ @Override
+ public int getRemainingOperandSizeInBytes() {
+ return endOfCurrentInstruction - currentByteIndex;
+ }
+
+ @Override
+ public boolean hasMoreOperands() {
+ return currentByteIndex < endOfCurrentInstruction;
+ }
+
+ @Override
+ public int getNextConstantOperand() {
+ assert hasMoreOperands();
+ return u4();
+ }
+
+ @Override
+ public int getNextValueOperand() {
+ assert hasMoreOperands();
+ return u4();
}
private void skip(int i) {
@@ -58,4 +92,9 @@
++currentByteIndex;
return ByteUtils.fromU1(iterator.nextByte());
}
+
+ private int u4() {
+ currentByteIndex += 4;
+ return ByteUtils.readEncodedInt(iterator);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
index 85cc9f1..2141611 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.lightir;
+import com.android.tools.r8.errors.Unreachable;
+
/**
* Constants related to LIR.
*
@@ -179,4 +181,312 @@
int LCONST = 201;
int FCONST = 202;
int DCONST = 203;
+
+ static String toString(int opcode) {
+ switch (opcode) {
+ case NOP:
+ return "NOP";
+ case ACONST_NULL:
+ return "ACONST_NULL";
+ case ICONST_M1:
+ return "ICONST_M1";
+ case ICONST_0:
+ return "ICONST_0";
+ case ICONST_1:
+ return "ICONST_1";
+ case ICONST_2:
+ return "ICONST_2";
+ case ICONST_3:
+ return "ICONST_3";
+ case ICONST_4:
+ return "ICONST_4";
+ case ICONST_5:
+ return "ICONST_5";
+ case LCONST_0:
+ return "LCONST_0";
+ case LCONST_1:
+ return "LCONST_1";
+ case FCONST_0:
+ return "FCONST_0";
+ case FCONST_1:
+ return "FCONST_1";
+ case FCONST_2:
+ return "FCONST_2";
+ case DCONST_0:
+ return "DCONST_0";
+ case DCONST_1:
+ return "DCONST_1";
+ // case BIPUSH: return "BIPUSH";
+ // case SIPUSH: return "SIPUSH";
+ case LDC:
+ return "LDC";
+ // case ILOAD: return "ILOAD";
+ // case LLOAD: return "LLOAD";
+ // case FLOAD: return "FLOAD";
+ // case DLOAD: return "DLOAD";
+ // case ALOAD: return "ALOAD";
+ case IALOAD:
+ return "IALOAD";
+ case LALOAD:
+ return "LALOAD";
+ case FALOAD:
+ return "FALOAD";
+ case DALOAD:
+ return "DALOAD";
+ case AALOAD:
+ return "AALOAD";
+ case BALOAD:
+ return "BALOAD";
+ case CALOAD:
+ return "CALOAD";
+ case SALOAD:
+ return "SALOAD";
+ // case ISTORE: return "ISTORE";
+ // case LSTORE: return "LSTORE";
+ // case FSTORE: return "FSTORE";
+ // case DSTORE: return "DSTORE";
+ // case ASTORE: return "ASTORE";
+ case IASTORE:
+ return "IASTORE";
+ case LASTORE:
+ return "LASTORE";
+ case FASTORE:
+ return "FASTORE";
+ case DASTORE:
+ return "DASTORE";
+ case AASTORE:
+ return "AASTORE";
+ case BASTORE:
+ return "BASTORE";
+ case CASTORE:
+ return "CASTORE";
+ case SASTORE:
+ return "SASTORE";
+ // case POP: return "POP";
+ // case POP2: return "POP2";
+ // case DUP: return "DUP";
+ // case DUP_X1: return "DUP_X1";
+ // case DUP_X2: return "DUP_X2";
+ // case DUP2: return "DUP2";
+ // case DUP2_X1: return "DUP2_X1";
+ // case DUP2_X2: return "DUP2_X2";
+ // case SWAP: return "SWAP";
+ case IADD:
+ return "IADD";
+ case LADD:
+ return "LADD";
+ case FADD:
+ return "FADD";
+ case DADD:
+ return "DADD";
+ case ISUB:
+ return "ISUB";
+ case LSUB:
+ return "LSUB";
+ case FSUB:
+ return "FSUB";
+ case DSUB:
+ return "DSUB";
+ case IMUL:
+ return "IMUL";
+ case LMUL:
+ return "LMUL";
+ case FMUL:
+ return "FMUL";
+ case DMUL:
+ return "DMUL";
+ case IDIV:
+ return "IDIV";
+ case LDIV:
+ return "LDIV";
+ case FDIV:
+ return "FDIV";
+ case DDIV:
+ return "DDIV";
+ case IREM:
+ return "IREM";
+ case LREM:
+ return "LREM";
+ case FREM:
+ return "FREM";
+ case DREM:
+ return "DREM";
+ case INEG:
+ return "INEG";
+ case LNEG:
+ return "LNEG";
+ case FNEG:
+ return "FNEG";
+ case DNEG:
+ return "DNEG";
+ case ISHL:
+ return "ISHL";
+ case LSHL:
+ return "LSHL";
+ case ISHR:
+ return "ISHR";
+ case LSHR:
+ return "LSHR";
+ case IUSHR:
+ return "IUSHR";
+ case LUSHR:
+ return "LUSHR";
+ case IAND:
+ return "IAND";
+ case LAND:
+ return "LAND";
+ case IOR:
+ return "IOR";
+ case LOR:
+ return "LOR";
+ case IXOR:
+ return "IXOR";
+ case LXOR:
+ return "LXOR";
+ // case IINC: return "IINC";
+ case I2L:
+ return "I2L";
+ case I2F:
+ return "I2F";
+ case I2D:
+ return "I2D";
+ case L2I:
+ return "L2I";
+ case L2F:
+ return "L2F";
+ case L2D:
+ return "L2D";
+ case F2I:
+ return "F2I";
+ case F2L:
+ return "F2L";
+ case F2D:
+ return "F2D";
+ case D2I:
+ return "D2I";
+ case D2L:
+ return "D2L";
+ case D2F:
+ return "D2F";
+ case I2B:
+ return "I2B";
+ case I2C:
+ return "I2C";
+ case I2S:
+ return "I2S";
+ case LCMP:
+ return "LCMP";
+ case FCMPL:
+ return "FCMPL";
+ case FCMPG:
+ return "FCMPG";
+ case DCMPL:
+ return "DCMPL";
+ case DCMPG:
+ return "DCMPG";
+ case IFEQ:
+ return "IFEQ";
+ case IFNE:
+ return "IFNE";
+ case IFLT:
+ return "IFLT";
+ case IFGE:
+ return "IFGE";
+ case IFGT:
+ return "IFGT";
+ case IFLE:
+ return "IFLE";
+ case IF_ICMPEQ:
+ return "IF_ICMPEQ";
+ case IF_ICMPNE:
+ return "IF_ICMPNE";
+ case IF_ICMPLT:
+ return "IF_ICMPLT";
+ case IF_ICMPGE:
+ return "IF_ICMPGE";
+ case IF_ICMPGT:
+ return "IF_ICMPGT";
+ case IF_ICMPLE:
+ return "IF_ICMPLE";
+ case IF_ACMPEQ:
+ return "IF_ACMPEQ";
+ case IF_ACMPNE:
+ return "IF_ACMPNE";
+ case GOTO:
+ return "GOTO";
+ // case JSR: return "JSR";
+ // case RET: return "RET";
+ case TABLESWITCH:
+ return "TABLESWITCH";
+ case LOOKUPSWITCH:
+ return "LOOKUPSWITCH";
+ case IRETURN:
+ return "IRETURN";
+ case LRETURN:
+ return "LRETURN";
+ case FRETURN:
+ return "FRETURN";
+ case DRETURN:
+ return "DRETURN";
+ case ARETURN:
+ return "ARETURN";
+ case RETURN:
+ return "RETURN";
+ case GETSTATIC:
+ return "GETSTATIC";
+ case PUTSTATIC:
+ return "PUTSTATIC";
+ case GETFIELD:
+ return "GETFIELD";
+ case PUTFIELD:
+ return "PUTFIELD";
+ case INVOKEVIRTUAL:
+ return "INVOKEVIRTUAL";
+ case INVOKESPECIAL:
+ return "INVOKESPECIAL";
+ case INVOKESTATIC:
+ return "INVOKESTATIC";
+ case INVOKEINTERFACE:
+ return "INVOKEINTERFACE";
+ case INVOKEDYNAMIC:
+ return "INVOKEDYNAMIC";
+ case NEW:
+ return "NEW";
+ case NEWARRAY:
+ return "NEWARRAY";
+ case ANEWARRAY:
+ return "ANEWARRAY";
+ case ARRAYLENGTH:
+ return "ARRAYLENGTH";
+ case ATHROW:
+ return "ATHROW";
+ case CHECKCAST:
+ return "CHECKCAST";
+ case INSTANCEOF:
+ return "INSTANCEOF";
+ case MONITORENTER:
+ return "MONITORENTER";
+ case MONITOREXIT:
+ return "MONITOREXIT";
+ case MULTIANEWARRAY:
+ return "MULTIANEWARRAY";
+ case IFNULL:
+ return "IFNULL";
+ case IFNONNULL:
+ return "IFNONNULL";
+
+ // Non-CF instructions.
+ case ICONST:
+ return "ICONST";
+ case LCONST:
+ return "LCONST";
+ case FCONST:
+ return "FCONST";
+ case DCONST:
+ return "DCONST";
+
+ default:
+ throw new Unreachable("Unexpected LIR opcode: " + opcode);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
new file mode 100644
index 0000000..8ffc285
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2022, 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.lightir;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/**
+ * Structured callbacks for interpreting LIR.
+ *
+ * <p>This callback parses the actual instructions and dispatches to instruction specific methods
+ * where the parsed data is provided as arguments. Instructions that are part of a family of
+ * instructions have a default implementation that will call the "instruction family" methods (e.g.,
+ * onInvokeVirtual will default dispatch to onInvokedMethodInstruction).
+ *
+ * <p>Due to the parsing of the individual instructions, this parser has a higher overhead than
+ * using the basic {@code LIRInstructionView}.
+ */
+public class LIRParsedInstructionCallback implements LIRInstructionCallback {
+
+ private final LIRCode code;
+
+ public LIRParsedInstructionCallback(LIRCode code) {
+ this.code = code;
+ }
+
+ public void onConstNull() {}
+
+ public void onConstString(DexString string) {}
+
+ public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {}
+
+ public void onInvokeVirtual(DexMethod method, IntList arguments) {
+ onInvokeMethodInstruction(method, arguments);
+ }
+
+ public void onFieldInstruction(DexField field) {
+ onFieldInstruction(field);
+ }
+
+ public void onStaticGet(DexField field) {
+ onFieldInstruction(field);
+ }
+
+ public void onReturnVoid() {}
+
+ private DexItem getConstantItem(int index) {
+ return code.getConstantItem(index);
+ }
+
+ @Override
+ public final void onInstructionView(LIRInstructionView view) {
+ switch (view.getOpcode()) {
+ case LIROpcodes.ACONST_NULL:
+ {
+ onConstNull();
+ break;
+ }
+ case LIROpcodes.LDC:
+ {
+ DexItem item = getConstantItem(view.getNextConstantOperand());
+ if (item instanceof DexString) {
+ onConstString((DexString) item);
+ }
+ break;
+ }
+ case LIROpcodes.INVOKEVIRTUAL:
+ {
+ DexMethod target = (DexMethod) getConstantItem(view.getNextConstantOperand());
+ IntList arguments = new IntArrayList();
+ while (view.hasMoreOperands()) {
+ arguments.add(view.getNextValueOperand());
+ }
+ onInvokeVirtual(target, arguments);
+ break;
+ }
+ case LIROpcodes.GETSTATIC:
+ {
+ DexField field = (DexField) getConstantItem(view.getNextConstantOperand());
+ onStaticGet(field);
+ break;
+ }
+ case LIROpcodes.RETURN:
+ {
+ onReturnVoid();
+ break;
+ }
+ default:
+ throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(view.getOpcode()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
index 0602eb3..302f82f 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
@@ -11,7 +11,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.IRMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -30,10 +31,15 @@
@Test
public void test() throws Exception {
- LIRCode code = LIRCode.builder().addConstNull().addConstInt(42).build();
-
- // State to keep track of position in the byte array as we don't expose this in the iterator.
- IntBox offset = new IntBox(0);
+ LIRCode code =
+ LIRCode.builder(
+ v -> {
+ throw new Unreachable();
+ })
+ .setMetadata(IRMetadata.unknown())
+ .addConstNull()
+ .addConstInt(42)
+ .build();
LIRIterator it = code.iterator();
@@ -43,23 +49,17 @@
assertSame(it, next);
it.accept(
- (int opcode, int operandOffset, int operandSize) -> {
- int headerSize = 1;
- assertEquals(LIROpcodes.ACONST_NULL, opcode);
- assertEquals(offset.get() + headerSize, operandOffset);
- assertEquals(0, operandSize);
- offset.increment(headerSize + operandSize);
+ insn -> {
+ assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode());
+ assertEquals(0, insn.getRemainingOperandSizeInBytes());
});
assertTrue(it.hasNext());
it.next();
it.accept(
- (int opcode, int operandOffset, int operandSize) -> {
- int headerSize = 2; // opcode + payload-size
- assertEquals(LIROpcodes.ICONST, opcode);
- assertEquals(offset.get() + headerSize, operandOffset);
- assertEquals(4, operandSize);
- offset.increment(headerSize + operandSize);
+ insn -> {
+ assertEquals(LIROpcodes.ICONST, insn.getOpcode());
+ assertEquals(4, insn.getRemainingOperandSizeInBytes());
});
assertFalse(it.hasNext());
@@ -69,10 +69,10 @@
for (LIRInstructionView view : code) {
if (oldView == null) {
oldView = view;
- view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ACONST_NULL, opcode));
+ view.accept(insn -> assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode()));
} else {
assertSame(oldView, view);
- view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ICONST, opcode));
+ view.accept(insn -> assertEquals(LIROpcodes.ICONST, insn.getOpcode()));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
new file mode 100644
index 0000000..6bf8f98
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2022, 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.lightir;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
+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.ClassKind;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.graph.LazyLoadedDexApplication.Builder;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Timing;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LIRRoundtripTest extends TestBase {
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ private final TestParameters parameters;
+ private AppView<?> appView;
+ private ProgramMethod method;
+
+ public LIRRoundtripTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ DexItemFactory factory = new DexItemFactory();
+ InternalOptions options = new InternalOptions(factory, new Reporter());
+ options.programConsumer = ClassFileConsumer.emptyConsumer();
+ Builder builder = DexApplication.builder(options, Timing.empty());
+ JarClassFileReader<DexProgramClass> reader =
+ new JarClassFileReader<>(
+ new JarApplicationReader(options),
+ clazz -> {
+ builder.addProgramClass(clazz);
+ clazz
+ .programMethods()
+ .forEach(
+ m -> {
+ if (m.getReference().qualifiedName().endsWith("main")) {
+ method = m;
+ }
+ });
+ },
+ ClassKind.PROGRAM);
+ reader.read(Origin.unknown(), ToolHelper.getClassAsBytes(TestClass.class));
+ appView =
+ AppView.createForD8(
+ AppInfo.createInitialAppInfo(
+ builder.build(), GlobalSyntheticsStrategy.forNonSynthesizing()));
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ @Test
+ public void testRoundtrip() throws Exception {
+ IRCode irCode1 = translateCf2IR();
+ LIRCode lirCode = translateIR2LIR(irCode1);
+ IRCode irCode2 = translateLIR2IR(lirCode);
+ translateIR2Cf(irCode2);
+ Path out = writeToFile();
+ testForJvm()
+ .addProgramFiles(out)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private Path writeToFile() {
+ Path out = temp.getRoot().toPath().resolve("out.jar");
+ Marker fakeMarker = new Marker(Tool.D8);
+ ArchiveConsumer consumer = new ArchiveConsumer(out);
+ new CfApplicationWriter(appView, fakeMarker).write(consumer);
+ consumer.finished(appView.reporter());
+ return out;
+ }
+
+ private IRCode translateCf2IR() {
+ CfCode cfCode = method.getDefinition().getCode().asCfCode();
+ return cfCode.buildIR(method, appView, Origin.unknown());
+ }
+
+ private LIRCode translateIR2LIR(IRCode irCode) {
+ Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
+ int index = 0;
+ for (Instruction instruction : irCode.instructions()) {
+ if (instruction.hasOutValue()) {
+ values.put(instruction.outValue(), index);
+ }
+ index++;
+ }
+ LIRBuilder<Value> builder =
+ new LIRBuilder<Value>(values::getInt).setMetadata(irCode.metadata());
+ BasicBlockIterator blockIt = irCode.listIterator();
+ while (blockIt.hasNext()) {
+ BasicBlock block = blockIt.next();
+ // TODO(b/225838009): Support control flow.
+ assert !block.hasPhis();
+ InstructionIterator it = block.iterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ instruction.buildLIR(builder);
+ }
+ }
+ return builder.build();
+ }
+
+ private IRCode translateLIR2IR(LIRCode lirCode) {
+ return new LIR2IRBuilder(appView).translate(method, lirCode);
+ }
+
+ private void translateIR2Cf(IRCode irCode) {
+ CodeRewriter codeRewriter = new CodeRewriter(appView);
+ DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+ CfCode cfCode =
+ new CfBuilder(appView, method, irCode, BytecodeMetadataProvider.empty())
+ .build(deadCodeRemover);
+ method.setCode(cfCode, appView);
+ }
+}