From c3cb7cb884e2e9bd134811fa102ed2af7b15098b Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:36:55 -0400 Subject: [PATCH] separated functionality of generator state machine builders into separate classes --- src/Examples.java | 53 +++- src/Main.java | 17 ++ src/generator/Gen.java | 3 +- src/generator/runtime/GeneratorBuilder.java | 284 ++---------------- .../runtime/GeneratorClassLoader.java | 6 +- src/generator/runtime/LocalTracker.java | 138 +++++++++ .../runtime/StateMachineBuilder.java | 233 ++++++++++++++ 7 files changed, 449 insertions(+), 285 deletions(-) create mode 100644 src/generator/runtime/LocalTracker.java create mode 100644 src/generator/runtime/StateMachineBuilder.java diff --git a/src/Examples.java b/src/Examples.java index cedbb5d..3cd7728 100644 --- a/src/Examples.java +++ b/src/Examples.java @@ -65,21 +65,42 @@ public class Examples { // return Gen.ret(); // } - public static Gen test(double[] nyas){ - - var test = 1+switch(nyas[0]){ - case 1.0 -> { - Gen.yield(11); - yield 2; - } - default -> { - Gen.yield(12); - yield 4; - } - }; - for(var d : nyas){ - Gen.yield(d); - } - return Gen.ret(); + public static Gen awaitTest2(int number){ + for(int i = 0; i < number; i ++)Gen.yield(); + return Gen.ret(number+""); } + + public static class Meow implements AutoCloseable{ + { + System.out.println("Opened"); + } + @Override + public void close() { + System.out.println("Closed"); + } + } + + public static Gen awaitTest(int number){ + try(var m = new Meow()){ + return Gen.ret(awaitTest2(number).await()); + } + } + +// public static Gen test(double[] nyas){ +// +// var test = 1+switch(nyas[0]){ +// case 1.0 -> { +// Gen.yield(11); +// yield 2; +// } +// default -> { +// Gen.yield(12); +// yield 4; +// } +// }; +// for(var d : nyas){ +// Gen.yield(d); +// } +// return Gen.ret(); +// } } diff --git a/src/Main.java b/src/Main.java index 190c73b..61f29d0 100644 --- a/src/Main.java +++ b/src/Main.java @@ -8,6 +8,23 @@ public class Main implements Runnable { @Override public void run() { + await(); +// lexer(); + } + + void await(){ + var gen = Examples.awaitTest(10); + while(true) { + var next = gen.next(); + if(next instanceof Gen.Yield(var e)) System.out.println(e); + else if(next instanceof Gen.Ret(var ret)){ + System.out.println(ret); + break; + } + } + } + + void lexer(){ var gen = Lexer.parse("f7(x,y,z,w, u,v, othersIg) = v-(x*y+y+ln(z)^2*sin(z*pi/2))/(w*u)+sqrt(othersIg*120e-1)"); // var gen = Examples.test(new double[]{1,2,3,4}); while(gen.next() instanceof Gen.Yield(var tok)) { diff --git a/src/generator/Gen.java b/src/generator/Gen.java index c4ef367..e3d53ba 100644 --- a/src/generator/Gen.java +++ b/src/generator/Gen.java @@ -17,8 +17,7 @@ public interface Gen { default R await(){ while(true){ var res = next(); - if(res instanceof Ret(R r))return r; - Gen.yield(); + if(res instanceof Ret r)return (R)r.v; } } diff --git a/src/generator/runtime/GeneratorBuilder.java b/src/generator/runtime/GeneratorBuilder.java index 5e4ee10..b257ed6 100644 --- a/src/generator/runtime/GeneratorBuilder.java +++ b/src/generator/runtime/GeneratorBuilder.java @@ -3,36 +3,31 @@ package generator.runtime; import generator.Gen; import java.lang.classfile.*; -import java.lang.classfile.attribute.StackMapFrameInfo; -import java.lang.classfile.instruction.*; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.TOP; +import java.util.*; public class GeneratorBuilder { - private final static String PARAM_PREFIX = "param_"; - private final static String LOCAL_PREFIX = "local_"; - private final static String STACK_PREFIX = "stack_"; - private final static String STATE_NAME = "state"; + public final static String PARAM_PREFIX = "param_"; + public final static String LOCAL_PREFIX = "local_"; + public final static String STACK_PREFIX = "stack_"; + public final static String STATE_NAME = "state"; - private final static ClassDesc CD_Gen = ClassDesc.ofDescriptor(Gen.class.descriptorString()); - private final static ClassDesc CD_Res = ClassDesc.ofDescriptor(Gen.Res.class.descriptorString()); - private final static ClassDesc CD_Yield = ClassDesc.ofDescriptor(Gen.Yield.class.descriptorString()); - private final static ClassDesc CD_Ret = ClassDesc.ofDescriptor(Gen.Ret.class.descriptorString()); - private final static MethodTypeDesc MTD_Res = MethodTypeDesc.of(CD_Res); - private final static MethodTypeDesc MTD_Gen_Obj = MethodTypeDesc.of(CD_Gen, ConstantDescs.CD_Object); + public final static ClassDesc CD_Gen = ClassDesc.ofDescriptor(Gen.class.descriptorString()); + public final static ClassDesc CD_Res = ClassDesc.ofDescriptor(Gen.Res.class.descriptorString()); + public final static ClassDesc CD_Yield = ClassDesc.ofDescriptor(Gen.Yield.class.descriptorString()); + public final static ClassDesc CD_Ret = ClassDesc.ofDescriptor(Gen.Ret.class.descriptorString()); + public final static MethodTypeDesc MTD_Res = MethodTypeDesc.of(CD_Res); + public final static MethodTypeDesc MTD_Gen_Obj = MethodTypeDesc.of(CD_Gen, ConstantDescs.CD_Object); + public final static MethodTypeDesc MTD_Gen = MethodTypeDesc.of(CD_Gen); + public static MethodTypeDesc MTD_Obj = MethodTypeDesc.of(ConstantDescs.CD_Object); - private final String name; + public final String name; public final ClassDesc CD_this_gen; - private final ClassDesc[] params; - private final MethodTypeDesc MTD_init; - private final int paramSlotOff; + public final ClassDesc[] params; + public final MethodTypeDesc MTD_init; + public final int paramSlotOff; public interface ParamConsumer{ void consume(String param, int slot, ClassDesc type); @@ -63,7 +58,7 @@ public class GeneratorBuilder { } public byte[] buildGenerator(CodeModel com){ - return ClassFile.of(ClassFile.StackMapsOption.STACK_MAPS_WHEN_REQUIRED).build(CD_this_gen, clb -> { + return ClassFile.of(ClassFile.StackMapsOption.STACK_MAPS_WHEN_REQUIRED, ClassFile.DebugElementsOption.PASS_DEBUG, ClassFile.LineNumbersOption.PASS_LINE_NUMBERS, ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES).build(CD_this_gen, clb -> { clb.withInterfaces(List.of(clb.constantPool().classEntry(CD_Gen))); // parameter fields params(0, (param, _, type) -> { @@ -90,252 +85,11 @@ public class GeneratorBuilder { private void buildGeneratorNext(ClassBuilder clb, CodeBuilder cob, CodeModel com){ cob.trying( - tcob -> generateStateMachine(clb, tcob, com), + tcob -> new StateMachineBuilder(this, clb, tcob, com).generateStateMachine(), // catch anything set our state to -1 and throw the exception ctb -> ctb.catchingAll( blc -> blc.aload(0).loadConstant(-1).putfield(CD_this_gen, STATE_NAME, ConstantDescs.CD_int).athrow() ) ).aconst_null().areturn(); } - - private void generateStateMachine(ClassBuilder clb, CodeBuilder cob, CodeModel com){ - - var stateSwitchCases = new ArrayList(); - var invalidState = cob.newLabel(); - stateSwitchCases.add(SwitchCase.of(0, cob.newLabel())); - int switchCase = 1; - for (CodeElement coe : com) { - if (coe instanceof InvokeInstruction is && is.opcode().equals(Opcode.INVOKESTATIC) && is.owner().asSymbol().equals(CD_Gen) && (is.name().equalsString("yield"))) { - stateSwitchCases.add(SwitchCase.of(switchCase, cob.newLabel())); - switchCase++; - } - } - cob.aload(0).getfield(CD_this_gen, STATE_NAME, TypeKind.INT.upperBound()).lookupswitch(invalidState, stateSwitchCases); - var start = cob.startLabel(); - var end = cob.newLabel(); - cob.localVariable(0, "this", CD_this_gen, start, end); - - var localTracker = new LocalTracker(com); - - switchCase = 1; - cob.labelBinding(stateSwitchCases.removeFirst().target()); - final boolean[] ignore_next_return = {false}; - final boolean[] ignore_next_pop = {false}; - for (CodeElement coe : com) { - switch (coe) { - case Instruction ins when ins.opcode() == Opcode.POP && ignore_next_pop[0] -> { - ignore_next_pop[0] = false; - continue; - } - case ReturnInstruction _ when ignore_next_return[0] -> { - ignore_next_return[0] = false; - continue; - } - case Instruction _ when ignore_next_return[0] || ignore_next_pop[0] -> throw new RuntimeException(); - - case Label l -> localTracker.encounterLabel(l); - - default -> {} - } - if(coe instanceof InvokeInstruction is - && is.opcode().equals(Opcode.INVOKESTATIC) - && is.owner().asSymbol().equals(CD_Gen) - && (is.name().equalsString("yield") || is.name().equalsString("ret"))){ - if (MethodTypeDesc.ofDescriptor(is.method().type().stringValue()).parameterArray().length == 0) { - cob.aconst_null(); - } - - if (is.name().equalsString("ret")) { - cob.aload(0).loadConstant(-1).putfield(CD_this_gen, STATE_NAME, TypeKind.INT.upperBound()) - .new_(CD_Ret) - .dup_x1() - .swap() - .invokespecial(CD_Ret, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object)) - .areturn(); - ignore_next_return[0] = true; - } else { - int finalSwitchCase = switchCase; - switchCase++; - localTracker.savingLocals(CD_this_gen, cob, () -> { - cob.aload(0).loadConstant(finalSwitchCase).putfield(CD_this_gen, STATE_NAME, TypeKind.INT.upperBound()) - .new_(CD_Yield) - .dup_x1() - .swap() - .invokespecial(CD_Yield, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object)) - .areturn(); - cob.labelBinding(stateSwitchCases.removeFirst().target()); - }); - - ignore_next_pop[0] = true; - } - continue; - } - switch (coe) { - // locals which were once function parameters can be ignored - case LocalVariable lv when lv.slot() < paramSlotOff -> {} - case LocalVariable lv -> cob.localVariable(lv.slot() - paramSlotOff + 1, lv.name(), lv.type(), lv.startScope(), lv.endScope()); - - // increment indexes into the stack - case IncrementInstruction ii when ii.slot() < paramSlotOff -> - cob.aload(0).dup().getfield(CD_this_gen, PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int) - .loadConstant(ii.constant()).iadd() - .putfield(CD_this_gen, PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int); - case IncrementInstruction ii -> cob.iinc(ii.slot() - paramSlotOff + 1, ii.constant()); - - // convert local function parameters to class fields and offset regular locals - case LoadInstruction li when li.slot() < paramSlotOff -> - cob.aload(0).getfield(CD_this_gen, PARAM_PREFIX + li.slot(), localTracker.paramType(li.slot())); - case LoadInstruction li -> cob.loadLocal(li.typeKind(), li.slot() - paramSlotOff + 1); - - // convert local function parameters to class fields and offset regular locals - case StoreInstruction ls when ls.slot() < paramSlotOff && ls.typeKind().slotSize() == 2 -> - cob.aload(0).dup_x2().pop().putfield(CD_this_gen, PARAM_PREFIX + ls.slot(), localTracker.paramType(ls.slot())); - case StoreInstruction ls when ls.slot() < paramSlotOff -> - cob.aload(0).swap().putfield(CD_this_gen, PARAM_PREFIX + ls.slot(), localTracker.paramType(ls.slot())); - case StoreInstruction ls -> { - localTracker.trackLocal(ls.slot() - paramSlotOff + 1, ls.typeKind()); - cob.storeLocal(ls.typeKind(), ls.slot() - paramSlotOff + 1); - } - - default -> cob.with(coe); - } - } - cob.labelBinding(invalidState); - cob.new_(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString())).dup() - .invokespecial(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString()), ConstantDescs.INIT_NAME, ConstantDescs.MTD_void) - .athrow(); - cob.labelBinding(end); - - localTracker.createLocalStoreFields(clb); - } - - - private class LocalTracker { - - record LocalStore(String name, ClassDesc cd) { - } - - HashMap parameter_map = new HashMap<>(); - - HashMap localVarTypes = new HashMap<>(); - HashMap stackMapFrames = new HashMap<>(); - StackMapFrameInfo currentFrame; - ArrayList localStore = new ArrayList<>(); - - - private LocalTracker(CodeModel com) { - int offset = 0; - for (var param : params) { - parameter_map.put(offset, param); - offset += TypeKind.from(param).slotSize(); - } - - for (var attr : com.findAttributes(Attributes.stackMapTable())) { - var entries = new ArrayList(); - for (var smfi : attr.entries()) { - var locals = new ArrayList<>(smfi.locals()); - for (int i = 0; i < params.length; i++) locals.removeFirst(); - locals.addFirst(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CD_this_gen)); - entries.add(StackMapFrameInfo.of(smfi.target(), locals, smfi.stack())); - stackMapFrames.put(smfi.target(), entries.getLast()); - } - } - } - - - //Tries its best to reuse old saved locals field slots, only reuses if types exactly match - public void savingLocals(ClassDesc cd, CodeBuilder cob, Runnable run) { - record Saved(int slot, String name, ClassDesc cd) { - } - - var saved = new ArrayList(); - var lls = new ArrayList<>(localStore); - currentLocals((slot, tk, desc) -> { - String name = null; - for (int i = 0; i < lls.size(); i++) - if (lls.get(i).cd.equals(desc)) { - name = lls.get(i).name; - lls.remove(i); - break; - } - if (name == null) { - name = LOCAL_PREFIX + localStore.size(); - localStore.add(new LocalStore(name, desc)); - } - saved.add(new Saved(slot, name, desc)); - cob.aload(0).loadLocal(tk, slot).putfield(cd, name, desc); - }); - run.run(); - - for (var save : saved) { - cob.aload(0).getfield(cd, save.name, save.cd).storeLocal(TypeKind.from(save.cd), save.slot); - } - } - - public void createLocalStoreFields(ClassBuilder clb) { - for (var local : localStore) { - clb.withField(local.name, local.cd, ClassFile.ACC_PRIVATE); - } - } - - public void encounterLabel(Label l) { - var tmp = stackMapFrames.get(l); - if (tmp != null) { - localVarTypes.clear(); - currentFrame = tmp; - } - } - - public ClassDesc paramType(int slot) { - return parameter_map.get(slot); - } - - public void trackLocal(int slot, TypeKind typeKind) { - localVarTypes.put(slot, typeKind); - } - - interface LocalConsumer { - void consume(int slot, TypeKind tk, ClassDesc desc); - } - - void currentLocals(LocalConsumer consumer) { - var slot = 0; - if (currentFrame != null) - for (var kind : currentFrame.locals()) { - switch (kind) { - case StackMapFrameInfo.ObjectVerificationTypeInfo o -> { - if (slot != 0) - consumer.consume(slot, o.className().typeKind(), o.classSymbol()); - slot += 1; - } - case StackMapFrameInfo.SimpleVerificationTypeInfo ti -> { - if (kind == TOP) { - slot += 1; - if (localVarTypes.get(slot - 1) instanceof TypeKind tk) { - ClassDesc cd = tk.upperBound(); - consumer.consume(slot - 1, tk, cd); - } - continue; - } - var type = switch (ti) { - case INTEGER -> TypeKind.INT; - case FLOAT -> TypeKind.FLOAT; - case DOUBLE -> TypeKind.DOUBLE; - case LONG -> TypeKind.LONG; - case NULL -> TypeKind.REFERENCE; - default -> throw new IllegalStateException(); - }; - consumer.consume(slot, type, type.upperBound()); - slot += 1; - } - case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> throw new IllegalStateException(); - } - } - for (var entry : localVarTypes.entrySet()) { - if (entry.getKey() < slot) continue; - ClassDesc cd = entry.getValue().upperBound(); - consumer.consume(entry.getKey(), TypeKind.from(cd), cd); - } - } - } } diff --git a/src/generator/runtime/GeneratorClassLoader.java b/src/generator/runtime/GeneratorClassLoader.java index 8754c72..69f2b11 100644 --- a/src/generator/runtime/GeneratorClassLoader.java +++ b/src/generator/runtime/GeneratorClassLoader.java @@ -31,6 +31,8 @@ public class GeneratorClassLoader extends ClassLoader { @Override public Class loadClass(String name) throws ClassNotFoundException { +// if(customClazzDefMap.containsKey(name)) +// return super.loadClass(name); if (customClazzDefMap.get(name) instanceof byte[] bytes) customClazzMap.put(name, defineClass(name, bytes, 0, bytes.length)); if (customClazzMap.get(name) instanceof Class clazz) @@ -51,9 +53,9 @@ public class GeneratorClassLoader extends ClassLoader { } public byte[] searchForGenerators(byte[] in) { - var clm = ClassFile.of().parse(in); + var clm = ClassFile.of(ClassFile.DebugElementsOption.PASS_DEBUG, ClassFile.LineNumbersOption.PASS_LINE_NUMBERS, ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES).parse(in); var isGen = clm.thisClass().asSymbol().descriptorString().equals(Gen.class.descriptorString()); - return ClassFile.of().build(clm.thisClass().asSymbol(), cb -> { + return ClassFile.of(ClassFile.DebugElementsOption.PASS_DEBUG, ClassFile.LineNumbersOption.PASS_LINE_NUMBERS, ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES).build(clm.thisClass().asSymbol(), cb -> { for (var ce : clm) { if (ce instanceof MethodModel mem && !isGen) { var methodRetGen = mem.methodTypeSymbol().returnType().descriptorString().equals(Gen.class.descriptorString()); diff --git a/src/generator/runtime/LocalTracker.java b/src/generator/runtime/LocalTracker.java new file mode 100644 index 0000000..8e993fd --- /dev/null +++ b/src/generator/runtime/LocalTracker.java @@ -0,0 +1,138 @@ +package generator.runtime; + +import java.lang.classfile.*; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.constant.ClassDesc; +import java.util.ArrayList; +import java.util.HashMap; + +import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.TOP; + +class LocalTracker { + + record LocalStore(String name, ClassDesc cd) { + } + + HashMap parameter_map = new HashMap<>(); + + HashMap localVarTypes = new HashMap<>(); + HashMap stackMapFrames = new HashMap<>(); + StackMapFrameInfo currentFrame; + ArrayList localStore = new ArrayList<>(); + + + LocalTracker(StateMachineBuilder smb, CodeModel com) { + int offset = 0; + for (var param : smb.gb.params) { + parameter_map.put(offset, param); + offset += TypeKind.from(param).slotSize(); + } + + for (var attr : com.findAttributes(Attributes.stackMapTable())) { + var entries = new ArrayList(); + for (var smfi : attr.entries()) { + var locals = new ArrayList<>(smfi.locals()); + for (int i = 0; i < smb.gb.params.length; i++) locals.removeFirst(); + locals.addFirst(StackMapFrameInfo.ObjectVerificationTypeInfo.of(smb.gb.CD_this_gen)); + entries.add(StackMapFrameInfo.of(smfi.target(), locals, smfi.stack())); + stackMapFrames.put(smfi.target(), entries.getLast()); + } + } + } + + + //Tries its best to reuse old saved locals field slots, only reuses if types exactly match + public void savingLocals(ClassDesc cd, CodeBuilder cob, Runnable run) { + record Saved(int slot, String name, ClassDesc cd) { + } + + var saved = new ArrayList(); + var lls = new ArrayList<>(localStore); + currentLocals((slot, tk, desc) -> { + String name = null; + for (int i = 0; i < lls.size(); i++) + if (lls.get(i).cd.equals(desc)) { + name = lls.get(i).name; + lls.remove(i); + break; + } + if (name == null) { + name = GeneratorBuilder.LOCAL_PREFIX + localStore.size(); + localStore.add(new LocalStore(name, desc)); + } + saved.add(new Saved(slot, name, desc)); + cob.aload(0).loadLocal(tk, slot).putfield(cd, name, desc); + }); + run.run(); + + for (var save : saved) { + cob.aload(0).getfield(cd, save.name, save.cd).storeLocal(TypeKind.from(save.cd), save.slot); + } + } + + public void createLocalStoreFields(ClassBuilder clb) { + for (var local : localStore) { + clb.withField(local.name, local.cd, ClassFile.ACC_PRIVATE); + } + } + + public void encounterLabel(Label l) { + var tmp = stackMapFrames.get(l); + if (tmp != null) { + localVarTypes.clear(); + currentFrame = tmp; + } + } + + public ClassDesc paramType(int slot) { + return parameter_map.get(slot); + } + + public void trackLocal(int slot, TypeKind typeKind) { + localVarTypes.put(slot, typeKind); + } + + public interface LocalConsumer { + void consume(int slot, TypeKind tk, ClassDesc desc); + } + + public void currentLocals(LocalConsumer consumer) { + var slot = 0; + if (currentFrame != null) + for (var kind : currentFrame.locals()) { + switch (kind) { + case StackMapFrameInfo.ObjectVerificationTypeInfo o -> { + if (slot != 0) + consumer.consume(slot, o.className().typeKind(), o.classSymbol()); + slot += 1; + } + case StackMapFrameInfo.SimpleVerificationTypeInfo ti -> { + if (kind == TOP) { + slot += 1; + if (localVarTypes.get(slot - 1) instanceof TypeKind tk) { + ClassDesc cd = tk.upperBound(); + consumer.consume(slot - 1, tk, cd); + } + continue; + } + var type = switch (ti) { + case INTEGER -> TypeKind.INT; + case FLOAT -> TypeKind.FLOAT; + case DOUBLE -> TypeKind.DOUBLE; + case LONG -> TypeKind.LONG; + case NULL -> TypeKind.REFERENCE; + default -> throw new IllegalStateException(); + }; + consumer.consume(slot, type, type.upperBound()); + slot += 1; + } + case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> throw new IllegalStateException(); + } + } + for (var entry : localVarTypes.entrySet()) { + if (entry.getKey() < slot) continue; + ClassDesc cd = entry.getValue().upperBound(); + consumer.consume(entry.getKey(), TypeKind.from(cd), cd); + } + } +} diff --git a/src/generator/runtime/StateMachineBuilder.java b/src/generator/runtime/StateMachineBuilder.java new file mode 100644 index 0000000..53094b2 --- /dev/null +++ b/src/generator/runtime/StateMachineBuilder.java @@ -0,0 +1,233 @@ +package generator.runtime; + +import java.lang.classfile.*; +import java.lang.classfile.instruction.*; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.function.Function; + +public class StateMachineBuilder { + final ClassBuilder clb; + final CodeBuilder cob; + final CodeModel com; + final GeneratorBuilder gb; + final LocalTracker lt; + + private HashMap> smmap = new HashMap<>(); + private boolean ignore_next_pop = false; + final ArrayList stateSwitchCases = new ArrayList<>(); + final Label invalidState; + + + public enum HandlerRan{ + ImmediateRemovePop, + Immediate, + ReplacingNextReturn, + } + public interface SpecialMethodHandler { + void handle(StateMachineBuilder smb); + default boolean removeCall(){return true;} + default HandlerRan handlerRan(){return HandlerRan.ImmediateRemovePop;} + } + + public record SpecialMethod(ClassDesc owner, String name, MethodTypeDesc desc) { + } + + static class YieldHandler implements SpecialMethodHandler { + final int resume_state; + final Label resume_label; + final boolean is_void; + + public YieldHandler(StateMachineBuilder smb, boolean is_void) { + resume_state = smb.add_state(resume_label = smb.cob.newLabel()); + this.is_void = is_void; + } + + @Override + public void handle(StateMachineBuilder smb) { + if(is_void)smb.cob.aconst_null(); + + smb.lt.savingLocals(smb.gb.CD_this_gen, smb.cob, () -> { + smb.cob.aload(0).loadConstant(resume_state).putfield(smb.gb.CD_this_gen, GeneratorBuilder.STATE_NAME, TypeKind.INT.upperBound()) + .new_(GeneratorBuilder.CD_Yield) + .dup_x1() + .swap() + .invokespecial(GeneratorBuilder.CD_Yield, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object)) + .areturn(); + smb.cob.labelBinding(resume_label); + }); + smb.ignore_next_pop = true; + } + } + + static class RetHandler implements SpecialMethodHandler { + final boolean is_void; + + public RetHandler(boolean is_void) { + this.is_void = is_void; + } + + public HandlerRan handlerRan(){return HandlerRan.ReplacingNextReturn;} + + @Override + public void handle(StateMachineBuilder smb) { + if(is_void)smb.cob.aconst_null(); + + smb.cob.aload(0).loadConstant(-1).putfield(smb.gb.CD_this_gen, GeneratorBuilder.STATE_NAME, TypeKind.INT.upperBound()) + .new_(GeneratorBuilder.CD_Ret) + .dup_x1() + .swap() + .invokespecial(GeneratorBuilder.CD_Ret, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object)) + .areturn(); + } + } + + static class AwaitHandler implements SpecialMethodHandler{ + final int yield_state; + final Label yield_label; + + public AwaitHandler(StateMachineBuilder smb) { + yield_state = smb.add_state(yield_label = smb.cob.newLabel()); + } + + public HandlerRan handlerRan(){return HandlerRan.Immediate;} + + @Override + public void handle(StateMachineBuilder smb) { + smb.cob.aload(0).loadConstant(yield_state).putfield(smb.gb.CD_this_gen, GeneratorBuilder.STATE_NAME, TypeKind.INT.upperBound()); + var start = smb.cob.newBoundLabel(); + smb.cob.dup().dup() + .invokeinterface(GeneratorBuilder.CD_Gen, "next", MethodTypeDesc.of(GeneratorBuilder.CD_Res)).dup() + .instanceOf(GeneratorBuilder.CD_Ret); + smb.cob.ifThenElse(bcb -> { + bcb.checkcast(GeneratorBuilder.CD_Ret).invokevirtual(GeneratorBuilder.CD_Ret, "v", MethodTypeDesc.of(ConstantDescs.CD_Object)).swap().pop(); + }, bcb -> { + smb.lt.savingLocals(smb.gb.CD_this_gen, bcb, () -> { + bcb.swap().loadLocal(TypeKind.from(smb.gb.CD_this_gen), 0).swap().putfield(smb.gb.CD_this_gen, "meow", GeneratorBuilder.CD_Gen); + bcb.areturn().labelBinding(yield_label); + bcb.loadLocal(TypeKind.from(smb.gb.CD_this_gen), 0).getfield(smb.gb.CD_this_gen, "meow", GeneratorBuilder.CD_Gen); + }); + bcb.goto_(start); + }); + + } + } + + StateMachineBuilder(GeneratorBuilder gb, ClassBuilder clb, CodeBuilder cob, CodeModel com) { + this.gb = gb; + this.clb = clb; + this.cob = cob; + this.com = com; + this.lt = new LocalTracker(this, com); + invalidState = cob.newLabel(); + + smmap.put(new SpecialMethod(GeneratorBuilder.CD_Gen, "yield", GeneratorBuilder.MTD_Gen_Obj),smb -> new YieldHandler(smb, false)); + smmap.put(new SpecialMethod(GeneratorBuilder.CD_Gen, "yield", GeneratorBuilder.MTD_Gen),smb -> new YieldHandler(smb, true)); + smmap.put(new SpecialMethod(GeneratorBuilder.CD_Gen, "ret", GeneratorBuilder.MTD_Gen_Obj),_ -> new RetHandler(false)); + smmap.put(new SpecialMethod(GeneratorBuilder.CD_Gen, "ret", GeneratorBuilder.MTD_Gen),_ -> new RetHandler(true)); + smmap.put(new SpecialMethod(GeneratorBuilder.CD_Gen, "await", GeneratorBuilder.MTD_Obj), AwaitHandler::new); + } + + int add_state(Label label) { + stateSwitchCases.add(SwitchCase.of(stateSwitchCases.size(), label)); + return stateSwitchCases.size() - 1; + } + + public void generateStateMachine() { + var start_label = cob.newLabel(); + add_state(start_label); + + var handlers = new ArrayList(); + for (CodeElement coe : com) { + if (coe instanceof InvokeInstruction is){ + var handler = smmap.get(new SpecialMethod(is.owner().asSymbol(), is.name().stringValue(), is.typeSymbol())); + if(handler != null) + handlers.add(handler.apply(this)); + } + } + cob.aload(0).getfield(gb.CD_this_gen, GeneratorBuilder.STATE_NAME, TypeKind.INT.upperBound()).lookupswitch(invalidState, stateSwitchCases); + var start = cob.startLabel(); + var end = cob.newLabel(); + cob.localVariable(0, "this", gb.CD_this_gen, start, end); + + SpecialMethodHandler currentHandler = null; + + cob.labelBinding(start_label); + for (CodeElement coe : com) { + if (coe instanceof Instruction i) { + if (ignore_next_pop) + if (i.opcode() == Opcode.POP) { + ignore_next_pop = false; + continue; + }else throw new RuntimeException("Expected Pop Instruction"); + if (i.opcode() == Opcode.ARETURN){ + if (currentHandler !=null && currentHandler.handlerRan() == HandlerRan.ReplacingNextReturn){ + currentHandler.handle(this); + currentHandler = null; + continue; + } + } + } + if (coe instanceof Label l) { + lt.encounterLabel(l); + } + if (coe instanceof InvokeInstruction is){ + if(smmap.get(new SpecialMethod(is.owner().asSymbol(), is.name().stringValue(), is.typeSymbol())) != null){ + if(currentHandler!=null)throw new RuntimeException("Multiple method handlers at once not supported"); + var handler = handlers.removeFirst(); + if(!handler.removeCall()) cob.with(coe); + if(handler.handlerRan() == HandlerRan.Immediate) handler.handle(this); + else if(handler.handlerRan() == HandlerRan.ImmediateRemovePop) { + handler.handle(this); + ignore_next_pop = true; + }else + currentHandler = handler; + continue; + } + } + + switch (coe) { + // locals which were once function parameters can be ignored + case LocalVariable lv when lv.slot() < gb.paramSlotOff -> { + } + case LocalVariable lv -> + cob.localVariable(lv.slot() - gb.paramSlotOff + 1, lv.name(), lv.type(), lv.startScope(), lv.endScope()); + + // increment indexes into the stack + case IncrementInstruction ii when ii.slot() < gb.paramSlotOff -> + cob.aload(0).dup().getfield(gb.CD_this_gen, GeneratorBuilder.PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int) + .loadConstant(ii.constant()).iadd() + .putfield(gb.CD_this_gen, GeneratorBuilder.PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int); + case IncrementInstruction ii -> cob.iinc(ii.slot() - gb.paramSlotOff + 1, ii.constant()); + + // convert local function parameters to class fields and offset regular locals + case LoadInstruction li when li.slot() < gb.paramSlotOff -> + cob.aload(0).getfield(gb.CD_this_gen, GeneratorBuilder.PARAM_PREFIX + li.slot(), lt.paramType(li.slot())); + case LoadInstruction li -> cob.loadLocal(li.typeKind(), li.slot() - gb.paramSlotOff + 1); + + // convert local function parameters to class fields and offset regular locals + case StoreInstruction ls when ls.slot() < gb.paramSlotOff && ls.typeKind().slotSize() == 2 -> + cob.aload(0).dup_x2().pop().putfield(gb.CD_this_gen, GeneratorBuilder.PARAM_PREFIX + ls.slot(), lt.paramType(ls.slot())); + case StoreInstruction ls when ls.slot() < gb.paramSlotOff -> + cob.aload(0).swap().putfield(gb.CD_this_gen, GeneratorBuilder.PARAM_PREFIX + ls.slot(), lt.paramType(ls.slot())); + case StoreInstruction ls -> { + lt.trackLocal(ls.slot() - gb.paramSlotOff + 1, ls.typeKind()); + cob.storeLocal(ls.typeKind(), ls.slot() - gb.paramSlotOff + 1); + } + + default -> cob.with(coe); + } + } + cob.labelBinding(invalidState); + cob.new_(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString())).dup() + .invokespecial(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString()), ConstantDescs.INIT_NAME, ConstantDescs.MTD_void) + .athrow(); + cob.labelBinding(end); + + lt.createLocalStoreFields(clb); + clb.withField("meow", GeneratorBuilder.CD_Gen, ClassFile.ACC_PRIVATE); + } +}