updated Makefile.common
[org.ibex.core.git] / src / org / ibex / js / Interpreter.java
index 15d42be..04f6784 100644 (file)
@@ -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()); }
@@ -25,18 +23,28 @@ class Interpreter implements ByteCodes, Tokens {
     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,22 +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: 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;
@@ -168,14 +177,14 @@ 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.isWriteTrap() && !tm.cascadeHappened && !JS.toBoolean(retval);
                             if(cascade) {
                                 Trap t = tm.t.nextWriteTrap();
-                                if(t == null && tm.target instanceof JS.Clone) {
-                                    tm.target = ((JS.Clone)tm.target).clonee;
-                                    t = tm.target.getTrap(tm.key);
+                                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) {
@@ -184,7 +193,8 @@ class Interpreter implements ByteCodes, Tokens {
                                     pc--; // we increment it on the next iter
                                     continue OUTER;
                                 } else {
-                                    tm.target.put(tm.key,tm.val);
+                                    didTrapPut = true;
+                                    if(!tm.pauseOnPut) tm.t.target.put(tm.t.key,tm.val);
                                 }
                             }
                         }
@@ -192,15 +202,56 @@ class Interpreter implements ByteCodes, Tokens {
                         scope = cm.scope;
                         pc = cm.pc - 1;
                         f = cm.f;
-                        stack.push(retval);
-                        if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                        if(f == null) return retval;
+                        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();
@@ -208,24 +259,9 @@ 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)) {
-                    CallMarker o = stack.findCall();
-                    if(o instanceof TrapMarker) {
-                        tm = (TrapMarker) o;
-                        target = tm.target;
-                        key = tm.key;
-                        tm.cascadeHappened = true;
-                        t = tm.t;
-                        if(t.isReadTrap()) throw new JSExn("can't do a write cascade in a read trap");
-                        t = t.nextWriteTrap();
-                    }
-                }
-                if(tm == null) { // not cascading
-                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : target.getTrap(key);
-                    if(t != null) t = t.writeTrap();
-                }
+                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);
@@ -235,14 +271,13 @@ class Interpreter implements ByteCodes, Tokens {
                 stack.push(val);
                 
                 if(t != null) {
-                    setupTrap(t,val,new TrapMarker(this,t,target,key,val));
+                    setupTrap(t,val,new TrapMarker(this,t,val));
                     pc--; // we increment later
-                    break;
                 } else {
                     target.put(key,val);
                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                    break;
                 }
+                break;
             }
 
             case GET:
@@ -257,26 +292,12 @@ 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 = target.getTrap(key);
+                if(t != null) t = t.readTrap();
                 
-                Trap t = null;
-                TrapMarker tm = null;
-                if(target instanceof JSScope && key.jsequals(CASCADE)) {
-                    CallMarker o = stack.findCall();
-                    if(o instanceof TrapMarker) {
-                        tm = (TrapMarker) o;
-                        target = tm.target;
-                        key = tm.key;
-                        t = tm.t;
-                        if(t.isWriteTrap()) throw new JSExn("can't do a read cascade in a write trap");
-                        t = t.nextReadTrap();
-                    }
-                }
-                if(tm == null) { // not cascading
-                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)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);
@@ -284,16 +305,15 @@ class Interpreter implements ByteCodes, Tokens {
                 }
                 
                 if(t != null) {
-                    setupTrap(t,null,new TrapMarker(this,t,target,key,null));
+                    setupTrap(t,null,new TrapMarker(this,t,null));
                     pc--; // we increment later
-                    break;
                 } 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: {
@@ -327,7 +347,7 @@ class Interpreter implements ByteCodes, Tokens {
                     stack.push(new CallMarker(this));
                     stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
                     f = (JSFunction)object;
-                    scope = new JSScope(f.parentScope);
+                    scope = f.parentScope;
                     pc = -1;
                     break;
                 } else {
@@ -372,14 +392,6 @@ class Interpreter implements ByteCodes, Tokens {
                 JS js = stack.peek();
                 // A trap addition/removal
                 if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
-                if(js instanceof JSScope) {
-                    JSScope s = (JSScope) js;
-                    while(s.getParentScope() != null) {
-                        if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
-                        s = s.getParentScope();
-                    }
-                    js = s;
-                }
                 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
                 else js.delTrap(key, (JSFunction)val);
                 break;
@@ -518,9 +530,9 @@ class Interpreter implements ByteCodes, Tokens {
 
     void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
         stack.push(cm);
-        stack.push(t.isWriteTrap() ? new JSArgs(val,t.f) : new JSArgs(t.f));
+        stack.push(new TrapArgs(t,val));
         f = t.f;
-        scope = new TrapScope(t.f.parentScope,t.target,t.f,t.key);
+        scope = f.parentScope;
         pc = 0;
     }
 
@@ -538,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;
-        JS target;
-        final JS key;
-        final JS val;
+        JS val;
         boolean cascadeHappened;
-        public TrapMarker(Interpreter cx, Trap t, JS target, 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.target = target;
-            this.key = key;
             this.val = val;
+            this.pauseOnPut = pauseOnPut;
         }
     }
     
@@ -591,21 +605,18 @@ 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.isString(key)) {
-                //#switch(JS.toString(key))
-                case "trapee": return trapee;
-                case "callee": return callee;
-                case "trapname": return trapname;
-                //#end
-            }
+            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);
         }
     }
@@ -636,7 +647,7 @@ class Interpreter implements ByteCodes, Tokens {
                     default: return n>= 0 && n < nargs ? rest[n-3] : null;
                 }
             }
-            //#switch(JS.toString(key))
+            //#jswitch(key)
             case "callee": return callee;
             case "length": return JS.N(nargs);
             //#end
@@ -685,7 +696,7 @@ class Interpreter implements ByteCodes, Tokens {
                     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).key) + ")";
+                        s += " (trap on " + JS.debugToString(((Interpreter.TrapMarker)cm).t.key) + ")";
                     e.addBacktrace(s);
                 }
             }