Skip to content

Commit 6c71e4f

Browse files
Espresso: Workaround issue with merged exits for merge-explode loop
Loop exits are merged for merge-explode loops. As a result, as soon as a branch is going to exit the interpreter loop (i.e., a branch that ends in a return or a throw that isn't caught in the interpreter loop), we can't rely on the bci or top being a constant. As a workaround, we force the interpreter to go around the loop once more to avoid the loop exit in those branches.
1 parent 74d03ef commit 6c71e4f

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/bytecode/Bytecodes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ public final class Bytecodes {
259259
// Espresso quickened bytecodes.
260260
public static final int QUICK = 203; // 0xCB
261261
public static final int SLIM_QUICK = 204; // 0xCC
262+
// Espresso special bytecodes.
263+
public static final int RETURN_VALUE = 205; // 0xCD
264+
public static final int THROW_VALUE = 206; // 0xCE
262265

263266
public static final int ILLEGAL = 255;
264267
public static final int END = 256;

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,14 @@
221221
import static com.oracle.truffle.espresso.bytecode.Bytecodes.QUICK;
222222
import static com.oracle.truffle.espresso.bytecode.Bytecodes.RET;
223223
import static com.oracle.truffle.espresso.bytecode.Bytecodes.RETURN;
224+
import static com.oracle.truffle.espresso.bytecode.Bytecodes.RETURN_VALUE;
224225
import static com.oracle.truffle.espresso.bytecode.Bytecodes.SALOAD;
225226
import static com.oracle.truffle.espresso.bytecode.Bytecodes.SASTORE;
226227
import static com.oracle.truffle.espresso.bytecode.Bytecodes.SIPUSH;
227228
import static com.oracle.truffle.espresso.bytecode.Bytecodes.SLIM_QUICK;
228229
import static com.oracle.truffle.espresso.bytecode.Bytecodes.SWAP;
229230
import static com.oracle.truffle.espresso.bytecode.Bytecodes.TABLESWITCH;
231+
import static com.oracle.truffle.espresso.bytecode.Bytecodes.THROW_VALUE;
230232
import static com.oracle.truffle.espresso.bytecode.Bytecodes.WIDE;
231233
import static com.oracle.truffle.espresso.nodes.EspressoFrame.clear;
232234
import static com.oracle.truffle.espresso.nodes.EspressoFrame.createFrameDescriptor;
@@ -266,6 +268,7 @@
266268
import static com.oracle.truffle.espresso.nodes.EspressoFrame.startingStackOffset;
267269
import static com.oracle.truffle.espresso.nodes.EspressoFrame.swapSingle;
268270

271+
import java.io.Serial;
269272
import java.util.Arrays;
270273
import java.util.List;
271274
import java.util.Locale;
@@ -381,7 +384,6 @@
381384
/**
382385
* Bytecode interpreter loop.
383386
*
384-
*
385387
* Calling convention uses strict Java primitive types although internally the VM basic types are
386388
* used with conversions at the boundaries.
387389
*
@@ -393,7 +395,6 @@
393395
* {@code top} of the stack index is adjusted depending on the bytecode stack offset.
394396
*/
395397
public final class BytecodeNode extends AbstractInstrumentableBytecodeNode implements BytecodeOSRNode, GuestAllocator.AllocationProfiler {
396-
397398
private static final DebugCounter EXECUTED_BYTECODES_COUNT = DebugCounter.create("Executed bytecodes");
398399
private static final DebugCounter QUICKENED_BYTECODES = DebugCounter.create("Quickened bytecodes");
399400
private static final DebugCounter QUICKENED_INVOKES = DebugCounter.create("Quickened invokes (excluding INDY)");
@@ -456,13 +457,19 @@ public final class BytecodeNode extends AbstractInstrumentableBytecodeNode imple
456457
private final MethodVersion methodVersion;
457458

458459
@CompilationFinal(dimensions = 1) private final byte[] code;
460+
private final int returnValueBci;
461+
private final int throwValueBci;
459462

460463
public BytecodeNode(MethodVersion methodVersion) {
461464
CompilerAsserts.neverPartOfCompilation();
462465
Method method = methodVersion.getMethod();
463466
assert method.hasBytecodes();
464467
this.methodVersion = methodVersion;
465-
this.code = method.getOriginalCode().clone();
468+
byte[] originalCode = method.getOriginalCode();
469+
byte[] customCode = Arrays.copyOf(originalCode, originalCode.length + 2);
470+
customCode[returnValueBci = originalCode.length] = (byte) Bytecodes.RETURN_VALUE;
471+
customCode[throwValueBci = originalCode.length + 1] = (byte) THROW_VALUE;
472+
this.code = customCode;
466473
this.bs = new BytecodeStream(code);
467474
this.stackOverflowErrorInfo = method.getSOEHandlerInfo();
468475
this.frameDescriptor = createFrameDescriptor(methodVersion.getMaxLocals(), methodVersion.getMaxStackSize());
@@ -473,7 +480,7 @@ public BytecodeNode(MethodVersion methodVersion) {
473480
* The "triviality" is partially computed here since isTrivial is called from a compiler
474481
* thread where the context is not accessible.
475482
*/
476-
this.trivialBytecodesCache = method.getOriginalCode().length <= method.getContext().getEspressoEnv().TrivialMethodSize
483+
this.trivialBytecodesCache = originalCode.length <= method.getContext().getEspressoEnv().TrivialMethodSize
477484
? TRIVIAL_UNINITIALIZED
478485
: TRIVIAL_NO;
479486
}
@@ -1283,7 +1290,14 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
12831290
if (instrument != null) {
12841291
instrument.exitAt(frame, statementIndex, returnValue);
12851292
}
1286-
return returnValue;
1293+
1294+
// This branch must not be a loop exit.
1295+
// Let the next loop iteration return this
1296+
top = startingStackOffset(getMethodVersion().getMaxLocals());
1297+
frame.setObjectStatic(top, returnValue);
1298+
top++;
1299+
curBCI = returnValueBci;
1300+
continue loop;
12871301
}
12881302
// @formatter:off
12891303
// TODO(peterssen): Order shuffled.
@@ -1484,6 +1498,24 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
14841498
case SLIM_QUICK:
14851499
top += sparseNodes[curBCI].execute(frame, false);
14861500
break;
1501+
case RETURN_VALUE:
1502+
/*
1503+
* Synthetic bytecode used to avoid merging interpreter loop exits too early
1504+
* (and thus lose partial-evaluation constants too early). When reached, the
1505+
* object at stack slot 0 should be returned.
1506+
*/
1507+
assert top == startingStackOffset(getMethodVersion().getMaxLocals()) + 1;
1508+
assert curBCI == returnValueBci;
1509+
return frame.getObjectStatic(top - 1);
1510+
case THROW_VALUE:
1511+
/*
1512+
* Synthetic bytecode used to avoid merging interpreter loop exits too early
1513+
* (and thus lose partial-evaluation constants too early). When reached, the
1514+
* object at stack slot 0 should be thrown.
1515+
*/
1516+
assert top == startingStackOffset(getMethodVersion().getMaxLocals()) + 1;
1517+
assert curBCI == throwValueBci;
1518+
throw new ThrowOutOfInterpreterLoop((RuntimeException) frame.getObjectStatic(top - 1));
14871519

14881520
default:
14891521
CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -1496,7 +1528,13 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
14961528
*/
14971529
// Get the frame from the stack into the VM heap.
14981530
copyFrameToUnwindRequest(frame, unwindContinuationExceptionRequest, curBCI, top);
1499-
throw unwindContinuationExceptionRequest;
1531+
1532+
// This branch must not be a loop exit. Let the next loop iteration throw this
1533+
top = startingStackOffset(getMethodVersion().getMaxLocals());
1534+
frame.setObjectStatic(top, unwindContinuationExceptionRequest);
1535+
top++;
1536+
curBCI = throwValueBci;
1537+
continue loop;
15001538
} catch (AbstractTruffleException | StackOverflowError | OutOfMemoryError e) {
15011539
CompilerAsserts.partialEvaluationConstant(curBCI);
15021540
// Handle both guest and host StackOverflowError.
@@ -1536,6 +1574,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
15361574
if (CompilerDirectives.hasNextTier() && loopCount.value > 0) {
15371575
LoopNode.reportLoopCount(this, loopCount.value);
15381576
}
1577+
// this branch is not compiled, it can be a loop exit
15391578
throw wrappedStackOverflowError;
15401579

15411580
} else /* EspressoException or AbstractTruffleException or OutOfMemoryError */ {
@@ -1547,6 +1586,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
15471586
CompilerDirectives.transferToInterpreter();
15481587
getRoot().abortMonitor(frame);
15491588
// Tearing down the VM, no need to report loop count.
1589+
// this branch is not compiled, it can be a loop exit
15501590
throw e;
15511591
}
15521592
assert getContext().getEspressoEnv().Polyglot;
@@ -1596,7 +1636,14 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
15961636
if (CompilerDirectives.hasNextTier() && loopCount.value > 0) {
15971637
LoopNode.reportLoopCount(this, loopCount.value);
15981638
}
1599-
throw e;
1639+
1640+
// This branch must not be a loop exit.
1641+
// Let the next loop iteration throw this
1642+
top = startingStackOffset(getMethodVersion().getMaxLocals());
1643+
frame.setObjectStatic(top, wrappedException);
1644+
top++;
1645+
curBCI = throwValueBci;
1646+
continue loop;
16001647
}
16011648
}
16021649
} catch (EspressoOSRReturnException e) {
@@ -1607,7 +1654,15 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
16071654
if (instrument != null) {
16081655
instrument.notifyReturn(frame, statementIndex, returnValue);
16091656
}
1610-
return returnValue;
1657+
1658+
// This branch must not be a loop exit. Let the next loop iteration return this
1659+
top = startingStackOffset(getMethodVersion().getMaxLocals());
1660+
frame.setObjectStatic(top, returnValue);
1661+
top++;
1662+
curBCI = returnValueBci;
1663+
continue loop;
1664+
} catch (ThrowOutOfInterpreterLoop e) {
1665+
throw e.reThrow();
16111666
}
16121667
assert curOpcode != WIDE && curOpcode != LOOKUPSWITCH && curOpcode != TABLESWITCH;
16131668

@@ -1621,6 +1676,19 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
16211676
}
16221677
}
16231678

1679+
private static final class ThrowOutOfInterpreterLoop extends ControlFlowException {
1680+
@Serial private static final long serialVersionUID = 774753014650104744L;
1681+
private final RuntimeException exception;
1682+
1683+
private ThrowOutOfInterpreterLoop(RuntimeException exception) {
1684+
this.exception = exception;
1685+
}
1686+
1687+
RuntimeException reThrow() {
1688+
throw exception;
1689+
}
1690+
}
1691+
16241692
private void copyFrameToUnwindRequest(VirtualFrame frame, UnwindContinuationException unwindContinuationExceptionRequest, int bci, int top) {
16251693
// Extend the linked list of frame records as we unwind.
16261694
unwindContinuationExceptionRequest.head = HostFrameRecord.recordFrame(frame, getMethodVersion(), bci, top, unwindContinuationExceptionRequest.head);

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/EspressoFrame.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ private EspressoFrame() {
5858
private static final int VALUES_START = 1;
5959

6060
public static FrameDescriptor createFrameDescriptor(int locals, int stack) {
61-
int slotCount = locals + stack;
61+
// at least one stack slot for the return / exception value
62+
int slotCount = locals + Math.max(stack, 1);
6263
FrameDescriptor.Builder builder = FrameDescriptor.newBuilder(slotCount + VALUES_START);
6364
int bciSlot = builder.addSlot(FrameSlotKind.Static, null, null); // BCI
6465
assert bciSlot == BCI_SLOT;

0 commit comments

Comments
 (0)