/* poob: object-oriented bytecode * * In its current incomplete form, this is a virtual machine for a * fairly compact bytecode based on the Smalltalk-76 bytecode format. * I anticipate that the whole bytecode engine will be under 1 * kilobyte, not counting primitive objects, when it is runnable, * based on the following: $ gcc -Wall -Werror --std=gnu99 -c -Os poob.c $ size poob.o text data bss dec hex filename 872 0 0 872 368 poob.o $ gcc -m32 -Wall -Werror --std=gnu99 -c -Os poob.c $ size poob.o text data bss dec hex filename 736 0 0 736 2e0 poob.o XXX rename, there’s some restaurant-selecting app with this name. pooby, maybe. Or soob, small object-oriented bytecode. */ #include #include #include enum { op_invoke, op_new, op_at, op_at_put, op_var, op_var_put, op_lit, op_if, op_goto, op_loop, op_mark, op_ret }; enum { stack_size = 256 }; #define IF break; case #define ELSE break; default typedef int val; enum { false_val = 0, mark_val = 1 }; typedef unsigned char insn; typedef signed char s8; typedef struct { } boom; val boom_get(boom *mem, val object, int offset) { return 0; } void boom_set(boom *mem, val object, int offset, val new_value) { } typedef struct { val *constants; int entry_point; } method; val *allocate_locals(method *m) { return 0; } method *find_method(boom *mem, val receiver, int selector) { return 0; } void panic(char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); abort(); } void interp(insn *boot_code, boom *mem) { insn op, arg, *ip = boot_code; val data_stack[stack_size], *sp = data_stack, return_stack[stack_size], *rsp = return_stack; /* XXX mark_stack? like with the depths of where the marks are */ val *locals = 0, self = 0, *constants = 0; /* XXX what about globals? */ for (;;) { arg = (op = *ip++) & 0xf; switch (op >> 4) { /* XXX maybe if invoke is going to use the same constants table, * it should have two or three opcodes instead of just one? * Because it seems like going over 16 will be easy... */ IF op_invoke: { val receiver = *--sp; int selector = constants[arg ?: *ip++]; method *m = find_method(mem, receiver, selector); *rsp++ = self; /* XXX leave arguments on the stack? */ // *rsp++ = locals; /* XXX allocate on return stack */ // *rsp++ = constants; *rsp++ = ip - boot_code; self = receiver; locals = allocate_locals(m); constants = m->constants; ip = boot_code + m->entry_point; } IF op_new: /* XXX */ IF op_at: *sp++ = boom_get(mem, self, arg ?: *ip++); IF op_at_put: boom_set(mem, self, arg ?: *ip++, *--sp); IF op_var: *sp++ = locals[arg ?: *ip++]; IF op_var_put: locals[arg ?: *ip++] = *--sp; IF op_lit: *sp++ = constants[arg ?: *ip++]; IF op_if: { int size = arg ?: *ip++; if (false_val == *--sp) ip += size; } IF op_loop: { int size = arg ?: *ip++; if (false_val != *--sp) ip -= size; } IF op_goto: ip += arg ? (int)arg - 4 : (int)(s8)(ip[0]) << 8 | ip[1]; // XXX arguably these two should be condensed into one opcode IF op_mark: *sp++ = mark_val; IF op_ret: if (0 == rsp - return_stack) return; ip = *--rsp + boot_code; // constants = (val*)*--rsp; // locals = (val*)*--rsp; self = *--rsp; /* XXX what about return values and discarded values? */ ELSE: panic("bad insn %0xu", op); } } }