summaryrefslogtreecommitdiff
path: root/src/jvm
diff options
context:
space:
mode:
authorAlexander Taggart <alex.taggart@expojure.com>2011-03-04 15:19:13 -0800
committerStuart Halloway <stu@thinkrelevance.com>2011-04-29 11:12:40 -0400
commit34489bddcceb2c1102c30e6b3e417d981097453c (patch)
treee063dc249f312d8cea5907bf27bc411b728018e8 /src/jvm
parent71b5461dca083a4a60b65d6d2e46322d977774c9 (diff)
case changes: handles hash collisions, can emit return type, performance path for all-int test constants
Signed-off-by: Stuart Halloway <stu@thinkrelevance.com>
Diffstat (limited to 'src/jvm')
-rw-r--r--src/jvm/clojure/lang/Compiler.java309
1 files changed, 253 insertions, 56 deletions
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index d037186e..89d5df4e 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -1212,6 +1212,31 @@ static Class maybePrimitiveType(Expr e){
return null;
}
+static Class maybeJavaClass(Collection<Expr> exprs){
+ Class match = null;
+ try
+ {
+ for (Expr e : exprs)
+ {
+ if (e instanceof ThrowExpr)
+ continue;
+ if (!e.hasJavaClass())
+ return null;
+ Class c = e.getJavaClass();
+ if (match == null)
+ match = c;
+ else if (match != c)
+ return null;
+ }
+ }
+ catch(Exception e)
+ {
+ return null;
+ }
+ return match;
+}
+
+
static abstract class MethodExpr extends HostExpr{
static void emitArgsAsArray(IPersistentVector args, ObjExpr objx, GeneratorAdapter gen){
gen.push(args.count());
@@ -7700,23 +7725,32 @@ static public class MethodParamExpr implements Expr, MaybePrimitiveExpr{
}
}
-public static class CaseExpr extends UntypedExpr{
+public static class CaseExpr implements Expr, MaybePrimitiveExpr{
public final LocalBindingExpr expr;
public final int shift, mask, low, high;
public final Expr defaultExpr;
- public final HashMap<Integer,Expr> tests;
+ public final SortedMap<Integer,Expr> tests;
public final HashMap<Integer,Expr> thens;
- public final boolean allKeywords;
-
+ public final Keyword switchType;
+ public final Keyword testType;
+ public final Set<Integer> skipCheck;
+ public final Class returnType;
public final int line;
+ final static Type NUMBER_TYPE = Type.getType(Number.class);
+ final static Method intValueMethod = Method.getMethod("int intValue()");
+
final static Method hashMethod = Method.getMethod("int hash(Object)");
final static Method hashCodeMethod = Method.getMethod("int hashCode()");
- final static Method equalsMethod = Method.getMethod("boolean equals(Object, Object)");
-
-
+ final static Method equivMethod = Method.getMethod("boolean equiv(Object, Object)");
+ final static Keyword compactKey = Keyword.intern(null, "compact");
+ final static Keyword sparseKey = Keyword.intern(null, "sparse");
+ final static Keyword hashIdentityKey = Keyword.intern(null, "hash-identity");
+ final static Keyword hashEquivKey = Keyword.intern(null, "hash-equiv");
+ final static Keyword intKey = Keyword.intern(null, "int");
+ //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
public CaseExpr(int line, LocalBindingExpr expr, int shift, int mask, int low, int high, Expr defaultExpr,
- HashMap<Integer,Expr> tests,HashMap<Integer,Expr> thens, boolean allKeywords){
+ SortedMap<Integer,Expr> tests,HashMap<Integer,Expr> thens, Keyword switchType, Keyword testType, Set<Integer> skipCheck){
this.expr = expr;
this.shift = shift;
this.mask = mask;
@@ -7726,66 +7760,217 @@ public static class CaseExpr extends UntypedExpr{
this.tests = tests;
this.thens = thens;
this.line = line;
- this.allKeywords = allKeywords;
+ if (switchType != compactKey && switchType != sparseKey)
+ throw new IllegalArgumentException("Unexpected switch type: "+switchType);
+ this.switchType = switchType;
+ if (testType != intKey && testType != hashEquivKey && testType != hashIdentityKey)
+ throw new IllegalArgumentException("Unexpected test type: "+switchType);
+ this.testType = testType;
+ this.skipCheck = skipCheck;
+ Collection<Expr> returns = new ArrayList(thens.values());
+ returns.add(defaultExpr);
+ this.returnType = maybeJavaClass(returns);
+ if(RT.count(skipCheck) > 0 && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Performance warning, %s:%d - hash collision of some case test constants; if selected, those entries will be tested sequentially.\n",
+ SOURCE_PATH.deref(), line);
+ }
+ }
+
+ public boolean hasJavaClass(){
+ return returnType != null;
+ }
+
+ public boolean canEmitPrimitive(){
+ return Util.isPrimitive(returnType);
+ }
+
+ public Class getJavaClass(){
+ return returnType;
}
public Object eval() {
throw new UnsupportedOperationException("Can't eval case");
}
- public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
+ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
+ doEmit(context, objx, gen, false);
+ }
+
+ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
+ doEmit(context, objx, gen, true);
+ }
+
+ public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUnboxed){
Label defaultLabel = gen.newLabel();
Label endLabel = gen.newLabel();
- HashMap<Integer,Label> labels = new HashMap();
+ SortedMap<Integer,Label> labels = new TreeMap();
for(Integer i : tests.keySet())
{
labels.put(i, gen.newLabel());
}
- Label[] la = new Label[(high-low)+1];
+ gen.visitLineNumber(line, gen.mark());
- for(int i=low;i<=high;i++)
- {
- la[i-low] = labels.containsKey(i) ? labels.get(i) : defaultLabel;
- }
+ Class primExprClass = maybePrimitiveType(expr);
+ Type primExprType = primExprClass == null ? null : Type.getType(primExprClass);
- gen.visitLineNumber(line, gen.mark());
- expr.emit(C.EXPRESSION, objx, gen);
- gen.invokeStatic(UTIL_TYPE,hashMethod);
- gen.push(shift);
- gen.visitInsn(ISHR);
- gen.push(mask);
- gen.visitInsn(IAND);
- gen.visitTableSwitchInsn(low, high, defaultLabel, la);
+ if (testType == intKey)
+ emitExprForInts(objx, gen, primExprType, defaultLabel);
+ else
+ emitExprForHashes(objx, gen);
+
+ if (switchType == sparseKey)
+ {
+ Label[] la = new Label[labels.size()];
+ la = labels.values().toArray(la);
+ int[] ints = Numbers.int_array(tests.keySet());
+ gen.visitLookupSwitchInsn(defaultLabel, ints, la);
+ }
+ else
+ {
+ Label[] la = new Label[(high-low)+1];
+ for(int i=low;i<=high;i++)
+ {
+ la[i-low] = labels.containsKey(i) ? labels.get(i) : defaultLabel;
+ }
+ gen.visitTableSwitchInsn(low, high, defaultLabel, la);
+ }
for(Integer i : labels.keySet())
{
gen.mark(labels.get(i));
- expr.emit(C.EXPRESSION, objx, gen);
- tests.get(i).emit(C.EXPRESSION, objx, gen);
- if(allKeywords)
- {
- gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
- }
+ if (testType == intKey)
+ emitThenForInts(objx, gen, primExprType, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
+ else if (RT.contains(skipCheck, i) == RT.T)
+ emitExpr(objx, gen, thens.get(i), emitUnboxed);
else
- {
- gen.invokeStatic(UTIL_TYPE, equalsMethod);
- gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
- }
- thens.get(i).emit(C.EXPRESSION,objx,gen);
+ emitThenForHashes(objx, gen, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
gen.goTo(endLabel);
}
gen.mark(defaultLabel);
- defaultExpr.emit(C.EXPRESSION, objx, gen);
+ emitExpr(objx, gen, defaultExpr, emitUnboxed);
gen.mark(endLabel);
if(context == C.STATEMENT)
gen.pop();
}
+ private boolean isShiftMasked(){
+ return mask != 0;
+ }
+
+ private void emitShiftMask(GeneratorAdapter gen){
+ if (isShiftMasked())
+ {
+ gen.push(shift);
+ gen.visitInsn(ISHR);
+ gen.push(mask);
+ gen.visitInsn(IAND);
+ }
+ }
+
+ private void emitExprForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Label defaultLabel){
+ if (exprType == null)
+ {
+ if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Performance warning, %s:%d - case has int tests, but tested expression is not primitive.\n",
+ SOURCE_PATH.deref(), line);
+ }
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.instanceOf(NUMBER_TYPE);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.checkCast(NUMBER_TYPE);
+ gen.invokeVirtual(NUMBER_TYPE, intValueMethod);
+ emitShiftMask(gen);
+ }
+ else if (exprType == Type.LONG_TYPE
+ || exprType == Type.INT_TYPE
+ || exprType == Type.SHORT_TYPE
+ || exprType == Type.BYTE_TYPE)
+ {
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.cast(exprType, Type.INT_TYPE);
+ emitShiftMask(gen);
+ }
+ else
+ {
+ gen.goTo(defaultLabel);
+ }
+ }
+
+ private void emitThenForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
+ if (exprType == null)
+ {
+ expr.emit(C.EXPRESSION, objx, gen);
+ test.emit(C.EXPRESSION, objx, gen);
+ gen.invokeStatic(UTIL_TYPE, equivMethod);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else if (exprType == Type.LONG_TYPE)
+ {
+ ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else if (exprType == Type.INT_TYPE
+ || exprType == Type.SHORT_TYPE
+ || exprType == Type.BYTE_TYPE)
+ {
+ if (isShiftMasked())
+ {
+ ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.cast(exprType, Type.LONG_TYPE);
+ gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
+ }
+ // else direct match
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else
+ {
+ gen.goTo(defaultLabel);
+ }
+ }
+
+ private void emitExprForHashes(ObjExpr objx, GeneratorAdapter gen){
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.invokeStatic(UTIL_TYPE,hashMethod);
+ emitShiftMask(gen);
+ }
+
+ private void emitThenForHashes(ObjExpr objx, GeneratorAdapter gen, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
+ expr.emit(C.EXPRESSION, objx, gen);
+ test.emit(C.EXPRESSION, objx, gen);
+ if(testType == hashIdentityKey)
+ {
+ gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
+ }
+ else
+ {
+ gen.invokeStatic(UTIL_TYPE, equivMethod);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ }
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+
+ private static void emitExpr(ObjExpr objx, GeneratorAdapter gen, Expr expr, boolean emitUnboxed){
+ if (emitUnboxed && expr instanceof MaybePrimitiveExpr)
+ ((MaybePrimitiveExpr)expr).emitUnboxed(C.EXPRESSION,objx,gen);
+ else
+ expr.emit(C.EXPRESSION,objx,gen);
+ }
+
+
static class Parser implements IParser{
- //(case* expr shift mask low high default map<minhash, [test then]> identity?)
+ //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
//prepared by case macro and presumed correct
//case macro binds actual expr in let so expr is always a local,
//no need to worry about multiple evaluation
@@ -7794,25 +7979,43 @@ public static class CaseExpr extends UntypedExpr{
if(context == C.EVAL)
return analyze(context, RT.list(RT.list(FN, PersistentVector.EMPTY, form)));
PersistentVector args = PersistentVector.create(form.next());
- HashMap<Integer,Expr> tests = new HashMap();
- HashMap<Integer,Expr> thens = new HashMap();
- LocalBindingExpr testexpr = (LocalBindingExpr) analyze(C.EXPRESSION, args.nth(0));
+ Object exprForm = args.nth(0);
+ int shift = ((Number)args.nth(1)).intValue();
+ int mask = ((Number)args.nth(2)).intValue();
+ Object defaultForm = args.nth(3);
+ Map caseMap = (Map)args.nth(4);
+ Keyword switchType = ((Keyword)args.nth(5));
+ Keyword testType = ((Keyword)args.nth(6));
+ Set skipCheck = RT.count(args) < 8 ? null : (Set)args.nth(7);
+
+ ISeq keys = RT.keys(caseMap);
+ int low = ((Number)RT.first(keys)).intValue();
+ int high = ((Number)RT.nth(keys, RT.count(keys)-1)).intValue();
+
+ LocalBindingExpr testexpr = (LocalBindingExpr) analyze(C.EXPRESSION, exprForm);
testexpr.shouldClear = false;
-
+
+ SortedMap<Integer,Expr> tests = new TreeMap();
+ HashMap<Integer,Expr> thens = new HashMap();
+
PathNode branch = new PathNode(PATHTYPE.BRANCH, (PathNode) CLEAR_PATH.get());
- for(Object o : ((Map)args.nth(6)).entrySet())
+
+ for(Object o : caseMap.entrySet())
{
Map.Entry e = (Map.Entry) o;
Integer minhash = ((Number)e.getKey()).intValue();
- MapEntry me = (MapEntry) e.getValue();
- Expr testExpr = new ConstantExpr(me.getKey());
- tests.put(minhash, testExpr);
+ Object pair = e.getValue(); // [test-val then-expr]
+ Expr testExpr = testType == intKey
+ ? NumberExpr.parse(((Number)RT.first(pair)).intValue())
+ : new ConstantExpr(RT.first(pair));
+ tests.put(minhash, testExpr);
+
Expr thenExpr;
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
- thenExpr = analyze(context, me.getValue());
+ thenExpr = analyze(context, RT.second(pair));
}
finally{
Var.popThreadBindings();
@@ -7824,21 +8027,15 @@ public static class CaseExpr extends UntypedExpr{
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
- defaultExpr = analyze(context, args.nth(5));
+ defaultExpr = analyze(context, args.nth(3));
}
finally{
Var.popThreadBindings();
}
- return new CaseExpr(((Number)LINE.deref()).intValue(),
- testexpr,
- ((Number)args.nth(1)).intValue(),
- ((Number)args.nth(2)).intValue(),
- ((Number)args.nth(3)).intValue(),
- ((Number)args.nth(4)).intValue(),
- defaultExpr,
- tests,thens,args.nth(7) != RT.F);
-
+ int line = ((Number)LINE.deref()).intValue();
+ return new CaseExpr(line, testexpr, shift, mask, low, high,
+ defaultExpr, tests, thens, switchType, testType, skipCheck);
}
}
}