/*
* Copyright (c) 2015, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.truffle.test;
import org.junit.Assert;
import org.junit.Test;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.FrameSlotTypeException;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ControlFlowException;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind;
import com.oracle.truffle.api.nodes.RootNode;
public class BytecodeInterpreterPartialEvaluationTest extends PartialEvaluationTest {
public static class Bytecode {
public static final byte CONST = 0;
public static final byte RETURN = 1;
public static final byte ADD = 2;
public static final byte IFZERO = 3;
public static final byte POP = 4;
public static final byte JMP = 5;
public static final byte DUP = 6;
public static final byte SWITCH = 7;
}
public static boolean TRACE = false;
/*
* A method with a non-exploded loop, which goes away after loop unrolling as long as the
* parameter is a compilation constant. The method is called from multiple places to inject
* additional loops into the test cases, i.e., to stress the partial evaluator and compiler
* optimizations.
*/
static int nonExplodedLoop(int x) {
if (x >= 0 && x < 50) {
int result = 0;
for (int i = 0; i < x; i++) {
result++;
if (result > 100) {
/* Dead branch because result < 50, just to complicate the loop structure. */
CompilerDirectives.transferToInterpreter();
}
}
if (result > 100) {
/* Dead branch, just to have exception-throwing calls during partial evaluation. */
try {
boundary();
boundary();
} catch (ControlFlowException ex) {
CompilerDirectives.transferToInterpreter();
} catch (RuntimeException ex) {
/* A complicated exception handler to stress the loop detection. */
for (int i = 0; i < result; i++) {
if (i == 42) {
throw ex;
}
}
CompilerDirectives.transferToInterpreter();
}
}
return result;
} else {
return x;
}
}
@TruffleBoundary(throwsControlFlowException = true)
static void boundary() {
}
public static class Program extends RootNode {
private final String name;
@CompilationFinal(dimensions = 1) private final byte[] bytecodes;
@CompilationFinal(dimensions = 1) private final FrameSlot[] locals;
@CompilationFinal(dimensions = 1) private final FrameSlot[] stack;
public Program(String name, byte[] bytecodes, int maxLocals, int maxStack) {
super(null);
this.name = name;
this.bytecodes = bytecodes;
locals = new FrameSlot[maxLocals];
stack = new FrameSlot[maxStack];
for (int i = 0; i < maxLocals; ++i) {
locals[i] = this.getFrameDescriptor().addFrameSlot("local" + i);
locals[i].setKind(FrameSlotKind.Int);
}
for (int i = 0; i < maxStack; ++i) {
stack[i] = this.getFrameDescriptor().addFrameSlot("stack" + i);
stack[i].setKind(FrameSlotKind.Int);
}
}
protected void setInt(VirtualFrame frame, int stackIndex, int value) {
frame.setInt(stack[stackIndex], value);
}
protected int getInt(VirtualFrame frame, int stackIndex) {
try {
return frame.getInt(stack[stackIndex]);
} catch (FrameSlotTypeException e) {
throw new IllegalStateException("Error accessing stack slot " + stackIndex);
}
}
@Override
public String toString() {
return name;
}
public void trace(String format, Object... args) {
if (CompilerDirectives.inInterpreter() && TRACE) {
System.out.println(String.format(format, args));
}
}
@Override
@ExplodeLoop(kind = LoopExplosionKind.MERGE_EXPLODE)
public Object execute(VirtualFrame frame) {
trace("Start program");
int topOfStack = -1;
int bci = 0;
int result = 0;
boolean running = true;
outer: while (running) {
CompilerAsserts.partialEvaluationConstant(bci);
switch (bytecodes[bci]) {
case Bytecode.CONST: {
byte value = bytecodes[bci + 1];
trace("%d (%d): CONST %d", bci, topOfStack, value);
topOfStack++;
setInt(frame, topOfStack, nonExplodedLoop(value));
bci = bci + 2;
continue;
}
case Bytecode.RETURN: {
int value = getInt(frame, topOfStack);
trace("%d (%d): RETURN %d", bci, topOfStack, value);
result = nonExplodedLoop(value);
running = false;
continue;
}
case Bytecode.ADD: {
int left = getInt(frame, topOfStack);
int right = getInt(frame, topOfStack - 1);
trace("%d (%d): ADD %d %d", bci, topOfStack, left, right);
topOfStack--;
setInt(frame, topOfStack, left + right);
bci = bci + 1;
continue;
}
case Bytecode.IFZERO: {
int value = getInt(frame, topOfStack);
byte trueBci = bytecodes[bci + 1];
trace("%d (%d): IFZERO %d to %d", bci, topOfStack, value, trueBci);
topOfStack--;
if (value == 0) {
bci = trueBci;
} else {
bci = bci + 2;
}
continue;
}
case Bytecode.SWITCH: {
int value = getInt(frame, topOfStack);
byte numCases = bytecodes[bci + 1];
trace("%d (%d): SWITCH", bci, topOfStack);
topOfStack--;
for (int i = 0; i < numCases; ++i) {
if (value == i) {
bci = bytecodes[bci + i + 2];
continue outer;
}
}
// Continue with the code after the switch.
bci += numCases + 2;
continue;
}
case Bytecode.POP: {
int value = getInt(frame, topOfStack);
trace("%d (%d): POP %d", bci, topOfStack, value);
topOfStack--;
bci++;
continue;
}
case Bytecode.JMP: {
byte newBci = bytecodes[bci + 1];
trace("%d (%d): JMP to %d", bci, topOfStack, newBci);
bci = newBci;
continue;
}
case Bytecode.DUP: {
int dupValue = getInt(frame, topOfStack);
trace("%d (%d): DUP %d", bci, topOfStack, dupValue);
topOfStack++;
setInt(frame, topOfStack, dupValue);
bci++;
continue;
}
}
}
return nonExplodedLoop(result);
}
}
public static Object constant42() {
return 42;
}
private static void assertReturns42(RootNode program) {
Object result = Truffle.getRuntime().createCallTarget(program).call();
Assert.assertEquals(Integer.valueOf(42), result);
}
private void assertPartialEvalEqualsAndRunsCorrect(RootNode program) {
assertReturns42(program);
assertPartialEvalEquals("constant42", program);
}
@Test
public void constReturnProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */42,
/* 2: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("constReturnProgram", bytecodes, 0, 2));
}
@Test
public void constAddProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */40,
/* 2: */Bytecode.CONST,
/* 3: */2,
/* 4: */Bytecode.ADD,
/* 5: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("constAddProgram", bytecodes, 0, 2));
}
@Test
public void simpleIfProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */40,
/* 2: */Bytecode.CONST,
/* 3: */1,
/* 4: */Bytecode.IFZERO,
/* 5: */8,
/* 6: */Bytecode.CONST,
/* 7: */42,
/* 8: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("simpleIfProgram", bytecodes, 0, 3));
}
@Test
public void ifAndPopProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */40,
/* 2: */Bytecode.CONST,
/* 3: */1,
/* 4: */Bytecode.IFZERO,
/* 5: */9,
/* 6: */Bytecode.POP,
/* 7: */Bytecode.CONST,
/* 8: */42,
/* 9: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("ifAndPopProgram", bytecodes, 0, 3));
}
@Test
public void simpleLoopProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */42,
/* 2: */Bytecode.CONST,
/* 3: */-12,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.ADD,
/* 7: */Bytecode.DUP,
/* 8: */Bytecode.IFZERO,
/* 9: */12,
/* 10: */Bytecode.JMP,
/* 11: */4,
/* 12: */Bytecode.POP,
/* 13: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("simpleLoopProgram", bytecodes, 0, 3));
}
@Test
public void nestedLoopsProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */42,
/* 2: */Bytecode.CONST,
/* 3: */-2,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.ADD,
/* 7: */Bytecode.DUP,
/* 8: */Bytecode.CONST,
/* 9: */-2,
/* 10: */Bytecode.CONST,
/* 11: */1,
/* 12: */Bytecode.ADD,
/* 13: */Bytecode.DUP,
/* 14: */Bytecode.IFZERO,
/* 15: */18,
/* 16: */Bytecode.JMP,
/* 17: */10,
/* 18: */Bytecode.POP,
/* 19: */Bytecode.IFZERO,
/* 20: */23,
/* 21: */Bytecode.JMP,
/* 22: */4,
/* 23: */Bytecode.POP,
/* 24: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("nestedLoopsProgram", bytecodes, 0, 6));
}
@Test
public void nestedLoopsProgram2() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */42,
/* 2: */Bytecode.CONST,
/* 3: */-2,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.ADD,
/* 7: */Bytecode.DUP,
/* 8: */Bytecode.CONST,
/* 9: */-2,
/* 10: */Bytecode.CONST,
/* 11: */0,
/* 12: */Bytecode.IFZERO,
/* 13: */17,
/* 14: */Bytecode.POP,
/* 15: */Bytecode.JMP,
/* 16: */30,
/* 17: */Bytecode.CONST,
/* 18: */1,
/* 19: */Bytecode.ADD,
/* 10: */Bytecode.DUP,
/* 21: */Bytecode.IFZERO,
/* 22: */25,
/* 23: */Bytecode.JMP,
/* 24: */10,
/* 25: */Bytecode.POP,
/* 26: */Bytecode.IFZERO,
/* 27: */30,
/* 28: */Bytecode.JMP,
/* 29: */4,
/* 30: */Bytecode.POP,
/* 31: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("nestedLoopsProgram2", bytecodes, 0, 6));
}
@Test
public void nestedLoopsProgram3() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */42,
/* 2: */Bytecode.CONST,
/* 3: */-2,
/* 4: */Bytecode.DUP,
/* 5: */Bytecode.POP,
/* 6: */Bytecode.DUP,
/* 7: */Bytecode.IFZERO,
/* 8: */22,
/* 9: */Bytecode.CONST,
/* 10: */1,
/* 11: */Bytecode.ADD,
/* 12: */Bytecode.CONST,
/* 13: */-2,
/* 14: */Bytecode.DUP,
/* 15: */Bytecode.IFZERO,
/* 16: */5,
/* 17: */Bytecode.CONST,
/* 18: */1,
/* 19: */Bytecode.ADD,
/* 20: */Bytecode.JMP,
/* 21: */14,
/* 22: */Bytecode.POP,
/* 23: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("nestedLoopsProgram", bytecodes, 0, 8));
}
@Test
public void irreducibleLoop01() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */0,
/* 2: */Bytecode.IFZERO,
/* 3: */7,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.POP,
/* 7: */Bytecode.CONST,
/* 8: */1,
/* 9: */Bytecode.IFZERO,
/* 10: */4,
/* 11: */Bytecode.CONST,
/* 12: */42,
/* 13: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("irreducibleLoop01", bytecodes, 0, 3));
}
@Test
public void irreducibleLoop02() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */0,
/* 2: */Bytecode.IFZERO,
/* 3: */7,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.POP,
/* 7: */Bytecode.CONST,
/* 8: */1,
/* 9: */Bytecode.IFZERO,
/* 10: */4,
/* 11: */Bytecode.CONST,
/* 12: */1,
/* 13: */Bytecode.IFZERO,
/* 14: */0,
/* 15: */Bytecode.CONST,
/* 16: */42,
/* 17: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("irreducibleLoop02", bytecodes, 0, 3));
}
@Test
public void irreducibleLoop03() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */1,
/* 2: */Bytecode.POP,
/* 3: */Bytecode.CONST,
/* 4: */0,
/* 5: */Bytecode.IFZERO,
/* 6: */10,
/* 7: */Bytecode.CONST,
/* 8: */1,
/* 9: */Bytecode.POP,
/* 10: */Bytecode.CONST,
/* 11: */1,
/* 12: */Bytecode.IFZERO,
/* 13: */7,
/* 14: */Bytecode.CONST,
/* 15: */1,
/* 16: */Bytecode.IFZERO,
/* 17: */3,
/* 18: */Bytecode.CONST,
/* 19: */1,
/* 20: */Bytecode.IFZERO,
/* 21: */18,
/* 22: */Bytecode.CONST,
/* 23: */42,
/* 24: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("irreducibleLoop03", bytecodes, 0, 3));
}
@Test
public void irreducibleLoop04() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */0,
/* 2: */Bytecode.IFZERO,
/* 3: */7,
/* 4: */Bytecode.CONST,
/* 5: */1,
/* 6: */Bytecode.POP,
/* 7: */Bytecode.CONST,
/* 8: */1,
/* 9: */Bytecode.IFZERO,
/* 10: */7,
/* 11: */Bytecode.CONST,
/* 12: */1,
/* 13: */Bytecode.IFZERO,
/* 14: */4,
/* 15: */Bytecode.CONST,
/* 16: */1,
/* 17: */Bytecode.IFZERO,
/* 18: */15,
/* 19: */Bytecode.CONST,
/* 20: */42,
/* 21: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("irreducibleLoop04", bytecodes, 0, 3));
}
@Test(timeout = 5000)
public void manyIfsProgram() {
initializeForTimeout();
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */40,
/* 2: */Bytecode.CONST,
/* 3: */1,
/* 4: */Bytecode.IFZERO,
/* 5: */8,
/* 6: */Bytecode.CONST,
/* 7: */1,
/* 8: */Bytecode.IFZERO,
/* 9: */12,
/* 10: */Bytecode.CONST,
/* 11: */1,
/* 12: */Bytecode.IFZERO,
/* 13: */16,
/* 14: */Bytecode.CONST,
/* 15: */1,
/* 16: */Bytecode.IFZERO,
/* 17: */20,
/* 18: */Bytecode.CONST,
/* 19: */1,
/* 20: */Bytecode.IFZERO,
/* 21: */24,
/* 22: */Bytecode.CONST,
/* 23: */1,
/* 24: */Bytecode.IFZERO,
/* 25: */28,
/* 26: */Bytecode.CONST,
/* 27: */1,
/* 28: */Bytecode.IFZERO,
/* 29: */32,
/* 30: */Bytecode.CONST,
/* 31: */1,
/* 32: */Bytecode.IFZERO,
/* 33: */36,
/* 34: */Bytecode.CONST,
/* 35: */1,
/* 36: */Bytecode.IFZERO,
/* 37: */40,
/* 38: */Bytecode.CONST,
/* 39: */1,
/* 40: */Bytecode.IFZERO,
/* 41: */44,
/* 42: */Bytecode.CONST,
/* 43: */42,
/* 44: */Bytecode.RETURN};
assertPartialEvalEqualsAndRunsCorrect(new Program("manyIfsProgram", bytecodes, 0, 3));
}
public abstract static class Inst {
public abstract boolean execute(VirtualFrame frame);
public abstract int getTrueSucc();
public abstract int getFalseSucc();
public static class Const extends Inst {
private final FrameSlot slot;
private final int value;
private final int next;
public Const(FrameSlot slot, int value, int next) {
this.slot = slot;
this.value = value;
this.next = next;
}
@Override
public boolean execute(VirtualFrame frame) {
frame.setInt(slot, nonExplodedLoop(value));
return true;
}
@Override
public int getTrueSucc() {
return next;
}
@Override
public int getFalseSucc() {
return next;
}
}
public static class Return extends Inst {
public Return() {
}
@Override
public boolean execute(VirtualFrame frame) {
return true;
}
@Override
public int getTrueSucc() {
return -1;
}
@Override
public int getFalseSucc() {
return -1;
}
}
public static class IfZero extends Inst {
private final FrameSlot slot;
private final int thenInst;
private final int elseInst;
public IfZero(FrameSlot slot, int thenInst, int elseInst) {
this.slot = slot;
this.thenInst = thenInst;
this.elseInst = elseInst;
}
@Override
public boolean execute(VirtualFrame frame) {
return (FrameUtil.getIntSafe(frame, slot) == 0);
}
@Override
public int getTrueSucc() {
return thenInst;
}
@Override
public int getFalseSucc() {
return elseInst;
}
}
public static class IfLt extends Inst {
private final FrameSlot slot1;
private final FrameSlot slot2;
private final int thenInst;
private final int elseInst;
public IfLt(FrameSlot slot1, FrameSlot slot2, int thenInst, int elseInst) {
this.slot1 = slot1;
this.slot2 = slot2;
this.thenInst = thenInst;
this.elseInst = elseInst;
}
@Override
public boolean execute(VirtualFrame frame) {
return (FrameUtil.getIntSafe(frame, slot1) < FrameUtil.getIntSafe(frame, slot2));
}
@Override
public int getTrueSucc() {
return thenInst;
}
@Override
public int getFalseSucc() {
return elseInst;
}
}
}
public static class InstArrayProgram extends RootNode {
private final String name;
@CompilationFinal(dimensions = 1) protected final Inst[] inst;
protected final FrameSlot returnSlot;
public InstArrayProgram(String name, Inst[] inst, FrameSlot returnSlot, FrameDescriptor fd) {
super(null, fd);
this.name = name;
this.inst = inst;
this.returnSlot = returnSlot;
}
@Override
public String toString() {
return name;
}
@Override
@ExplodeLoop(kind = LoopExplosionKind.MERGE_EXPLODE)
public Object execute(VirtualFrame frame) {
int ip = 0;
while (ip != -1) {
CompilerAsserts.partialEvaluationConstant(ip);
if (inst[ip].execute(frame)) {
ip = inst[ip].getTrueSucc();
} else {
ip = inst[ip].getFalseSucc();
}
}
return nonExplodedLoop(FrameUtil.getIntSafe(frame, returnSlot));
}
}
@Test
public void instArraySimpleIfProgram() {
FrameDescriptor fd = new FrameDescriptor();
FrameSlot valueSlot = fd.addFrameSlot("value", FrameSlotKind.Int);
FrameSlot returnSlot = fd.addFrameSlot("return", FrameSlotKind.Int);
Inst[] inst = new Inst[]{
/* 0: */new Inst.Const(valueSlot, 1, 1),
/* 1: */new Inst.IfZero(valueSlot, 2, 4),
/* 2: */new Inst.Const(returnSlot, 41, 3),
/* 3: */new Inst.Return(),
/* 4: */new Inst.Const(returnSlot, 42, 5),
/* 5: */new Inst.Return()};
assertPartialEvalEqualsAndRunsCorrect(new InstArrayProgram("instArraySimpleIfProgram", inst, returnSlot, fd));
}
@Test
@SuppressWarnings("try")
public void simpleSwitchProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */1,
/* 2: */Bytecode.SWITCH,
/* 3: */2,
/* 4: */9,
/* 5: */12,
/* 6: */Bytecode.CONST,
/* 7: */40,
/* 8: */Bytecode.RETURN,
/* 9: */Bytecode.CONST,
/* 10: */41,
/* 11: */Bytecode.RETURN,
/* 12: */Bytecode.CONST,
/* 13: */42,
/* 14: */Bytecode.RETURN};
Program program = new Program("simpleSwitchProgram", bytecodes, 0, 3);
assertPartialEvalEqualsAndRunsCorrect(program);
}
@Test
@SuppressWarnings("try")
public void loopSwitchProgram() {
byte[] bytecodes = new byte[]{
/* 0: */Bytecode.CONST,
/* 1: */1,
/* 2: */Bytecode.SWITCH,
/* 3: */2,
/* 4: */0,
/* 5: */9,
/* 6: */Bytecode.CONST,
/* 7: */40,
/* 8: */Bytecode.RETURN,
/* 9: */Bytecode.CONST,
/* 10: */42,
/* 11: */Bytecode.RETURN};
Program program = new Program("loopSwitchProgram", bytecodes, 0, 3);
assertPartialEvalEqualsAndRunsCorrect(program);
}
}