Outputting Java Bytecode ======================== We want to convert flattened Avalanche to jbc. It looks like Jacob Stanley has the majority of a decent library for doing this: [https://github.com/jystic/tonic]. There are other libraries which may be better baked, for example hs-java, but I just dislike its imperative way of constructing bytecode. I think I will start with Jacob's library, and if I run into too many shortcomings, switch to hs-java. IcicleState ========== ``` // The type parameter here is the type of the concrete feature // We do not have a type parameter for the virtual feature, because // we may have multiple virtual features being computed. interface IcicleState { // There is a phantom field keeping track of allowed operations in a given "state". // This would be tracked by types normally, but keeping everything in one place // actually makes code generation easier. // However for sanity, I need to keep track of this stuff, so I will use pre and postconditions. // data State = Start | ReadingHistory | ReadingNew | Done // state : State = Start // Class-wide invariant: // state can only "increase": Start -> Done, Start -> ReadingNew, but not ReadingHistory -> Start. // state <= state' // We also have extra phantom fields for keeping track of whether a value can be read: // canPull : Bool = False // hasRow : Bool = False // Get the date for which we are calculating the feature. // // Pre: state == Start // // Post: - DateTime snapshotDate(); // For resumable accumulators like "sum over all time", // load the last saved value. // // Pre: state == Start // /\ virtualFeature in features // // Post: null if no value, or last saved value for (virtualFeature, accumulatorName). U loadResumable(String virtualFeature, String accumulatorName); // For resumable accumulators like "sum over all time", // load the last saved value. // // Pre: state == Done // /\ virtualFeature in features // /\ value != null // // Post: - void saveResumable(String virtualFeature, String accumulatorName, U value); // Record actual output value of computed virtual feature // // Pre: state == Done // /\ virtualFeature in features // /\ value != null // // Post: - void output(String virtualFeature, U value); // Start reading history // // Pre: state == Start // /\ ! hasRow // /\ ! canPull // // Post: state' == ReadingHistory // /\ ! hasRow' // /\ canPull' void startHistory(); // Start reading new stuff // // Pre: (state == Start \/ state == ReadingHistory) // /\ ! hasRow // /\ ! canPull // // Post: state' == ReadingNew // /\ ! hasRow' // /\ canPull' void startNew(); // Pull the next row in // // Pre: (state == ReadingHistory \/ state == ReadingNew) // /\ canPull // // Post: hasRow' == canPull' == return // at end of ReadingNew, state' becomes Done boolean nextRow(); // Note that the current fact is used // // Pre: (state == ReadingHistory \/ state == ReadingNew) // /\ hasRow // // Post: on the next run, this fact will be included in startHistory / nextRow void keepFactInHistory(); // Read the value of the current fact // // Pre: (state == ReadingHistory \/ state == ReadingNew) // /\ hasRow // // Post: - T currentRow(); } ``` Examples ======== CountAboveTen ------------- Let's start with a very simple one. Given the input query: ``` feature salary ~> filter value > 10 ~> count ``` We get the following flattened Avalanche. ``` gen$date = DATE { init acc$conv$5 = 0 : Int (Resumable); for_facts (gen$fact : Int) in new { let anf$3 = fst# [Int] [DateTime] gen$fact; if (gt# [Int] anf$3 (10 : Int)) { read acc$conv$5 = acc$conv$5; write acc$conv$5 = add# acc$conv$5 (1 : Int); } } read conv$5 = acc$conv$5; return conv$5; } ``` I'm imagining the Java would be something like this. ``` void compute(IcicleState icicle) { // gen$date = DATE DateTime gen_date = icicle.snapshotDate(); // init acc$conv$5 = 0 : Int (Resumable); Integer acc_conv_5_load = icicle.loadResumable("CountAboveTen", "acc$conv$5"); int acc_conv_5 = 0; if (acc_conv_5_load != null) { acc_conv_5 = acc_conv_5_load.value(); } // for_facts (gen$fact : Int) in new icicle.startNew(); while (icicle.nextRow()) { // let anf$3 = fst# [Int] [DateTime] gen$fact; int anf_3 = icicle.currentRow().value(); // if (gt# [Int] anf$3 (10 : Int)) if (anf_3 > 10) { // read acc$conv$5 = acc$conv$5; int read_acc_conv_5 = acc_conv_5; // write acc$conv$5 = add# acc$conv$5 (1 : Int); acc_conv_5 = read_acc_conv_5 + 1; } } // init acc$conv$5 = 0 : Int (Resumable); icicle.saveResumable("CountAboveTen", "acc$conv$5", new Integer(acc_conv_5)); // read conv$5 = acc$conv$5; int conv_5 = acc_conv_5 // return conv$5; icicle.output("CountAboveTen", new Integer(conv_5)); } ``` And the very very rough bytecode. This is not quite right syntax but the idea is there. It could also be improved a bit like not storing variables before using them just once. ``` // void Compute(IcicleState icicle) aload ICICLE invokeinterface #IcicleState.snapShotDate astore GEN_DATE // gen_date = icicle.snapShotDate() aload ICICLE ldc "CountAboveTen" ldc "acc$conv$5" invokeinterface #IcicleState.loadResumable astore ACC_CONV_5_LOAD // acc_conv_5_load = icicle.loadResumable(...) iconst 0 istore ACC_CONV_5 // acc_conv_5 = 0 aload ACC_CONV_5_LOAD // acc_conv_5_load aconstnull // null if_acmpne BR_LOAD_ACC_CONV_5 // if (acc_conv_5_load != null) goto BR_SKIP_ACC_CONV_5 BR_LOAD_ACC_CONV_5: aload ACC_CONV_5_LOAD invokestatic #Integer.value istore ACC_CONV_5 // acc_conv_5 = acc_conv_5_load.value() BR_SKIP_ACC_CONV_5: aload ICICLE invokevirtual #IcicleState.startNew BR_WHILE_START: aload ICICLE invokevirtual #IcicleState.nextRow ifeq BR_WHILE_END // if (icicle.nextRow() == 0) goto end aload ICICLE invokevirtual #IcicleState.currentRow checkcast #Integer invokevirtual #Integer.value istore ANF_3 // anf_3 = icicle.currentRow().value(); iload ANF_3 iconst 10 if_icmpgt BR_ANF_3_THEN // if (anf_3 > 10) goto BR_ANF_3_ELSE BR_ANF_3_THEN: iload ACC_CONV_5 istore READ_ACC_CONV_5 // read_acc_conv_5 = acc_conv_5; iload READ_ACC_CONV_5 iconst 1 iadd istore ACC_CONV_5 // acc_conv_5 = read_acc_conv_5 + 1 BR_ANF_3_ELSE: goto BR_WHILE_START BR_WHILE_END: aload ICICLE ldc "CountAboveTen" ldc "acc$conv$5" new #Integer dup iload ACC_CONV_5 invokespecial #Integer. invokeinterface #IcicleState.saveResumable // icicle.saveResumable(...); iload ACC_CONV_5 istore CONV_5 aload ICICLE ldc "CountAboveTen" new #Integer dup iload ACC_CONV_5 invokespecial #Integer. invokeinterface #IcicleState.output // icicle.output("CountAboveTen", new Integer(conv_5)); } ``` CountLastWeek ------------- How many things happened last week? Input query: ``` feature salary ~> windowed between 7 days and 14 days ~> count ``` Flattened Avalanche. ``` gen$date = DATE { init acc$conv$4 = 0 : Int (Windowed); for_facts (gen$fact : Int) in history { let anf$4 = snd# [Int] [DateTime] gen$fact; let anf$5 = DateTime_daysDifference# anf$4 gen$date; let anf$6 = le# [Int] anf$5 (14 : Int); let anf$8 = ge# [Int] anf$5 (7 : Int); if (and# anf$6 anf$8) { read acc$conv$4 = acc$conv$4; write acc$conv$4 = add# acc$conv$4 (1 : Int); keep_fact_in_history; } else { if (lt# [Int] anf$5 (7 : Int)) { keep_fact_in_history; } } } for_facts (gen$fact : Int) in new { let anf$16 = snd# [Int] [DateTime] gen$fact; let anf$17 = DateTime_daysDifference# anf$16 gen$date; let anf$18 = le# [Int] anf$17 (14 : Int); let anf$20 = ge# [Int] anf$17 (7 : Int); if (and# anf$18 anf$20) { read acc$conv$4 = acc$conv$4; write acc$conv$4 = add# acc$conv$4 (1 : Int); keep_fact_in_history; } else { if (lt# [Int] anf$17 (7 : Int)) { keep_fact_in_history; } } } read conv$4 = acc$conv$4; return conv$4; } ``` Java: ``` void compute(IcicleState icicle) { // gen$date = DATE DateTime gen_date = icicle.snapshotDate(); // init acc$conv$4 = 0 : Int (Windowed); int acc_conv_4 = 0; // for_facts (gen$fact : Int) in history icicle.startHistory(); while (icicle.nextRow()) { // let anf$4 = snd# [Int] [DateTime] gen$fact; DateTime anf_4 = icicle.currentTime(); // let anf$5 = DateTime_daysDifference# anf$4 gen$date; int anf_5 = DateTime.daysDifference(anf_4, gen_date); // let anf$6 = le# [Int] anf$5 (14 : Int); bool anf_6 = anf_5 <= 14; // let anf$8 = ge# [Int] anf$5 (7 : Int); bool anf_8 = anf_8 >= 7; // if (and# anf$6 anf$8) if (anf_6 && anf_8) { // read acc$conv$4 = acc$conv$4; int acc_conv_4_read = acc_conv_4; // write acc$conv$4 = add# acc$conv$4 (1 : Int); acc_conv_4 = acc_conv_4_read + 1; icicle.keepFactInHistory(); } else { // if (lt# [Int] anf$5 (7 : Int)) if (anf_5 < 7) { // keep_fact_in_history; icicle.keepFactInHistory(); } } } // for_facts (gen$fact : Int) in new icicle.startNew(); while (icicle.nextRow()) { // let anf$16 = snd# [Int] [DateTime] gen$fact; DateTime anf_16 = icicle.currentTime(); // let anf$17 = DateTime_daysDifference# anf$16 gen$date; int anf_17 = DateTime.daysDifference(anf_16, gen_date); // let anf$18 = le# [Int] anf$17 (14 : Int); bool anf_18 = anf_17 <= 14; // let anf$20 = ge# [Int] anf$17 (7 : Int); bool anf_20 = anf_17 >= 7; // if (and# anf$18 anf$20) if (anf_18 && anf_20) { // read acc$conv$4 = acc$conv$4; int acc_conv_4_read = acc_conv_4; // write acc$conv$4 = add# acc$conv$4 (1 : Int); acc_conv_4 = acc_conv_4_read + 1; icicle.keepFactInHistory(); } else { // if (lt# [Int] anf$17 (7 : Int)) if (anf_17 < 7) { icicle.keepFactInHistory(); } } } // read conv$4 = acc$conv$4; int conv_4 = acc_conv_4 // return conv$4; icicle.output("CountLastWeek", new Integer(conv_4)); } ```