mirror of
https://github.com/ParkerTenBroeck/coroutines.git
synced 2026-06-06 21:00:35 -04:00
separated functionality of generator state machine builders into separate classes
This commit is contained in:
parent
10d07e2c80
commit
c3cb7cb884
7 changed files with 449 additions and 285 deletions
|
|
@ -65,21 +65,42 @@ public class Examples {
|
|||
// return Gen.ret();
|
||||
// }
|
||||
|
||||
public static Gen<Double, Void> 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<Void, String> 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<Void, String> awaitTest(int number){
|
||||
try(var m = new Meow()){
|
||||
return Gen.ret(awaitTest2(number).await());
|
||||
}
|
||||
}
|
||||
|
||||
// public static Gen<Double, Void> 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();
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ public interface Gen<Y, R> {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<SwitchCase>();
|
||||
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<Integer, ClassDesc> parameter_map = new HashMap<>();
|
||||
|
||||
HashMap<Integer, TypeKind> localVarTypes = new HashMap<>();
|
||||
HashMap<Label, StackMapFrameInfo> stackMapFrames = new HashMap<>();
|
||||
StackMapFrameInfo currentFrame;
|
||||
ArrayList<LocalStore> 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<StackMapFrameInfo>();
|
||||
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<Saved>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
138
src/generator/runtime/LocalTracker.java
Normal file
138
src/generator/runtime/LocalTracker.java
Normal file
|
|
@ -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<Integer, ClassDesc> parameter_map = new HashMap<>();
|
||||
|
||||
HashMap<Integer, TypeKind> localVarTypes = new HashMap<>();
|
||||
HashMap<Label, StackMapFrameInfo> stackMapFrames = new HashMap<>();
|
||||
StackMapFrameInfo currentFrame;
|
||||
ArrayList<LocalStore> 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<StackMapFrameInfo>();
|
||||
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<Saved>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
233
src/generator/runtime/StateMachineBuilder.java
Normal file
233
src/generator/runtime/StateMachineBuilder.java
Normal file
|
|
@ -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<SpecialMethod, Function<StateMachineBuilder, SpecialMethodHandler>> smmap = new HashMap<>();
|
||||
private boolean ignore_next_pop = false;
|
||||
final ArrayList<SwitchCase> 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<SpecialMethodHandler>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue