summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Hickey <richhickey@gmail.com>2010-10-18 18:12:27 -0400
committerRich Hickey <richhickey@gmail.com>2010-10-18 18:12:27 -0400
commitd1006391595641f16e9c54187a8df0a0138d3a53 (patch)
treeb6c1b2f7b30b0e8ced595ba6796f9a5966cbf03c
parent58be0895bc3c8248ee2c1bb862cf28b86dce8b6c (diff)
Stable var caching. The values of non-dynamic ns-resolved (def'ed) vars appearing by name in code are fixed at fn entry point, and will be cached for subsequent calls until the vars change (e.g. via a new def). Access to unbound vars will not always throw an exception, and may instead return Unbound objects. These Unbound objects throw exceptions when invoked.
Note that modifications of var roots in a fn body will not be seen by the code compiled against those names in the same fn body. If you want to treat top-level vars as boxes, use #' This change allows the overhead of placing things in vars and small wrapper fns to be substantially eliminated.
-rw-r--r--src/jvm/clojure/lang/Compiler.java197
-rw-r--r--src/jvm/clojure/lang/Var.java19
-rw-r--r--test/clojure/test_clojure/protocols.clj9
-rw-r--r--test/clojure/test_clojure/test.clj4
4 files changed, 191 insertions, 38 deletions
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index 04a17424..38a22c57 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -195,7 +195,7 @@ static final public Var KEYWORD_CALLSITES = Var.create().setDynamic();
//vector<var>
static final public Var PROTOCOL_CALLSITES = Var.create().setDynamic();
-//vector<var>
+//set<var>
static final public Var VAR_CALLSITES = Var.create().setDynamic();
//keyword->constid
@@ -532,8 +532,9 @@ public static class VarExpr implements Expr, AssignableExpr{
}
public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
- objx.emitVar(gen, var);
- gen.invokeVirtual(VAR_TYPE, getMethod);
+// objx.emitVar(gen, var);
+// gen.invokeVirtual(VAR_TYPE, getMethod);
+ objx.emitVarValue(gen,var);
if(context == C.STATEMENT)
{
gen.pop();
@@ -3170,15 +3171,16 @@ static class InvokeExpr implements Expr{
}
}
else if(pvar == null && VAR_CALLSITES.isBound()
- && fvar.ns.name.name.startsWith("clojure")
+ // && fvar.ns.name.name.startsWith("clojure")
&& !RT.booleanCast(RT.get(RT.meta(fvar),dynamicKey))
// && !fvar.sym.name.equals("report")
// && fvar.isBound() && fvar.get() instanceof IFn
)
{
//todo - more specific criteria for binding these
-// this.isDirect = true;
-// this.siteIndex = registerVarCallsite(((VarExpr) fexpr).var);
+ this.isDirect = true;
+// this.siteIndex =
+ registerVarCallsite(((VarExpr) fexpr).var);
}
}
this.tag = tag != null ? tag : (fexpr instanceof VarExpr ? ((VarExpr) fexpr).tag : null);
@@ -3210,20 +3212,22 @@ static class InvokeExpr implements Expr{
}
else if(isDirect)
{
- Label callLabel = gen.newLabel();
-
- gen.getStatic(objx.objtype, objx.varCallsiteName(siteIndex), IFN_TYPE);
- gen.dup();
- gen.ifNonNull(callLabel);
-
- gen.pop();
fexpr.emit(C.EXPRESSION, objx, gen);
- gen.checkCast(IFN_TYPE);
-// gen.dup();
-// gen.putStatic(objx.objtype, objx.varCallsiteName(siteIndex), IFN_TYPE);
-
- gen.mark(callLabel);
emitArgsAndCall(0, context,objx,gen);
+// Label callLabel = gen.newLabel();
+//
+// gen.getStatic(objx.objtype, objx.varCallsiteName(siteIndex), IFN_TYPE);
+// gen.dup();
+// gen.ifNonNull(callLabel);
+//
+// gen.pop();
+// fexpr.emit(C.EXPRESSION, objx, gen);
+// gen.checkCast(IFN_TYPE);
+//// gen.dup();
+//// gen.putStatic(objx.objtype, objx.varCallsiteName(siteIndex), IFN_TYPE);
+//
+// gen.mark(callLabel);
+// emitArgsAndCall(0, context,objx,gen);
}
else
{
@@ -3449,7 +3453,7 @@ static public class FnExpr extends ObjExpr{
VARS, PersistentHashMap.EMPTY,
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
- VAR_CALLSITES, PersistentVector.EMPTY
+ VAR_CALLSITES, emptyVarCallSites()
));
//arglist might be preceded by symbol naming this fn
@@ -3507,7 +3511,7 @@ static public class FnExpr extends ObjExpr{
fn.constants = (PersistentVector) CONSTANTS.deref();
fn.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
fn.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
- fn.varCallsites = (IPersistentVector) VAR_CALLSITES.deref();
+ fn.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
fn.constantsID = RT.nextID();
// DynamicClassLoader loader = (DynamicClassLoader) LOADER.get();
@@ -3569,7 +3573,7 @@ static public class ObjExpr implements Expr{
IPersistentVector keywordCallsites;
IPersistentVector protocolCallsites;
- IPersistentVector varCallsites;
+ IPersistentSet varCallsites;
boolean onceOnly = false;
Object src;
@@ -3726,11 +3730,11 @@ static public class ObjExpr implements Expr{
null, null);
}
- for(int i=0;i<varCallsites.count();i++)
- {
- cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL
- , varCallsiteName(i), IFN_TYPE.getDescriptor(), null, null);
- }
+// for(int i=0;i<varCallsites.count();i++)
+// {
+// cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL
+// , varCallsiteName(i), IFN_TYPE.getDescriptor(), null, null);
+// }
//static init for constants, keywords and vars
GeneratorAdapter clinitgen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
@@ -3749,6 +3753,7 @@ static public class ObjExpr implements Expr{
if(keywordCallsites.count() > 0)
emitKeywordCallsites(clinitgen);
+ /*
for(int i=0;i<varCallsites.count();i++)
{
Label skipLabel = clinitgen.newLabel();
@@ -3774,7 +3779,7 @@ static public class ObjExpr implements Expr{
clinitgen.mark(endLabel);
}
-
+ */
clinitgen.returnValue();
clinitgen.endMethod();
@@ -3823,6 +3828,64 @@ static public class ObjExpr implements Expr{
cv.visitField(ACC_PRIVATE, cachedProtoImplName(i), IFN_TYPE.getDescriptor(), null, null);
}
+ //instance fields for cached vars
+ for(ISeq es = RT.seq(vars);es != null;es = es.next())
+ {
+ Map.Entry e = (Map.Entry)es.first();
+ Var v = (Var)e.getKey();
+ Integer i = (Integer) e.getValue();
+ if(!v.isDynamic())
+ cv.visitField(ACC_PRIVATE, cachedVarName(i),
+ varCallsites.contains(v)?IFN_TYPE.getDescriptor():OBJECT_TYPE.getDescriptor(), null, null);
+ }
+
+ //track var rev if we use any vars
+ if(vars.count() > 0)
+ {
+ cv.visitField(ACC_PRIVATE, "__varrev__", Type.INT_TYPE.getDescriptor(), null, null);
+
+ final Method getMethod = Method.getMethod("Object getRawRoot()");
+ Method meth = new Method("__reloadVars__",Type.VOID_TYPE, new Type[]{});
+
+ GeneratorAdapter gen = new GeneratorAdapter(ACC_PRIVATE,
+ meth,
+ null,
+ null,
+ cv);
+ gen.visitCode();
+ Label repeat = new Label();
+
+
+ gen.visitLabel(repeat);
+ gen.loadThis();
+ gen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ gen.putField(objtype, "__varrev__", Type.INT_TYPE);
+
+ for(ISeq es = RT.seq(vars);es != null;es = es.next())
+ {
+ Map.Entry e = (Map.Entry)es.first();
+ Var v = (Var)e.getKey();
+ Integer i = (Integer) e.getValue();
+ if(!v.isDynamic())
+ {
+ gen.loadThis();
+ emitConstant(gen,i);
+ gen.invokeVirtual(VAR_TYPE, getMethod);
+ Type ft = varCallsites.contains(v)?IFN_TYPE:OBJECT_TYPE;
+ gen.checkCast(ft);
+ gen.putField(objtype(), cachedVarName(i), ft);
+ }
+ }
+
+ gen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ gen.loadThis();
+ gen.getField(objtype, "__varrev__", Type.INT_TYPE);
+ gen.ifICmp(GeneratorAdapter.NE,repeat);
+
+ gen.returnValue();
+ gen.endMethod();
+ }
+
//ctor that takes closed-overs and inits base + fields
Method m = new Method("<init>", Type.VOID_TYPE, ctorTypes());
GeneratorAdapter ctorgen = new GeneratorAdapter(ACC_PUBLIC,
@@ -3845,6 +3908,16 @@ static public class ObjExpr implements Expr{
// }
// else
// ctorgen.invokeConstructor(aFnType, voidctor);
+
+ if(vars.count() > 0)
+ {
+ ctorgen.loadThis();
+ ctorgen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ ctorgen.push(-1);
+ ctorgen.visitInsn(Opcodes.IADD);
+ ctorgen.putField(objtype, "__varrev__", Type.INT_TYPE);
+ }
+
if(!isDeftype())
{
ctorgen.loadThis();
@@ -4447,6 +4520,23 @@ static public class ObjExpr implements Expr{
//gen.getStatic(fntype, munge(var.sym.toString()), VAR_TYPE);
}
+ final static Method varGetMethod = Method.getMethod("Object get()");
+
+ public void emitVarValue(GeneratorAdapter gen, Var v){
+ Integer i = (Integer) vars.valAt(v);
+ if(varCallsites != null && !v.isDynamic())
+ {
+ Type ft = varCallsites.contains(v)?IFN_TYPE:OBJECT_TYPE;
+ gen.loadThis();
+ gen.getField(objtype(), cachedVarName(i), ft);
+ }
+ else
+ {
+ emitConstant(gen, i);
+ gen.invokeVirtual(VAR_TYPE, varGetMethod);
+ }
+ }
+
public void emitKeyword(GeneratorAdapter gen, Keyword k){
Integer i = (Integer) keywords.valAt(k);
emitConstant(gen, i);
@@ -4478,6 +4568,10 @@ static public class ObjExpr implements Expr{
return "__cached_class__" + n;
}
+ String cachedVarName(int n){
+ return "__cached_var__" + n;
+ }
+
String cachedProtoFnName(int n){
return "__cached_proto_fn__" + n;
}
@@ -4753,10 +4847,22 @@ public static class FnMethod extends ObjMethod{
cv);
gen.visitCode();
Label loopLabel = gen.mark();
+ Label bodyLabel = new Label();
gen.visitLineNumber(line, loopLabel);
try
{
Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));
+ if(fn.vars().count() > 0)
+ {
+ gen.loadThis();
+ gen.getField(fn.objtype, "__varrev__", Type.INT_TYPE);
+ gen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ gen.ifICmp(GeneratorAdapter.EQ,bodyLabel);
+ gen.loadThis();
+ gen.invokeVirtual(fn.objtype(), new Method("__reloadVars__",Type.VOID_TYPE, new Type[]{}));
+ }
+
+ gen.visitLabel(bodyLabel);
body.emit(C.RETURN, fn, gen);
Label end = gen.mark();
@@ -4946,10 +5052,22 @@ abstract public static class ObjMethod{
cv);
gen.visitCode();
Label loopLabel = gen.mark();
+ Label bodyLabel = new Label();
gen.visitLineNumber(line, loopLabel);
try
{
Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));
+ if(fn.vars().count() > 0)
+ {
+ gen.loadThis();
+ gen.getField(fn.objtype, "__varrev__", Type.INT_TYPE);
+ gen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ gen.ifICmp(GeneratorAdapter.EQ,bodyLabel);
+ gen.loadThis();
+ gen.invokeVirtual(fn.objtype(), new Method("__reloadVars__",Type.VOID_TYPE, new Type[]{}));
+ }
+
+ gen.visitLabel(bodyLabel);
body.emit(C.RETURN, fn, gen);
Label end = gen.mark();
gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
@@ -6102,15 +6220,15 @@ private static int registerProtocolCallsite(Var v){
return protocolCallsites.count()-1;
}
-private static int registerVarCallsite(Var v){
+private static void registerVarCallsite(Var v){
if(!VAR_CALLSITES.isBound())
throw new IllegalAccessError("VAR_CALLSITES is not bound");
- IPersistentVector varCallsites = (IPersistentVector) VAR_CALLSITES.deref();
+ IPersistentCollection varCallsites = (IPersistentCollection) VAR_CALLSITES.deref();
varCallsites = varCallsites.cons(v);
VAR_CALLSITES.set(varCallsites);
- return varCallsites.count()-1;
+// return varCallsites.count()-1;
}
static ISeq fwdPath(PathNode p1){
@@ -6821,7 +6939,7 @@ static public class NewInstanceExpr extends ObjExpr{
VARS, PersistentHashMap.EMPTY,
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
- VAR_CALLSITES, PersistentVector.EMPTY
+ VAR_CALLSITES, emptyVarCallSites()
));
if(ret.isDeftype())
{
@@ -6848,7 +6966,7 @@ static public class NewInstanceExpr extends ObjExpr{
ret.constantsID = RT.nextID();
ret.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
ret.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
- ret.varCallsites = (IPersistentVector) VAR_CALLSITES.deref();
+ ret.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
}
finally
{
@@ -7272,10 +7390,23 @@ public static class NewInstanceMethod extends ObjMethod{
}
gen.visitCode();
Label loopLabel = gen.mark();
+ Label bodyLabel = new Label();
+
gen.visitLineNumber(line, loopLabel);
try
{
Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));
+ if(obj.vars().count() > 0)
+ {
+ gen.loadThis();
+ gen.getField(obj.objtype, "__varrev__", Type.INT_TYPE);
+ gen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
+ gen.ifICmp(GeneratorAdapter.EQ,bodyLabel);
+ gen.loadThis();
+ gen.invokeVirtual(obj.objtype(), new Method("__reloadVars__",Type.VOID_TYPE, new Type[]{}));
+ }
+
+ gen.visitLabel(bodyLabel);
emitBody(objx, gen, retClass, body);
Label end = gen.mark();
gen.visitLocalVariable("this", obj.objtype.getDescriptor(), null, loopLabel, end, 0);
@@ -7511,4 +7642,6 @@ public static class CaseExpr extends UntypedExpr{
}
}
+static IPersistentCollection emptyVarCallSites(){return PersistentHashSet.EMPTY;}
+
}
diff --git a/src/jvm/clojure/lang/Var.java b/src/jvm/clojure/lang/Var.java
index 5572a267..e88dad7a 100644
--- a/src/jvm/clojure/lang/Var.java
+++ b/src/jvm/clojure/lang/Var.java
@@ -28,6 +28,22 @@ public TBox(Thread t, Object val){
}
}
+static public class Unbound extends AFn{
+ final public Var v;
+
+ public Unbound(Var v){
+ this.v = v;
+ }
+
+ public String toString(){
+ return "Unbound: " + v;
+ }
+
+ public Object throwArity(int n){
+ throw new IllegalStateException("Attempting to call unbound fn: " + v);
+ }
+}
+
static class Frame{
//Var->TBox
Associative bindings;
@@ -159,6 +175,7 @@ Var(Namespace ns, Symbol sym){
Var(Namespace ns, Symbol sym, Object root){
this(ns, sym);
this.root = root;
+ ++rev;
}
public boolean isBound(){
@@ -247,7 +264,9 @@ public Object getRoot(){
}
public Object getRawRoot(){
+ if(hasRoot())
return root;
+ return new Unbound(this);
}
public Object getTag(){
diff --git a/test/clojure/test_clojure/protocols.clj b/test/clojure/test_clojure/protocols.clj
index 44969756..d208c5fd 100644
--- a/test/clojure/test_clojure/protocols.clj
+++ b/test/clojure/test_clojure/protocols.clj
@@ -16,6 +16,7 @@
(:import [clojure.test_clojure.protocols.examples ExampleInterface]))
;; temporary hack until I decide how to cleanly reload protocol
+;; this no longer works
(defn reload-example-protocols
[]
(alter-var-root #'clojure.test-clojure.protocols.examples/ExampleProtocol
@@ -111,7 +112,7 @@
(deftype ExtendsTestWidget []
ExampleProtocol)
-(deftest extends?-test
+#_(deftest extends?-test
(reload-example-protocols)
(testing "returns false if a type does not implement the protocol at all"
(is (false? (extends? other/SimpleProtocol ExtendsTestWidget))))
@@ -125,7 +126,7 @@
(is (true? (extends? other/SimpleProtocol ExtendsTestWidget)))))
(deftype ExtendersTestWidget [])
-(deftest extenders-test
+#_(deftest extenders-test
(reload-example-protocols)
(testing "a fresh protocol has no extenders"
(is (nil? (extenders ExampleProtocol))))
@@ -139,7 +140,7 @@
(deftype SatisfiesTestWidget []
ExampleProtocol)
-(deftest satisifies?-test
+#_(deftest satisifies?-test
(reload-example-protocols)
(let [whatzit (SatisfiesTestWidget.)]
(testing "returns false if a type does not implement the protocol at all"
@@ -154,7 +155,7 @@
(is (true? (satisfies? other/SimpleProtocol whatzit))))) )
(deftype ReExtendingTestWidget [])
-(deftest re-extending-test
+#_(deftest re-extending-test
(reload-example-protocols)
(extend
ReExtendingTestWidget
diff --git a/test/clojure/test_clojure/test.clj b/test/clojure/test_clojure/test.clj
index 086223f8..ae30b068 100644
--- a/test/clojure/test_clojure/test.clj
+++ b/test/clojure/test_clojure/test.clj
@@ -78,10 +78,10 @@
;; still have to declare the symbol before testing unbound symbols
(declare does-not-exist)
-(deftest can-test-unbound-symbol
+#_(deftest can-test-unbound-symbol
(is (= nil does-not-exist) "Should error"))
-(deftest can-test-unbound-function
+#_(deftest can-test-unbound-function
(is (does-not-exist) "Should error"))