221
221
import static com .oracle .truffle .espresso .bytecode .Bytecodes .QUICK ;
222
222
import static com .oracle .truffle .espresso .bytecode .Bytecodes .RET ;
223
223
import static com .oracle .truffle .espresso .bytecode .Bytecodes .RETURN ;
224
+ import static com .oracle .truffle .espresso .bytecode .Bytecodes .RETURN_VALUE ;
224
225
import static com .oracle .truffle .espresso .bytecode .Bytecodes .SALOAD ;
225
226
import static com .oracle .truffle .espresso .bytecode .Bytecodes .SASTORE ;
226
227
import static com .oracle .truffle .espresso .bytecode .Bytecodes .SIPUSH ;
227
228
import static com .oracle .truffle .espresso .bytecode .Bytecodes .SLIM_QUICK ;
228
229
import static com .oracle .truffle .espresso .bytecode .Bytecodes .SWAP ;
229
230
import static com .oracle .truffle .espresso .bytecode .Bytecodes .TABLESWITCH ;
231
+ import static com .oracle .truffle .espresso .bytecode .Bytecodes .THROW_VALUE ;
230
232
import static com .oracle .truffle .espresso .bytecode .Bytecodes .WIDE ;
231
233
import static com .oracle .truffle .espresso .nodes .EspressoFrame .clear ;
232
234
import static com .oracle .truffle .espresso .nodes .EspressoFrame .createFrameDescriptor ;
266
268
import static com .oracle .truffle .espresso .nodes .EspressoFrame .startingStackOffset ;
267
269
import static com .oracle .truffle .espresso .nodes .EspressoFrame .swapSingle ;
268
270
271
+ import java .io .Serial ;
269
272
import java .util .Arrays ;
270
273
import java .util .List ;
271
274
import java .util .Locale ;
381
384
/**
382
385
* Bytecode interpreter loop.
383
386
*
384
- *
385
387
* Calling convention uses strict Java primitive types although internally the VM basic types are
386
388
* used with conversions at the boundaries.
387
389
*
393
395
* {@code top} of the stack index is adjusted depending on the bytecode stack offset.
394
396
*/
395
397
public final class BytecodeNode extends AbstractInstrumentableBytecodeNode implements BytecodeOSRNode , GuestAllocator .AllocationProfiler {
396
-
397
398
private static final DebugCounter EXECUTED_BYTECODES_COUNT = DebugCounter .create ("Executed bytecodes" );
398
399
private static final DebugCounter QUICKENED_BYTECODES = DebugCounter .create ("Quickened bytecodes" );
399
400
private static final DebugCounter QUICKENED_INVOKES = DebugCounter .create ("Quickened invokes (excluding INDY)" );
@@ -456,13 +457,19 @@ public final class BytecodeNode extends AbstractInstrumentableBytecodeNode imple
456
457
private final MethodVersion methodVersion ;
457
458
458
459
@ CompilationFinal (dimensions = 1 ) private final byte [] code ;
460
+ private final int returnValueBci ;
461
+ private final int throwValueBci ;
459
462
460
463
public BytecodeNode (MethodVersion methodVersion ) {
461
464
CompilerAsserts .neverPartOfCompilation ();
462
465
Method method = methodVersion .getMethod ();
463
466
assert method .hasBytecodes ();
464
467
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 ;
466
473
this .bs = new BytecodeStream (code );
467
474
this .stackOverflowErrorInfo = method .getSOEHandlerInfo ();
468
475
this .frameDescriptor = createFrameDescriptor (methodVersion .getMaxLocals (), methodVersion .getMaxStackSize ());
@@ -473,7 +480,7 @@ public BytecodeNode(MethodVersion methodVersion) {
473
480
* The "triviality" is partially computed here since isTrivial is called from a compiler
474
481
* thread where the context is not accessible.
475
482
*/
476
- this .trivialBytecodesCache = method . getOriginalCode () .length <= method .getContext ().getEspressoEnv ().TrivialMethodSize
483
+ this .trivialBytecodesCache = originalCode .length <= method .getContext ().getEspressoEnv ().TrivialMethodSize
477
484
? TRIVIAL_UNINITIALIZED
478
485
: TRIVIAL_NO ;
479
486
}
@@ -1283,7 +1290,14 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1283
1290
if (instrument != null ) {
1284
1291
instrument .exitAt (frame , statementIndex , returnValue );
1285
1292
}
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 ;
1287
1301
}
1288
1302
// @formatter:off
1289
1303
// TODO(peterssen): Order shuffled.
@@ -1484,6 +1498,24 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1484
1498
case SLIM_QUICK :
1485
1499
top += sparseNodes [curBCI ].execute (frame , false );
1486
1500
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 ));
1487
1519
1488
1520
default :
1489
1521
CompilerDirectives .transferToInterpreterAndInvalidate ();
@@ -1496,7 +1528,13 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1496
1528
*/
1497
1529
// Get the frame from the stack into the VM heap.
1498
1530
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 ;
1500
1538
} catch (AbstractTruffleException | StackOverflowError | OutOfMemoryError e ) {
1501
1539
CompilerAsserts .partialEvaluationConstant (curBCI );
1502
1540
// Handle both guest and host StackOverflowError.
@@ -1536,6 +1574,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1536
1574
if (CompilerDirectives .hasNextTier () && loopCount .value > 0 ) {
1537
1575
LoopNode .reportLoopCount (this , loopCount .value );
1538
1576
}
1577
+ // this branch is not compiled, it can be a loop exit
1539
1578
throw wrappedStackOverflowError ;
1540
1579
1541
1580
} else /* EspressoException or AbstractTruffleException or OutOfMemoryError */ {
@@ -1547,6 +1586,7 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1547
1586
CompilerDirectives .transferToInterpreter ();
1548
1587
getRoot ().abortMonitor (frame );
1549
1588
// Tearing down the VM, no need to report loop count.
1589
+ // this branch is not compiled, it can be a loop exit
1550
1590
throw e ;
1551
1591
}
1552
1592
assert getContext ().getEspressoEnv ().Polyglot ;
@@ -1596,7 +1636,14 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1596
1636
if (CompilerDirectives .hasNextTier () && loopCount .value > 0 ) {
1597
1637
LoopNode .reportLoopCount (this , loopCount .value );
1598
1638
}
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 ;
1600
1647
}
1601
1648
}
1602
1649
} catch (EspressoOSRReturnException e ) {
@@ -1607,7 +1654,15 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1607
1654
if (instrument != null ) {
1608
1655
instrument .notifyReturn (frame , statementIndex , returnValue );
1609
1656
}
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 ();
1611
1666
}
1612
1667
assert curOpcode != WIDE && curOpcode != LOOKUPSWITCH && curOpcode != TABLESWITCH ;
1613
1668
@@ -1621,6 +1676,19 @@ private Object executeBodyFromBCI(VirtualFrame frame, int startBCI, int startTop
1621
1676
}
1622
1677
}
1623
1678
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
+
1624
1692
private void copyFrameToUnwindRequest (VirtualFrame frame , UnwindContinuationException unwindContinuationExceptionRequest , int bci , int top ) {
1625
1693
// Extend the linked list of frame records as we unwind.
1626
1694
unwindContinuationExceptionRequest .head = HostFrameRecord .recordFrame (frame , getMethodVersion (), bci , top , unwindContinuationExceptionRequest .head );
0 commit comments