Skip to content

Instantly share code, notes, and snippets.

@aji
Created July 24, 2012 16:47
Show Gist options
  • Select an option

  • Save aji/3171120 to your computer and use it in GitHub Desktop.

Select an option

Save aji/3171120 to your computer and use it in GitHub Desktop.

This is a simple JIT-based VM that implements the following instruction set:

  • 0x1 — Increment the A register
  • 0x2 — Increment the B register
  • 0x3 — Decrement the A register
  • 0x4 — Decrement the B register

The VM is implemented in vm.c and has both naive and JIT backends. The naive method is pure C and uses a switch statement to determine the appropriate action. The JIT backend compiles a program into x86 machine code using the definitions in the op-x86.s file.

The two vm_run_* functions have a "times" argument that specifies the number of times to repeat the program. This is a simple way of testing the performance increase gained by running JIT code while ignoring the overhead incurred from the JIT process itself. In a real JIT scenario, compiled code sections would be cached.

Here are two example runs of the following sources:

[188] aiadicicco@sphinx jit $ time ./jit naive 50000000
a = 0x00
b = 0x00

real    0m2.572s
user    0m2.564s
sys     0m0.000s
[189] aiadicicco@sphinx jit $ time ./jit jit 50000000
a = 0x00
b = 0x00

real    0m0.805s
user    0m0.800s
sys     0m0.000s

The program being tested increments and decrements the A register 4 times and the B register 3 times. This can be compiled on x86 and x86-64 systems with the following command:

$ gcc -o jit -m32 main.c vm.c op-x86.s
#include <stdio.h>
#include <string.h>
#include "vm.h"
static char prog[] = { 1, 1, 1, 2, 2, 3, 1, 2, 3, 3, 3, 4, 4, 4, 0 };
int main(int argc, char *argv[])
{
struct vm vm;
int n;
if (argc != 3) {
printf("Usage: %s (jit|naive) NNN\n", argv[0]);
return 1;
}
n = atoi(argv[2]);
vm_reset(&vm);
if (!strcmp(argv[1], "jit")) {
vm_run_jit(&vm, prog, n);
} else if (!strcmp(argv[1], "naive")) {
vm_run_naive(&vm, prog, n);
} else {
printf("Unknown vm backend '%s'\n", argv[1]);
return 1;
}
vm_dump(&vm);
return 0;
}
.section .data
.global jit_prologue
jit_prologue:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
.byte 0
.global jit_epilogue
jit_epilogue:
xorl %eax, %eax
popl %ebp
ret
.byte 0
.global jit_inc_a
jit_inc_a:
/* math with (%eax) introduces 0x0 */
xorl %ebx, %ebx
movb (%eax), %bl
addb $1, %bl
movb %bl, (%eax)
.byte 0
.global jit_inc_b
jit_inc_b:
incb 1(%eax)
.byte 0
.global jit_dec_a
jit_dec_a:
decb (%eax)
.byte 0
.global jit_dec_b
jit_dec_b:
decb 1(%eax)
.byte 0
#ifndef __INC_JIT_OP_H__
#define __INC_JIT_OP_H__
extern char jit_prologue[];
extern char jit_epilogue[];
extern char jit_inc_a[];
extern char jit_inc_b[];
extern char jit_dec_a[];
extern char jit_dec_b[];
#endif
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include "vm.h"
#include "op.h"
void vm_reset(struct vm *vm)
{
vm->a = 0;
vm->b = 0;
}
struct jit_build {
void *code;
size_t n;
};
static char *jit_table[] = {
NULL,
jit_inc_a,
jit_inc_b,
jit_dec_a,
jit_dec_b,
};
static void jit_add_insns(struct jit_build *jit, char *insns)
{
size_t m;
m = strlen(insns);
memcpy(jit->code + jit->n, insns, m);
jit->n += m;
}
static void *jit_compile(char *prog)
{
struct jit_build jit;
char *insns;
jit.n = 0;
jit.code = mmap(NULL, 4096, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (jit.code == NULL)
return NULL;
jit_add_insns(&jit, jit_prologue);
for (; *prog; prog++) {
insns = jit_table[*prog];
if (insns == NULL)
goto fail;
jit_add_insns(&jit, insns);
}
jit_add_insns(&jit, jit_epilogue);
mprotect(jit.code, 4096, PROT_READ | PROT_EXEC);
return jit.code;
fail:
munmap(jit.code, 4096);
return NULL;
}
static void jit_release(void *jit)
{
munmap(jit, 4096);
}
void vm_run_jit(struct vm *vm, char *prog, int times)
{
int n;
void (*jit)(struct vm*);
jit = (void(*)(struct vm*))jit_compile(prog);
if (jit == NULL) {
printf("JIT error!\n");
return;
}
for (n=0; n<times; n++) jit(vm);
jit_release((void*)jit);
}
void vm_run_naive(struct vm *vm, char *prog, int times)
{
int n;
char *p;
for (n=0; n<times; n++) {
for (p=prog; *p; p++) {
switch (*p) {
case 1: vm->a ++; break;
case 2: vm->b ++; break;
case 3: vm->a --; break;
case 4: vm->b --; break;
}
}
}
}
void vm_dump(struct vm *vm)
{
printf("a = 0x%02x\n", vm->a);
printf("b = 0x%02x\n", vm->b);
}
#ifndef __INC_JIT_VM_H__
#define __INC_JIT_VM_H__
#include <stdint.h>
struct vm {
uint8_t a;
uint8_t b;
};
extern void vm_reset(struct vm *vm);
extern void vm_run_jit(struct vm *vm, char *prog, int times);
extern void vm_run_naive(struct vm *vm, char *prog, int times);
extern void vm_dump(struct vm *vm);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment