(refs #7250) Cleaning up CHR code generation 67/567/1
authorHannu Niemistö <hannu.niemisto@semantum.fi>
Mon, 29 May 2017 11:53:57 +0000 (14:53 +0300)
committerHannu Niemistö <hannu.niemisto@semantum.fi>
Mon, 29 May 2017 11:53:57 +0000 (14:53 +0300)
Change-Id: Iad454f1f7cdc0f27e21a2db03680312f49ab1059

bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/chr/relations/CHRConstraint.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/CHRCodeGenerator.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/CHRFactCodeGenerator.java [new file with mode: 0644]
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/StoreInitialization.java [new file with mode: 0644]

index 4a75a560414ad7c6ce69eea33d377a9738a84cd7..868d70a6f1b51faa2765fc14961d87c1007cd5e5 100644 (file)
@@ -18,6 +18,7 @@ import org.simantics.scl.compiler.elaboration.chr.CHRRelation;
 import org.simantics.scl.compiler.elaboration.chr.CHRRuleset;
 import org.simantics.scl.compiler.elaboration.chr.plan.PrioritizedPlan;
 import org.simantics.scl.compiler.internal.codegen.chr.CHRCodeGenerator;
+import org.simantics.scl.compiler.internal.codegen.chr.CHRFactCodeGenerator;
 import org.simantics.scl.compiler.internal.codegen.references.IVal;
 import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator;
 import org.simantics.scl.compiler.internal.codegen.types.StandardTypeConstructor;
@@ -115,7 +116,7 @@ public class CHRConstraint extends Symbol implements CHRRelation {
             if(typeDesc.equals(TypeDesc.VOID))
                 continue;
             this.accessors[i] = new CallJava(TVar.EMPTY_ARRAY, Types.NO_EFFECTS, parameterTypes[i], new Type[] {factType},
-                    null, new FieldRef(factClassName, CHRCodeGenerator.fieldName(i), jtt.toTypeDesc(parameterTypes[i])), null);
+                    null, new FieldRef(factClassName, CHRFactCodeGenerator.fieldName(i), jtt.toTypeDesc(parameterTypes[i])), null);
         }
         this.addProcedure = new CallJava(TVar.EMPTY_ARRAY, Types.PROC, Types.UNIT, new Type[] {parentRuleset.storeType, factType},
                 new StackItem[] {new ParameterStackItem(1, factType), new ParameterStackItem(0, parentRuleset.storeType)},
index c4842ea612a1ac23cf49b3d1ce4418d332715379..3a249c5deabaf38228a9fc4b268ecf5f01272832 100644 (file)
@@ -3,59 +3,32 @@ package org.simantics.scl.compiler.internal.codegen.chr;
 import java.util.ArrayList;
 
 import org.cojen.classfile.TypeDesc;
-import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.simantics.scl.compiler.elaboration.chr.CHRRuleset;
-import org.simantics.scl.compiler.elaboration.chr.plan.PrioritizedPlan;
 import org.simantics.scl.compiler.elaboration.chr.relations.CHRConstraint;
-import org.simantics.scl.compiler.elaboration.chr.relations.CHRConstraint.IndexInfo;
 import org.simantics.scl.compiler.internal.codegen.references.BoundVar;
-import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator;
 import org.simantics.scl.compiler.internal.codegen.utils.ClassBuilder;
-import org.simantics.scl.compiler.internal.codegen.utils.CodeBuilderUtils;
 import org.simantics.scl.compiler.internal.codegen.utils.Constants;
-import org.simantics.scl.compiler.internal.codegen.utils.LocalVariable;
-import org.simantics.scl.compiler.internal.codegen.utils.MethodBuilder;
 import org.simantics.scl.compiler.internal.codegen.utils.MethodBuilderBase;
 import org.simantics.scl.compiler.internal.codegen.utils.ModuleBuilder;
 
-import gnu.trove.list.array.TIntArrayList;
-import gnu.trove.set.hash.THashSet;
-
 public class CHRCodeGenerator {
-    
+
     public static final TypeDesc FACT_ID_TYPE = TypeDesc.INT;
-    public static final String CHRHashIndex_name = "org/simantics/scl/runtime/chr/CHRHashIndex";
-    public static final TypeDesc CHRHashIndex = TypeDesc.forClass(CHRHashIndex_name);
-    public static final String FactActivationQueue_name = "org/simantics/scl/runtime/chr/FactActivationQueue";
-    public static final TypeDesc FactActivationQueue = TypeDesc.forClass(FactActivationQueue_name);
-    public static final String Fact_name = "org/simantics/scl/runtime/chr/Fact";
-    public static final TypeDesc Fact = TypeDesc.forClass(Fact_name);
-    public static final String QUEUE = "queue";
-    
-    private static class StoreInitialization {
-        final int access;
-        final String fieldName;
-        final TypeDesc fieldType;
-        final String className;
-        public StoreInitialization(int access, String fieldName, TypeDesc fieldType, String className) {
-            this.access = access;
-            this.fieldName = fieldName;
-            this.fieldType = fieldType;
-            this.className = className;
-        }
-    }
-    
+    private static final String FactActivationQueue_name = "org/simantics/scl/runtime/chr/FactActivationQueue";
+    private static final TypeDesc FactActivationQueue = TypeDesc.forClass(FactActivationQueue_name);
+    private static final String QUEUE = "queue";
+
     public static void generateStore(ModuleBuilder moduleBuilder, CHRRuleset ruleset) {
         ClassBuilder storeClassBuilder = new ClassBuilder(moduleBuilder, Opcodes.ACC_PUBLIC, ruleset.storeClassName, "java/lang/Object");
         if(ruleset.parameters == null)
             ruleset.parameters = new BoundVar[0];
         ruleset.parameterTypeDescs = moduleBuilder.getJavaTypeTranslator().getTypeDescs(ruleset.parameters); 
-        
+
         ArrayList<StoreInitialization> hashIndexInitializations = new ArrayList<>();
         for(CHRConstraint constraint : ruleset.constraints)
             generateFact(storeClassBuilder, constraint, hashIndexInitializations);
-        
+
         // Fields
         for(int i=0;i<ruleset.parameterTypeDescs.length;++i) {
             TypeDesc typeDesc = ruleset.parameterTypeDescs[i];
@@ -67,9 +40,9 @@ public class CHRCodeGenerator {
         for(StoreInitialization ini : hashIndexInitializations)
             storeClassBuilder.addField(ini.access, ini.fieldName, ini.fieldType);
         storeClassBuilder.addField(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, QUEUE, FactActivationQueue);
-        
+
         // Constructors
-        
+
         {
             MethodBuilderBase mb = storeClassBuilder.addConstructor(Opcodes.ACC_PUBLIC, ruleset.parameterTypeDescs);
             mb.loadThis();
@@ -103,9 +76,9 @@ public class CHRCodeGenerator {
             mb.returnVoid();
             mb.finish();
         }
-        
+
         // Activate
-        
+
         {
             MethodBuilderBase mb = storeClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "activate", TypeDesc.VOID, new TypeDesc[] {TypeDesc.INT});
             mb.loadThis();
@@ -116,559 +89,12 @@ public class CHRCodeGenerator {
             mb.returnVoid();
             mb.finish();
         }
-        
-        moduleBuilder.addClass(storeClassBuilder);
-    }
-    
-    private static void generateFact(ClassBuilder storeClassBuilder, CHRConstraint constraint, ArrayList<StoreInitialization> hashIndexInitializations) {
-        CHRRuleset ruleset = constraint.parentRuleset;
-        boolean supportsRemoval = constraint.mayBeRemoved();
-        
-        ModuleBuilder moduleBuilder = storeClassBuilder.getModuleBuilder();
-        JavaTypeTranslator jtt = moduleBuilder.getJavaTypeTranslator();
-        TypeDesc storeTypeDesc = storeClassBuilder.getType();
-        TypeDesc[] storeTypeDescArray = new TypeDesc[] { storeTypeDesc };
-        
-        String factClassName = storeClassBuilder.getClassName() + "$" + constraint.name;
-        TypeDesc factTypeDesc = TypeDesc.forClass(factClassName);
-        ClassBuilder factClassBuilder = new ClassBuilder(moduleBuilder, Opcodes.ACC_PUBLIC, factClassName, "java/lang/Object", Fact_name);
-        
-        // Fields
-        
-        /* public int id;
-           public int c0; // key
-           public int c1;
-           public ExampleFact bfPrev;
-           public ExampleFact bfNext;
-         */
-        TypeDesc[] parameterTypeDescs = jtt.toTypeDescs(constraint.parameterTypes);
-        factClassBuilder.addField(Opcodes.ACC_PUBLIC, "id", FACT_ID_TYPE);
-        for(int i=0;i<constraint.parameterTypes.length;++i) {
-            TypeDesc typeDesc = parameterTypeDescs[i];
-            if(typeDesc.equals(TypeDesc.VOID))
-                continue;
-            if(parameterTypeDescs[i] != TypeDesc.VOID)
-                factClassBuilder.addField(Opcodes.ACC_PUBLIC, fieldName(i), typeDesc);
-        }
-        
-        for(IndexInfo indexInfo : constraint.getIndices()) {
-            if(supportsRemoval)
-                factClassBuilder.addField(Opcodes.ACC_PUBLIC, indexInfo.indexName + "Prev", factTypeDesc);
-            factClassBuilder.addField(Opcodes.ACC_PUBLIC, indexInfo.indexName + "Next", factTypeDesc);
-            
-            String hashIndexField = constraint.name + "$" + indexInfo.indexName;
-            if(indexInfo.indexMask == 0) {
-                // If there are now bound parameters, use just a direct reference to a fact
-                storeClassBuilder.addField(Opcodes.ACC_PUBLIC, hashIndexField, factTypeDesc);
-            }
-            else {
-                ClassBuilder hashClass = generateSpecializedHashIndex(storeClassBuilder, constraint, indexInfo, factTypeDesc, factClassName);
-                moduleBuilder.addClass(hashClass);
-                hashIndexInitializations.add(new StoreInitialization(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, hashIndexField, CHRHashIndex, hashClass.getClassName()));
-            }
-        }
-        
-        // Method: get
-        
-        hashIndexInitializations.add(new StoreInitialization(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, constraint.name + "$temp", factTypeDesc, factClassName));
-
-        
-        {
-            /*
-            public ExampleFact ExampleFact$bf(int c0) {
-                ExampleFact$temp.c0 = c0;
-                return (ExampleFact)ExampleFact_bfIndex.getEqual(ExampleFact$temp);
-            }
-            */
-            for(IndexInfo indexInfo : constraint.getIndices()) {
-                /*if(indexInfo.indexMask == 0) {
-                    MethodBuilderBase mb = storeClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, constraint.name + "$" + indexInfo.indexName,
-                            factTypeDesc, Constants.EMPTY_TYPEDESC_ARRAY);
-                    mb.loadThis();
-                    mb.loadField(storeClassBuilder.getClassName(), constraint.name + "$" + indexInfo.indexName, factTypeDesc);
-                    mb.returnValue(factTypeDesc);
-                    mb.finish();
-                }*/
-                if(indexInfo.indexMask != 0) {
-                    ArrayList<TypeDesc> getParameterTypeDescs = new ArrayList<TypeDesc>(constraint.parameterTypes.length);
-                    for(int i=0;i<constraint.parameterTypes.length;++i)
-                        if(((indexInfo.indexMask>>i)&1)==1)
-                            getParameterTypeDescs.add(parameterTypeDescs[i]);
-                    MethodBuilderBase mb = storeClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, constraint.name + "$" + indexInfo.indexName, factTypeDesc,
-                            getParameterTypeDescs.toArray(new TypeDesc[getParameterTypeDescs.size()]));
-                    mb.loadThis();
-                    mb.loadField(storeClassBuilder.getClassName(), constraint.name + "$temp", factTypeDesc);
-                    LocalVariable tempFactVar = mb.createLocalVariable("temp", factTypeDesc);
-                    mb.storeLocal(tempFactVar);
-                    int parameterId=0;
-                    for(int i=0;i<constraint.parameterTypes.length;++i)
-                        if(((indexInfo.indexMask>>i)&1)==1) {
-                            TypeDesc typeDesc = parameterTypeDescs[i];
-                            if(!typeDesc.equals(TypeDesc.VOID)) {
-                                mb.loadLocal(tempFactVar);
-                                mb.loadLocal(mb.getParameter(parameterId));
-                                mb.storeField(factClassName, fieldName(i), typeDesc);
-                            }
-                            ++parameterId;
-                        }
-
-                    mb.loadThis();
-                    mb.loadField(storeClassBuilder.getClassName(), constraint.name + "$" + indexInfo.indexName, CHRHashIndex);
-                    mb.loadLocal(tempFactVar);
-                    mb.invokeVirtual(CHRHashIndex_name, supportsRemoval ? "getEqual" : "getEqualNoRemovals", TypeDesc.OBJECT, Constants.OBJECTS[1]);
-                    mb.checkCast(factTypeDesc);
-                
-                    mb.returnValue(factTypeDesc);
-                    mb.finish();
-                }
-            }   
-        }
-        
-        // Method: add
-        
-        {
-            MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "add", TypeDesc.VOID, storeTypeDescArray);
-            LocalVariable storeParameter = mb.getParameter(0);
-            for(IndexInfo indexInfo : constraint.getIndices()) {
-                String linkedListPrev = indexInfo.indexName + "Prev";
-                String linkedListNext = indexInfo.indexName + "Next";
-                String storeHashIndexName = constraint.name + "$" + indexInfo.indexName;
-                
-                // public void add(ExampleStore store) {
-                //     bfNext = (ExampleFact)store.ExampleFact_bfIndex.addFreshAndReturnOld(this);
-                //     if(bfNext != null)
-                //         bfNext.bfPrev = this;
-                // }
-                
-                if(indexInfo.indexMask == 0) {
-                    mb.loadThis();
-                    mb.loadLocal(storeParameter);
-                    mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
-                    if(supportsRemoval)
-                        mb.dupX1();
-                    mb.storeField(factClassName, linkedListNext, factTypeDesc);
-                    if(supportsRemoval) {
-                        Label cont = new Label();
-                        mb.ifNullBranch(cont, true);
-                        mb.loadThis();
-                        mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                        mb.loadThis();
-                        mb.storeField(factClassName, linkedListPrev, factTypeDesc);
-                        mb.setLocation(cont);
-                    }
-                    mb.loadLocal(storeParameter);
-                    mb.loadThis();
-                    mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
-                }
-                else {
-                    // bfNext = (ExampleFact)store.ExampleFact_bfIndex.addFreshAndReturnOld(this);
-                    mb.loadThis();
-                    mb.loadLocal(storeParameter);
-                    mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
-                    mb.loadThis();
-                    mb.invokeVirtual(CHRHashIndex_name, supportsRemoval ? "addFreshAndReturnOld" : "addFreshAndReturnOld", TypeDesc.OBJECT, Constants.OBJECTS[1]);
-                    mb.checkCast(factTypeDesc);
-                    if(supportsRemoval)
-                        mb.dupX1();
-                    mb.storeField(factClassName, linkedListNext, factTypeDesc);
-                    // leaves bfNext on the stack
-
-                    //if(bfNext != null)
-                    //    bfNext.bfPrev = this;
-                    if(supportsRemoval) {
-                        Label cont = new Label();
-                        mb.ifNullBranch(cont, true);
-                        mb.loadThis();
-                        mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                        mb.loadThis();
-                        mb.storeField(factClassName, linkedListPrev, factTypeDesc);
-                        mb.setLocation(cont);
-                    }
-                }
-            }            
-            if(!constraint.isPassive()) {
-                mb.loadLocal(storeParameter);
-                mb.loadField(storeClassBuilder.getClassName(), QUEUE, FactActivationQueue);
-                mb.loadConstant(constraint.getMinimumPriority());
-                mb.loadThis();
-                mb.invokeVirtual(FactActivationQueue_name, "add", TypeDesc.VOID, new TypeDesc[] {TypeDesc.INT, Fact});
-            }
-            mb.returnVoid();
-            mb.finish();
-        }
-        
-        // Method: remove
-
-        if(supportsRemoval) {
-            // public void remove(ExampleStore store) {
-            //     if(bfPrev == null) {
-            //         if(bfNext == null)
-            //             store.ExampleFact_bfIndex.removeKnownToExistKey(this);
-            //         else {
-            //             bfNext.bfPrev = null;
-            //             store.ExampleFact_bfIndex.replaceKnownToExistKey(this, bfNext);
-            //         }
-            //     }
-            //     else {
-            //         bfPrev.bfNext = bfNext;
-            //         if(bfNext != null)
-            //             bfNext.bfPrev = bfPrev;
-            //     }
-            // }
-            
-            MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "remove", TypeDesc.VOID, storeTypeDescArray);
-            LocalVariable storeParameter = mb.getParameter(0);
-            for(IndexInfo indexInfo : constraint.getIndices()) {
-                String linkedListPrev = indexInfo.indexName + "Prev";
-                String linkedListNext = indexInfo.indexName + "Next";
-                String storeHashIndexName = constraint.name + "$" + indexInfo.indexName;
-                
-                Label nextIndex = mb.createLabel();
-                
-                // if(bfPrev == null) {
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListPrev, factTypeDesc);
-                Label else1 = new Label();
-                mb.ifNullBranch(else1, false);
-                
-                //     if(bfNext == null)
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                Label else2 = new Label();
-                mb.ifNullBranch(else2, false);
-                
-                //         store.ExampleFact_bfIndex.removeKnownToExistKey(this);
-                if(indexInfo.indexMask == 0) {
-                    mb.loadLocal(storeParameter);
-                    mb.loadNull();
-                    mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
-                }
-                else {
-                    mb.loadLocal(storeParameter);
-                    mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
-                    mb.loadThis();
-                    mb.invokeVirtual(CHRHashIndex_name, "removeKnownToExistKey", TypeDesc.VOID, Constants.OBJECTS[1]);
-                }
-                mb.branch(nextIndex);
-                
-                //     else {
-                mb.setLocation(else2);
-                //         bfNext.bfPrev = null;
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                mb.loadNull();
-                mb.storeField(factClassName, linkedListPrev, factTypeDesc);
-                //         store.ExampleFact_bfIndex.replaceKnownToExistKey(this, bfNext);
-                if(indexInfo.indexMask == 0) {
-                    mb.loadLocal(storeParameter);
-                    mb.loadThis();
-                    mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                    mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
-                }
-                else {
-                    mb.loadLocal(storeParameter);
-                    mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
-                    mb.loadThis();
-                    mb.loadThis();
-                    mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                    mb.invokeVirtual(CHRHashIndex_name, "replaceKnownToExistKey", TypeDesc.VOID, Constants.OBJECTS[2]);
-                }
-                mb.branch(nextIndex);
-                //     }
-                
-                // else {
-                mb.setLocation(else1);
-                //     bfPrev.bfNext = bfNext;
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListPrev, factTypeDesc);
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                mb.storeField(factClassName, linkedListNext, factTypeDesc);
-                //     if(bfNext != null)
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                Label else3 = new Label();
-                mb.ifNullBranch(else3, true);
-                //         bfNext.bfPrev = bfPrev;
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListNext, factTypeDesc);
-                mb.loadThis();
-                mb.loadField(factClassName, linkedListPrev, factTypeDesc);
-                mb.storeField(factClassName, linkedListPrev, factTypeDesc);
-                mb.setLocation(else3);
-                mb.branch(nextIndex);
-                // }
-                
-                mb.setLocation(nextIndex);
-            }
-            mb.loadThis();
-            mb.loadConstant(-1);
-            mb.storeField(factClassName, "id", FACT_ID_TYPE);
-            mb.returnVoid();
-            mb.finish();
-        }
-        
-        // Method: isAlive
-
-        {
-            // @Override
-            // public boolean isAlive() {
-            //     return id >= 0;
-            // }
-            
-            MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "isAlive", TypeDesc.BOOLEAN, Constants.EMPTY_TYPEDESC_ARRAY);
-            if(supportsRemoval) {
-                mb.loadThis();
-                mb.loadField(factClassName, "id", FACT_ID_TYPE);
-
-                Label thenBranch = mb.createLabel();
-                mb.ifZeroComparisonBranch(thenBranch, "<");
-                mb.loadConstant(true);
-                mb.returnValue(TypeDesc.BOOLEAN);
-
-                mb.setLocation(thenBranch);
-                mb.loadConstant(false);
-                mb.returnValue(TypeDesc.BOOLEAN);
-            }
-            else {
-                mb.loadConstant(true);
-                mb.returnValue(TypeDesc.BOOLEAN);
-            }
-            mb.finish();
-        }
-        
-        // activate parts
-        
-        THashSet<BoundVar> usedParameters = new THashSet<BoundVar>();
-        for(int i=0;i<constraint.plans.size();++i) {
-            PrioritizedPlan plan = constraint.plans.get(i);
-            MethodBuilder mb = factClassBuilder.addMethod(Opcodes.ACC_PUBLIC, "activate" + i, TypeDesc.BOOLEAN, new TypeDesc[] {storeTypeDesc});
-            LocalVariable storeVar = mb.getParameter(0);
-            LocalVariable factVar = new LocalVariable(0, factTypeDesc);
-            mb.setLocalVariable(ruleset.this_, storeVar);
-            mb.setLocalVariable(plan.implementation.getParameters()[0], factVar);
-            
-            // Set closure parameters
-            usedParameters.clear();
-            plan.implementation.forValRefs(valRef -> {
-                if(valRef.getBinding() instanceof BoundVar)
-                    usedParameters.add((BoundVar)valRef.getBinding());
-            });
-            for(int j=0;j<ruleset.parameters.length;++j) {
-                BoundVar parameter = ruleset.parameters[j];
-                if(!usedParameters.contains(parameter))
-                    continue;
-                mb.loadLocal(storeVar);
-                mb.loadField(storeClassBuilder.getClassName(), "p"+j, ruleset.parameterTypeDescs[j]);
-                mb.store(parameter);
-            }
-            
-            // Generate code
-            //System.out.println("=== activate" + i + " ==========================================================");
-            //System.out.println(plan.implementation);
-            plan.implementation.markGenerateOnFly();
-            plan.implementation.generateCodeWithAlreadyPreparedParameters(mb);
-            mb.finish();
-        }
-        
-        // Method: activate
-
-        {
-            // @Override
-            // public int activate(Object context, int priority) {
-            //     return -1;
-            // }
-            
-            MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "activate", TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.INT});
-            Label defaultLabel = mb.createLabel();
-            
-            if(!constraint.isPassive()) {
-                // Check if the fact is alive
-                mb.loadThis();
-                mb.loadField(factClassName, "id", TypeDesc.INT);
-                mb.ifZeroComparisonBranch(defaultLabel, "<");
-
-                mb.loadLocal(mb.getParameter(0));
-                mb.checkCast(storeTypeDesc);
-                LocalVariable storeVariable = new LocalVariable(1, storeTypeDesc);
-                mb.storeLocal(storeVariable);
-
-                TIntArrayList priorities = new TIntArrayList(constraint.plans.size());
-                ArrayList<Label> labels = new ArrayList<Label>();
-                int lastPriority = -1;
-                for(PrioritizedPlan plan : constraint.plans)
-                    if(plan.priority != lastPriority) {
-                        priorities.add(plan.priority);
-                        labels.add(mb.createLabel());
-                        lastPriority = plan.priority;
-                    }
 
-                mb.loadLocal(mb.getParameter(1));
-                mb.switch_(priorities.toArray(), labels.toArray(new Label[labels.size()]), defaultLabel);
-                int labelId = -1;
-                for(int i=0;i<constraint.plans.size();++i) {
-                    PrioritizedPlan plan = constraint.plans.get(i);
-                    if(labelId == -1 || plan.priority != priorities.get(labelId)) {
-                        if(labelId >= 0) {
-                            mb.loadConstant(plan.priority);
-                            mb.returnValue(TypeDesc.INT);
-                        }
-                        ++labelId;
-                        mb.setLocation(labels.get(labelId));
-                    }
-                    mb.loadThis();
-                    mb.loadLocal(storeVariable);
-                    mb.invokeVirtual(factClassName, "activate" + i, TypeDesc.BOOLEAN, new TypeDesc[] {storeTypeDesc});
-                    mb.ifZeroComparisonBranch(defaultLabel, "==");
-                }
-                mb.setLocation(defaultLabel);
-            }
-            mb.loadConstant(-1);
-            mb.returnValue(TypeDesc.INT);
-            mb.finish();
-        }
-        
-        // Constructors
-        
-        {
-            // public ExampleFact(int id, int c0, int c1) {
-            //     this.id = id;            
-            //     this.c0 = c0;
-            //     this.c1 = c1;
-            // }
-            
-            ArrayList<TypeDesc> constructorParameters = new ArrayList<TypeDesc>(parameterTypeDescs.length+1);
-            constructorParameters.add(FACT_ID_TYPE);
-            for(TypeDesc typeDesc : parameterTypeDescs) {
-                if(typeDesc.equals(TypeDesc.VOID))
-                    continue;
-                constructorParameters.add(typeDesc);
-            }
-            MethodBuilderBase mb = factClassBuilder.addConstructor(Opcodes.ACC_PUBLIC, constructorParameters.toArray(new TypeDesc[constructorParameters.size()]));
-            mb.loadThis();
-            mb.invokeConstructor(factClassBuilder.getSuperClassName(), Constants.EMPTY_TYPEDESC_ARRAY);
-            mb.loadThis();
-            mb.loadLocal(mb.getParameter(0));
-            mb.storeField(factClassName, "id", FACT_ID_TYPE);
-            for(int i=0,parameterId=1;i<constraint.parameterTypes.length;++i) {
-                TypeDesc typeDesc = parameterTypeDescs[i];
-                if(typeDesc.equals(TypeDesc.VOID))
-                    continue;
-                mb.loadThis();
-                mb.loadLocal(mb.getParameter(parameterId++));
-                mb.storeField(factClassName, fieldName(i), typeDesc);
-            }
-            mb.returnVoid();
-            mb.finish();
-        }
-        factClassBuilder.addDefaultConstructor();
-        
-        moduleBuilder.addClass(factClassBuilder);
+        moduleBuilder.addClass(storeClassBuilder);
     }
 
-    private static ClassBuilder generateSpecializedHashIndex(ClassBuilder storeClassBuilder, CHRConstraint constraint, IndexInfo indexInfo, TypeDesc factClassTypeDesc, String factClassName) {
-        // new CHRHashIndex() {
-        //     @Override
-        //     protected boolean keyEquals(Object a, Object b) {
-        //         return ((ExampleFact)a).c0 == ((ExampleFact)b).c0;
-        //     }
-        //     @Override
-        //     protected int keyHashCode(Object key) {
-        //         return ((ExampleFact)key).c0;
-        //     }
-        // }
-
-        ModuleBuilder moduleBuilder = storeClassBuilder.getModuleBuilder();
-        JavaTypeTranslator jtt = moduleBuilder.getJavaTypeTranslator();
-        
-        String hashIndexClassName = factClassName + "$" + indexInfo.indexName; 
-        ClassBuilder hashIndexClassBuilder = new ClassBuilder(moduleBuilder, Opcodes.ACC_PUBLIC, hashIndexClassName, "org/simantics/scl/runtime/chr/CHRHashIndex");
-        
-        // Method: keyEquals
-
-        {
-
-            // @Override
-            // protected boolean keyEquals(Object a, Object b) {
-            //     return ((ExampleFact)a).c0 == ((ExampleFact)b).c0;
-            // }
-            
-            MethodBuilderBase mb = hashIndexClassBuilder.addMethodBase(Opcodes.ACC_PROTECTED, "keyEquals", TypeDesc.BOOLEAN, Constants.OBJECTS[2]);
-            mb.loadLocal(mb.getParameter(0));
-            mb.checkCast(factClassTypeDesc);
-            LocalVariable aVar = mb.createLocalVariable("a", factClassTypeDesc);
-            mb.storeLocal(aVar);
-            
-            mb.loadLocal(mb.getParameter(1));
-            mb.checkCast(factClassTypeDesc);
-            LocalVariable bVar = mb.createLocalVariable("b", factClassTypeDesc);
-            mb.storeLocal(bVar);
-
-            Label failure = mb.createLabel();
-            
-            int curMask = indexInfo.indexMask;
-            for(int i=0;i<constraint.parameterTypes.length;++i,curMask>>=1)
-                if((curMask&1) == 1) {
-                    TypeDesc fieldTypeDesc = jtt.toTypeDesc(constraint.parameterTypes[i]);
-                    if(fieldTypeDesc.equals(TypeDesc.VOID))
-                        continue;
-                    mb.loadLocal(aVar);
-                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
-                    
-                    mb.loadLocal(bVar);
-                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
-
-                    CodeBuilderUtils.equals(mb, fieldTypeDesc, failure);
-                }
-            mb.loadConstant(true);
-            mb.returnValue(TypeDesc.BOOLEAN);
-            
-            mb.setLocation(failure);
-            mb.loadConstant(false);
-            mb.returnValue(TypeDesc.BOOLEAN);
-            mb.finish();
-        }
-        
-        // Method: keyHashCode
-
-        {
-            // @Override
-            // protected int keyHashCode(Object key) {
-            //     return (0x811C9DC5^((ExampleFact)key).c0)*16777619;
-            // }
-            
-            MethodBuilderBase mb = hashIndexClassBuilder.addMethodBase(Opcodes.ACC_PROTECTED, "keyHashCode", TypeDesc.INT, Constants.OBJECTS[1]);
-            mb.loadLocal(mb.getParameter(0));
-            mb.checkCast(factClassTypeDesc);
-            LocalVariable factVar = mb.createLocalVariable("fact", factClassTypeDesc);
-            mb.storeLocal(factVar);
-
-            mb.loadConstant(0x811C9DC5);
-
-            int curMask = indexInfo.indexMask;
-            for(int i=0;i<constraint.parameterTypes.length;++i,curMask>>=1)
-                if((curMask&1) == 1) {
-                    TypeDesc fieldTypeDesc = jtt.toTypeDesc(constraint.parameterTypes[i]);
-                    if(fieldTypeDesc.equals(TypeDesc.VOID))
-                        continue;
-                    mb.loadLocal(factVar);
-                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
-                    CodeBuilderUtils.hashCode(mb, fieldTypeDesc);
-                    mb.math(Opcodes.IXOR);
-                    mb.loadConstant(16777619);
-                    mb.math(Opcodes.IMUL);
-
-                }
-            mb.returnValue(TypeDesc.INT);
-            mb.finish();
-        }
-
-        hashIndexClassBuilder.addDefaultConstructor();
-        
-        return hashIndexClassBuilder;
-    }
-    
-    public static String fieldName(int id) {
-        return "c" + id;
+    private static void generateFact(ClassBuilder storeClassBuilder, CHRConstraint constraint, ArrayList<StoreInitialization> hashIndexInitializations) {
+        CHRFactCodeGenerator generator = new CHRFactCodeGenerator(storeClassBuilder, constraint);
+        generator.generate(hashIndexInitializations);
     }
 }
diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/CHRFactCodeGenerator.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/CHRFactCodeGenerator.java
new file mode 100644 (file)
index 0000000..06725ec
--- /dev/null
@@ -0,0 +1,602 @@
+package org.simantics.scl.compiler.internal.codegen.chr;
+
+import java.util.ArrayList;
+
+import org.cojen.classfile.TypeDesc;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.simantics.scl.compiler.elaboration.chr.CHRRuleset;
+import org.simantics.scl.compiler.elaboration.chr.plan.PrioritizedPlan;
+import org.simantics.scl.compiler.elaboration.chr.relations.CHRConstraint;
+import org.simantics.scl.compiler.elaboration.chr.relations.CHRConstraint.IndexInfo;
+import org.simantics.scl.compiler.internal.codegen.references.BoundVar;
+import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator;
+import org.simantics.scl.compiler.internal.codegen.utils.ClassBuilder;
+import org.simantics.scl.compiler.internal.codegen.utils.CodeBuilderUtils;
+import org.simantics.scl.compiler.internal.codegen.utils.Constants;
+import org.simantics.scl.compiler.internal.codegen.utils.LocalVariable;
+import org.simantics.scl.compiler.internal.codegen.utils.MethodBuilder;
+import org.simantics.scl.compiler.internal.codegen.utils.MethodBuilderBase;
+import org.simantics.scl.compiler.internal.codegen.utils.ModuleBuilder;
+
+import gnu.trove.list.array.TIntArrayList;
+import gnu.trove.set.hash.THashSet;
+
+public class CHRFactCodeGenerator {
+    private static final TypeDesc FACT_ID_TYPE = TypeDesc.INT;
+    private static final String CHRHashIndex_name = "org/simantics/scl/runtime/chr/CHRHashIndex";
+    private static final TypeDesc CHRHashIndex = TypeDesc.forClass(CHRHashIndex_name);
+    private static final String FactActivationQueue_name = "org/simantics/scl/runtime/chr/FactActivationQueue";
+    private static final TypeDesc FactActivationQueue = TypeDesc.forClass(FactActivationQueue_name);
+    private static final String Fact_name = "org/simantics/scl/runtime/chr/Fact";
+    private static final TypeDesc Fact = TypeDesc.forClass(Fact_name);
+    private static final String QUEUE = "queue";
+
+    private ModuleBuilder moduleBuilder; 
+    private JavaTypeTranslator jtt;
+    private CHRRuleset ruleset;
+
+    private ClassBuilder storeClassBuilder;
+    private CHRConstraint constraint;
+
+    private String factClassName;
+    private TypeDesc factTypeDesc;
+    private ClassBuilder factClassBuilder;
+
+    private TypeDesc storeTypeDesc;
+    private TypeDesc[] storeTypeDescArray;
+
+    private TypeDesc[] parameterTypeDescs;
+    private boolean supportsRemoval;
+
+    CHRFactCodeGenerator(ClassBuilder storeClassBuilder, CHRConstraint constraint) {
+        this.storeClassBuilder = storeClassBuilder;
+        this.constraint = constraint;
+        this.ruleset = constraint.parentRuleset;
+
+        this.moduleBuilder = storeClassBuilder.getModuleBuilder();
+        this.jtt = moduleBuilder.getJavaTypeTranslator();
+        this.storeTypeDesc = storeClassBuilder.getType();
+        this.storeTypeDescArray = new TypeDesc[] { storeTypeDesc };
+
+        this.factClassName = storeClassBuilder.getClassName() + "$" + constraint.name;
+        this.factTypeDesc = TypeDesc.forClass(factClassName);
+        this.factClassBuilder = new ClassBuilder(moduleBuilder, Opcodes.ACC_PUBLIC, factClassName, "java/lang/Object", Fact_name);
+
+        this.parameterTypeDescs = jtt.toTypeDescs(constraint.parameterTypes);
+        this.supportsRemoval = constraint.mayBeRemoved();
+    }
+
+    public void generate(ArrayList<StoreInitialization> hashIndexInitializations) {
+        generateFields(hashIndexInitializations);
+        hashIndexInitializations.add(new StoreInitialization(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, constraint.name + "$temp", factTypeDesc, factClassName));
+
+        generateIndices();
+        generateAdd();
+
+        if(supportsRemoval)
+            generateRemove();
+
+        generateIsAlive();
+
+        for(int i=0;i<constraint.plans.size();++i) 
+            generateActivateI(i);
+        generateActivate();
+
+        generateConstructor();
+        factClassBuilder.addDefaultConstructor();
+
+        moduleBuilder.addClass(factClassBuilder);
+    }
+
+    private void generateIndices() {
+        // public ExampleFact ExampleFact$bf(int c0) {
+        //     ExampleFact$temp.c0 = c0;
+        //     return (ExampleFact)ExampleFact_bfIndex.getEqual(ExampleFact$temp);
+        // }
+
+        for(IndexInfo indexInfo : constraint.getIndices()) {
+            if(indexInfo.indexMask != 0) {
+                ArrayList<TypeDesc> getParameterTypeDescs = new ArrayList<TypeDesc>(constraint.parameterTypes.length);
+                for(int i=0;i<constraint.parameterTypes.length;++i)
+                    if(((indexInfo.indexMask>>i)&1)==1)
+                        getParameterTypeDescs.add(parameterTypeDescs[i]);
+                MethodBuilderBase mb = storeClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, constraint.name + "$" + indexInfo.indexName, factTypeDesc,
+                        getParameterTypeDescs.toArray(new TypeDesc[getParameterTypeDescs.size()]));
+
+                // ExampleFact$temp.c0 = c0;
+                mb.loadThis();
+                mb.loadField(storeClassBuilder.getClassName(), constraint.name + "$temp", factTypeDesc);
+                LocalVariable tempFactVar = mb.createLocalVariable("temp", factTypeDesc);
+                mb.storeLocal(tempFactVar);
+                int parameterId=0;
+                for(int i=0;i<constraint.parameterTypes.length;++i)
+                    if(((indexInfo.indexMask>>i)&1)==1) {
+                        TypeDesc typeDesc = parameterTypeDescs[i];
+                        if(!typeDesc.equals(TypeDesc.VOID)) {
+                            mb.loadLocal(tempFactVar);
+                            mb.loadLocal(mb.getParameter(parameterId));
+                            mb.storeField(factClassName, fieldName(i), typeDesc);
+                        }
+                        ++parameterId;
+                    }
+
+                // return (ExampleFact)ExampleFact_bfIndex.getEqual(ExampleFact$temp);
+                mb.loadThis();
+                mb.loadField(storeClassBuilder.getClassName(), constraint.name + "$" + indexInfo.indexName, CHRHashIndex);
+                mb.loadLocal(tempFactVar);
+                mb.invokeVirtual(CHRHashIndex_name, supportsRemoval ? "getEqual" : "getEqualNoRemovals", TypeDesc.OBJECT, Constants.OBJECTS[1]);
+                mb.checkCast(factTypeDesc);
+                mb.returnValue(factTypeDesc);
+                mb.finish();
+            }
+        }   
+    }
+
+    private THashSet<BoundVar> usedParameters = new THashSet<BoundVar>();
+
+    private void generateActivateI(int i) {
+        PrioritizedPlan plan = constraint.plans.get(i);
+        MethodBuilder mb = factClassBuilder.addMethod(Opcodes.ACC_PUBLIC, "activate" + i, TypeDesc.BOOLEAN, storeTypeDescArray);
+        LocalVariable storeVar = mb.getParameter(0);
+        LocalVariable factVar = new LocalVariable(0, factTypeDesc);
+        mb.setLocalVariable(ruleset.this_, storeVar);
+        mb.setLocalVariable(plan.implementation.getParameters()[0], factVar);
+
+        // Set closure parameters
+        usedParameters.clear();
+        plan.implementation.forValRefs(valRef -> {
+            if(valRef.getBinding() instanceof BoundVar)
+                usedParameters.add((BoundVar)valRef.getBinding());
+        });
+        for(int j=0;j<ruleset.parameters.length;++j) {
+            BoundVar parameter = ruleset.parameters[j];
+            if(!usedParameters.contains(parameter))
+                continue;
+            mb.loadLocal(storeVar);
+            mb.loadField(storeClassBuilder.getClassName(), "p"+j, ruleset.parameterTypeDescs[j]);
+            mb.store(parameter);
+        }
+
+        // Generate code
+        //System.out.println("=== activate" + i + " ==========================================================");
+        //System.out.println(plan.implementation);
+        plan.implementation.markGenerateOnFly();
+        plan.implementation.generateCodeWithAlreadyPreparedParameters(mb);
+        mb.finish();
+    }
+
+    private void generateActivate() {
+        // @Override
+        // public int activate(Object context, int priority) {
+        //     return -1;
+        // }
+
+        MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "activate", TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.INT});
+        Label defaultLabel = mb.createLabel();
+
+        if(!constraint.isPassive()) {
+            // Check if the fact is alive
+            mb.loadThis();
+            mb.loadField(factClassName, "id", TypeDesc.INT);
+            mb.ifZeroComparisonBranch(defaultLabel, "<");
+
+            mb.loadLocal(mb.getParameter(0));
+            mb.checkCast(storeTypeDesc);
+            LocalVariable storeVariable = new LocalVariable(1, storeTypeDesc);
+            mb.storeLocal(storeVariable);
+
+            TIntArrayList priorities = new TIntArrayList(constraint.plans.size());
+            ArrayList<Label> labels = new ArrayList<Label>();
+            int lastPriority = -1;
+            for(PrioritizedPlan plan : constraint.plans)
+                if(plan.priority != lastPriority) {
+                    priorities.add(plan.priority);
+                    labels.add(mb.createLabel());
+                    lastPriority = plan.priority;
+                }
+
+            mb.loadLocal(mb.getParameter(1));
+            mb.switch_(priorities.toArray(), labels.toArray(new Label[labels.size()]), defaultLabel);
+            int labelId = -1;
+            for(int i=0;i<constraint.plans.size();++i) {
+                PrioritizedPlan plan = constraint.plans.get(i);
+                if(labelId == -1 || plan.priority != priorities.get(labelId)) {
+                    if(labelId >= 0) {
+                        mb.loadConstant(plan.priority);
+                        mb.returnValue(TypeDesc.INT);
+                    }
+                    ++labelId;
+                    mb.setLocation(labels.get(labelId));
+                }
+                mb.loadThis();
+                mb.loadLocal(storeVariable);
+                mb.invokeVirtual(factClassName, "activate" + i, TypeDesc.BOOLEAN, new TypeDesc[] {storeTypeDesc});
+                mb.ifZeroComparisonBranch(defaultLabel, "==");
+            }
+            mb.setLocation(defaultLabel);
+        }
+        mb.loadConstant(-1);
+        mb.returnValue(TypeDesc.INT);
+        mb.finish();
+    }
+
+    private void generateConstructor() {
+        // public ExampleFact(int id, int c0, int c1) {
+        //     this.id = id;            
+        //     this.c0 = c0;
+        //     this.c1 = c1;
+        // }
+
+        ArrayList<TypeDesc> constructorParameters = new ArrayList<TypeDesc>(parameterTypeDescs.length+1);
+        constructorParameters.add(FACT_ID_TYPE);
+        for(TypeDesc typeDesc : parameterTypeDescs) {
+            if(typeDesc.equals(TypeDesc.VOID))
+                continue;
+            constructorParameters.add(typeDesc);
+        }
+        MethodBuilderBase mb = factClassBuilder.addConstructor(Opcodes.ACC_PUBLIC, constructorParameters.toArray(new TypeDesc[constructorParameters.size()]));
+        mb.loadThis();
+        mb.invokeConstructor(factClassBuilder.getSuperClassName(), Constants.EMPTY_TYPEDESC_ARRAY);
+        mb.loadThis();
+        mb.loadLocal(mb.getParameter(0));
+        mb.storeField(factClassName, "id", FACT_ID_TYPE);
+        for(int i=0,parameterId=1;i<constraint.parameterTypes.length;++i) {
+            TypeDesc typeDesc = parameterTypeDescs[i];
+            if(typeDesc.equals(TypeDesc.VOID))
+                continue;
+            mb.loadThis();
+            mb.loadLocal(mb.getParameter(parameterId++));
+            mb.storeField(factClassName, fieldName(i), typeDesc);
+        }
+        mb.returnVoid();
+        mb.finish();
+    }
+
+    private void generateIsAlive() {
+        // @Override
+        // public boolean isAlive() {
+        //     return id >= 0;
+        // }
+
+        MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "isAlive", TypeDesc.BOOLEAN, Constants.EMPTY_TYPEDESC_ARRAY);
+        if(supportsRemoval) {
+            mb.loadThis();
+            mb.loadField(factClassName, "id", FACT_ID_TYPE);
+
+            Label thenBranch = mb.createLabel();
+            mb.ifZeroComparisonBranch(thenBranch, "<");
+            mb.loadConstant(true);
+            mb.returnValue(TypeDesc.BOOLEAN);
+
+            mb.setLocation(thenBranch);
+            mb.loadConstant(false);
+            mb.returnValue(TypeDesc.BOOLEAN);
+        }
+        else {
+            mb.loadConstant(true);
+            mb.returnValue(TypeDesc.BOOLEAN);
+        }
+        mb.finish();
+    }
+
+    private void generateAdd() {
+        MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "add", TypeDesc.VOID, storeTypeDescArray);
+        LocalVariable storeParameter = mb.getParameter(0);
+        for(IndexInfo indexInfo : constraint.getIndices()) {
+            String linkedListPrev = indexInfo.indexName + "Prev";
+            String linkedListNext = indexInfo.indexName + "Next";
+            String storeHashIndexName = constraint.name + "$" + indexInfo.indexName;
+
+            // public void add(ExampleStore store) {
+            //     bfNext = (ExampleFact)store.ExampleFact_bfIndex.addFreshAndReturnOld(this);
+            //     if(bfNext != null)
+            //         bfNext.bfPrev = this;
+            // }
+
+            if(indexInfo.indexMask == 0) {
+                mb.loadThis();
+                mb.loadLocal(storeParameter);
+                mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
+                if(supportsRemoval)
+                    mb.dupX1();
+                mb.storeField(factClassName, linkedListNext, factTypeDesc);
+                if(supportsRemoval) {
+                    Label cont = new Label();
+                    mb.ifNullBranch(cont, true);
+                    mb.loadThis();
+                    mb.loadField(factClassName, linkedListNext, factTypeDesc);
+                    mb.loadThis();
+                    mb.storeField(factClassName, linkedListPrev, factTypeDesc);
+                    mb.setLocation(cont);
+                }
+                mb.loadLocal(storeParameter);
+                mb.loadThis();
+                mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
+            }
+            else {
+                // bfNext = (ExampleFact)store.ExampleFact_bfIndex.addFreshAndReturnOld(this);
+                mb.loadThis();
+                mb.loadLocal(storeParameter);
+                mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
+                mb.loadThis();
+                mb.invokeVirtual(CHRHashIndex_name, supportsRemoval ? "addFreshAndReturnOld" : "addFreshAndReturnOld", TypeDesc.OBJECT, Constants.OBJECTS[1]);
+                mb.checkCast(factTypeDesc);
+                if(supportsRemoval)
+                    mb.dupX1();
+                mb.storeField(factClassName, linkedListNext, factTypeDesc);
+                // leaves bfNext on the stack
+
+                //if(bfNext != null)
+                //    bfNext.bfPrev = this;
+                if(supportsRemoval) {
+                    Label cont = new Label();
+                    mb.ifNullBranch(cont, true);
+                    mb.loadThis();
+                    mb.loadField(factClassName, linkedListNext, factTypeDesc);
+                    mb.loadThis();
+                    mb.storeField(factClassName, linkedListPrev, factTypeDesc);
+                    mb.setLocation(cont);
+                }
+            }
+        }            
+        if(!constraint.isPassive()) {
+            mb.loadLocal(storeParameter);
+            mb.loadField(storeClassBuilder.getClassName(), QUEUE, FactActivationQueue);
+            mb.loadConstant(constraint.getMinimumPriority());
+            mb.loadThis();
+            mb.invokeVirtual(FactActivationQueue_name, "add", TypeDesc.VOID, new TypeDesc[] {TypeDesc.INT, Fact});
+        }
+        mb.returnVoid();
+        mb.finish();
+    }
+
+    private void generateFields(ArrayList<StoreInitialization> hashIndexInitializations) {
+        // public int id;
+        // public int c0; // key
+        // public int c1;
+        // public ExampleFact bfPrev;
+        // public ExampleFact bfNext;
+
+        factClassBuilder.addField(Opcodes.ACC_PUBLIC, "id", FACT_ID_TYPE);
+        for(int i=0;i<constraint.parameterTypes.length;++i) {
+            TypeDesc typeDesc = parameterTypeDescs[i];
+            if(typeDesc.equals(TypeDesc.VOID))
+                continue;
+            if(parameterTypeDescs[i] != TypeDesc.VOID)
+                factClassBuilder.addField(Opcodes.ACC_PUBLIC, fieldName(i), typeDesc);
+        }
+
+        for(IndexInfo indexInfo : constraint.getIndices()) {
+            if(supportsRemoval)
+                factClassBuilder.addField(Opcodes.ACC_PUBLIC, indexInfo.indexName + "Prev", factTypeDesc);
+            factClassBuilder.addField(Opcodes.ACC_PUBLIC, indexInfo.indexName + "Next", factTypeDesc);
+
+            String hashIndexField = constraint.name + "$" + indexInfo.indexName;
+            if(indexInfo.indexMask == 0) {
+                // If there are no bound parameters, use just a direct reference to a fact
+                storeClassBuilder.addField(Opcodes.ACC_PUBLIC, hashIndexField, factTypeDesc);
+            }
+            else {
+                ClassBuilder hashClass = generateSpecializedHashIndex(storeClassBuilder, constraint, indexInfo, factTypeDesc, factClassName);
+                moduleBuilder.addClass(hashClass);
+                hashIndexInitializations.add(new StoreInitialization(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, hashIndexField, CHRHashIndex, hashClass.getClassName()));
+            }
+        }
+    }
+
+    private void generateRemove() {
+        // public void remove(ExampleStore store) {
+        //     if(bfPrev == null) {
+        //         if(bfNext == null)
+        //             store.ExampleFact_bfIndex.removeKnownToExistKey(this);
+        //         else {
+        //             bfNext.bfPrev = null;
+        //             store.ExampleFact_bfIndex.replaceKnownToExistKey(this, bfNext);
+        //         }
+        //     }
+        //     else {
+        //         bfPrev.bfNext = bfNext;
+        //         if(bfNext != null)
+        //             bfNext.bfPrev = bfPrev;
+        //     }
+        // }
+
+        MethodBuilderBase mb = factClassBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "remove", TypeDesc.VOID, storeTypeDescArray);
+        LocalVariable storeParameter = mb.getParameter(0);
+        for(IndexInfo indexInfo : constraint.getIndices()) {
+            String linkedListPrev = indexInfo.indexName + "Prev";
+            String linkedListNext = indexInfo.indexName + "Next";
+            String storeHashIndexName = constraint.name + "$" + indexInfo.indexName;
+
+            Label nextIndex = mb.createLabel();
+
+            // if(bfPrev == null) {
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListPrev, factTypeDesc);
+            Label else1 = new Label();
+            mb.ifNullBranch(else1, false);
+
+            //     if(bfNext == null)
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListNext, factTypeDesc);
+            Label else2 = new Label();
+            mb.ifNullBranch(else2, false);
+
+            //         store.ExampleFact_bfIndex.removeKnownToExistKey(this);
+            if(indexInfo.indexMask == 0) {
+                mb.loadLocal(storeParameter);
+                mb.loadNull();
+                mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
+            }
+            else {
+                mb.loadLocal(storeParameter);
+                mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
+                mb.loadThis();
+                mb.invokeVirtual(CHRHashIndex_name, "removeKnownToExistKey", TypeDesc.VOID, Constants.OBJECTS[1]);
+            }
+            mb.branch(nextIndex);
+
+            //     else {
+            mb.setLocation(else2);
+            //         bfNext.bfPrev = null;
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListNext, factTypeDesc);
+            mb.loadNull();
+            mb.storeField(factClassName, linkedListPrev, factTypeDesc);
+            //         store.ExampleFact_bfIndex.replaceKnownToExistKey(this, bfNext);
+            if(indexInfo.indexMask == 0) {
+                mb.loadLocal(storeParameter);
+                mb.loadThis();
+                mb.loadField(factClassName, linkedListNext, factTypeDesc);
+                mb.storeField(storeClassBuilder.getClassName(), storeHashIndexName, factTypeDesc);
+            }
+            else {
+                mb.loadLocal(storeParameter);
+                mb.loadField(storeClassBuilder.getClassName(), storeHashIndexName, CHRHashIndex);
+                mb.loadThis();
+                mb.loadThis();
+                mb.loadField(factClassName, linkedListNext, factTypeDesc);
+                mb.invokeVirtual(CHRHashIndex_name, "replaceKnownToExistKey", TypeDesc.VOID, Constants.OBJECTS[2]);
+            }
+            mb.branch(nextIndex);
+            //     }
+
+            // else {
+            mb.setLocation(else1);
+            //     bfPrev.bfNext = bfNext;
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListPrev, factTypeDesc);
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListNext, factTypeDesc);
+            mb.storeField(factClassName, linkedListNext, factTypeDesc);
+            //     if(bfNext != null)
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListNext, factTypeDesc);
+            Label else3 = new Label();
+            mb.ifNullBranch(else3, true);
+            //         bfNext.bfPrev = bfPrev;
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListNext, factTypeDesc);
+            mb.loadThis();
+            mb.loadField(factClassName, linkedListPrev, factTypeDesc);
+            mb.storeField(factClassName, linkedListPrev, factTypeDesc);
+            mb.setLocation(else3);
+            mb.branch(nextIndex);
+            // }
+
+            mb.setLocation(nextIndex);
+        }
+        mb.loadThis();
+        mb.loadConstant(-1);
+        mb.storeField(factClassName, "id", FACT_ID_TYPE);
+        mb.returnVoid();
+        mb.finish();
+    }
+
+    public static String fieldName(int id) {
+        return "c" + id;
+    }
+
+    private static ClassBuilder generateSpecializedHashIndex(ClassBuilder storeClassBuilder, CHRConstraint constraint, IndexInfo indexInfo, TypeDesc factClassTypeDesc, String factClassName) {
+        // new CHRHashIndex() {
+        //     @Override
+        //     protected boolean keyEquals(Object a, Object b) {
+        //         return ((ExampleFact)a).c0 == ((ExampleFact)b).c0;
+        //     }
+        //     @Override
+        //     protected int keyHashCode(Object key) {
+        //         return ((ExampleFact)key).c0;
+        //     }
+        // }
+
+        ModuleBuilder moduleBuilder = storeClassBuilder.getModuleBuilder();
+        JavaTypeTranslator jtt = moduleBuilder.getJavaTypeTranslator();
+
+        String hashIndexClassName = factClassName + "$" + indexInfo.indexName; 
+        ClassBuilder hashIndexClassBuilder = new ClassBuilder(moduleBuilder, Opcodes.ACC_PUBLIC, hashIndexClassName, "org/simantics/scl/runtime/chr/CHRHashIndex");
+
+        // Method: keyEquals
+
+        {
+
+            // @Override
+            // protected boolean keyEquals(Object a, Object b) {
+            //     return ((ExampleFact)a).c0 == ((ExampleFact)b).c0;
+            // }
+
+            MethodBuilderBase mb = hashIndexClassBuilder.addMethodBase(Opcodes.ACC_PROTECTED, "keyEquals", TypeDesc.BOOLEAN, Constants.OBJECTS[2]);
+            mb.loadLocal(mb.getParameter(0));
+            mb.checkCast(factClassTypeDesc);
+            LocalVariable aVar = mb.createLocalVariable("a", factClassTypeDesc);
+            mb.storeLocal(aVar);
+
+            mb.loadLocal(mb.getParameter(1));
+            mb.checkCast(factClassTypeDesc);
+            LocalVariable bVar = mb.createLocalVariable("b", factClassTypeDesc);
+            mb.storeLocal(bVar);
+
+            Label failure = mb.createLabel();
+
+            int curMask = indexInfo.indexMask;
+            for(int i=0;i<constraint.parameterTypes.length;++i,curMask>>=1)
+                if((curMask&1) == 1) {
+                    TypeDesc fieldTypeDesc = jtt.toTypeDesc(constraint.parameterTypes[i]);
+                    if(fieldTypeDesc.equals(TypeDesc.VOID))
+                        continue;
+                    mb.loadLocal(aVar);
+                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
+
+                    mb.loadLocal(bVar);
+                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
+
+                    CodeBuilderUtils.equals(mb, fieldTypeDesc, failure);
+                }
+            mb.loadConstant(true);
+            mb.returnValue(TypeDesc.BOOLEAN);
+
+            mb.setLocation(failure);
+            mb.loadConstant(false);
+            mb.returnValue(TypeDesc.BOOLEAN);
+            mb.finish();
+        }
+
+        // Method: keyHashCode
+
+        {
+            // @Override
+            // protected int keyHashCode(Object key) {
+            //     return (0x811C9DC5^((ExampleFact)key).c0)*16777619;
+            // }
+
+            MethodBuilderBase mb = hashIndexClassBuilder.addMethodBase(Opcodes.ACC_PROTECTED, "keyHashCode", TypeDesc.INT, Constants.OBJECTS[1]);
+            mb.loadLocal(mb.getParameter(0));
+            mb.checkCast(factClassTypeDesc);
+            LocalVariable factVar = mb.createLocalVariable("fact", factClassTypeDesc);
+            mb.storeLocal(factVar);
+
+            mb.loadConstant(0x811C9DC5);
+
+            int curMask = indexInfo.indexMask;
+            for(int i=0;i<constraint.parameterTypes.length;++i,curMask>>=1)
+                if((curMask&1) == 1) {
+                    TypeDesc fieldTypeDesc = jtt.toTypeDesc(constraint.parameterTypes[i]);
+                    if(fieldTypeDesc.equals(TypeDesc.VOID))
+                        continue;
+                    mb.loadLocal(factVar);
+                    mb.loadField(factClassName, fieldName(i), fieldTypeDesc);
+                    CodeBuilderUtils.hashCode(mb, fieldTypeDesc);
+                    mb.math(Opcodes.IXOR);
+                    mb.loadConstant(16777619);
+                    mb.math(Opcodes.IMUL);
+
+                }
+            mb.returnValue(TypeDesc.INT);
+            mb.finish();
+        }
+
+        hashIndexClassBuilder.addDefaultConstructor();
+
+        return hashIndexClassBuilder;
+    }
+}
diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/StoreInitialization.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/chr/StoreInitialization.java
new file mode 100644 (file)
index 0000000..c11dc6d
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.scl.compiler.internal.codegen.chr;
+
+import org.cojen.classfile.TypeDesc;
+
+class StoreInitialization {
+    final int access;
+    final String fieldName;
+    final TypeDesc fieldType;
+    final String className;
+    public StoreInitialization(int access, String fieldName, TypeDesc fieldType, String className) {
+        this.access = access;
+        this.fieldName = fieldName;
+        this.fieldType = fieldType;
+        this.className = className;
+    }
+}
\ No newline at end of file