/***** * stack.cc * Andy Hammerlindl 2002/06/27 * * The general stack machine used to run compiled camp code. *****/ #include #include #include "stack.h" #include "program.h" #include "callable.h" #include "errormsg.h" #include "util.h" #include "runtime.h" #include "asyprocess.h" #include "profiler.h" #ifdef DEBUG_STACK #include namespace vm { void draw(ostream& out, vmFrame *v); } #endif namespace run { void breakpoint(vm::stack *Stack, absyntax::runnable *r); } namespace vm { const char *dereferenceNullPointer="dereference of null pointer"; mem::list bplist; namespace { position curPos = nullPos; const program::label nulllabel; } inline stack::vars_t base_frame( size_t size, size_t parentIndex, stack::vars_t closure #ifdef DEBUG_FRAME , string name #endif ) { stack::vars_t vars; #ifdef SIMPLE_FRAME vars = new item[size]; vars[parentIndex] = closure; #else # ifdef DEBUG_FRAME assert(!name.empty()); vars = new vmFrame(name, parentIndex, size); # else vars = new vmFrame(size); # endif (*vars)[parentIndex] = closure; #endif // TODO: Re-factor closure. return vars; } #ifdef DEBUG_FRAME #define BASEFRAME(s,p,c,n) (base_frame((s), (p), (c), (n))) #else #define BASEFRAME(s,p,c,n) (base_frame((s), (p), (c))) #endif // Abstractions needed. //accessor(frame_handle) // operator[] for accessor inline stack::vars_t make_frame(lambda *l, stack::vars_t closure) { return BASEFRAME(l->framesize, l->parentIndex, closure, l->name); } inline stack::vars_t make_pushframe(size_t size, stack::vars_t closure) { assert(size >= 1); return BASEFRAME(size, 0, closure, ""); } stack::vars_t make_dummyframe(string name) { return BASEFRAME(1, 0, 0, ""); } inline stack::vars_t make_globalframe(size_t size) { assert(size > 0); #ifdef SIMPLE_FRAME // The global frame is an indirect frame. It holds one item, which is the // link to another frame. stack::vars_t direct = new item[1]; stack::vars_t indirect = new item[size]; direct[0] = indirect; return direct; #else return BASEFRAME(size, 0, 0, ""); #if 0 #ifdef DEBUG_FRAME stack::vars_t vars = new frame("", 0, size); #else stack::vars_t vars = new frame(size); #endif return vars; #endif #endif } inline void resize_frame(vmFrame *f, size_t oldsize, size_t newsize) { //assert("Need to fix this" == 0); assert(newsize > oldsize); #ifdef SIMPLE_FRAME vmFrame *old_indirect = get(f[0]); vmFrame *new_indirect = new item[newsize]; std::copy(old_indirect, old_indirect+oldsize, new_indirect); f[0] = new_indirect; #else f->extend(newsize); #endif } void run(lambda *l) { func f; f.body = l; stack s; s.run(&f); } // Move arguments from stack to frame. void stack::marshall(size_t args, stack::vars_t vars) { for (size_t i = args; i > 0; --i) { # ifdef SIMPLE_FRAME vars[i-1] = pop(); # else (*vars)[i-1] = pop(); # endif } } #ifdef PROFILE #ifndef DEBUG_FRAME #pragma message("WARNING: profiler needs DEBUG_FRAME for function names") #endif #ifndef DEBUG_BLTIN #pragma message("WARNING: profiler needs DEBUG_BLTIN for builtin function names") #endif profiler prof; void dumpProfile() { std::ofstream out("asyprof"); if (!out.fail()) prof.dump(out); } #endif void assessClosure(lambda *body) { // If we have already determined if it needs closure, just return. if (body->closureReq != lambda::MAYBE_NEEDS_CLOSURE) return; for (program::label l = body->code->begin(); l != body->code->end(); ++l) if (l->op == inst::pushclosure || l->op == inst::pushframe) { body->closureReq = lambda::NEEDS_CLOSURE; return; } body->closureReq = lambda::DOESNT_NEED_CLOSURE; } void stack::run(func *f) { lambda *body = f->body; #ifdef PROFILE prof.beginFunction(body); #endif #ifdef DEBUG_STACK #ifdef DEBUG_FRAME cout << "running lambda " + body->name + ": \n"; #else cout << "running lambda: \n"; #endif print(cout, body->code); cout << endl; #endif runWithOrWithoutClosure(body, 0, f->closure); #ifdef PROFILE prof.endFunction(body); #endif } void stack::breakpoint(absyntax::runnable *r) { lastPos=curPos; indebugger=true; ::run::breakpoint(this,r); string s=vm::pop(this); debugOp=(s.length() > 0) ? s[0] : (char) 0; indebugger=false; } void stack::debug() { if(!curPos) return; if(indebugger) {em.clear(); return;} switch(debugOp) { case 'i': // inst breakpoint(); break; case 's': // step if((!curPos.match(lastPos.filename()) || !curPos.match(lastPos.Line()))) breakpoint(); break; case 'n': // next if(curPos.match(lastPos.filename()) && !curPos.match(lastPos.Line())) breakpoint(); break; case 'f': // file if(!curPos.match(lastPos.filename())) breakpoint(); break; case 'r': // return if(curPos.match(breakPos.filename())) breakpoint(); break; case 'c': // continue default: for(auto p=bplist.begin(); p != bplist.end(); ++p) { if(curPos.match(p->f.name()) && curPos.match(p->f.line()) && (newline || !curPos.match(breakPos.filename()) || !curPos.match(breakPos.Line()))) { breakPos=curPos; breakpoint(p->r); newline=false; break; } if(!newline && (curPos.match(lastPos.filename()) && !curPos.match(lastPos.Line()))) newline=true; } break; } } void stack::runWithOrWithoutClosure(lambda *l, vars_t vars, vars_t parent) { // The size of the frame (when running without closure). size_t frameSize = l->parentIndex; #ifdef SIMPLE_FRAME // Link to the variables, be they in a closure or on the stack. vmFrame *varlink; # define SET_VARLINK assert(vars); varlink = vars; # define VAR(n) ( (varlink)[(n) + frameStart] ) # define FRAMEVAR(frame,n) (frame[(n)]) #else // Link to the variables, be they in a closure or on the stack. mem::vector *varlink=NULL; # define SET_VARLINK assert(vars); varlink = &vars->vars # define VAR(n) ( (*varlink)[(n) + frameStart] ) # define FRAMEVAR(frame,n) ((*frame)[(n)]) #endif size_t frameStart = 0; // Set up the closure, if necessary. if (vars == 0) { #ifndef SIMPLE_FRAME assessClosure(l); if (l->closureReq == lambda::NEEDS_CLOSURE) #endif { /* make new activation record */ vars = vm::make_frame(l, parent); assert(vars); } #ifndef SIMPLE_FRAME else { assert(l->closureReq == lambda::DOESNT_NEED_CLOSURE); // Use the stack to store variables. varlink = &theStack; // Record where the parameters start on the stack. frameStart = theStack.size() - frameSize; // Add the parent's closure to the frame. push(parent); ++frameSize; size_t newFrameSize = (size_t)l->framesize; if (newFrameSize > frameSize) { theStack.resize(frameStart + newFrameSize); frameSize = newFrameSize; } } #endif } if (vars) { marshall(l->parentIndex, vars); SET_VARLINK; } /* start the new function */ program::label ip = l->code->begin(); processDataStruct& P=processData(); position& topPos=P.topPos; string& fileName=P.fileName; unsigned int offset=P.xmapCount; bool traceless=!settings::getSetting("debug"); bool xasy=settings::getSetting("xasy") || offset; if(xasy && curPos.filename() == fileName) topPos=curPos.shift(offset); try { for (;;) { const inst &i = *ip; curPos = i.pos; #ifdef PROFILE prof.recordInstruction(); #endif #ifdef DEBUG_STACK printInst(cout, ip, l->code->begin()); cout << " ("; i.pos.printTerse(cout); cout << ")\n"; #endif if(settings::verbose > 4) em.trace(curPos); if(!bplist.empty()) debug(); if(errorstream::interrupt) throw interrupted(); switch (i.op) { case inst::varpush: push(VAR(get(i))); break; case inst::varsave: VAR(get(i)) = top(); break; #ifdef COMBO case inst::varpop: VAR(get(i)) = pop(); break; #endif case inst::ret: { if (vars == 0) // Delete the frame from the stack. // TODO: Optimize for common cases. theStack.erase(theStack.begin() + frameStart, theStack.begin() + frameStart + frameSize); return; } case inst::pushframe: { assert(vars); Int size = get(i); vars=make_pushframe(size, vars); SET_VARLINK; break; } case inst::popframe: { assert(vars); vars=get(VAR(0)); SET_VARLINK; break; } case inst::pushclosure: assert(vars); push(vars); break; case inst::nop: break; case inst::pop: pop(); break; case inst::intpush: case inst::constpush: push(i.ref); break; case inst::fieldpush: { vars_t frame = pop(); if (!frame) error(dereferenceNullPointer); push(FRAMEVAR(frame, get(i))); break; } case inst::fieldsave: { vars_t frame = pop(); if (!frame) error(dereferenceNullPointer); FRAMEVAR(frame, get(i)) = top(); break; } #if COMBO case inst::fieldpop: { #error NOT REIMPLEMENTED vars_t frame = pop(); if (!frame) error(dereferenceNullPointer); FRAMEVAR(get(i)) = pop(); break; } #endif case inst::builtin: { bltin func = get(i); #ifdef PROFILE prof.beginFunction(func); #endif func(this); #ifdef PROFILE prof.endFunction(func); #endif break; } case inst::jmp: ip = get(i); continue; case inst::cjmp: if (pop()) { ip = get(i); continue; } break; case inst::njmp: if (!pop()) { ip = get(i); continue; } break; case inst::jump_if_not_default: if (!isdefault(pop())) { ip = get(i); continue; } break; #ifdef COMBO case inst::gejmp: { Int y = pop(); Int x = pop(); if (x>=y) { ip = get(i); continue; } break; } #if 0 case inst::jump_if_func_eq: { callable * b=pop(); callable * a=pop(); if (a->compare(b)) { ip = get(i); continue; } break; } case inst::jump_if_func_neq: { callable * b=pop(); callable * a=pop(); if (!a->compare(b)) { ip = get(i); continue; } break; } #endif #endif case inst::push_default: push(Default); break; case inst::popcall: { /* get the function reference off of the stack */ if(xasy && curPos.filename() == fileName) topPos=curPos.shift(offset); callable* f = pop(); if(traceless) f->call(this); else { em.traceback.push_back(curPos); f->call(this); if(em.traceback.size()) em.traceback.pop_back(); } break; } case inst::makefunc: { func *f = new func; f->closure = pop(); f->body = get(i); push((callable*)f); break; } default: error("Internal VM error: Bad stack operand"); } #ifdef DEBUG_STACK draw(cerr); vm::draw(cerr,vars); cerr << "\n"; #endif ++ip; } } catch (bad_item_value&) { error("Trying to use uninitialized value."); } #undef SET_VARLINK #undef VAR #undef FRAMEVAR } void stack::load(string index) { vmFrame *inst=instMap[index]; if (inst) { push(inst); } else { func f; assert(initMap); f.body=(*initMap)[index]; assert(f.body); run(&f); instMap[index]=get(top()); } } #ifdef DEBUG_STACK const size_t MAX_ITEMS=20; void stack::draw(ostream& out) { // out.setf(out.hex); out << "operands:"; stack_t::const_iterator left = theStack.begin(); if (theStack.size() > MAX_ITEMS) { left = theStack.end()-MAX_ITEMS; out << " ..."; } else out << " "; while (left != theStack.end()) { if (left != theStack.begin()) out << " | " ; out << *left; left++; } out << "\n"; } void draw(ostream& out, vmFrame* v) { out << "vars:" << endl; while (!!v) { item link=(*v)[v->getParentIndex()]; out << " " << v->getName() << ": "; for (size_t i = 0; i < MAX_ITEMS && i < v->size(); i++) { if (i > 0) out << " | "; out << i << ": "; if (i == v->getParentIndex()) { try { vmFrame *parent = get(link); out << (parent ? "link" : "----"); } catch (bad_item_value&) { out << "non-link " << (*v)[0]; } } else { out << (*v)[i]; } } if (v->size() > MAX_ITEMS) out << "..."; out << "\n"; vmFrame *parent; try { parent = get(link); } catch (bad_item_value&) { parent = 0; } v = parent; } } #endif // DEBUG_STACK position getPos() { return curPos; } void errornothrow(const char* message) { em.error(curPos); em << message; em.sync(true); } void error(const char* message) { errornothrow(message); throw handled_error(); } void error(const ostringstream& message) { const string& s=message.str(); error(s.c_str()); } const size_t STARTING_GLOBALS_SIZE = 1; interactiveStack::interactiveStack() : globals(make_globalframe(STARTING_GLOBALS_SIZE)), globals_size(STARTING_GLOBALS_SIZE) {} void interactiveStack::run(lambda *codelet) { if (globals_size < codelet->framesize) { resize_frame(globals, globals_size, codelet->framesize); globals_size = codelet->framesize; } stack::runWithOrWithoutClosure(codelet, globals, 0); } } // namespace vm