vx-scheme/src/vm.cpp

1053 lines
32 KiB
C++

//----------------------------------------------------------------------
// vx-scheme : Scheme interpreter.
// Copyright (c) 2002,2003,2006 and onwards Colin Smith.
//
// You may distribute under the terms of the Artistic License,
// as specified in the LICENSE file.
//
// vm.cpp : PAIP-style virtual machine for compiled Scheme code
//
#include "vx-scheme.h"
enum operand_type {
OP_NONE,
OP_INT,
OP_SYMBOL,
OP_SUBR,
OP_LEXADDR
};
// Extract information from a VM instruction.
#define INSN_OPCODE(_insn) ((_insn)->ca.i >> 24) // ca.i unsigned
#define SET_OPCODE(_insn, value) \
(((_insn)->ca.i = (_insn)->ca.i & 0xffffff | value<<24))
#define INSN_COUNT(_insn) (((_insn)->ca.i >> 16) & 0xff)
#define LEXA_ESKIP(_insn) ((_insn)->cd.i >> 16)
#define LEXA_BSKIP(_insn) ((_insn)->cd.i & 0xffff)
#define OPCODE(name,operand) {intern(name),operand}
typedef struct {
psymbol opcode;
enum operand_type opnd_type;
} vm_op;
// XXX issues:
// 1) the order of opcodes is willy-nilly.
// 2) there are magic number references to opcode numbers in this file.
// Be careful.
// 3) I forget what (3) is.
static vm_op optab [] = { // opcode number
OPCODE ("consti", OP_INT), // 0
OPCODE ("nil", OP_NONE),
OPCODE ("subr", OP_SUBR),
OPCODE ("gref", OP_SYMBOL),
OPCODE ("gset", OP_SYMBOL),
OPCODE ("lref", OP_LEXADDR), // 5
OPCODE ("lset", OP_LEXADDR),
OPCODE ("goto", OP_INT),
OPCODE ("false?p", OP_INT),
OPCODE ("false?", OP_INT),
OPCODE ("true?p", OP_INT), // 10
OPCODE ("true?", OP_INT),
OPCODE ("proc", OP_NONE),
OPCODE ("extend", OP_INT),
OPCODE ("extend!", OP_NONE),
OPCODE ("extend.", OP_INT), // 15
OPCODE ("save", OP_INT),
OPCODE ("return", OP_NONE),
OPCODE ("pop", OP_NONE),
OPCODE ("dup", OP_NONE),
OPCODE ("take", OP_INT), // 20
OPCODE ("cc", OP_NONE),
OPCODE ("resume", OP_NONE),
OPCODE ("apply.", OP_NONE),
OPCODE ("apply", OP_INT),
OPCODE ("unspc", OP_NONE), // 25
OPCODE ("unassn", OP_NONE),
OPCODE ("lit", OP_INT),
OPCODE ("vector-set!", OP_INT), // starting here:
OPCODE ("vector-ref", OP_INT), // scheme primitives allocated to opcode
OPCODE ("car", OP_INT), // 30
OPCODE ("cdr", OP_INT),
OPCODE ("zero?", OP_INT),
OPCODE ("+", OP_INT),
OPCODE ("*", OP_INT),
OPCODE ("quotient", OP_INT), // 35
OPCODE ("remainder", OP_INT),
OPCODE ("-", OP_INT),
OPCODE ("not", OP_INT),
OPCODE ("null?", OP_INT),
OPCODE ("eq?", OP_INT), // 40
OPCODE ("pair?", OP_INT),
OPCODE ("cons", OP_INT),
OPCODE ("gref.", OP_INT),
OPCODE ("false", OP_NONE),
OPCODE ("true", OP_NONE), // 45
OPCODE ("int", OP_INT),
OPCODE ("promise", OP_NONE),
OPCODE ("gset.", OP_INT),
};
static const int n_vmops = sizeof(optab) / sizeof(*optab);
// exact_top_n: return true if the top n elements of the stack contained
// in cv are of exact type (in this implementation, exact is synonymous
// with integer).
static bool exact_top_n (cellvector * cv, int n) {
int sz = cv->size();
for (int ix = sz - n; ix < sz; ++ix)
switch (cv->get_unchecked(ix)->type()) {
case Cell::Int: continue;
case Cell::Real: return false;
default: return false;
}
return true;
}
// Context::extend
// Extend an environment with the list of bindings in blist.
Cell* Context::extend(Cell* envt, Cell* blist) {
Cell * xe = gc_protect(make_vector(0));
cellvector * cv = xe->unsafe_vector_value();
FOR_EACH(b, blist)
cv->push (car (b));
envt = cons(xe, envt);
gc_unprotect();
return envt;
}
// Context::extend_from_vector
// Extend environment envt with elements from the vector v, in
// reverse order. (The compiler arranges to compile function
// arguments from left to right. This means that the "rightmost"
// argument to a function will be at the top of the stack.
// References to parameters are by integer index, with the leftmost
// argument numbered zero.)
Cell* Context::extend_from_vector (Cell* envt, cellvector* v, int n) {
int size = v->size();
r_nu = make_vector(n);
cellvector* new_vec = r_nu->unsafe_vector_value();
for (int ix = 0, iy = size - n; ix < n; ++ix, ++iy)
new_vec->set_unchecked(ix, v->get_unchecked(iy));
v->discard(n);
envt = cons(r_nu, envt);
return envt;
}
void Context::adjoin (Cell* envt, Cell* val) {
car(envt)->unsafe_vector_value()->push(val);
}
// Context::pop_list
// Context::push_list
// 'pop' pops the specified number of elements off the machine stack and
// returns a list of the elements. The last element popped will be first
// in the list. 'Push' pushes the supplied list onto the stack. Elements
// are pushed in the order given.
Cell* Context::pop_list (int n) {
r_tmp = nil;
for (int ix = 0; ix < n; ++ix) {
r_tmp = cons (gc_protect (m_stack.pop ()), r_tmp);
gc_unprotect ();
}
return r_tmp;
}
int Context::push_list(Cell* list) {
int count = 0;
FOR_EACH(a, list) {
m_stack.push(car(a));
++count;
}
return count;
}
void Context::print_insn(int addr, Cell* insn) {
vm_op * op = optab + INSN_OPCODE(insn);
printf ("%4d:\t%s\t", addr, op->opcode->key);
switch (op->opnd_type) {
case OP_INT:
printf ("%" PRIdPTR, insn->cd.i);
break;
case OP_SYMBOL:
printf ("%s", insn->cd.y->key);
break;
case OP_SUBR: printf ("%" PRIdPTR ",%s", INSN_COUNT (insn),
insn->flag(Cell::QUICK)
? insn->cd.f->name
: insn->cd.y->key);
// XXX comment
break;
case OP_LEXADDR:
printf ("%" PRIdPTR ",%" PRIdPTR, LEXA_ESKIP(insn), LEXA_BSKIP(insn));
break;
case OP_NONE:
;
}
printf("\n");
}
// Context::vm_evaluator
// Run the expression through the virtual machine's evaluator, if it's
// present. (The evaluator is compiled code produced by the bootstrapper.)
//
Cell* Context::vm_evaluator(Cell* form) {
if (!eval_cproc) {
Cell* binding;
if ((binding = find_var(root_envt, intern("eval"), 0)))
eval_cproc = cdr(binding);
}
if (eval_cproc) {
r_tmp = form;
r_exp = cons(form, nil);
// save(r_envt);
// r_envt = root_envt;
return execute(eval_cproc, r_exp);
// restore(r_envt);
}
error("can't find eval");
return make_boolean(false);
}
Cell* Context::execute (Cell* proc, Cell* args) {
cellvector *insns, *literals;
intptr_t pc;
int type;
intptr_t start;
unsigned int count;
unsigned int n_args = 0;
unsigned int b_skip = 0;
unsigned int e_skip = 0;
// Note the initial stack size.
int initial_stackdepth = m_stack.size();
save_i (-1);
// Push any arguments we received onto the stack.
FOR_EACH(a, args) {
++n_args;
save(car(a));
}
r_cproc = proc;
bool trace = OS::flag (TRACE_VM);
bool trace_stack = OS::flag (TRACE_VMSTACK);
bool count_insns = OS::flag (COUNT_INSNS);
int xcount [n_vmops];
if (count_insns)
for (int ix = 0; ix < n_vmops; ++ix)
xcount [ix] = 0;
cellvector* root_bindings = car(root_envt)->unsafe_vector_value();
PROC:
r_cproc->typecheck (Cell::Cproc);
insns = r_cproc->cd.cv->get (0)->unsafe_vector_value();
literals = r_cproc->cd.cv->get (1)->unsafe_vector_value();
r_envt = r_cproc->cd.cv->get (2);
pc = r_cproc->cd.cv->get (3)->IntValue ();
XEQ:
Cell * insn = insns->get_unchecked (pc); // trust compiler!
unsigned int opcode = INSN_OPCODE (insn);
if (count_insns)
++xcount [opcode];
if (trace) {
if (trace_stack) {
printf ("\t");
for (int ix = m_stack.size() - 1; ix >= 0; --ix) {
Cell * c = m_stack.get_unchecked(ix);
if (!((reinterpret_cast<intptr_t>(c))&1)) {
if (c == root_envt) printf("#<root-envt> ");
else c->write (stdout);
} else printf ("%" PRIdPTR, (reinterpret_cast<intptr_t>(c))>>1);
fputc (' ', stdout);
}
printf("\n");
}
print_insn(pc, insn);
}
switch (opcode)
{
case 0: // consti
save_i (insn->cd.i);
break;
case 1: // nil
m_stack.push (nil);
break;
case 2: // subr
if (!insn->flag(Cell::QUICK)) {
Cell* subr = find_var(root_envt, insn->cd.y, 0);
if (!subr) error("missing primitive procedure");
Cell* proc = cdr(subr);
type = proc->type();
if (type == Cell::Cproc) {
// Yuck. When the current procedure was compiled, the
// routine we are about to invoke was a builtin (subr): now
// it's a compiled procedure. The optimized calling
// convention for subrs no longer applies. We must pop
// the args off the stack, then push a continuation, then
// re-push the args, and dispatch to the procedure.
n_args = INSN_COUNT(insn);
cellvector cv;
for (unsigned int ix = 0; ix < n_args; ++ix)
cv.push(m_stack.pop());
save(r_envt);
save(r_cproc);
save_i(pc+1);
for (unsigned int ix = 0; ix < n_args; ++ix)
m_stack.push(cv.pop());
r_cproc = proc;
goto PROC;
} else if (type == Cell::Subr) {
insn->cd.f = cdr(subr)->SubrValue();
insn->flag(Cell::QUICK, true);
} else {
error("subr invoked on non-procedure");
}
}
r_val = pop_list (INSN_COUNT (insn));
// Subr's can change anything (in particular they can reenter execute).
save(r_envt);
save(r_cproc);
r_val = insn->cd.f->subr(this, r_val);
restore(r_cproc);
restore(r_envt);
m_stack.push(r_val);
break;
case 3: { // gref
unsigned int index;
r_val = find_var (root_envt, insn->cd.y, &index);
if (!r_val) {
error ("reference to undefined global variable: ", insn->cd.y->key);
} else {
if (cdr(r_val) == NULL) error("yikes"); // XXX
// Quicken the instruction.
SET_OPCODE(insn, 43); // gref. XXX: magic number (among others)
insn->cd.i = index;
m_stack.push (cdr (r_val));
}
break;
}
case 4: { // gset
unsigned int index;
set_var (root_envt, insn->cd.y, m_stack.pop (), &index);
// Quicken the instruction.
SET_OPCODE(insn, 48); // gset. XXX: magic number
insn->cd.i = index;
break;
}
case 5: // lref
e_skip = LEXA_ESKIP (insn);
b_skip = LEXA_BSKIP (insn);
r_tmp = r_envt;
for (unsigned int ix = 0; ix < e_skip; ++ix)
r_tmp = cdr (r_tmp);
m_stack.push (car (r_tmp)->cd.cv->get (b_skip));
break;
case 6: // lset
e_skip = LEXA_ESKIP (insn);
b_skip = LEXA_BSKIP (insn);
r_tmp = r_envt;
for (unsigned int ix = 0; ix < e_skip; ++ix)
r_tmp = cdr (r_tmp);
car (r_tmp)->cd.cv->set (b_skip, m_stack.pop ());
break;
case 7: // goto
pc = insn->cd.i;
goto XEQ;
case 8: // false?p
if (!m_stack.pop ()->istrue ()) {
pc = insn->cd.i;
goto XEQ;
}
break;
case 9: // false?
if (!m_stack.top ()->istrue ()) {
pc = insn->cd.i;
goto XEQ;
}
break;
case 10: // true?p
if (m_stack.pop ()->istrue ()) {
pc = insn->cd.i;
goto XEQ;
}
break;
case 11: // true?
if (m_stack.top ()->istrue ()) {
pc = insn->cd.i;
goto XEQ;
}
break;
case 12: // proc
// pop the starting instruction from the stack and compose it
// with the current environment.
restore_i (start);
m_stack.push (make_compiled_procedure (r_cproc->cd.cv->get_unchecked (0),
r_cproc->cd.cv->get_unchecked (1),
r_envt,
start));
break;
case 13: // extend
if (n_args < insn->cd.i)
error ("vm: not enough arguments to procedure");
r_envt = extend_from_vector (r_envt, &m_stack, insn->cd.i);
//r_envt = extend (r_envt, gc_protect (pop_list (insn->cd.i)));
//gc_unprotect ();
break;
case 14: // extend!
r_envt = extend (r_envt, gc_protect (pop_list (1)));
gc_unprotect ();
break;
case 15: // extend.
if (n_args < insn->cd.i)
error ("vm: not enough arguments to procedure");
r_val = pop_list (n_args - insn->cd.i);
r_envt = extend (r_envt, gc_protect (pop_list (insn->cd.i)));
gc_unprotect ();
adjoin (r_envt, r_val);
break;
case 16: // save
// make a continuation that will invoke the indicated
// instruction slot in this segment.
save (r_envt);
save (r_cproc);
save_i (insn->cd.i);
break;
case 17: // return
r_val = m_stack.pop (); // value
RETURN:
restore_i (pc);
if (pc < 0)
goto FINISH;
restore (r_cproc);
insns = r_cproc->cd.cv->get (0)->VectorValue ();
literals = r_cproc->cd.cv->get (1)->VectorValue ();
restore (r_envt);
save (r_val);
goto XEQ;
case 18: // pop
m_stack.pop ();
break;
case 19: // dup
m_stack.push (m_stack.top ());
break;
case 20: { // take
// Remove the n'th item from the stack and push it onto the top.
// (We count from zero). 'take 0' would be a no-op; 'take 1'
// would swap the top two elements. We use an unchecked get
// because we "trust the compiler."
intptr_t target = insn->cd.i;
int last = m_stack.size() - 1;
r_tmp = m_stack.get_unchecked(last-target);
for (int ix = last-target; ix < last; ++ix)
m_stack.set(ix, m_stack.get_unchecked(ix+1));
m_stack.set(last, r_tmp);
break;
}
case 21: { // cc
r_tmp = make_vector(m_stack.size());
cellvector* saved_stack = r_tmp->VectorValue();
for (int ix = 0; ix < m_stack.size(); ++ix)
saved_stack->set(ix, m_stack.get(ix));
r_nu = cons(r_tmp, nil);
r_envt = extend(r_envt, r_nu);
m_stack.push(make_compiled_procedure(cc_procedure, empty_vector,
r_envt, 0));
r_envt = cdr(r_envt);
// YYY
break;
}
case 22: { // resume
r_val = m_stack.pop();
r_tmp = m_stack.pop();
cellvector* new_stack = r_tmp->VectorValue();
m_stack.clear(); // !
for (int ix = 0; ix < new_stack->size(); ++ix)
m_stack.push(new_stack->get(ix));
goto RETURN;
}
case 23: // apply.
// Covert stack from: rest ... a2 a1 proc
// to: proc a1 a2 ... rest
// with 'rest' spliced in in the correct order.
// Then do as in an ordinary apply. This exists
// only to support the 'apply' special procedure.
r_tmp = m_stack.pop();
for (count = 0; count < n_args-2; ++count)
r_tmp = cons(m_stack.pop(), r_tmp);
r_proc = m_stack.pop();
count = push_list(r_tmp);
m_stack.push(r_proc);
// dummy up the 'real' arument count that the
// microcode for 'apply' will see below.
insn->cd.i = count;
/* FALL THROUGH */
case 24: // apply
r_exp = m_stack.pop ();
type = r_exp->type ();
if (type == Cell::Cproc) {
n_args = insn->cd.i;
r_cproc = r_exp;
goto PROC;
} else if (type == Cell::Subr) {
r_val = pop_list(insn->cd.i);
save(r_envt);
save(r_cproc);
r_val = r_exp->SubrValue()->subr(this, r_val);
restore(r_cproc);
restore(r_envt);
goto RETURN;
} else {
r_exp->write(stderr);
error ("vm: inapplicable");
}
break;
case 25: // unspc
m_stack.push (unspecified);
break;
case 26: // unassn
m_stack.push (unassigned);
break;
case 27: // lit
m_stack.push (literals->get (insn->cd.i));
break;
case 28: { // vector-set!
n_args = insn->cd.i;
if (n_args != 3)
error ("bad arguments to vector-set!");
int ix = m_stack.size() - 1;
cellvector * cv = m_stack.get(ix-2)->VectorValue();
cv->set(m_stack.get(ix-1)->IntValue(), m_stack.get(ix));
m_stack.discard(3);
m_stack.push(unspecified);
break;
}
case 29: { // vector-ref
n_args = insn->cd.i;
if (n_args != 2)
error ("bad arguments to vector-ref!");
intptr_t ix = m_stack.pop()->IntValue();
cellvector * cv = m_stack.pop()->VectorValue();
m_stack.push(cv->get(ix));
break;
}
case 30: // car
m_stack.push(car(m_stack.pop()));
break;
case 31: // cdr
m_stack.push(cdr(m_stack.pop()));
break;
case 32: { // zero?
Cell * c = m_stack.pop();
Cell::Type t = c->type();
if (t == Cell::Int)
m_stack.push(make_boolean(c->IntValue() == 0));
else if (t == Cell::Real)
m_stack.push(make_boolean(c->RealValue() == 0.0));
else
error ("non-numeric type");
break;
}
case 33: { // +
// get n; see if top n elements are all exact or not; add them
// accumulating in situ (to avoid consing an argument list),
// discard those elements and push the result.
n_args = insn->cd.i;
int sz = m_stack.size ();
if (exact_top_n (&m_stack, n_args)) {
intptr_t sum = 0;
for (int ix = sz - n_args; ix < sz; ++ix)
sum += m_stack.get (ix)->IntValue(); // exact_top_n guarantees this is OK
m_stack.discard (n_args);
m_stack.push(make_int(sum));
} else {
double sum = 0.0;
for (int ix = sz - n_args; ix < sz; ++ix)
sum += m_stack.get (ix)->asReal ();
m_stack.discard(n_args);
m_stack.push(make_real(sum));
}
break;
}
case 34: { // *
// much like +, above.
n_args = insn->cd.i;
int sz = m_stack.size ();
if (exact_top_n (&m_stack, n_args)) {
intptr_t product = 1;
for (int ix = sz - n_args; ix < sz; ++ix)
product *= m_stack.get (ix)->IntValue(); // exact_top_n says this is OK
m_stack.discard (n_args);
m_stack.push(make_int(product));
} else {
double product = 1.0;
for (int ix = sz - n_args; ix < sz; ++ix)
product *= m_stack.get (ix)->asReal ();
m_stack.discard(n_args);
m_stack.push(make_real(product));
}
break;
}
case 35: { // quotient
if (insn->cd.i != 2)
error ("wrong # args");
intptr_t d = m_stack.pop()->IntValue();
intptr_t n = m_stack.pop()->IntValue();
if (d == 0)
error ("/0");
m_stack.push (make_int (n/d));
break;
}
case 36: { // remainder
if (insn->cd.i != 2)
error ("wrong # args");
intptr_t d = m_stack.pop()->IntValue();
intptr_t n = m_stack.pop()->IntValue();
if (d == 0)
error ("/0");
m_stack.push (make_int (n%d));
break;
}
case 37: { // -
// get n; see if top n elements are all exact or not; add them
// accumulating in situ (to avoid consing an argument list),
// discard those elements and push the result.
n_args = insn->cd.i;
int sz = m_stack.size ();
if (exact_top_n (&m_stack, n_args)) {
if (n_args == 1) {
m_stack.push(make_int(-m_stack.pop()->IntValue()));
} else {
intptr_t difference = m_stack.get(sz-n_args)->IntValue();
for (int ix = sz - n_args + 1; ix < sz; ++ix)
difference -= m_stack.get (ix)->IntValue();
m_stack.discard (n_args);
m_stack.push(make_int(difference));
}
} else {
if (n_args == 1) {
m_stack.push(make_real(-m_stack.pop()->asReal()));
} else {
double difference = m_stack.get(sz-n_args)->asReal();
for (int ix = sz - n_args + 1; ix < sz; ++ix)
difference -= m_stack.get (ix)->asReal();
m_stack.discard(n_args);
m_stack.push(make_real(difference));
}
}
break;
}
case 38: // not
m_stack.push(m_stack.pop()->istrue()
? &Cell::Bool_F : &Cell::Bool_T);
break;
case 39: // null?
m_stack.push(m_stack.pop() == &Cell::Nil
? &Cell::Bool_T : &Cell::Bool_F);
break;
case 40: // eq?
m_stack.push(m_stack.pop()->eq(m_stack.pop())
? &Cell::Bool_T : &Cell::Bool_F);
break;
case 41: // pair?
m_stack.push(m_stack.pop()->ispair()
? &Cell::Bool_T : &Cell::Bool_F);
break;
case 42: // cons (watch out: order matters, and cons can provoke GC.)
r_tmp = m_stack.pop();
r_elt = m_stack.pop();
m_stack.push(cons(r_elt, r_tmp));
break;
case 43: { // gref. (quickened global ref; contains index of target binding)
m_stack.push(cdr(root_bindings->get(insn->cd.i)));
break;
}
case 44: // false
m_stack.push(&Cell::Bool_F);
break;
case 45: // true
m_stack.push(&Cell::Bool_T);
break;
case 46: // int
m_stack.push(make_int(insn->cd.i));
break;
case 47: // promise
restore_i(start);
r_tmp = make_compiled_procedure(r_cproc->cd.cv->get(0),
r_cproc->cd.cv->get(1),
r_envt,
start);
m_stack.push(make_compiled_promise(r_tmp));
break;
case 48: // gset.
Cell::setcdr(root_bindings->get(insn->cd.i), m_stack.pop());
break;
default:
error ("unimplemented opcode_");
}
++pc;
goto XEQ;
FINISH:
if (count_insns) {
for (int ix = 0; ix < n_vmops; ++ix)
printf ("%s:%d ", optab[ix].opcode->key, xcount [ix]);
printf ("\n");
}
if (m_stack.size() != initial_stackdepth) {
fprintf(stderr,"stack imbalance: %d (%d expected)\n", m_stack.size(),
initial_stackdepth);
}
return r_val;
}
// find_op: match the supplied opcode symbol in the vm_op table;
// return the index (or -1 if the opcode is not in the table).
int find_op (psymbol opsym)
{
for (int ix = 0; ix < n_vmops; ++ix)
if (optab[ix].opcode == opsym)
return ix;
return -1;
}
// Make compiled procedure (method and subr): store the
// current code segment, the environment, and program counter
// in an object.
static Cell* make_compiled_procedure (Context * ctx, Cell * arglist)
{
return ctx->make_compiled_procedure (car (arglist),
cadr (arglist),
nil,
0);
}
Cell * Context::make_compiled_procedure (Cell * insns,
Cell * literals,
Cell * envt,
int start)
{
Cell * c = gc_protect (alloc (Cell::Cproc));
cellvector * cv = cellvector::alloc(4);
c->cd.cv = cv;
c->flag (Cell::VREF, true);
cv->set (0, insns);
cv->set (1, literals);
cv->set (2, envt);
cv->set (3, make_int (start));
gc_unprotect ();
return c;
}
Cell* Context::make_compiled_promise(Cell* procedure) {
Cell * c = gc_protect(alloc(Cell::Cpromise));
cellvector* cv = cellvector::alloc(1);
c->cd.cv = cv;
c->flag(Cell::VREF, true);
cv->set(0, procedure);
gc_unprotect();
return c;
}
Cell* Context::force_compiled_promise(Cell* promise) {
promise->typecheck(Cell::Cpromise);
if (promise->flag(Cell::FORCED)) return promise->cd.cv->get(0);
Cell* val = execute(promise->cd.cv->get(0), nil);
// Did the promise become forced as a result of our evaluation?
// then that value is correct.
if (promise->flag(Cell::FORCED)) return promise->cd.cv->get(0);
promise->cd.cv->set(0, val);
promise->flag(Cell::FORCED, true);
return val;
}
// make_instruction: produce a packed machine instruction given
// an instruction in list form (e.g., '(consti 99) ).
static Cell* make_instruction (Context * ctx, Cell * arglist)
{
//return ctx->make_instruction (car (arglist));
return ctx->make_instruction (arglist);
}
Cell* Context::make_instruction (Cell * insn) {
psymbol op = car(insn)->SymbolValue();
int opcode = find_op(op);
if (opcode < 0)
error ("unknown opcode: ", op->key);
return make_instruction(opcode, cdr(insn));
}
Cell* Context::make_instruction(int opcode, Cell* operands)
{
unsigned int u1, u2;
psymbol y;
Cell * opnd = operands == nil ? nil : car(operands);
Cell * c = alloc (Cell::Insn);
c->ca.i |= (opcode & 0xff) << 24;
switch (optab[opcode].opnd_type)
{
case OP_INT:
c->cd.i = opnd->IntValue ();
break;
case OP_SYMBOL:
c->cd.y = opnd->SymbolValue ();
break;
case OP_SUBR: {
int count = cadr(operands)->IntValue ();
if (count < 0 || count > 255)
error ("count too large to store in instruction field");
c->ca.i |= count << 16;
y = opnd->SymbolValue();
// Store the symbol in the operand field. The evaluator
// will "quicken" the reference when the code is run.
c->cd.y = y;
break;
}
case OP_LEXADDR:
u1 = opnd->IntValue ();
u2 = cadr(operands)->IntValue ();
if (u1 > 65535 || u2 > 65535)
error ("lexical address too large");
c->cd.i = (u1 << 16) | u2;
break;
case OP_NONE:
break;
default:
error ("unhandled operand type");
}
return c;
}
static Cell* execute(Context* ctx, Cell* arglist) {
return ctx->execute (car (arglist), cdr(arglist));
}
static Cell* disassemble(Context* ctx, Cell* arglist) {
cellvector* cproc = car(arglist)->CProcValue();
cellvector* insns = cproc->get (0)->VectorValue ();
for (int ix = 0; ix < insns->size(); ++ix) {
ctx->print_insn(ix, insns->get(ix));
}
return unspecified;
}
static Cell* write_compiled_procedure(Context* ctx, Cell* arglist) {
return ctx->write_compiled_procedure(arglist);
}
// Context::load_compiled_procedure
// Turn a serialized compiled procedure into a "live" procedure, by
// reading the saved instructions and literals back into the Scheme
// heap.
// WARNING: This is expected to be called by the startup code with
// GC disabled.
Cell* Context::load_compiled_procedure(vm_cproc *cp) {
// We create a static argument list of two elements, which we reuse.
Cell* insns = load_instructions(cp);
Cell* literals = make_vector(cp->n_literals);
cellvector* litv = literals->VectorValue();
for (unsigned int ix = 0; ix < cp->n_literals; ++ix) {
sstring litstr;
litstr.append(cp->literals[ix]);
Cell* lit = read(litstr);
if (lit == NULL) error("undecipherable literal", cp->literals[ix]);
litv->set(ix, lit);
}
return make_compiled_procedure(insns, literals, nil, cp->entry);
}
Cell* Context::load_instructions(vm_cproc* cp) {
Cell* zero = make_int(0);
Cell* a1 = cons(zero, nil);
Cell* a0 = cons(zero, a1); // now a0 == '(0 0)
Cell* insns = make_vector(cp->n_insns);
cellvector* insv = insns->VectorValue();
for (unsigned int ix = 0; ix < cp->n_insns; ++ix) {
vm_insn* insn = cp->insns + ix;
int opcode = insn->opcode;
if (opcode > n_vmops) error("bad opcode in stored proc");
Cell::setcar(a0, zero);
Cell::setcar(a1, zero);
switch(optab[opcode].opnd_type) {
case OP_INT:
Cell::setcar(a0, make_int(reinterpret_cast<intptr_t>(insn->operand)));
break;
case OP_SYMBOL:
Cell::setcar(a0,
make_symbol(
intern(static_cast<const char*>(insn->operand))));
break;
case OP_LEXADDR: {
int la = reinterpret_cast<intptr_t>(insn->operand);
Cell::setcar(a0, make_int(la >> 16));
Cell::setcar(a1, make_int(la & 0xffff));
break;
}
case OP_SUBR:
Cell::setcar(a0,
make_symbol(
intern(static_cast<const char*>(insn->operand))));
Cell::setcar(a1, make_int(insn->count));
break;
case OP_NONE:
break;
}
insv->set(ix, make_instruction(opcode, a0));
}
return insns;
}
static void write_escaped_string(FILE* output, const char* str) {
char c;
fputc('"', output);
while ((c = *str++)) {
switch (c) {
case '\n':
fputc('\\', output);
fputc('n', output);
break;
case '"':
case '\\':
fputc('\\', output);
/* fall through */
default:
fputc(c, output);
}
}
fputc('"', output);
}
Cell* Context::write_compiled_procedure(Cell* arglist) {
cellvector* cproc = car(arglist)->CProcValue();
const char* name = cadr(arglist)->StringValue();
cellvector* insns = cproc->get(0)->VectorValue();
cellvector* literals = cproc->get(1)->VectorValue();
cellvector* root_bindings = car(root_envt)->VectorValue();
int entry = cproc->get(3)->IntValue();
FILE* output = current_output()->OportValue();
fprintf(output, "static vm_insn %s_insns[] = {\n", name);
for (int ix = 0; ix < insns->size(); ++ix) {
Cell* insn = insns->get(ix);
int opcode = INSN_OPCODE(insn);
// Horrible special cases: 'gref./gset.'. A "quickened global
// reference" is an index into a slot in the global environment.
// We can't write it out as is, since it's not likely that all
// global variables will have the same slot in the context into
// which this procedure will be loaded. Instead we write it out as
// an ordinary 'gref', so that it can be quickened in the
// environment in which it actually runs.
if (opcode == 43) { // XXX magic number
fprintf(output, " { %2d,0,", 3); // XXX magic number
write_escaped_string(output,
car(root_bindings->get(insn->cd.i))->SymbolValue()->key);
} else if (opcode == 48) {
fprintf(output, " { %2d,0,", 4); // XXX magic number
write_escaped_string(output,
car(root_bindings->get(insn->cd.i))->SymbolValue()->key);
} else { // not 'gref.'
vm_op* op = optab + opcode;
fprintf(output, " { %2d,", opcode); // XXX magic number
switch(op->opnd_type) {
case OP_NONE: fprintf(output, "0,0"); break;
case OP_INT: fprintf(output, "0,(void*)%" PRIdPTR, insn->cd.i); break;
case OP_SYMBOL: fprintf(output, "0,");
write_escaped_string(output, insn->cd.y->key); break;
case OP_SUBR:
// XXX write a comment
fprintf(output, "%" PRIdPTR ",", INSN_COUNT(insn));
if (insn->flag(Cell::QUICK))
write_escaped_string(output, insn->cd.f->name);
else
write_escaped_string(output, insn->cd.y->key);
break;
case OP_LEXADDR: fprintf(output, "0,(void*)%#" PRIxPTR, insn->cd.i); break;
}
}
fprintf(output, " },\n");
}
fprintf(output, "};\n\n");
if (literals->size() > 0) {
fprintf(output, "const char* %s_lit[] = {\n", name);
for (int ix = 0; ix < literals->size(); ++ix) {
sstring litstr;
fputs(" ", output);
literals->get(ix)->write(litstr);
write_escaped_string(output, litstr.str());
fputs(",\n", output);
}
fprintf(output, "};\n\n");
}
fprintf(output, "static vm_cproc %s = {\n %s_insns,\n %d,\n",
name, name, insns->size());
if (literals->size() > 0) {
fprintf(output, " %s_lit,\n %d,\n", name, literals->size());
} else {
fprintf(output, " 0,\n 0,\n");
}
fprintf(output, " %d,\n", entry);
fprintf(output, "};\n\n");
return unspecified;
}
// ================================
// PROVISIONING THE VIRTUAL MACHINE
//
class VmExtension : SchemeExtension {
public:
VmExtension () {
Register (this);
}
virtual void Install (Context * ctx, Cell * envt) {
static struct {
const char* name;
subr_f subr;
} bindings[] = {
{ "make-instruction", make_instruction },
{ "make-compiled-procedure", make_compiled_procedure },
{ "write-compiled-procedure", write_compiled_procedure },
{ "disassemble", disassemble },
{ "execute", execute },
};
static const unsigned int n_bindings = sizeof(bindings)/sizeof(*bindings);
for (unsigned int ix = 0; ix < n_bindings; ++ix) {
ctx->bind_subr(bindings[ix].name, bindings[ix].subr);
}
// Initialize the macro table.
ctx->set_var(envt, intern("__macro_table"), nil);
// Attach VM execution function to context, so the interpreter may
// invoke compiled procedures.
ctx->vm_execute = &Context::execute;
ctx->vm_eval = &Context::vm_evaluator;
}
};
static VmExtension vm_extension;