// t = u
jit_insn_store(F, t, u); // 类似 mov 指令
// u = v
jit_insn_store(F, u, v);
// v = t % v
jit_value_t rem = jit_insn_rem(F, t, v); // 求余指令
jit_insn_store(F, v, rem);
// 该函数遍历所有指令,找出所有需要打标签的指令位置
private List<Integer> createLabels(List<InstParser.Instruction> jbytecode) {
List<Integer> labels = new LinkedList<>();
for (InstParser.Instruction i : jbytecode) {
LabelParser labelParser = labelParsers.get(i.opcode);
if (labelParser != null) { // 不为空时表示是跳转指令
int pc = labelParser.parse(i); // 不同的跳转指令地址解析不同,解析得到跳转的目标地址
labels.add(pc); // 保存起来返回
}
}
return labels;
}
然后在翻译指令的过程中,发现当前翻译的指令地址是跳转的目标位置时,则生成标签指令:
123456789
List<Integer> labels = createLabels(jbytecode);
...
Iterator<InstParser.Instruction> it = jbytecode.iterator();
while (it.hasNext()) {
InstParser.Instruction inst = it.next();
int label = labels.indexOf(inst.pc);
if (label >= 0) {
state.addIR(new Inst(op_label, label)); // 生成标签指令,label就是标签编号
}
在处理跳转指令时,则填入标签编号:
1234567
translators.put(Opcode.op_ifgt, (state, inst, iterator) -> {
short offset = (short)((inst.op1 << 8) + inst.op2);
int pc = inst.pc + offset;
int label = state.findLabel(pc); // 找到标签编号
int var = state.popStack();
state.addIR(new Inst(op_jmp_gt, var, label));
});
private void runNative() {
int arg_cnt = getArgsCount();
int[] args = new int[arg_cnt];
for (int i = 0; i < arg_cnt; ++i) {
if (mLocals[i].type != Slot.Type.NUM) throw new RuntimeException("only supported number arg in jit");
args[i] = mLocals[i].i;
}
int ret = mJIT.invoke(args); // mJIT后面会看到,主要就是将参数以数组形式传递到libjit中,并做JIT函数调用
mThread.popFrame();
if (hasReturnType() && mThread.topFrame() != null) {
mThread.topFrame().pushInt(ret); // 目前只支持int返回类型
}
}
public class ToyJIT {
private long jitPtr = 0;
public void initialize(byte[] bytes, int maxLocals, int maxLabels, int argCnt, int retType) {
jitPtr = compile(bytes, maxLocals, maxLabels, argCnt, retType);
}
public int invoke(int... args) {
return invoke(jitPtr, args);
}
static {
System.loadLibrary("toyjit");
}
private static native long compile(byte[] bytes, int maxLocals, int maxLabels, int argCnt, int retType);
private static native int invoke(long jitPtr, int[] args);
即,libtoyjit.so 主要提供翻译接口 compile 及执行接口 invoke。
性能对比
简单测试了下一个阶乘计算函数:
123456789101112131415
public static int fac2(int n) {
int r = 1;
do {
r = r * n;
n = n - 1;
} while (n > 0);
return r;
}
...
int i = 0;
for (; i < 10000; ++i) {
fac2(100);
}
...
fac2函数会被JIT,测试发现不开启JIT时需要16秒,开启后1秒,差距还是很明显的。
最后奉上代码,toy_jit,就是前面说的C API部分,翻译IR到libjit API call,包装接口用于JNI调用。redhat 7.2下编译,需要先编译出libjit,我是直接clone的libjit master编译的。Java部分还是在toy_jvm中。