X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=04f6784c0e14b265e79d976f78a52664dddf3e09;hp=73de955e3ecb05427fc8da62ded68a918d3b4f01;hb=3f8aa5300e178e8975b0edc896a5a9d303e7bdf3;hpb=4c96b0f7e40e4689434c46e4300cc9bef9d0fab9 diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 73de955..04f6784 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -6,8 +6,6 @@ import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ class Interpreter implements ByteCodes, Tokens { - private static final JS CASCADE = JSString.intern("cascade"); - // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); } @@ -22,21 +20,31 @@ class Interpreter implements ByteCodes, Tokens { final Stack stack = new Stack(); ///< the object stack int pc = 0; ///< the program counter - Interpreter(JSFunction f, boolean pauseable, JSArray args) { + Interpreter(JSFunction f, boolean pauseable, JSArgs args) { this.f = f; this.pausecount = pauseable ? 0 : -1; - this.scope = new JSScope(f.parentScope); + this.scope = f.parentScope; try { - stack.push(new CallMarker()); // the "root function returned" marker -- f==null + stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null stack.push(args); } catch(JSExn e) { throw new Error("should never happen"); } } + Interpreter(Trap t, JS val, boolean pauseOnPut) { + this.pausecount = -1; + try { + setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut)); + } catch(JSExn e) { + throw new Error("should never happen"); + } + } + /** this is the only synchronization point we need in order to be threadsafe */ synchronized JS resume() throws JSExn { if(f == null) throw new RuntimeException("function already finished"); + if(scope == null) throw new RuntimeException("scope is null"); Thread t = Thread.currentThread(); Interpreter old = (Interpreter)threadToInterpreter.get(t); threadToInterpreter.put(t, this); @@ -80,29 +88,23 @@ class Interpreter implements ByteCodes, Tokens { case LITERAL: stack.push((JS)arg); break; case OBJECT: stack.push(new JS.O()); break; case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break; - case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; - case TOPSCOPE: stack.push(scope); break; + //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break; case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break; case JMP: pc += JS.toInt((JS)arg) - 1; break; case POP: stack.pop(); break; - case SWAP: { - int depth = (arg == null ? 1 : JS.toInt((JS)arg)); - JS save = stack.elementAt(stack.size() - 1); - for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--) - stack.setElementAt(stack.elementAt(i-1), i); - stack.setElementAt(save, stack.size() - depth - 1); - break; - } + case SWAP: stack.swap(); break; case DUP: stack.push(stack.peek()); break; - case NEWSCOPE: scope = new JSScope(scope); break; - case OLDSCOPE: scope = scope.getParentScope(); break; - case ASSERT: { - JS o = stack.pop(); - if (JS.checkAssertions && !JS.toBoolean(o)) - throw je("ibex.assertion.failed"); + case NEWSCOPE: { + int n = JS.toInt((JS)arg); + scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff); break; } + case OLDSCOPE: scope = scope.parent; break; + case GLOBALSCOPE: stack.push(scope.getGlobal()); break; + case SCOPEGET: stack.push(scope.get((JS)arg)); break; + case SCOPEPUT: scope.put((JS)arg,stack.peek()); break; + case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed"); break; case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break; case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break; case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break; @@ -120,11 +122,7 @@ class Interpreter implements ByteCodes, Tokens { case PUSHKEYS: { JS o = stack.peek(); - Enumeration e = o.keys(); - JSArray a = new JSArray(); - // FEATURE: Take advantage of the Enumeration, don't create a JSArray - while(e.hasMoreElements()) a.addElement((JS)e.nextElement()); - stack.push(a); + stack.push(o == null ? null : o.keys()); break; } @@ -135,7 +133,7 @@ class Interpreter implements ByteCodes, Tokens { case BREAK: case CONTINUE: - while(stack.size() > 0) { + while(!stack.empty()) { JS o = stack.pop(); if (o instanceof CallMarker) je("break or continue not within a loop"); if (o instanceof TryMarker) { @@ -169,7 +167,7 @@ class Interpreter implements ByteCodes, Tokens { case RETURN: { JS retval = stack.pop(); - while(stack.size() > 0) { + while(!stack.empty()) { Object o = stack.pop(); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; @@ -179,39 +177,81 @@ class Interpreter implements ByteCodes, Tokens { pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } else if (o instanceof CallMarker) { - if (o instanceof TrapMarker) { // handles return component of a read trap + boolean didTrapPut = false; + if (o instanceof TrapMarker) { // handles return component of a write trap TrapMarker tm = (TrapMarker) o; - boolean cascade = tm.t.writeTrap() && !tm.cascadeHappened && !JS.toBoolean(retval); + boolean cascade = tm.t.isWriteTrap() && !tm.cascadeHappened && !JS.toBoolean(retval); if(cascade) { - Trap t = tm.t.next; - while(t != null && t.readTrap()) t = t.next; + Trap t = tm.t.nextWriteTrap(); + if(t == null && tm.t.target instanceof JS.Clone) { + t = ((JS.Clone)tm.t.target).clonee.getTrap(tm.t.key); + if(t != null) t = t.writeTrap(); + } if(t != null) { - tm.t = t; - stack.push(tm); - JSArray args = new JSArray(); - args.addElement(tm.val); - stack.push(args); - f = t.f; - scope = new JSScope(f.parentScope); - pc = -1; + tm.t = t; // we reuse the old trap marker + setupTrap(t,tm.val,tm); + pc--; // we increment it on the next iter continue OUTER; } else { - tm.trapee.put(tm.key,tm.val); + didTrapPut = true; + if(!tm.pauseOnPut) tm.t.target.put(tm.t.key,tm.val); } } } - scope = ((CallMarker)o).scope; - pc = ((CallMarker)o).pc - 1; - f = (JSFunction)((CallMarker)o).f; - stack.push(retval); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused - if(f == null) return retval; + CallMarker cm = (CallMarker) o; + scope = cm.scope; + pc = cm.pc - 1; + f = cm.f; + if (didTrapPut) { + if (((TrapMarker)cm).pauseOnPut) { pc++; return ((TrapMarker)cm).val; } + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + } else { + stack.push(retval); + } + if (f == null) return retval; continue OUTER; } } throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); } - + + case CASCADE: { + boolean write = JS.toBoolean((JS)arg); + JS val = write ? stack.pop() : null; + CallMarker o = stack.findCall(); + if(!(o instanceof TrapMarker)) throw new JSExn("tried to CASCADE while not in a trap"); + TrapMarker tm = (TrapMarker) o; + JS key = tm.t.key; + JS target = tm.t.target; + if(tm.t.isWriteTrap() != write) throw new JSExn("tried to do a " + (write?"write":"read") + " cascade in a " + (write?"read":"write") + " trap"); + Trap t = write ? tm.t.nextWriteTrap() : tm.t.nextReadTrap(); + // FIXME: Doesn't handle multiple levels of clone's (probably can just make this a while loop) + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = write ? t.writeTrap() : t.readTrap(); + } + if(write) { + tm.cascadeHappened = true; + stack.push(val); + } + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut)); + pc--; // we increment later + } else { + if(write) { + if (tm.pauseOnPut) { pc++; return val; } + target.put(key,val); + } else { + JS ret = target.get(key); + if (ret == JS.METHOD) ret = new Stub(target, key); + stack.push(ret); + } + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + } + break; + } + case PUT: { JS val = stack.pop(); JS key = stack.pop(); @@ -219,45 +259,25 @@ class Interpreter implements ByteCodes, Tokens { if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value"); if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key"); - Trap t = null; - TrapMarker tm = null; - if(target instanceof JSScope && key.jsequals(CASCADE)) { - Object o=null; - int i; - for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break; - if(i==0) throw new Error("didn't find a call marker while doing cascade"); - if(o instanceof TrapMarker) { - tm = (TrapMarker) o; - target = tm.trapee; - key = tm.key; - tm.cascadeHappened = true; - t = tm.t; - if(t.readTrap()) throw new JSExn("can't do a write cascade in a read trap"); - t = t.next; - while(t != null && t.readTrap()) t = t.next; - } - } - if(tm == null) { // didn't find a trap marker, try to find a trap - t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key); - while(t != null && t.readTrap()) t = t.next; - } + Trap t = target.getTrap(key); + if(t != null) t = t.writeTrap(); + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.writeTrap(); + } + stack.push(val); if(t != null) { - stack.push(new TrapMarker(this,t,target,key,val)); - JSArray args = new JSArray(); - args.addElement(val); - stack.push(args); - f = t.f; - scope = new TrapScope(f.parentScope,target,f,key); - pc = -1; - break; + setupTrap(t,val,new TrapMarker(this,t,val)); + pc--; // we increment later } else { target.put(key,val); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused - break; } + break; } case GET: @@ -272,50 +292,39 @@ class Interpreter implements ByteCodes, Tokens { stack.push(key); } JS ret = null; - if (key == null) throw je("tried to get the null key from " + target); - if (target == null) throw je("tried to get property \"" + key + "\" from the null object"); + if (key == null) throw je("tried to get the null key from " + JS.debugToString(target)); + if (target == null) throw je("tried to get property \"" + JS.debugToString(key) + "\" from the null object"); - Trap t = null; - TrapMarker tm = null; - if(target instanceof JSScope && key.jsequals(CASCADE)) { - JS o=null; - int i; - for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break; - if(i==0) throw new Error("didn't find a call marker while doing cascade"); - if(o instanceof TrapMarker) { - tm = (TrapMarker) o; - target = tm.trapee; - key = tm.key; - t = tm.t; - if(t.writeTrap()) throw new JSExn("can't do a read cascade in a write trap"); - t = t.next; - while(t != null && t.writeTrap()) t = t.next; - if(t != null) tm.cascadeHappened = true; - } - } - if(tm == null) { // didn't find a trap marker, try to find a trap - t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key); - while(t != null && t.writeTrap()) t = t.next; + Trap t = target.getTrap(key); + if(t != null) t = t.readTrap(); + + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.readTrap(); } if(t != null) { - stack.push(new TrapMarker(this,t,(JS)target,key,null)); - stack.push(new JSArray()); - f = t.f; - scope = new TrapScope(f.parentScope,target,f,key); - pc = -1; - break; + setupTrap(t,null,new TrapMarker(this,t,null)); + pc--; // we increment later } else { ret = target.get(key); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused if (ret == JS.METHOD) ret = new Stub(target, key); stack.push(ret); - break; } + break; } case CALL: case CALLMETHOD: { int numArgs = JS.toInt((JS)arg); + + JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null; + for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop(); + JS a2 = numArgs <= 2 ? null : stack.pop(); + JS a1 = numArgs <= 1 ? null : stack.pop(); + JS a0 = numArgs <= 0 ? null : stack.pop(); + JS method = null; JS ret = null; JS object = stack.pop(); @@ -333,21 +342,12 @@ class Interpreter implements ByteCodes, Tokens { stack.pop(); } } - JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null; - for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop(); - JS a2 = numArgs <= 2 ? null : stack.pop(); - JS a1 = numArgs <= 1 ? null : stack.pop(); - JS a0 = numArgs <= 0 ? null : stack.pop(); - if (object instanceof JSFunction) { - // FIXME: use something similar to call0/call1/call2 here - JSArray arguments = new JSArray(); - for(int i=0; i 0) { + while(!stack.empty()) { JS o = stack.pop(); if (o instanceof CatchMarker || o instanceof TryMarker) { boolean inCatch = o instanceof CatchMarker; @@ -536,6 +528,13 @@ class Interpreter implements ByteCodes, Tokens { throw e; } + void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn { + stack.push(cm); + stack.push(new TrapArgs(t,val)); + f = t.f; + scope = f.parentScope; + pc = 0; + } // Markers ////////////////////////////////////////////////////////////////////// @@ -551,22 +550,24 @@ class Interpreter implements ByteCodes, Tokens { final int pc; final JSScope scope; final JSFunction f; - public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } - public CallMarker() { pc = -1; scope = null; f = null; } + public CallMarker(Interpreter cx) { + pc = cx == null ? -1 : cx.pc + 1; + scope = cx == null ? null : cx.scope; + f = cx == null ? null : cx.f; + } } static class TrapMarker extends CallMarker { Trap t; - final JS trapee; - final JS key; - final JS val; + JS val; boolean cascadeHappened; - public TrapMarker(Interpreter cx, Trap t, JS trapee, JS key, JS val) { + final boolean pauseOnPut; + public TrapMarker(Interpreter cx, Trap t, JS val) { this(cx,t,val,false); } + public TrapMarker(Interpreter cx, Trap t, JS val, boolean pauseOnPut) { super(cx); this.t = t; - this.trapee = trapee; - this.key = key; this.val = val; + this.pauseOnPut = pauseOnPut; } } @@ -604,21 +605,52 @@ class Interpreter implements ByteCodes, Tokens { public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn } - static class TrapScope extends JSScope { - JS trapee; - JS callee; - JS trapname; - public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) { - super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname; + static class TrapArgs extends JS { + private Trap t; + private JS val; + public TrapArgs(Trap t, JS val) { this.t = t; this.val = val; } + public JS get(JS key) throws JSExn { + if(JS.isInt(key) && JS.toInt(key) == 0) return val; + //#jswitch(key) + case "trapee": return t.target; + case "callee": return t.f; + case "trapname": return t.key; + case "length": return t.isWriteTrap() ? ONE : ZERO; + //#end + return super.get(key); } + } + + static class JSArgs extends JS { + private final JS a0; + private final JS a1; + private final JS a2; + private final JS[] rest; + private final int nargs; + private final JS callee; + + public JSArgs(JS callee) { this(null,null,null,null,0,callee); } + public JSArgs(JS a0, JS callee) { this(a0,null,null,null,1,callee); } + public JSArgs(JS a0, JS a1, JS a2, JS[] rest, int nargs, JS callee) { + this.a0 = a0; this.a1 = a1; this.a2 = a2; + this.rest = rest; this.nargs = nargs; + this.callee = callee; + } + public JS get(JS key) throws JSExn { - if(JS.isString(key)) { - //#switch(JS.toString(key)) - case "trapee": return trapee; - case "callee": return callee; - case "trapname": return trapname; - //#end + if(JS.isInt(key)) { + int n = JS.toInt(key); + switch(n) { + case 0: return a0; + case 1: return a1; + case 2: return a2; + default: return n>= 0 && n < nargs ? rest[n-3] : null; + } } + //#jswitch(key) + case "callee": return callee; + case "length": return JS.N(nargs); + //#end return super.get(key); } } @@ -636,26 +668,38 @@ class Interpreter implements ByteCodes, Tokens { private static final int MAX_STACK_SIZE = 512; private JS[] stack = new JS[64]; private int sp = 0; - public final void push(JS o) throws JSExn { - if(sp == stack.length) grow(); - stack[sp++] = o; - } - public final JS peek() { - if(sp == 0) throw new RuntimeException("Stack underflow"); - return stack[sp-1]; + + boolean empty() { return sp == 0; } + void push(JS o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } + JS peek() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[sp-1]; } + final JS pop() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[--sp]; } + void swap() throws JSExn { + if(sp < 2) throw new JSExn("stack overflow"); + JS tmp = stack[sp-2]; + stack[sp-2] = stack[sp-1]; + stack[sp-1] = tmp; } - public final JS pop() { - if(sp == 0) throw new RuntimeException("Stack underflow"); - return stack[--sp]; + CallMarker findCall() { + for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i]; + return null; } - private void grow() throws JSExn { + void grow() throws JSExn { if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow"); JS[] stack2 = new JS[stack.length * 2]; System.arraycopy(stack,0,stack2,0,stack.length); + } + + void backtrace(JSExn e) { + for(int i=sp-1;i>=0;i--) { + if (stack[i] instanceof CallMarker) { + CallMarker cm = (CallMarker)stack[i]; + if(cm.f == null) break; + String s = cm.f.sourceName + ":" + cm.f.line[cm.pc-1]; + if(cm instanceof Interpreter.TrapMarker) + s += " (trap on " + JS.debugToString(((Interpreter.TrapMarker)cm).t.key) + ")"; + e.addBacktrace(s); + } + } } - // FIXME: Eliminate all uses of SWAP n>1 so we don't need this - public int size() { return sp; } - public void setElementAt(JS o, int i) { stack[i] = o; } - public JS elementAt(int i) { return stack[i]; } } }