Designing a virtual machine using JIT

I am developing a scripting language that compiles for my own virtual machine, a simple one that has instructions for working with certain types of data, such as points, vectors, floats, etc. The memory cell is represented in this way.

struct memory_cell { u32 id; u8 type; union { u8 b; /* boolean */ double f; /* float */ struct { double x, y, z; } v; /* vector */ struct { double r, g, b; } c; /* color */ struct { double r, g, b; } cw; /* color weight */ struct { double x, y, z; } p; /* point variable */ struct { u16 length; memory_cell **cells; } l; /* list variable */ }; }; 

Instructions are general and can work with many different operands. for instance

 ADD dest, src1, src2 

can work with floats, vectors, dots, colors that specify the correct type of destination in accordance with the operands.

The main execution loop simply checks the instruction operation code (which is a structure containing unions to define any command) and executes it. I used a simplified approach in which I have no registers, but just a large array of memory cells.

I was wondering if JIT could help me achieve better results or not and how to achieve it.

As I said, the best implementation achieved so far is something like this:

  void VirtualMachine::executeInstruction(instr i) { u8 opcode = (i.opcode[0] & (u8)0xFC) >> 2; if (opcode >= 1 && opcode <= 17) /* RTL instruction */ { memory_cell *dest; memory_cell *src1; memory_cell *src2; /* fetching destination */ switch (i.opcode[0] & 0x03) { /* skip fetching for optimization */ case 0: { break; } case MEM_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]; break; } case ARRAY_VAL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[i.rtl.dest.index]; break; } case ARRAY_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[(int)i.rtl.dest.value]; break; } } /* omitted code */ switch (opcode) { case ADD: { if (src1->type == M_VECTOR && src2->type == M_VECTOR) { dest->type = M_VECTOR; dest->vx = src1->vx + src2->vx; dest->vy = src1->vy + src2->vy; dest->vz = src1->vz + src2->vz; } /* omitted code */ 

Is it easy / convenient to try jit compilation? But I really don’t know where to start, so I ask a few tips.

Also, are there any other tips that I should consider when developing it?

This virtual machine should be fast enough to calculate the shaders for the ray tracer, but I still haven't done any benchmark.

+4
source share
3 answers

Before you write the JIT ("Just-in-time") compiler, you should at least think about how to write the "Way-forward-of-time" compiler.

That is, given the program consisting of instructions for your virtual machine, how do you create a program consisting of x86 instructions (or any other) that does the same as the original program? How would you optimize the output for different sets of instructions and different versions of the same architecture? The example operation code that you specified has a rather complicated implementation, so which operating codes could implement the “built-in” code by simply emitting the code that performs the task and which you would execute by sending a call to some common code?

JIT should be able to do this, and it should also make decisions while the virtual machine is running, about what code it does, when it does it, and how it presents the resulting mixture of VM commands and native instructions.

If you are not a jockey collector yet, then I do not recommend writing JIT. It doesn’t mean “don’t do it ever,” but before you start seriously, you must become a jockey picker.

An alternative would be to write a non-JIT compiler to convert your VM instructions (or source scripting language) into Java or LLVM bytecode, as Jeff Foster says. Then let the tool binding for this bytecode do a complex CPU-dependent job.

+7
source

An important task is a big task. Did you think that you base your virtual machine on something like LLVM ?

LLVM will provide a good starting point, and there are many examples of projects that you can use to understand.

+6
source

Steve Jessop has a point: the JIT compiler is much more complicated than a regular compiler. And a regular compiler on its own.

But after reading the last part of the question, I wonder if you really want a JIT compiler.

If your problem is this:

I want to create a ray tracing program that allows the user to provide their shader procedures, etc. using your own language specific to your domain. Everything goes well. I defined my language, the interpreter is implemented, and it works well and correctly. But this is slow: how can I execute it as native code?

Then here is what I did, these are similar situations:

  • Translate user-provided procedures into C functions that can be called from your program.

  • Write them to a regular C source file with the corresponding #includes, etc.

  • Compile them as .dll (or .so in * nix) using the regular C compiler.

  • Load the .dll dynamically into your program, recognize your function pointers and use them in your ray tracer instead of interpreted versions.

Some notes:

  • In some environments this may not be possible: there is no access to the C compiler or system policy, which prevents you from loading your own dll. So check before you try.

  • Do not discard the translator. Keep it as a reference implementation of your language.

+3
source

All Articles