Implement join of cf locals
Bug: 231521474
Change-Id: If06a5c6cd054f6fa062eddc5fd1090107ddf7ae9
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 4bbd2bc..c0e229d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -6,6 +6,9 @@
import static org.objectweb.asm.Opcodes.F_NEW;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.frame.SingleFrameType;
+import com.android.tools.r8.cf.code.frame.WideFrameType;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
@@ -50,7 +53,7 @@
public abstract static class FrameType {
public static FrameType initialized(DexType type) {
- return new InitializedType(type);
+ return type.isWideType() ? new WideInitializedType(type) : new SingleInitializedType(type);
}
public static FrameType uninitializedNew(CfLabel label, DexType typeToInitialize) {
@@ -73,6 +76,10 @@
return TwoWord.SINGLETON;
}
+ public FrameType asFrameType() {
+ return this;
+ }
+
abstract Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
public boolean isObject() {
@@ -88,10 +95,18 @@
return !isWide();
}
+ public SingleFrameType asSingle() {
+ return null;
+ }
+
public boolean isWide() {
return false;
}
+ public WideFrameType asWide() {
+ return null;
+ }
+
public boolean isUninitializedNew() {
return false;
}
@@ -226,16 +241,22 @@
return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
}
- private static class InitializedType extends FrameType {
+ private static class SingleInitializedType extends FrameType implements SingleFrameType {
private final DexType type;
- private InitializedType(DexType type) {
+ private SingleInitializedType(DexType type) {
assert type != null;
this.type = type;
}
@Override
+ public SingleFrameType join(SingleFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -243,7 +264,7 @@
if (obj == null || getClass() != obj.getClass()) {
return false;
}
- InitializedType initializedType = (InitializedType) obj;
+ SingleInitializedType initializedType = (SingleInitializedType) obj;
return type == initializedType.type;
}
@@ -280,8 +301,13 @@
}
@Override
+ public SingleFrameType asSingle() {
+ return this;
+ }
+
+ @Override
public boolean isWide() {
- return type.isPrimitiveType() && (type.toShorty() == 'J' || type.toShorty() == 'D');
+ return false;
}
@Override
@@ -306,13 +332,105 @@
}
}
- private static class Top extends SingletonFrameType {
+ private static class WideInitializedType extends FrameType implements WideFrameType {
+
+ private final DexType type;
+
+ private WideInitializedType(DexType type) {
+ assert type != null;
+ this.type = type;
+ }
+
+ @Override
+ public boolean isWide() {
+ return true;
+ }
+
+ @Override
+ public WideFrameType asWide() {
+ return this;
+ }
+
+ @Override
+ public WideFrameType join(WideFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ WideInitializedType initializedType = (WideInitializedType) obj;
+ return type == initializedType.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Initialized(" + type.toString() + ")";
+ }
+
+ @Override
+ Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+ switch (type.toShorty()) {
+ case 'J':
+ return Opcodes.LONG;
+ case 'D':
+ return Opcodes.DOUBLE;
+ default:
+ throw new Unreachable("Unexpected value type: " + type);
+ }
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ }
+
+ @Override
+ public DexType getInitializedType() {
+ return type;
+ }
+
+ @Override
+ public boolean isObject() {
+ return type.isNullValueType() || type.isReferenceType();
+ }
+
+ @Override
+ public DexType getObjectType(ProgramMethod context) {
+ assert isObject() : "Unexpected use of getObjectType() for non-object FrameType";
+ return type;
+ }
+ }
+
+ private static class Top extends SingletonFrameType implements SingleFrameType {
private static final Top SINGLETON = new Top();
private Top() {}
@Override
+ public SingleFrameType asSingle() {
+ return this;
+ }
+
+ @Override
+ public SingleFrameType join(SingleFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
public String toString() {
return "top";
}
@@ -328,7 +446,7 @@
}
}
- private static class UninitializedNew extends FrameType {
+ private static class UninitializedNew extends FrameType implements SingleFrameType {
private final CfLabel label;
private final DexType type;
@@ -339,6 +457,17 @@
}
@Override
+ public SingleFrameType asSingle() {
+ return this;
+ }
+
+ @Override
+ public SingleFrameType join(SingleFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -396,13 +525,24 @@
}
}
- private static class UninitializedThis extends SingletonFrameType {
+ private static class UninitializedThis extends SingletonFrameType implements SingleFrameType {
private static final UninitializedThis SINGLETON = new UninitializedThis();
private UninitializedThis() {}
@Override
+ public SingleFrameType asSingle() {
+ return this;
+ }
+
+ @Override
+ public SingleFrameType join(SingleFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
return Opcodes.UNINITIALIZED_THIS;
}
@@ -433,13 +573,24 @@
}
}
- private static class OneWord extends SingletonFrameType {
+ private static class OneWord extends SingletonFrameType implements SingleFrameType {
private static final OneWord SINGLETON = new OneWord();
private OneWord() {}
@Override
+ public SingleFrameType asSingle() {
+ return this;
+ }
+
+ @Override
+ public SingleFrameType join(SingleFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
throw new Unreachable("Should only be used for verification");
}
@@ -455,20 +606,31 @@
}
}
- private static class TwoWord extends SingletonFrameType {
+ private static class TwoWord extends SingletonFrameType implements WideFrameType {
private static final TwoWord SINGLETON = new TwoWord();
private TwoWord() {}
@Override
- Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
- throw new Unreachable("Should only be used for verification");
+ public boolean isWide() {
+ return true;
}
@Override
- public boolean isWide() {
- return true;
+ public WideFrameType asWide() {
+ return this;
+ }
+
+ @Override
+ public WideFrameType join(WideFrameType frameType) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
+ @Override
+ Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+ throw new Unreachable("Should only be used for verification");
}
@Override
@@ -786,10 +948,17 @@
return this;
}
+ public boolean hasLocal(int localIndex) {
+ return locals.containsKey(localIndex);
+ }
+
+ public FrameType getLocal(int localIndex) {
+ assert hasLocal(localIndex);
+ return locals.get(localIndex);
+ }
+
public Builder push(FrameType frameType) {
- if (stack == EMPTY_STACK) {
- stack = new ArrayDeque<>();
- }
+ ensureMutableStack();
stack.addLast(frameType);
return this;
}
@@ -800,9 +969,7 @@
}
private Builder internalStore(int localIndex, FrameType frameType) {
- if (locals == EMPTY_LOCALS) {
- locals = new Int2ObjectAVLTreeMap<>();
- }
+ ensureMutableLocals();
locals.put(localIndex, frameType);
if (frameType.isWide()) {
locals.put(localIndex + 1, frameType);
@@ -813,5 +980,23 @@
public CfFrame build() {
return new CfFrame(locals, stack);
}
+
+ public CfFrame buildMutable() {
+ ensureMutableLocals();
+ ensureMutableStack();
+ return build();
+ }
+
+ private void ensureMutableLocals() {
+ if (locals == EMPTY_LOCALS) {
+ locals = new Int2ObjectAVLTreeMap<>();
+ }
+ }
+
+ private void ensureMutableStack() {
+ if (stack == EMPTY_STACK) {
+ stack = new ArrayDeque<>();
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
new file mode 100644
index 0000000..2f1ead8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/SingleFrameType.java
@@ -0,0 +1,14 @@
+// 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.cf.code.frame;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+
+public interface SingleFrameType {
+
+ FrameType asFrameType();
+
+ SingleFrameType join(SingleFrameType frameType);
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java
new file mode 100644
index 0000000..52efbcc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/WideFrameType.java
@@ -0,0 +1,14 @@
+// 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.cf.code.frame;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+
+public interface WideFrameType {
+
+ FrameType asFrameType();
+
+ WideFrameType join(WideFrameType frameType);
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index 0c527a1..46c1930 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -25,6 +25,11 @@
}
@Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
public CfFrameState check(AppView<?> appView, CfFrame frame) {
if (CfAssignability.isFrameAssignable(new CfFrame(), frame, appView).isFailed()) {
return error();
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index ead8fb6..4e63781 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.cf.code.CfAssignability;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -23,7 +22,7 @@
public abstract class CfFrameState extends AbstractState<CfFrameState> {
- public static CfFrameState bottom() {
+ public static BottomCfFrameState bottom() {
return BottomCfFrameState.getInstance();
}
@@ -36,6 +35,22 @@
return this;
}
+ public boolean isBottom() {
+ return false;
+ }
+
+ public boolean isConcrete() {
+ return false;
+ }
+
+ public ConcreteCfFrameState asConcrete() {
+ return null;
+ }
+
+ public boolean isError() {
+ return false;
+ }
+
public abstract CfFrameState check(AppView<?> appView, CfFrame frame);
public abstract CfFrameState clear();
@@ -216,8 +231,15 @@
@Override
public final CfFrameState join(CfFrameState state) {
- // TODO(b/214496607): Implement join.
- throw new Unimplemented();
+ if (state.isBottom() || isError()) {
+ return this;
+ }
+ if (isBottom() || state.isError()) {
+ return state;
+ }
+ assert isConcrete();
+ assert state.isConcrete();
+ return asConcrete().join(state.asConcrete());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
index feed0d2..288f281 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.optimize.interfaces.analysis;
import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -68,9 +67,8 @@
@Override
public CfFrameState computeBlockEntryState(
- CfBlock cfBlock, CfBlock predecessor, CfFrameState predecessorExitState) {
- // TODO(b/214496607): Implement this.
- throw new Unimplemented();
+ CfBlock block, CfBlock predecessor, CfFrameState predecessorExitState) {
+ return predecessorExitState;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index 31484f5..b3aa677 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -9,6 +9,9 @@
import com.android.tools.r8.cf.code.CfAssignability;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.frame.SingleFrameType;
+import com.android.tools.r8.cf.code.frame.WideFrameType;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -16,7 +19,9 @@
import com.android.tools.r8.ir.code.ValueType;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
@@ -37,6 +42,16 @@
}
@Override
+ public boolean isConcrete() {
+ return true;
+ }
+
+ @Override
+ public ConcreteCfFrameState asConcrete() {
+ return this;
+ }
+
+ @Override
public CfFrameState check(AppView<?> appView, CfFrame frame) {
if (CfAssignability.isFrameAssignable(new CfFrame(), frame, appView).isFailed()) {
return error();
@@ -199,6 +214,196 @@
return this;
}
+ public CfFrameState join(ConcreteCfFrameState state) {
+ CfFrame.Builder builder = CfFrame.builder();
+ joinLocals(state.locals, builder);
+ joinStack(state.stack, builder);
+ CfFrame frame = builder.buildMutable();
+ return new ConcreteCfFrameState(frame.getLocals(), frame.getStack());
+ }
+
+ private void joinLocals(Int2ObjectSortedMap<FrameType> locals, CfFrame.Builder builder) {
+ ObjectBidirectionalIterator<Entry<FrameType>> iterator =
+ this.locals.int2ObjectEntrySet().iterator();
+ ObjectBidirectionalIterator<Entry<FrameType>> otherIterator =
+ locals.int2ObjectEntrySet().iterator();
+ while (iterator.hasNext() && otherIterator.hasNext()) {
+ Entry<FrameType> entry = iterator.next();
+ int localIndex = entry.getIntKey();
+ FrameType frameType = entry.getValue();
+
+ Entry<FrameType> otherEntry = otherIterator.next();
+ int otherLocalIndex = otherEntry.getIntKey();
+ FrameType otherFrameType = otherEntry.getValue();
+
+ if (localIndex < otherLocalIndex) {
+ joinLocalsWithDifferentIndices(
+ localIndex,
+ frameType,
+ otherLocalIndex,
+ otherFrameType,
+ iterator,
+ otherIterator,
+ builder);
+ } else if (otherLocalIndex < localIndex) {
+ joinLocalsWithDifferentIndices(
+ otherLocalIndex,
+ otherFrameType,
+ localIndex,
+ frameType,
+ otherIterator,
+ iterator,
+ builder);
+ } else {
+ joinLocalsWithSameIndex(
+ localIndex, frameType, otherFrameType, iterator, otherIterator, builder);
+ }
+ }
+ iterator.forEachRemaining(entry -> joinLocalOnlyPresentInOne(entry, builder));
+ otherIterator.forEachRemaining(entry -> joinLocalOnlyPresentInOne(entry, builder));
+ }
+
+ private void joinLocalsWithDifferentIndices(
+ int localIndex,
+ FrameType frameType,
+ int otherLocalIndex,
+ FrameType otherFrameType,
+ ObjectBidirectionalIterator<Entry<FrameType>> iterator,
+ ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
+ CfFrame.Builder builder) {
+ assert localIndex < otherLocalIndex;
+
+ // Check if the smaller local does not overlap with the larger local.
+ if (frameType.isSingle() || localIndex + 1 < otherLocalIndex) {
+ setLocalToTop(localIndex, frameType, builder);
+ otherIterator.previous();
+ return;
+ }
+
+ // The smaller local is a wide that overlaps with the larger local.
+ setLocalToTop(localIndex, frameType, builder);
+
+ // If the larger local is a wide, then its high part is no longer usable. We also need to handle
+ // overlapping of the other local and the next local in the iterator.
+ if (otherFrameType.isWide()) {
+ int lastLocalIndexMarkedTop = otherLocalIndex + 1;
+ setSingleLocalToTop(lastLocalIndexMarkedTop, builder);
+ handleOverlappingLocals(lastLocalIndexMarkedTop, iterator, otherIterator, builder);
+ }
+ }
+
+ private void joinLocalsWithSameIndex(
+ int localIndex,
+ FrameType frameType,
+ FrameType otherFrameType,
+ ObjectBidirectionalIterator<Entry<FrameType>> iterator,
+ ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
+ CfFrame.Builder builder) {
+ if (frameType.isSingle()) {
+ if (otherFrameType.isSingle()) {
+ joinSingleLocalsWithSameIndex(
+ localIndex, frameType.asSingle(), otherFrameType.asSingle(), builder);
+ } else {
+ setWideLocalToTop(localIndex, builder);
+ handleOverlappingLocals(localIndex + 1, iterator, otherIterator, builder);
+ }
+ } else {
+ if (otherFrameType.isWide()) {
+ joinWideLocalsWithSameIndex(
+ localIndex, frameType.asWide(), otherFrameType.asWide(), builder);
+ } else {
+ setWideLocalToTop(localIndex, builder);
+ handleOverlappingLocals(localIndex + 1, otherIterator, iterator, builder);
+ }
+ }
+ }
+
+ private void joinSingleLocalsWithSameIndex(
+ int localIndex,
+ SingleFrameType frameType,
+ SingleFrameType otherFrameType,
+ CfFrame.Builder builder) {
+ builder.store(localIndex, frameType.join(otherFrameType).asFrameType());
+ }
+
+ private void joinWideLocalsWithSameIndex(
+ int localIndex,
+ WideFrameType frameType,
+ WideFrameType otherFrameType,
+ CfFrame.Builder builder) {
+ builder.store(localIndex, frameType.join(otherFrameType).asFrameType());
+ }
+
+ // TODO(b/231521474): By splitting each wide type into single left/right types, the join of each
+ // (single) local index can be determined by looking at only locals[i] and otherLocals[i] (i.e.,
+ // there is no carry-over). Thus this entire method could be avoided.
+ private void handleOverlappingLocals(
+ int lastLocalIndexMarkedTop,
+ ObjectBidirectionalIterator<Entry<FrameType>> iterator,
+ ObjectBidirectionalIterator<Entry<FrameType>> otherIterator,
+ CfFrame.Builder builder) {
+ ObjectBidirectionalIterator<Entry<FrameType>> currentIterator = iterator;
+ while (currentIterator.hasNext()) {
+ Entry<FrameType> entry = currentIterator.next();
+ int currentLocalIndex = entry.getIntKey();
+ FrameType currentFrameType = entry.getValue();
+
+ // Check if this local overlaps with the previous wide local that was set to top. If not, then
+ // this local is not affected.
+ if (lastLocalIndexMarkedTop < currentLocalIndex) {
+ // The current local still needs to be handled, thus this rewinds the iterator.
+ currentIterator.previous();
+ break;
+ }
+
+ // Verify that the low part of the current local has been set to top.
+ assert builder.hasLocal(currentLocalIndex);
+ assert builder.getLocal(currentLocalIndex).isTop();
+
+ // If the current local is not a wide, then we're done.
+ if (currentFrameType.isSingle()) {
+ // The current local has become top due to the overlap with a wide local. Therefore, this
+ // intentionally does not rewind the iterator.
+ break;
+ }
+
+ // The current local is a wide. We mark its high local index as top due to the overlap, and
+ // check if this wide local overlaps with a wide local in the other locals.
+ lastLocalIndexMarkedTop = currentLocalIndex + 1;
+ setSingleLocalToTop(lastLocalIndexMarkedTop, builder);
+ currentIterator = currentIterator == iterator ? otherIterator : iterator;
+ }
+ }
+
+ private void joinLocalOnlyPresentInOne(Entry<FrameType> entry, CfFrame.Builder builder) {
+ setLocalToTop(entry.getIntKey(), entry.getValue(), builder);
+ }
+
+ private void setLocalToTop(int localIndex, FrameType frameType, CfFrame.Builder builder) {
+ if (frameType.isSingle()) {
+ setSingleLocalToTop(localIndex, builder);
+ } else {
+ setWideLocalToTop(localIndex, builder);
+ }
+ }
+
+ private void setSingleLocalToTop(int localIndex, CfFrame.Builder builder) {
+ assert !builder.hasLocal(localIndex);
+ builder.store(localIndex, FrameType.top());
+ }
+
+ private void setWideLocalToTop(int localIndex, CfFrame.Builder builder) {
+ assert !builder.hasLocal(localIndex);
+ assert !builder.hasLocal(localIndex + 1);
+ setSingleLocalToTop(localIndex, builder);
+ setSingleLocalToTop(localIndex + 1, builder);
+ }
+
+ private void joinStack(Deque<FrameType> stack, CfFrame.Builder builder) {
+ // TODO(b/214496607): Implement this.
+ throw new Unimplemented();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index a84041c..8c846ed 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -25,6 +25,11 @@
}
@Override
+ public boolean isError() {
+ return true;
+ }
+
+ @Override
public CfFrameState check(AppView<?> appView, CfFrame frame) {
return this;
}