added support for parameter cancellation

This commit is contained in:
Parker TenBroeck 2025-05-05 16:25:33 -04:00
parent f0bc740e82
commit 051d5142d1
10 changed files with 154 additions and 64 deletions

View file

@ -20,6 +20,14 @@ tasks.withType<JavaCompile> {
options.compilerArgs.add("--enable-preview")
}
tasks.register<JavaExec>("basic"){
javaLauncher.set(javaToolchains.launcherFor(java.toolchain))
jvmArgs("--enable-preview")
group = "Demos"
mainClass = "basic.Main"
classpath = sourceSets["main"].runtimeClasspath
}
tasks.register<JavaExec>("lexer"){
javaLauncher.set(javaToolchains.launcherFor(java.toolchain))
jvmArgs("--enable-preview")

View file

@ -0,0 +1,7 @@
package basic;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
public class Futures {
}

View file

@ -0,0 +1,17 @@
package basic;
import com.parkertenbroeck.generator.Gen;
public class Gens {
public static Gen<Long, Void> primes(){
long number = 1;
Gen.yield(2L);
outer: while(true){
number += 2;
for(long i=2; i <= Math.sqrt(number); i ++){
if(number%i==0)continue outer;
}
Gen.yield(number);
}
}
}

View file

@ -0,0 +1,20 @@
package basic;
import com.parkertenbroeck.bcsm.RT;
import com.parkertenbroeck.bcsm.loadtime.StateMachineClassLoader;
import com.parkertenbroeck.generator.Gen;
public class Main {
public static void main(String[] args) {
RT.runWithStateMachines(StateMachineClassLoader.Config.builtin().write_classes("build/modified/generators/"), (Object) args);
primes();
}
static void primes(){
var gen = Gens.primes();
while(gen.next() instanceof Gen.Yield(var tok)) {
System.out.println(tok);
}
}
}

View file

@ -6,6 +6,7 @@ import com.parkertenbroeck.async_runtime.io.net.ServerSocket;
import com.parkertenbroeck.async_runtime.io.net.Socket;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.bcsm.loadtime.future.Cancellation;
import com.parkertenbroeck.future.Waker;
import java.io.IOException;
import java.net.InetSocketAddress;

View file

@ -1,6 +1,5 @@
package com.parkertenbroeck.bcsm.loadtime;
import java.lang.classfile.*;
import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute;
import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute;
@ -8,6 +7,7 @@ import java.lang.classfile.attribute.StackMapFrameInfo;
import java.lang.classfile.attribute.StackMapTableAttribute;
import java.lang.classfile.instruction.*;
import java.lang.constant.*;
import java.lang.reflect.AccessFlag;
import java.util.*;
import static java.lang.constant.ConstantDescs.*;
@ -180,24 +180,28 @@ public class FrameTracker {
this.smb = smb;
int offset = 0;
for (var param : smb.params) {
if(param == CD_long){
for (int i = 0; i < smb.params.length; i++) {
var param = smb.params[i];
if (param == CD_long) {
setLocal2(offset, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
}else if(param == CD_double){
} else if (param == CD_double) {
setLocal2(offset, Type.LONG_TYPE, Type.LONG2_TYPE);
}else if(!param.isPrimitive()){
} else if (!param.isPrimitive()) {
setLocal(offset, Type.referenceType(param));
}else if(param == CD_float){
} else if (param == CD_float) {
setLocal(offset, Type.FLOAT_TYPE);
}else if(param == CD_char){
} else if (param == CD_char) {
setLocal(offset, Type.CHAR_TYPE);
}else if(param == CD_boolean){
} else if (param == CD_boolean) {
setLocal(offset, Type.BOOLEAN_TYPE);
}else if(param == CD_byte){
} else if (param == CD_byte) {
setLocal(offset, Type.BYTE_TYPE);
}else if(param == CD_short){
} else if (param == CD_short) {
setLocal(offset, Type.SHORT_TYPE);
}else{
} else {
setLocal(offset, Type.INTEGER_TYPE);
}
offset += TypeKind.from(param).slotSize();
@ -412,6 +416,11 @@ public class FrameTracker {
decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
case DNEG ->
decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
case LCMP, DCMPL, DCMPG ->
decStack(4).pushStack(Type.INTEGER_TYPE);
case FCMPL, FCMPG ->
decStack(2).pushStack(Type.INTEGER_TYPE);
default -> throw new RuntimeException();
}
}

View file

@ -54,7 +54,7 @@ public class SavedStateTracker {
}
public LocalState load_param(int slot) {
public LocalState get_saved_local(int slot) {
for(var saved : saved){
if (saved instanceof LocalState(var name, var desc, int s) && s == slot) {
return (LocalState) saved;

View file

@ -12,6 +12,8 @@ import java.lang.reflect.AccessFlag;
import java.util.*;
import java.util.stream.Collectors;
import static java.lang.constant.ConstantDescs.*;
public abstract class StateMachineBuilder<T extends StateMachineBuilder<T>> {
public final static String PARAM_PREFIX = "param_";
public final static String LOCAL_PREFIX = "local_";
@ -33,6 +35,8 @@ public abstract class StateMachineBuilder<T extends StateMachineBuilder<T>> {
protected final HashMap<SpecialMethod, SpecialMethodBuilder<T>> smmap = new HashMap<>();
public record ParameterVariableAnnotation(Annotation annotation, String param, ClassDesc desc){}
protected final List<ParameterVariableAnnotation> parameterVariableAnnotations = new ArrayList<>();
record LState(String name, ClassDesc cd) {
@ -63,6 +67,7 @@ public abstract class StateMachineBuilder<T extends StateMachineBuilder<T>> {
.replace("/", "___")
.replace(";", "____")
.replace("[", "_____")
.replace(".", "______")
)
.collect(Collectors.joining("$"));
innerClassName = method_name + "______" + param_cnd;
@ -73,6 +78,25 @@ public abstract class StateMachineBuilder<T extends StateMachineBuilder<T>> {
this.MTD_init = MethodTypeDesc.of(ConstantDescs.CD_void, params);
this.paramSlotOff = Arrays.stream(params).mapToInt(p -> TypeKind.from(p).slotSize()).sum();
ArrayList<List<Annotation>> param_attrs;
var param_ann = src_mem.findAttributes(Attributes.runtimeVisibleParameterAnnotations());
if(!param_ann.isEmpty()) {
param_attrs = new ArrayList<>(param_ann.getFirst().parameterAnnotations());
if(!src_mem.flags().has(AccessFlag.STATIC)){
param_attrs.addFirst(List.of());
}
}else
param_attrs = new ArrayList<>(Collections.nCopies((src_mem.flags().has(AccessFlag.STATIC)?0:1)+src_mem.methodTypeSymbol().parameterList().size(), List.of()));
int offset = 0;
for (int i = 0; i < params.length; i++) {
var param = params[i];
for(var attr : param_attrs.get(i))
parameterVariableAnnotations.add(new ParameterVariableAnnotation(attr, StateMachineBuilder.PARAM_PREFIX + offset, param));
offset += TypeKind.from(param).slotSize();
}
frames = FrameTracker.frames(this, src_com);
}

View file

@ -3,7 +3,7 @@ package com.parkertenbroeck.bcsm.loadtime.future;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.LOCAL_VARIABLE})
@Target({ElementType.TYPE_USE, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER})
public @interface Cancellation {
String value() default "cancel";
}

View file

@ -183,58 +183,9 @@ public class FutureSMBuilder extends StateMachineBuilder<FutureSMBuilder> {
ArrayList<Consumer<CodeBuilder>> field_cancellation_behavior = new ArrayList<>();
for(var ann : frame.local_annotations()){
if(ann.annotation().classSymbol().descriptorString().equals(Cancellation.class.descriptorString())){
var param = sst.load_param(ann.slot()-paramSlotOff+2);
var param = sst.get_saved_local(ann.slot()-paramSlotOff+2);
ClassDesc owner = frame.locals()[ann.slot()].sym();
String name = "cancel";
for(var el : ann.annotation().elements()){
switch(el.name().stringValue()){
case "value" -> name = annotationStringValue(el.value());
case "owner" -> owner = annotationClassValue(el.value());
}
}
if(!owner.isClassOrInterface()){
throw new RuntimeException("Owner " + owner + " is not a class/interface cannot be used here");
}
boolean is_interface;
TypeKind ret;
try{
Class<?> clazz = getClass().getClassLoader().loadClass(owner.descriptorString().replace("/", ".").replace(";", "").substring(1));
var method = findMethod(clazz, name);
if(method==null){
throw new RuntimeException("Cannot find method '"+name+"' for class " + clazz);
}
clazz = method.getDeclaringClass();
owner = ClassDesc.ofDescriptor(clazz.descriptorString());
is_interface = clazz.isInterface();
ret = TypeKind.from(ClassDesc.ofDescriptor(method.getReturnType().descriptorString()));
}catch (Exception e){
throw new RuntimeException(e);
}
String final_name = name;
ClassDesc final_owner = owner;
field_cancellation_behavior.add(cob -> {
cob.trying(tcob -> {
tcob.aload(0).getfield(CD_this, param.name(), param.desc());
if(is_interface){
tcob.invokeinterface(final_owner, final_name, MethodTypeDesc.of(ConstantDescs.CD_void));
}else{
tcob.invokevirtual(final_owner, final_name, MethodTypeDesc.of(ConstantDescs.CD_void));
}
if(ret.slotSize()==1)tcob.pop();
if(ret.slotSize()==2)tcob.pop2();
}, cb -> cb.catchingAll(ccob -> {
ccob.aload(2).ifThenElse(Opcode.IFNONNULL, bcob -> {
bcob.aload(2).swap();
addSuppressed(bcob);
}, bcob -> {
bcob.astore(2);
});
}));
});
field_cancellation_behavior.add(make_canceler(param.name(), param.desc(), methodFromCancellationAnnotation(ann.annotation(), owner)));
}
}
cancellation_behavior.put(state.id(), cob -> {
@ -242,6 +193,50 @@ public class FutureSMBuilder extends StateMachineBuilder<FutureSMBuilder> {
});
}
private Method methodFromCancellationAnnotation(Annotation annotation, ClassDesc owner){
String name = "cancel";
for(var el : annotation.elements()){
if (el.name().stringValue().equals("value")) {
name = annotationStringValue(el.value());
}
}
try{
Class<?> clazz = getClass().getClassLoader().loadClass(owner.descriptorString().replace("/", ".").replace(";", "").substring(1));
var method = findMethod(clazz, name);
if(method==null)
throw new RuntimeException("Cannot find method '"+name+"' for class " + clazz);
return method;
}catch (Exception e){
throw new RuntimeException(e);
}
}
private Consumer<CodeBuilder> make_canceler(String field_name, ClassDesc field_desc, Method method) {
var owner = ClassDesc.ofDescriptor(method.getDeclaringClass().descriptorString());
return cob -> {
cob.trying(tcob -> {
tcob.aload(0).getfield(CD_this, field_name, field_desc);
if (method.getDeclaringClass().isInterface()) {
tcob.invokeinterface(owner, method.getName(), MethodTypeDesc.of(ConstantDescs.CD_void));
} else {
tcob.invokevirtual(owner, method.getName(), MethodTypeDesc.of(ConstantDescs.CD_void));
}
var ret = TypeKind.from(method.getReturnType());
if (ret.slotSize() == 1) tcob.pop();
if (ret.slotSize() == 2) tcob.pop2();
}, cb -> cb.catchingAll(ccob -> {
ccob.aload(2).ifThenElse(Opcode.IFNONNULL, bcob -> {
bcob.aload(2).swap();
addSuppressed(bcob);
}, bcob -> {
bcob.astore(2);
});
}));
};
}
private static Method findMethod(Class<?> owner, String name){
try{
return owner.getDeclaredMethod(name);
@ -274,8 +269,13 @@ public class FutureSMBuilder extends StateMachineBuilder<FutureSMBuilder> {
clb.withMethod("cancel", MethodTypeDesc.of(ConstantDescs.CD_void), ClassFile.ACC_PUBLIC, mb -> mb.withCode(cob -> {
this.synchronized_start(cob);
cob.aload(0).getfield(CD_this, STATE_NAME, ConstantDescs.CD_int).istore(1);// state
cob.aconst_null().astore(2);// exception
cob.iload(1).loadConstant(-1).ifThen(Opcode.IF_ICMPEQ, bob -> {
this.synchronized_exit(bob);
bob.return_();
});
cob.aload(0).loadConstant(-1).putfield(CD_this, STATE_NAME, ConstantDescs.CD_int);
cob.aload(0).getfield(CD_this, AWAITING_FIELD_NAME, CD_Future).ifThen(Opcode.IFNONNULL, boc -> {
@ -310,6 +310,10 @@ public class FutureSMBuilder extends StateMachineBuilder<FutureSMBuilder> {
cob.labelBinding(end);
}
for(var la : this.parameterVariableAnnotations){
make_canceler(la.param(), la.desc(), methodFromCancellationAnnotation(la.annotation(), la.desc())).accept(cob);
}
this.synchronized_exit(cob);
cob.aload(2).ifThen(Opcode.IFNONNULL, bcob -> {