migrated to gradle

This commit is contained in:
Parker TenBroeck 2025-05-04 23:57:13 -04:00
parent 0dd6fb237d
commit ecb18b417e
43 changed files with 619 additions and 177 deletions

View file

@ -0,0 +1,56 @@
package com.parkertenbroeck.async_runtime;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.util.Timer;
import java.util.TimerTask;
public class Delay implements Future<Void, RuntimeException> {
private final static Timer timer;
private TimerTask task;
static {
timer = new Timer(true);
}
private int delay;
private boolean ready;
protected Delay(int ms) {
if (ms < 0) throw new IllegalArgumentException("async_example.Delay cannot be negative");
delay = ms;
}
public static Future<Void, RuntimeException> delay(int ms){
return new Delay(ms);
}
@Override
public void cancel() {
if (task != null) task.cancel();
}
@Override
public synchronized Object poll(Waker waker) {
if (delay == 0) {
ready = true;
delay = -1;
return null;
}
if (delay != -1) {
task = new TimerTask() {
@Override
public void run() {
ready = true;
waker.wake();
}
};
timer.schedule(task, delay);
delay = -1;
}
if (ready) return null;
return Pending.INSTANCE;
}
}

View file

@ -0,0 +1,136 @@
package com.parkertenbroeck.async_runtime;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.util.ArrayDeque;
import java.util.HashSet;
public class Jokio implements Runnable{
public class TaskHandle<T, E extends Throwable> implements Waker, Future<T, E>{
private final Future<T, E> future;
private Object result = Pending.INSTANCE;
private Throwable err;
private TaskHandle(Future<T, E> future) {
this.future = future;
}
@Override
public void wake() {
synchronized (Jokio.this){
if(currentSet.contains(this)&&wokeSet.add(this))
wokeQueue.add(this);
Jokio.this.notifyAll();
}
}
public Jokio runtime(){
return Jokio.this;
}
@Override
public Object poll(Waker waker) throws E {
if(err!=null)throw (E)err;
return result;
}
public T blocking() throws E{
while(result == Pending.INSTANCE) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(err!=null)throw (E)err;
}
return (T) result;
}
@Override
public synchronized void cancel() throws E {
synchronized (Jokio.this){
currentSet.remove(this);
}
future.cancel();
}
}
public static Future<Jokio, RuntimeException> runtime(){
return Jokio::runtime;
}
public static Jokio runtime(Waker waker){
return ((TaskHandle<?, ?>)waker).runtime();
}
private final ArrayDeque<TaskHandle<?, ?>> wokeQueue = new ArrayDeque<>();
private final HashSet<TaskHandle<?, ?>> wokeSet = new HashSet<>();
private final HashSet<TaskHandle<?, ?>> currentSet = new HashSet<>();
public <T, E extends Throwable> T blocking(Future<T, E> fut) throws E {
var result = spawn(fut);
run();
return result.blocking();
}
public <T, E extends Throwable> TaskHandle<T, E> spawn(Future<T, E> future){
var task = new TaskHandle<>(future);
synchronized (this){
currentSet.add(task);
wokeQueue.add(task);
wokeSet.add(task);
}
return task;
}
@Override
public void run(){
while(true) {
synchronized (this) {
if(currentSet.isEmpty())break;
while (wokeQueue.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
TaskHandle<?, ?> task;
synchronized (this){
task = wokeQueue.poll();
wokeSet.remove(task);
if(!currentSet.contains(task))continue;
}
Object result;
try{
synchronized (task){
result = task.future.poll(task);
}
}catch (Throwable t){
synchronized (task){
task.err = t;
task.notify();
}
System.out.println("Future " + task.future + " Threw Exception");
t.printStackTrace();
synchronized (this){
currentSet.remove(task);
}
continue;
}
synchronized (this){
if(result!=Future.Pending.INSTANCE) {
synchronized (task){
task.result = result;
task.notify();
}
currentSet.remove(task);
System.out.println(result);
}
}
}
}
}

View file

@ -0,0 +1,122 @@
package com.parkertenbroeck.async_runtime;
import com.parkertenbroeck.future.Future;
import java.util.*;
import java.util.function.Function;
public class Util {
@SafeVarargs
public static <T, E extends Throwable> Future<T, E> first(Future<T, E>... futures){
return waker -> {
int i;
boolean resolved = false;
Object result = Future.Pending.INSTANCE;
for(i = 0; i < futures.length; i ++){
result = futures[i].poll(waker);
resolved = result!=Future.Pending.INSTANCE;
if(resolved)break;
}
if(resolved){
for(int j = 0; j < futures.length; j ++)
if(j!=i)
futures[j].cancel();
}
return result;
};
}
public static <T, E extends Throwable> Future<T, E> first(List<Future<T, E>> futures){
return waker -> {
int i = 0;
boolean resolved = false;
Object result = Future.Pending.INSTANCE;
for(var el : futures){
result = el.poll(waker);
resolved = result!=Future.Pending.INSTANCE;
if(resolved)break;
i++;
}
if(resolved){
int j = 0;
for(var el : futures){
if(i==j)
el.cancel();
j++;
}
}
return result;
};
}
public interface Func<R, T, E extends Throwable>{
R call(T p) throws E;
}
public interface FuncV<T, E extends Throwable>{
void call(T p) throws E;
}
public record Selectee<R, T, E extends Throwable>(Future<T, ? extends E> future, Func<R, T, ? extends E> acceptor){ }
public static <R, T, E extends Throwable> Selectee<R, T, E> selectee(Future<T, ? extends E> future, Func<R, T, ? extends E> acceptor){
return new Selectee<>(future, acceptor);
}
public static <T, E extends Throwable> Selectee<Void, T, E> selectee(Future<T, ? extends E> future, FuncV<T, ? extends E> acceptor){
return new Selectee<>(future, v -> {
acceptor.call(v);
return null;
});
}
@SuppressWarnings("unchecked")
public static <R, E extends Throwable> Future<R, ? extends E> select(Selectee<? extends R, ?, ? extends E>... selectees){
return waker -> {
int i;
Object result = Future.Pending.INSTANCE;
for(i = 0; i < selectees.length; i ++){
result = selectees[i].future.poll(waker);
if(result!=Future.Pending.INSTANCE)break;
}
if(result!=Future.Pending.INSTANCE) {
for (int j = 0; j < selectees.length; j++)
if(j==i)
selectees[i].future.cancel();
return ((Func<R, Object, E>)selectees[i].acceptor).call(result);
}
return Future.Pending.INSTANCE;
};
}
public static <T, E extends Throwable> Future<T[], E> all(Future<T, E>... futures){
var resolved = new Object[futures.length];
Arrays.fill(resolved, Future.Pending.INSTANCE);
return waker -> {
boolean done = true;
for(int i = 0; i < resolved.length; i ++){
if(resolved[i]==Future.Pending.INSTANCE){
var result = futures[i].poll(waker);
resolved[i] = result;
done &= result != Future.Pending.INSTANCE;
}
}
if(done)return resolved;
return Future.Pending.INSTANCE;
};
}
public static <T, E extends Throwable> Future<List<T>, E> all(List<Future<T, E>> futures){
var resolved = new ArrayList<Object>(Collections.nCopies(futures.size(), Future.Pending.INSTANCE));
return waker -> {
boolean done = true;
for(int i = 0; i < resolved.size(); i ++){
if(resolved.get(i)==Future.Pending.INSTANCE){
var result = futures.get(i).poll(waker);
resolved.set(i, result);
done &= result != Future.Pending.INSTANCE;
}
}
if(done)return resolved;
return Future.Pending.INSTANCE;
};
}
}

View file

@ -0,0 +1,10 @@
package com.parkertenbroeck.async_runtime.io;
import com.parkertenbroeck.future.Future;
import java.nio.ByteBuffer;
public interface Readable<E extends Throwable> {
Future<Integer, E> read(ByteBuffer buffer);
Future<Integer, E> read_all(ByteBuffer buffer);
}

View file

@ -0,0 +1,57 @@
package com.parkertenbroeck.async_runtime.io;
import java.io.IOException;
import java.nio.channels.*;
import java.util.ArrayDeque;
public abstract class SelectorThread<T extends SelectableChannel, A> extends Thread{
private final Selector selector;
private record ToRegister<T, A>(T sc, int ops, A waker){}
private final ArrayDeque<ToRegister<T, A>> to_register = new ArrayDeque<>();
public SelectorThread(String name) throws IOException {
selector = Selector.open();
this.setName(name + " Polling Thread");
this.setDaemon(true);
this.start();
}
public abstract void handle(SelectionKey key, T t, A a) throws IOException;
public void register(T sc, int ops, A waker){
synchronized (to_register){
to_register.add(new ToRegister<>(sc, ops, waker));
}
selector.wakeup();
}
@SuppressWarnings("unchecked")
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
try{
synchronized (to_register){
while(!to_register.isEmpty()){
var to = to_register.poll();
to.sc.register(selector, to.ops, to.waker);
}
}
selector.select();
var keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
handle(key, (T)key.channel(), (A)key.attachment());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,10 @@
package com.parkertenbroeck.async_runtime.io;
import com.parkertenbroeck.future.Future;
import java.nio.ByteBuffer;
public interface Writable<E extends Throwable> {
Future<Integer, E> write(ByteBuffer buffer);
Future<Integer, E> write_all(ByteBuffer buffer);
}

View file

@ -0,0 +1,165 @@
package com.parkertenbroeck.async_runtime.io.fs;
import com.parkertenbroeck.async_runtime.io.Readable;
import com.parkertenbroeck.async_runtime.io.Writable;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class File implements AutoCloseable, Readable<IOException>, Writable<IOException> {
private final AsynchronousFileChannel channel;
protected File(AsynchronousFileChannel sc){
this.channel = sc;
}
public static File open(Path path) throws IOException {
return new File(AsynchronousFileChannel.open(path, StandardOpenOption.READ));
}
public long size() throws IOException {
return channel.size();
}
@Override
public Future<Integer, IOException> read(ByteBuffer buffer) {
return read(buffer, 0);
}
@Override
public Future<Integer, IOException> read_all(ByteBuffer buffer) {
return read_all(buffer, 0);
}
@Override
public Future<Integer, IOException> write(ByteBuffer buffer) {
return write(buffer, 0);
}
@Override
public Future<Integer, IOException> write_all(ByteBuffer buffer) {
return write_all(buffer, 0);
}
public Future<Integer, IOException> write_all(ByteBuffer buffer, long position){
return new Future<>() {
int written = 0;
Throwable t;
@Override
public Object poll(Waker waker) throws IOException {
if(t!=null)throw (IOException) t;
if(!buffer.hasRemaining()) return written;
channel.write(buffer, written+position, waker, new CompletionHandler<>() {
@Override
public void completed(Integer result, Waker attachment) {
written = result;
waker.wake();
}
@Override
public void failed(Throwable exc, Waker attachment) {
t = exc;
attachment.wake();;
}
});
return Pending.INSTANCE;
}
};
}
public Future<Integer, IOException> write(ByteBuffer buffer, long position){
return new Future<>() {
int written = 0;
Throwable t;
@Override
public Object poll(Waker waker) throws IOException {
if(t!=null)throw (IOException) t;
if(written!=0) return written;
channel.write(buffer, written+position, waker, new CompletionHandler<>() {
@Override
public void completed(Integer result, Waker attachment) {
written = result;
waker.wake();
}
@Override
public void failed(Throwable exc, Waker attachment) {
t = exc;
attachment.wake();;
}
});
return Pending.INSTANCE;
}
};
}
public Future<Integer, IOException> read(ByteBuffer buffer, long position){
return new Future<>() {
int read = 0;
boolean eos = false;
Throwable t;
@Override
public Object poll(Waker waker) throws IOException {
if(t!=null)throw (IOException) t;
if(eos) return read;
if(read!=0) return read;
channel.read(buffer, read+position, waker, new CompletionHandler<>() {
@Override
public void completed(Integer result, Waker attachment) {
if(result==-1)eos = true;
else read += result;
waker.wake();
waker.wake();
}
@Override
public void failed(Throwable exc, Waker attachment) {
t = exc;
attachment.wake();;
}
});
return Pending.INSTANCE;
}
};
}
public Future<Integer, IOException> read_all(ByteBuffer buffer, long position){
return new Future<>() {
int read = 0;
boolean eos = false;
Throwable t;
@Override
public Object poll(Waker waker) throws IOException {
if(t!=null)throw (IOException) t;
if(eos) return read;
if(!buffer.hasRemaining()) return read;
channel.read(buffer, read+position, waker, new CompletionHandler<>() {
@Override
public void completed(Integer result, Waker attachment) {
if(result==-1)eos = true;
else read += result;
waker.wake();
}
@Override
public void failed(Throwable exc, Waker attachment) {
t = exc;
attachment.wake();;
}
});
return Pending.INSTANCE;
}
};
}
@Override
public void close() throws IOException {
channel.close();
}
}

View file

@ -0,0 +1,146 @@
package com.parkertenbroeck.async_runtime.io.net;
import com.parkertenbroeck.async_runtime.io.Readable;
import com.parkertenbroeck.async_runtime.io.SelectorThread;
import com.parkertenbroeck.async_runtime.io.Writable;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
public class DatagramSocket implements AutoCloseable, Readable<IOException>, Writable<IOException> {
private final static SelectorThread<DatagramChannel, Waker> SELECTOR;
static {
try {
SELECTOR = new SelectorThread<>("DatagramSocket") {
@Override
public void handle(SelectionKey key, DatagramChannel c, Waker w) {
if (!key.isValid()) {
}else if(key.isAcceptable()){
}else if(key.isConnectable()){
w.wake();
}else if(key.isReadable()){
w.wake();
}else if(key.isWritable()){
w.wake();
}
}
};
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private final DatagramChannel socket;
protected DatagramSocket(DatagramChannel sc){
this.socket = sc;
}
public static DatagramSocket open(InetSocketAddress inet) throws IOException {
var socket = DatagramChannel.open();
socket.configureBlocking(false);
return new DatagramSocket(socket);
}
public DatagramSocket bind(InetSocketAddress inet) throws IOException {
socket.bind(inet);
return this;
}
public DatagramSocket connect(InetSocketAddress inet) throws IOException {
socket.connect(inet);
return this;
}
public DatagramSocket disconnect() throws IOException{
socket.disconnect();
return this;
}
public <T> DatagramSocket set_options(SocketOption<T> option, T value) throws IOException{
socket.setOption(option, value);
return this;
}
public SocketAddress local_address() throws IOException {
return socket.getLocalAddress();
}
public SocketAddress remote_address() throws IOException {
return socket.getRemoteAddress();
}
@Override
public Future<Integer, IOException> write(ByteBuffer buffer){
return waker -> {
var wrote = socket.write(buffer);
if(wrote!=0) return wrote;
SELECTOR.register(socket, SelectionKey.OP_WRITE, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> write_all(ByteBuffer buffer){
var wrote = buffer.remaining();
return waker -> {
socket.write(buffer);
if(!buffer.hasRemaining()) return wrote;
SELECTOR.register(socket, SelectionKey.OP_WRITE, waker);
return Future.Pending.INSTANCE;
};
}
public Future<Integer, IOException> send(ByteBuffer buffer, SocketAddress address){
return waker -> {
var sent = socket.send(buffer, address);
if(sent!=0)return sent;
SELECTOR.register(socket, SelectionKey.OP_WRITE, waker);
return Future.Pending.INSTANCE;
};
}
public Future<SocketAddress, IOException> receive(ByteBuffer buffer){
return waker -> {
var address = socket.receive(buffer);
if(address!=null)return address;
SELECTOR.register(socket, SelectionKey.OP_READ, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> read(ByteBuffer buffer){
return waker -> {
var read = socket.read(buffer);
if(read!=0) return read;
SELECTOR.register(socket, SelectionKey.OP_READ, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> read_all(ByteBuffer buffer){
int read = buffer.remaining();
return waker -> {
var read_now = socket.read(buffer);
if(read_now ==-1)throw new IOException("Reached EOS while filling buffer");
if(!buffer.hasRemaining()) return read;
SELECTOR.register(socket, SelectionKey.OP_READ, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public void close() throws IOException {
socket.close();
}
}

View file

@ -0,0 +1,71 @@
package com.parkertenbroeck.async_runtime.io.net;
import com.parkertenbroeck.async_runtime.io.SelectorThread;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
public class ServerSocket implements AutoCloseable {
private final static SelectorThread<ServerSocketChannel, Waker> SELECTOR;
static {
try {
SELECTOR = new SelectorThread<>("ServerSocket") {
@Override
public void handle(SelectionKey key, ServerSocketChannel c, Waker w) {
if (!key.isValid()) {
}else if(key.isAcceptable()){
w.wake();
}
}
};
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private final ServerSocketChannel socket;
private ServerSocket(ServerSocketChannel sc){
this.socket = sc;
}
public static ServerSocket bind(InetSocketAddress inet) throws IOException {
var socket = ServerSocketChannel.open();
socket.configureBlocking(false);
socket.bind(inet);
return new ServerSocket(socket);
}
public Future<Socket, IOException> accept(){
return waker -> {
var accepted = socket.accept();
if(accepted==null) {
SELECTOR.register(socket, SelectionKey.OP_ACCEPT, waker);
return Future.Pending.INSTANCE;
}
accepted.configureBlocking(false);
return new Socket(accepted);
};
}
public <T> void set_options(SocketOption<T> option, T value) throws IOException{
socket.setOption(option, value);
}
public SocketAddress local_address() throws IOException {
return socket.getLocalAddress();
}
@Override
public void close() throws IOException {
socket.close();
}
}

View file

@ -0,0 +1,128 @@
package com.parkertenbroeck.async_runtime.io.net;
import com.parkertenbroeck.async_runtime.io.Readable;
import com.parkertenbroeck.async_runtime.io.SelectorThread;
import com.parkertenbroeck.async_runtime.io.Writable;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
public class Socket implements AutoCloseable, Readable<IOException>, Writable<IOException> {
private final static SelectorThread<SocketChannel, Waker> SELECTOR;
static {
try {
SELECTOR = new SelectorThread<>("Socket") {
@Override
public void handle(SelectionKey key, SocketChannel c, Waker w) throws IOException {
if (!key.isValid()) {
}else if(key.isAcceptable()){
}else if(key.isConnectable()){
c.finishConnect();
w.wake();
}else if(key.isReadable()){
w.wake();
}else if(key.isWritable()){
w.wake();
}
}
};
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private final SocketChannel socket;
protected Socket(SocketChannel sc){
this.socket = sc;
}
public static Future<Socket, IOException> connect(InetSocketAddress inet) {
return new Future<>() {
public SocketChannel socket;
@Override
public Object poll(Waker waker) throws IOException {
if(socket==null){
socket = SocketChannel.open();
socket.configureBlocking(false);
var connected = socket.connect(inet);
if(!connected) {
SELECTOR.register(socket, SelectionKey.OP_CONNECT, waker);
return Pending.INSTANCE;
}
}
if(socket.isConnected()) return new Socket(socket);
return Pending.INSTANCE;
}
};
}
public <T> Socket set_options(SocketOption<T> option, T value) throws IOException{
socket.setOption(option, value);
return this;
}
public SocketAddress local_address() throws IOException {
return socket.getLocalAddress();
}
public SocketAddress remote_address() throws IOException {
return socket.getRemoteAddress();
}
@Override
public Future<Integer, IOException> write(ByteBuffer buffer){
return waker -> {
var wrote = socket.write(buffer);
if(wrote!=0) return wrote;
SELECTOR.register(socket, SelectionKey.OP_WRITE, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> write_all(ByteBuffer buffer){
var wrote = buffer.remaining();
return waker -> {
socket.write(buffer);
if(!buffer.hasRemaining()) return wrote;
SELECTOR.register(socket, SelectionKey.OP_WRITE, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> read(ByteBuffer buffer){
return waker -> {
var read = socket.read(buffer);
if(read!=0) return read;
SELECTOR.register(socket, SelectionKey.OP_READ, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public Future<Integer, IOException> read_all(ByteBuffer buffer){
int read = buffer.remaining();
return waker -> {
var read_now = socket.read(buffer);
if(read_now ==-1)throw new IOException("Reached EOS while filling buffer");
if(!buffer.hasRemaining()) return read;
SELECTOR.register(socket, SelectionKey.OP_READ, waker);
return Future.Pending.INSTANCE;
};
}
@Override
public void close() throws IOException {
socket.close();
}
}

View file

@ -0,0 +1,33 @@
package com.parkertenbroeck.future;
public interface Future<R, E extends Throwable> {
Object poll(Waker waker) throws E;
default R await() throws E{
throw new RuntimeException("NO!");
}
default void cancel() throws E{}
default Future<R, E> non_cancelable(){
return this::poll;
}
static <R, E extends Throwable> Future<R, E> ret(R r){
throw new RuntimeException("NO!");
}
static <E extends Throwable> Future<Void, E> ret(){
throw new RuntimeException();
}
static void yield() {
throw new RuntimeException("NO!");
}
final class Pending{
public static final Pending INSTANCE = new Pending();
private Pending(){}
}
}

View file

@ -0,0 +1,10 @@
package com.parkertenbroeck.future;
public interface Waker {
static Waker waker() {
throw new RuntimeException("NO!");
}
void wake();
}

View file

@ -0,0 +1,27 @@
package com.parkertenbroeck.gen;
public interface Gen<Y, R> {
Res<Y, R> next();
static <Y, R> Gen<Y, R> yield(Y y) {
throw new RuntimeException();
}
static <Y, R> Gen<Void, R> yield() {
throw new RuntimeException();
}
static <Y, R> Gen<Y, R> ret(R r) {throw new RuntimeException();}
static <Y, R> Gen<Y, Void> ret() {
throw new RuntimeException();
}
default R await(){
while(true){
var res = next();
if(res instanceof Ret r)return (R)r.v;
}
}
sealed interface Res<Y, R>{}
record Yield<Y, R>(Y v) implements Res<Y, R>{}
record Ret<Y, R>(R v) implements Res<Y, R>{}
}

View file

@ -0,0 +1,15 @@
package com.parkertenbroeck.generators;
import com.parkertenbroeck.generators.loadtime.GeneratorClassLoader;
public class RT {
public static void runWithGeneratorSupport(Class<? extends Runnable> clazz){
var loader = new GeneratorClassLoader(RT.class.getClassLoader());
try{
((Runnable)loader.loadClass(clazz.getName()).getConstructor().newInstance()).run();
}catch (Exception e){
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,48 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.instruction.LineNumber;
import java.util.Arrays;
public record Frame(FrameTracker.Type[] locals, FrameTracker.Type[] stack, int bci, LineNumber line, FrameTracker.LocalVariableAnnotation[] local_annotations) {
@Override
public String toString() {
return "Frame[l=" + Arrays.toString(locals)
+ ", s=" + Arrays.toString(stack)
+ ", bci=" + bci
+ ", line="+line
+ ", local_annotations=" + Arrays.toString(local_annotations)
+ "]";
}
public void save_locals(StateMachineBuilder smb, CodeBuilder cob, SavedStateTracker sst, int loc_off){
int slot = 0;
for (var entry : locals) {
slot++;// <-----
if (slot <= smb.paramSlotOff) continue;
if(entry.tag() == FrameTracker.Type.TOP_TYPE.tag())continue;
if (entry.isCategory2_2nd()) continue;
sst.save_local(smb, cob, entry.toCD(), slot - smb.paramSlotOff + loc_off - 1); //minus one cause increment before here
}
}
public void save_stack(StateMachineBuilder smb, CodeBuilder cob, SavedStateTracker sst, int stack_off) {
for(int i = stack.length-1-stack_off; i >= 0; i --){
if(stack[i].isCategory2_2nd())continue;
sst.save_stack(smb, cob, stack[i].toCD());
}
}
public SavedStateTracker save(StateMachineBuilder smb, CodeBuilder cob, SavedStateTracker sst, int loc_off, int stack_off) {
save_locals(smb, cob, sst, loc_off);
save_stack(smb, cob, sst, stack_off);
return sst;
}
public SavedStateTracker save(StateMachineBuilder smb, CodeBuilder cob, int loc_off, int stack_off) {
var sst = new SavedStateTracker();
return save(smb, cob, sst, loc_off, stack_off);
}
}

View file

@ -0,0 +1,517 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.*;
import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute;
import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute;
import java.lang.classfile.attribute.StackMapFrameInfo;
import java.lang.classfile.attribute.StackMapTableAttribute;
import java.lang.classfile.instruction.*;
import java.lang.constant.*;
import java.util.*;
import static java.lang.constant.ConstantDescs.*;
public class FrameTracker {
private static final int ITEM_TOP = 0,
ITEM_INTEGER = 1,
ITEM_FLOAT = 2,
ITEM_DOUBLE = 3,
ITEM_LONG = 4,
ITEM_NULL = 5,
ITEM_UNINITIALIZED_THIS = 6,
ITEM_OBJECT = 7,
ITEM_UNINITIALIZED = 8,
ITEM_BOOLEAN = 9,
ITEM_BYTE = 10,
ITEM_SHORT = 11,
ITEM_CHAR = 12,
ITEM_LONG_2ND = 13,
ITEM_DOUBLE_2ND = 14;
public static ArrayList<Frame> frames(StateMachineBuilder smb, CodeModel src_com) {
var ft = new FrameTracker(smb, src_com);
var frames = new ArrayList<Frame>();
for(var coe : src_com){
if(coe instanceof Instruction) {
frames.add(new Frame(ft.locals(), ft.stack(), ft.bci, ft.current_line_number, ft.local_annotations()));
}
ft.encounter(coe);
}
frames.add(new Frame(ft.locals(), ft.stack(), ft.bci, null, ft.local_annotations()));
return frames;
}
private LocalVariableAnnotation[] local_annotations() {
return this.activeAnnotations.toArray(LocalVariableAnnotation[]::new);
}
public Type[] locals() {
return locals.toArray(Type[]::new);
}
public Type[] stack() {
return stack.toArray(Type[]::new);
}
public record Type(int tag, ClassDesc sym, int bci) {
static final Type TOP_TYPE = simpleType(ITEM_TOP),
NULL_TYPE = simpleType(ITEM_NULL),
INTEGER_TYPE = simpleType(ITEM_INTEGER),
FLOAT_TYPE = simpleType(ITEM_FLOAT),
LONG_TYPE = simpleType(ITEM_LONG),
LONG2_TYPE = simpleType(ITEM_LONG_2ND),
DOUBLE_TYPE = simpleType(ITEM_DOUBLE),
BOOLEAN_TYPE = simpleType(ITEM_BOOLEAN),
BYTE_TYPE = simpleType(ITEM_BYTE),
CHAR_TYPE = simpleType(ITEM_CHAR),
SHORT_TYPE = simpleType(ITEM_SHORT),
DOUBLE2_TYPE = simpleType(ITEM_DOUBLE_2ND),
UNITIALIZED_THIS_TYPE = simpleType(ITEM_UNINITIALIZED_THIS);
static final Type STRING_TYPE = referenceType(CD_String);
static final Type METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle);
static final Type METHOD_TYPE = referenceType(CD_MethodType);
@Override
public String toString(){
return switch(tag){
case ITEM_TOP -> "TOP";
case ITEM_INTEGER -> "int";
case ITEM_FLOAT -> "float";
case ITEM_DOUBLE -> "double";
case ITEM_LONG -> "long";
case ITEM_NULL -> sym==null?"null":"null("+sym.displayName()+")";
case ITEM_UNINITIALIZED_THIS -> sym==null?"uninitialized(this)":"uninitialized(this "+sym.displayName()+")";
case ITEM_OBJECT -> "Object("+sym.displayName()+")";
case ITEM_UNINITIALIZED -> sym==null?"uninitialized()":"uninitialized("+sym.displayName()+")";
case ITEM_BOOLEAN -> "boolean";
case ITEM_BYTE -> "byte";
case ITEM_SHORT -> "short";
case ITEM_CHAR -> "char";
case ITEM_LONG_2ND -> "long2";
case ITEM_DOUBLE_2ND -> "double2";
default -> throw new IllegalStateException("Unexpected value: " + tag);
};
}
private static Type simpleType(int tag) {
return new Type(tag, null, 0);
}
static Type referenceType(ClassDesc desc) {
return new Type(ITEM_OBJECT, desc, 0);
}
static Type uninitializedType(ClassDesc sym, int bci) {
return new Type(ITEM_UNINITIALIZED, sym, bci);
}
@Override
public boolean equals(Object o) {
return (o instanceof Type(int tag, ClassDesc sym, int bci))
&& tag == this.tag && bci == this.bci && Objects.equals(this.sym, sym);
}
boolean isCategory2_2nd() {
return this == DOUBLE2_TYPE || this == LONG2_TYPE;
}
boolean isReference() {
return tag == ITEM_OBJECT || this == NULL_TYPE;
}
boolean isObject() {
return tag == ITEM_OBJECT && sym.isClassOrInterface();
}
boolean isArray() {
return tag == ITEM_OBJECT && sym.isArray();
}
static Type verificationType(StackMapFrameInfo.VerificationTypeInfo v, FrameTracker t){
return switch (v){
case StackMapFrameInfo.ObjectVerificationTypeInfo o ->
Type.referenceType(o.classSymbol());
case StackMapFrameInfo.SimpleVerificationTypeInfo s ->
Type.simpleType(s.tag());
case StackMapFrameInfo.UninitializedVerificationTypeInfo u ->
Type.uninitializedType(null, t.bciMap.get(u.newTarget()));
};
}
ClassDesc toCD(){
return switch (tag) {
case ITEM_BOOLEAN -> CD_boolean;
case ITEM_BYTE -> CD_byte;
case ITEM_CHAR -> CD_char;
case ITEM_SHORT -> CD_short;
case ITEM_INTEGER -> CD_int;
case ITEM_LONG -> CD_long;
case ITEM_FLOAT -> CD_float;
case ITEM_DOUBLE -> CD_double;
case ITEM_OBJECT -> sym;
case ITEM_NULL -> CD_Object;
default -> throw new RuntimeException();
};
}
}
final ArrayList<Type> stack = new ArrayList<>();
final ArrayList<Type> locals = new ArrayList<>();
final StateMachineBuilder smb;
LineNumber current_line_number = null;
int bci = 0;
HashMap<Label, StackMapFrameInfo> stackMapFrames = new HashMap<>();
HashMap<Label, Integer> bciMap = new HashMap<>();
public record LocalVariableAnnotation(Annotation annotation, int slot){}
final HashSet<LocalVariableAnnotation> activeAnnotations = new HashSet<>();
final HashMap<Label, List<LocalVariableAnnotation>> annotationStartMap = new HashMap<>();
final HashMap<Label, List<LocalVariableAnnotation>> annotationEndMap = new HashMap<>();
FrameTracker(StateMachineBuilder smb, CodeModel com) {
this.smb = smb;
int offset = 0;
for (var param : smb.params) {
if(param == CD_long){
setLocal2(offset, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
}else if(param == CD_double){
setLocal2(offset, Type.LONG_TYPE, Type.LONG2_TYPE);
}else if(!param.isPrimitive()){
setLocal(offset, Type.referenceType(param));
}else if(param == CD_float){
setLocal(offset, Type.FLOAT_TYPE);
}else if(param == CD_char){
setLocal(offset, Type.CHAR_TYPE);
}else if(param == CD_boolean){
setLocal(offset, Type.BOOLEAN_TYPE);
}else if(param == CD_byte){
setLocal(offset, Type.BYTE_TYPE);
}else if(param == CD_short){
setLocal(offset, Type.SHORT_TYPE);
}else{
setLocal(offset, Type.INTEGER_TYPE);
}
offset += TypeKind.from(param).slotSize();
}
int bci = 0;
for(var ce : com){
if(ce instanceof Instruction i)
bci += i.sizeInBytes();
if(ce instanceof Label l)
bciMap.put(l, bci);
if(ce instanceof RuntimeVisibleTypeAnnotationsAttribute tas){
for(var ta : tas.annotations()){
switch(ta.targetInfo()){
case TypeAnnotation.CatchTarget ct -> {}
case TypeAnnotation.EmptyTarget et -> {}
case TypeAnnotation.FormalParameterTarget fpt -> {}
case TypeAnnotation.LocalVarTarget lvt -> {
for(var el : lvt.table()){
var lva = new LocalVariableAnnotation(ta.annotation(), el.index());
annotationStartMap
.computeIfAbsent(el.startLabel(), k -> new ArrayList<>())
.add(lva);
annotationEndMap
.computeIfAbsent(el.endLabel(), k -> new ArrayList<>())
.add(lva);
}
}
case TypeAnnotation.OffsetTarget ot -> {}
case TypeAnnotation.SupertypeTarget stt -> {}
case TypeAnnotation.ThrowsTarget tt -> {}
case TypeAnnotation.TypeArgumentTarget tat -> {}
case TypeAnnotation.TypeParameterBoundTarget tpbt -> {}
case TypeAnnotation.TypeParameterTarget tpt -> {}
}
}
}
}
for (var attr : com.findAttributes(Attributes.stackMapTable())) {
for (var smfi : attr.entries()) {
stackMapFrames.put(smfi.target(), smfi);
}
}
}
FrameTracker pushStack(ClassDesc desc) {
if (desc == CD_long) return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
return desc == CD_void ? this
: pushStack(
desc.isPrimitive()
? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
: Type.referenceType(desc));
}
FrameTracker pushStack(Type... types) {
stack.addAll(Arrays.asList(types));
return this;
}
FrameTracker pushStack(Type type) {
stack.add(type);
return this;
}
FrameTracker pushStack(Type type1, Type type2) {
stack.add(type1);
stack.add(type2);
return this;
}
Type popStack() {
return stack.removeLast();
}
FrameTracker decStack(int size) {
for(int i = 0; i < size; i ++)stack.removeLast();
return this;
}
void setLocal(int slot, Type type){
while(locals.size()<=slot)locals.add(null);
var old = locals.get(slot);
if(old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE)
locals.set(slot+1, Type.TOP_TYPE);
if(old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE)
locals.set(slot-1, Type.TOP_TYPE);
locals.set(slot, type);
}
void setLocal2(int slot, Type type1, Type type2){
while(locals.size()<=slot+1)locals.add(null);
var old = locals.get(slot+1);
if(old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE)
locals.set(slot+2, Type.TOP_TYPE);
old = locals.get(slot);
if(old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE)
locals.set(slot-1, Type.TOP_TYPE);
locals.set(slot, type1);
locals.set(slot+1, type2);
}
public void encounter(CodeElement ce){
switch(ce){
case Label l -> encounterLabel(l);
case Instruction ins -> {
switch (ins) {
case ArrayLoadInstruction al -> {
popStack();// array
popStack();//index
var arr = popStack();//type
pushStack(arr.toCD().componentType());
}
case ArrayStoreInstruction as -> decStack(2 + as.typeKind().slotSize());
case BranchInstruction b when b.opcode() == Opcode.GOTO || b.opcode() == Opcode.GOTO_W -> {}
case BranchInstruction b -> popStack();
case ConstantInstruction c when ins.opcode() == Opcode.ACONST_NULL -> pushStack(Type.NULL_TYPE);
case ConstantInstruction c -> {
switch(c.constantValue()){
case Double _ -> pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
case Float _ -> pushStack(Type.FLOAT_TYPE);
case Integer _ -> pushStack(Type.INTEGER_TYPE);
case Long _ -> pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
case String _ -> pushStack(Type.STRING_TYPE);
case ClassDesc desc -> pushStack(desc);
case DynamicConstantDesc dynamicConstantDesc -> pushStack(dynamicConstantDesc.constantType());
case MethodHandleDesc _ -> pushStack(Type.METHOD_HANDLE_TYPE);
case MethodTypeDesc _ -> pushStack(Type.METHOD_TYPE);
}
}
case ConvertInstruction c -> decStack(c.fromType().slotSize()).pushStack(c.toType().upperBound());
case FieldInstruction f -> {
switch(f.opcode()){
case GETFIELD -> decStack(1).pushStack(f.typeSymbol());
case GETSTATIC -> pushStack(f.typeSymbol());
case PUTFIELD -> decStack(1 + TypeKind.from(f.typeSymbol()).slotSize());
case PUTSTATIC -> decStack(TypeKind.from(f.typeSymbol()).slotSize());
}
}
case IncrementInstruction i -> {}
case InvokeDynamicInstruction i -> {
for(var param : i.typeSymbol().parameterArray())
decStack(TypeKind.from(param).slotSize());
pushStack(i.typeSymbol().returnType());
}
case InvokeInstruction i when i.opcode() == Opcode.INVOKESTATIC -> {
for(var param : i.typeSymbol().parameterArray())
decStack(TypeKind.from(param).slotSize());
pushStack(i.typeSymbol().returnType());
}
case InvokeInstruction ii when ii.opcode() == Opcode.INVOKESPECIAL && ii.name().equalsString(INIT_NAME) -> {
for(var param : ii.typeSymbol().parameterArray())
decStack(TypeKind.from(param).slotSize());
var ty = popStack();
if(ty.tag == ITEM_UNINITIALIZED){
if(ty.sym!=null&&!ty.sym.equals(ii.owner().asSymbol()))
throw new RuntimeException();
var init_type = ii.owner().asSymbol();
for(int i = 0; i < stack.size(); i ++){
if(stack.get(i).bci==ty.bci&&stack.get(i).tag==ITEM_UNINITIALIZED){
stack.set(i, Type.referenceType(init_type));
}
}
for(int i = 0; i < locals.size(); i ++){
if(locals.get(i).bci==ty.bci&&locals.get(i).tag==ITEM_UNINITIALIZED){
locals.set(i, Type.referenceType(init_type));
}
}
}else{
throw new RuntimeException();
}
}
case InvokeInstruction i -> {
for(var param : i.typeSymbol().parameterArray())
decStack(TypeKind.from(param).slotSize());
popStack();
pushStack(i.typeSymbol().returnType());
}
case LoadInstruction l when locals.size()<=l.slot()||locals.get(l.slot())==null ->
pushStack(l.typeKind().upperBound());
case LoadInstruction l ->
pushStack(locals.get(l.slot()).toCD());
case LookupSwitchInstruction ls -> popStack();
case MonitorInstruction m -> popStack();
case NewMultiArrayInstruction nma -> decStack(nma.dimensions()).pushStack(nma.arrayType().asSymbol());
case NewObjectInstruction no -> pushStack(Type.uninitializedType(no.className().asSymbol(), bci));
case NewPrimitiveArrayInstruction npa -> decStack(1).pushStack(npa.typeKind().upperBound().arrayType());
case NewReferenceArrayInstruction nra -> decStack(1).pushStack(nra.componentType().asSymbol().arrayType());
case NopInstruction n -> {}
case OperatorInstruction o -> {
switch(o.opcode()){
case IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND ->
decStack(2).pushStack(Type.INTEGER_TYPE);
case INEG, ARRAYLENGTH, INSTANCEOF ->
decStack(1).pushStack(Type.INTEGER_TYPE);
case LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR ->
decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
case LNEG ->
decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
case LSHL, LSHR, LUSHR ->
decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
case FADD, FSUB, FMUL, FDIV, FREM ->
decStack(2).pushStack(Type.FLOAT_TYPE);
case FNEG ->
decStack(1).pushStack(Type.FLOAT_TYPE);
case DADD, DSUB, DMUL, DDIV, DREM ->
decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
case DNEG ->
decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
default -> throw new RuntimeException();
}
}
case StackInstruction s -> {
switch(s.opcode()){
case POP ->
decStack(1);
case POP2 ->
decStack(2);
case DUP -> {
var type1 = popStack();
pushStack(type1, type1);
}
case DUP_X1 -> {
var type1 = popStack();
var type2 = popStack();
pushStack(type1, type2, type1);
}
case DUP_X2 -> {
var type1 = popStack();
var type2 = popStack();
var type3 = popStack();
pushStack(type1, type3, type2, type1);
}
case DUP2 -> {
var type1 = popStack();
var type2 = popStack();
pushStack(type2, type1, type2, type1);
}
case DUP2_X1 -> {
var type1 = popStack();
var type2 = popStack();
var type3 = popStack();
pushStack(type2, type1, type3, type2, type1);
}
case DUP2_X2 -> {
var type1 = popStack();
var type2 = popStack();
var type3 = popStack();
var type4 = popStack();
pushStack(type2, type1, type4, type3, type2, type1);
}
case SWAP -> {
var type1 = popStack();
var type2 = popStack();
pushStack(type1, type2);
}
default -> throw new RuntimeException();
}
}
case ReturnInstruction r when r.typeKind()==TypeKind.VOID -> {}
case ReturnInstruction r -> decStack(r.typeKind().slotSize());
case StoreInstruction s when s.typeKind() == TypeKind.DOUBLE -> decStack(2).setLocal2(s.slot(), Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
case StoreInstruction s when s.typeKind() == TypeKind.LONG -> decStack(2).setLocal2(s.slot(), Type.LONG_TYPE, Type.LONG2_TYPE);
case StoreInstruction s -> setLocal(s.slot(), popStack());
case TableSwitchInstruction ts -> popStack();
case ThrowInstruction t -> popStack();
case TypeCheckInstruction tc -> decStack(1).pushStack(tc.type().asSymbol());
case DiscontinuedInstruction d -> throw new IllegalStateException(d.toString());
default -> throw new IllegalStateException();
}
bci += ins.sizeInBytes();
}
case PseudoInstruction p -> {
switch(p){
case LineNumber ln -> current_line_number = ln;
case CharacterRange cr -> {}
case ExceptionCatch ec -> {}
case LabelTarget lt -> {}
case LocalVariable lv -> {}
case LocalVariableType lvt -> {}
default -> {}
}
}
case RuntimeInvisibleTypeAnnotationsAttribute _ -> {}
case RuntimeVisibleTypeAnnotationsAttribute _ -> {}
case CustomAttribute<?> _ -> {}
case StackMapTableAttribute _ -> {}
}
}
public void encounterLabel(Label l) {
if(annotationStartMap.get(l) instanceof ArrayList<LocalVariableAnnotation> list)
activeAnnotations.addAll(list);
if(annotationEndMap.get(l) instanceof ArrayList<LocalVariableAnnotation> list)
activeAnnotations.removeAll(list);
var tmp = stackMapFrames.get(l);
if (tmp != null) {
stack.clear();
locals.clear();
for( var sl : tmp.stack())
pushStack(Type.verificationType(sl, this));
for( var sl : tmp.locals())
locals.add(Type.verificationType(sl, this));
}
}
}

View file

@ -0,0 +1,100 @@
package com.parkertenbroeck.generators.loadtime;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.gen.Gen;
import com.parkertenbroeck.generators.loadtime.future.FutureSMBuilder;
import com.parkertenbroeck.generators.loadtime.gen.GenSMBuilder;
import java.io.IOException;
import java.lang.classfile.*;
import java.lang.classfile.attribute.*;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.constant.ClassDesc;
import java.lang.reflect.AccessFlag;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class GeneratorClassLoader extends ClassLoader {
private final HashMap<String, Class<?>> customClazzMap = new HashMap<>();
public GeneratorClassLoader(ClassLoader parent) {
super(parent);
}
void add(String name, byte[] def){
try {
Files.createDirectories(Path.of("build/modified/generators/" + name.replace(".", "/")).getParent());
Files.write(Path.of("build/modified/generators/" + name.replace(".", "/") + ".class"), def);
} catch (IOException e) {
throw new RuntimeException(e);
}
customClazzMap.put(name, defineClass(name, def, 0, def.length));
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (customClazzMap.get(name) instanceof Class<?> clazz)
return clazz;
if (name.startsWith("java"))
return super.loadClass(name);
var p = "/" + name.replace('.', '/') + ".class";
try (var stream = GeneratorClassLoader.class.getResourceAsStream(p)) {
var bytes = Objects.requireNonNull(stream).readAllBytes();
add(name, searchReplaceMethods(bytes));
return customClazzMap.get(name);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
public byte[] searchReplaceMethods(byte[] in) {
var clm = ClassFile.of(ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES).parse(in);
var isGen = clm.thisClass().asSymbol().descriptorString().equals(Gen.class.descriptorString());
var isFuture = clm.thisClass().asSymbol().descriptorString().equals(Future.class.descriptorString());
var nestMem = new ArrayList<ClassDesc>();
var innerCl = new ArrayList<InnerClassInfo>();
clm.findAttributes(Attributes.nestMembers()).forEach(i -> nestMem.addAll(i.nestMembers().stream().map(ClassEntry::asSymbol).toList()));
clm.findAttributes(Attributes.innerClasses()).forEach(i -> innerCl.addAll(i.classes()));
return ClassFile.of(ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES, ClassFile.StackMapsOption.STACK_MAPS_WHEN_REQUIRED).build(clm.thisClass().asSymbol(), cb -> {
for (var ce : clm) {
if (ce instanceof MethodModel mem && !isGen && !isFuture && mem.code().isPresent()) {
StateMachineBuilder<?> builder;
if(mem.methodTypeSymbol().returnType().descriptorString().equals(Gen.class.descriptorString())){
builder = new GenSMBuilder(clm, mem, mem.code().get());
}else if(mem.methodTypeSymbol().returnType().descriptorString().equals(Future.class.descriptorString())){
builder = new FutureSMBuilder(clm, mem, mem.code().get());
}else{
builder= null;
}
if(builder!=null&&builder.hasAnyHandlers()){
add(builder.CD_this.packageName() + "." + builder.CD_this.displayName(), builder.buildStateMachine());
cb.withMethod(mem.methodName(), mem.methodType(), mem.flags().flagsMask()&~ClassFile.ACC_SYNCHRONIZED, mb -> {
mb.withCode(builder::buildSourceMethodShim);
});
if(builder.shouldBeInnerClass()){
innerCl.add(InnerClassInfo.of(builder.CD_this, Optional.of(clm.thisClass().asSymbol()), Optional.of(builder.innerClassName)));
nestMem.add(ClassDesc.of(builder.CD_this.displayName()));
}
}else{
cb.with(mem);
}
}
else if (ce instanceof Attribute<?> e){
if (e.attributeMapper() != Attributes.nestMembers() && e.attributeMapper() != Attributes.innerClasses())
cb.with(ce);
}
else cb.with(ce);
}
if(!innerCl.isEmpty())
cb.with(InnerClassesAttribute.of(innerCl));
if(!nestMem.isEmpty())
cb.with(NestMembersAttribute.ofSymbols(nestMem));
});
}
}

View file

@ -0,0 +1,7 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.constant.ClassDesc;
public interface ParamConsumer {
void consume(String param, int slot, ClassDesc type);
}

View file

@ -0,0 +1,7 @@
package com.parkertenbroeck.generators.loadtime;
public enum ReplacementKind {
ImmediateReplacingPop,
Immediate,
ReplacingNextReturn,
}

View file

@ -0,0 +1,95 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.TypeKind;
import java.lang.constant.ClassDesc;
import java.util.ArrayList;
public class SavedStateTracker {
private final ArrayList<SavedState> saved = new ArrayList<>();
public sealed interface SavedState{
String name();
}
public record StackState(String name, ClassDesc desc) implements SavedState{
}
public record LocalState(String name, ClassDesc desc, int slot) implements SavedState{
}
private String get_name(StateMachineBuilder<?> smb, ClassDesc desc){
var value = smb.lstate.stream() // find unused state
.filter(v -> saved.stream().noneMatch(s -> s.name().equals(v.name())))
.filter(v -> v.cd().equals(desc)).findFirst();
if(value.isPresent())
return value.get().name();
var name = StateMachineBuilder.LOCAL_PREFIX+smb.lstate.size();
smb.lstate.add(new StateMachineBuilder.LState(name, desc));
return name;
}
public SavedState save_stack(StateMachineBuilder<?> smb, CodeBuilder cob, ClassDesc desc){
var name = get_name(smb, desc);
if(TypeKind.from(desc).slotSize()==2){
cob.aload(0).dup_x2().pop().putfield(smb.CD_this, name, desc);
}else{
cob.aload(0).swap().putfield(smb.CD_this, name, desc);
}
var s = new StackState(name, desc);
saved.add(s);
return s;
}
public SavedState save_local(StateMachineBuilder<?> smb, CodeBuilder cob, ClassDesc desc, int slot){
var name = get_name(smb, desc);
cob.aload(0).loadLocal(TypeKind.from(desc), slot).putfield(smb.CD_this, name, desc);
var s = new LocalState(name, desc, slot);
saved.add(s);
return s;
}
public LocalState load_param(int slot) {
for(var saved : saved){
if (saved instanceof LocalState(var name, var desc, int s) && s == slot) {
return (LocalState) saved;
}
}
throw new RuntimeException();
}
public SavedStateTracker restore(StateMachineBuilder<?> smb, CodeBuilder cob, SavedState s){
if(!saved.remove(s))throw new IllegalStateException();
switch(s){
case LocalState(var name, var desc, int slot) ->
cob.aload(0).getfield(smb.CD_this, name, desc).storeLocal(TypeKind.from(desc), slot);
case StackState(var name, var desc) ->
cob.aload(0).getfield(smb.CD_this, name, desc);
}
return this;
}
public void restore_stack(StateMachineBuilder<?> smb, CodeBuilder cob){
for(int i = saved.size()-1; i >= 0; i --){
if(saved.get(i) instanceof StackState)
restore(smb, cob, saved.get(i));
}
}
public void restore_locals(StateMachineBuilder<?> smb, CodeBuilder cob){
for(int i = saved.size()-1; i >= 0; i --){
if(saved.get(i) instanceof StackState)
restore(smb, cob, saved.get(i));
}
}
public void restore_all(StateMachineBuilder<?> smb, CodeBuilder cob) {
while(!saved.isEmpty())
restore(smb, cob, saved.getLast());
}
}

View file

@ -0,0 +1,7 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
public record SpecialMethod(ClassDesc owner, String name, MethodTypeDesc desc) {
}

View file

@ -0,0 +1,7 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.CodeBuilder;
public interface SpecialMethodBuilder<T extends StateMachineBuilder<T>> {
SpecialMethodHandler<T> build(T smb, CodeBuilder cob, Frame frame, StateBuilder sb);
}

View file

@ -0,0 +1,13 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.CodeBuilder;
public interface SpecialMethodHandler<T extends StateMachineBuilder> {
void build_prelude(T smb, CodeBuilder cob, Frame frame);
default boolean removeCall() {
return true;
}
void build_inline(T smb, CodeBuilder cob, Frame frame);
ReplacementKind replacementKind();
}

View file

@ -0,0 +1,31 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.Label;
import java.lang.classfile.instruction.SwitchCase;
import java.lang.constant.ConstantDescs;
import java.util.ArrayList;
public class StateBuilder {
public record State(Label label, int id){
public void bind(CodeBuilder cob){
cob.labelBinding(label);
}
public void setState(StateMachineBuilder smb, CodeBuilder cob){
cob.aload(0).loadConstant(id).putfield(smb.CD_this, StateMachineBuilder.STATE_NAME, ConstantDescs.CD_int);
}
}
private final ArrayList<State> states = new ArrayList<>();
public State create(CodeBuilder cob){
var state = new State(cob.newLabel(), states.size());
states.add(state);
return state;
}
public void buildSwitch(CodeBuilder cob, Label default_label){
cob.lookupswitch(default_label, states.stream().map(l -> SwitchCase.of(l.id, l.label)).toList());
}
}

View file

@ -0,0 +1,312 @@
package com.parkertenbroeck.generators.loadtime;
import java.lang.classfile.*;
import java.lang.classfile.attribute.InnerClassInfo;
import java.lang.classfile.attribute.InnerClassesAttribute;
import java.lang.classfile.attribute.NestHostAttribute;
import java.lang.classfile.instruction.*;
import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.AccessFlag;
import java.util.*;
import java.util.stream.Collectors;
public abstract class StateMachineBuilder<T extends StateMachineBuilder<T>> {
public final static String PARAM_PREFIX = "param_";
public final static String LOCAL_PREFIX = "local_";
public final static String STATE_NAME = "state";
public final ClassDesc CD_this;
public final String innerClassName;
public final ClassDesc[] params;
public final MethodTypeDesc MTD_init;
public final int paramSlotOff;
public final ClassModel src_clm;
public final MethodModel src_mem;
public final CodeModel src_com;
ArrayList<LState> lstate = new ArrayList<>();
private final ArrayList<Frame> frames;
protected final HashMap<SpecialMethod, SpecialMethodBuilder<T>> smmap = new HashMap<>();
record LState(String name, ClassDesc cd) {
}
public void params(int slot_start, ParamConsumer consumer){
int offset = 0;
for (var param : params) {
consumer.consume(PARAM_PREFIX+offset, offset+slot_start, param);
offset += TypeKind.from(param).slotSize();
}
}
public StateMachineBuilder(ClassModel src_clm, MethodModel src_mem, CodeModel src_com, String namePostfix){
this.src_clm = src_clm;
this.src_mem = src_mem;
this.src_com = src_com;
var mts = src_mem.methodTypeSymbol();
mts = mts.changeReturnType(ConstantDescs.CD_void);
if (!src_mem.flags().has(AccessFlag.STATIC)) {
mts = mts.insertParameterTypes(0, src_clm.thisClass().asSymbol());
}
var cdn = src_clm.thisClass().asSymbol().displayName();
var method_name = src_mem.methodName().stringValue();
var param_cnd = src_mem.methodTypeSymbol().parameterList().stream().map(
desc -> desc.descriptorString()
.replace("/", "___")
.replace(";", "____")
.replace("[", "_____")
)
.collect(Collectors.joining("$"));
innerClassName = method_name;
var name = cdn + "$" +innerClassName;
this.CD_this = ClassDesc.of(src_clm.thisClass().asSymbol().packageName(), name);
this.params = mts.parameterArray();
this.MTD_init = MethodTypeDesc.of(ConstantDescs.CD_void, params);
this.paramSlotOff = Arrays.stream(params).mapToInt(p -> TypeKind.from(p).slotSize()).sum();
frames = FrameTracker.frames(this, src_com);
}
public record WithFrame(CodeElement coe, Frame frame){}
public Iterable<WithFrame> with_frames(){
return () -> {
Iterator<CodeElement> coes = src_com.iterator();
return new Iterator<>() {
int i = 0;
@Override
public boolean hasNext() {
return coes.hasNext();
}
@Override
public WithFrame next() {
var coe = coes.next();
var frame = frames.get(i);
if (coe instanceof Instruction) i++;
return new WithFrame(coe, frame);
}
};
};
}
public void buildSourceMethodShim(CodeBuilder cob){
cob.new_(CD_this).dup();
params(0, (_, slot, type) -> {
cob.loadLocal(TypeKind.from(type), slot);
});
cob.invokespecial(CD_this, ConstantDescs.INIT_NAME, MTD_init).areturn();
}
public boolean shouldBeInnerClass(){
return true;
}
public byte[] buildStateMachine(){
return ClassFile.of(ClassFile.AttributesProcessingOption.PASS_ALL_ATTRIBUTES).build(CD_this, clb -> {
if(shouldBeInnerClass()){
src_clm.findAttributes(Attributes.sourceFile()).forEach(clb::with);
clb.with(InnerClassesAttribute.of(InnerClassInfo.of(CD_this, Optional.of(src_clm.thisClass().asSymbol()), Optional.of(innerClassName), AccessFlag.PUBLIC)));
clb.with(NestHostAttribute.of(src_clm.thisClass()));
}
// parameter fields
params(0, (param, _, type) -> {
clb.withField(param, type, ClassFile.ACC_PRIVATE);
});
clb.withField(STATE_NAME, ConstantDescs.CD_int, ClassFile.ACC_PRIVATE);
// constructor
clb.withMethod(ConstantDescs.INIT_NAME, MTD_init, ClassFile.ACC_PUBLIC, mb -> mb.withCode(cob -> {
cob.aload(0).invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
params(1, (param, slot, type) -> {
cob.aload(0).loadLocal(TypeKind.from(type), slot).putfield(CD_this, param, type);
});
cob.return_();
}));
clb.withFlags(AccessFlag.PUBLIC, AccessFlag.FINAL);
buildStateMachineMethod(clb);
});
}
public boolean hasAnyHandlers(){
for(var wf : with_frames()){
if (wf.coe() instanceof InvokeInstruction is){
var handler = smmap.get(new SpecialMethod(is.owner().asSymbol(), is.name().stringValue(), is.typeSymbol()));
if(handler != null) return true;
}
}
return false;
}
public void nonresumable_return(CodeBuilder cob, TypeKind kind){
this.synchronized_exit(cob);
cob.return_(kind);
}
public void resumable_return(CodeBuilder cob, StateBuilder.State resume, TypeKind kind) {
this.synchronized_exit(cob);
cob.return_(kind);
resume.bind(cob);
}
protected void synchronized_start(CodeBuilder cob){
if(src_mem.flags().has(AccessFlag.SYNCHRONIZED)){
if(src_mem.flags().has(AccessFlag.STATIC)) cob.loadConstant(src_clm.thisClass().asSymbol());
else cob.aload(0).getfield(CD_this, PARAM_PREFIX+0, src_clm.thisClass().asSymbol());
cob.monitorenter();
}
}
public void synchronized_exit(CodeBuilder cob){
if(src_mem.flags().has(AccessFlag.SYNCHRONIZED)){
if(src_mem.flags().has(AccessFlag.STATIC)) cob.loadConstant(src_clm.thisClass().asSymbol());
else cob.aload(0).getfield(CD_this, PARAM_PREFIX+0, src_clm.thisClass().asSymbol());
cob.monitorexit();
}
}
protected abstract void buildStateMachineMethod(ClassBuilder clb);
public void buildStateMachineMethodCode(ClassBuilder clb, CodeBuilder cob, int loc_param_off){
this.synchronized_start(cob);
cob.trying(
tcob -> buildStateMachineCode(clb, tcob, loc_param_off),
// catch anything set our state to -1 and throw the exception
ctb -> ctb.catchingAll(
blc -> {
blc.aload(0).loadConstant(-1).putfield(CD_this, STATE_NAME, ConstantDescs.CD_int);
this.synchronized_exit(blc);
cob.athrow();
}
)
);
}
public void buildStateMachineCode(ClassBuilder clb, CodeBuilder cob, int loc_param_off) {
var stateBuilder = new StateBuilder();
var handlers = new ArrayList<SpecialMethodHandler<T>>();
boolean ignore_next_pop = false;
var invalid_state = cob.newLabel();
var start_state = stateBuilder.create(cob);
for(var wf : with_frames()){
if (wf.coe() instanceof InvokeInstruction is){
var handler = smmap.get(new SpecialMethod(is.owner().asSymbol(), is.name().stringValue(), is.typeSymbol()));
if(handler != null)
handlers.add(handler.build((T)this, cob, wf.frame(), stateBuilder));
}
}
cob.aload(0).getfield(CD_this, STATE_NAME, TypeKind.INT.upperBound());
stateBuilder.buildSwitch(cob, invalid_state);
cob.localVariable(0, "this", CD_this, cob.startLabel(), cob.endLabel());
{
int i = 0;
for (var wf : with_frames()) {
if (wf.coe() instanceof InvokeInstruction is) {
var h = smmap.get(new SpecialMethod(is.owner().asSymbol(), is.name().stringValue(), is.typeSymbol()));
if(h!=null) {
if(wf.frame.line()!=null)cob.with(wf.frame.line());
handlers.get(i++).build_prelude((T)this, cob, wf.frame());
}
}
}
}
SpecialMethodHandler<T> currentHandler = null;
start_state.bind(cob);
for (var wf : with_frames()) {
var coe = wf.coe();
var frame = wf.frame();
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.replacementKind() == ReplacementKind.ReplacingNextReturn){
currentHandler.build_inline((T)this, cob, frame);
currentHandler = null;
continue;
}
}
}
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.replacementKind() == ReplacementKind.Immediate) handler.build_inline((T)this, cob, frame);
else if(handler.replacementKind() == ReplacementKind.ImmediateReplacingPop) {
handler.build_inline((T)this, cob, frame);
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() < paramSlotOff -> {
}
case LocalVariable lv ->
cob.localVariable(lv.slot() - paramSlotOff + loc_param_off, 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, PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int)
.loadConstant(ii.constant()).iadd()
.putfield(CD_this, PARAM_PREFIX + ii.slot(), ConstantDescs.CD_int);
case IncrementInstruction ii -> cob.iinc(ii.slot() - paramSlotOff + loc_param_off, 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, PARAM_PREFIX + li.slot(), frame.locals()[li.slot()].toCD());
case LoadInstruction li ->
cob.loadLocal(li.typeKind(), li.slot() - paramSlotOff + loc_param_off);
// 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, PARAM_PREFIX + ls.slot(), frame.locals()[ls.slot()].toCD());
case StoreInstruction ls when ls.slot() < paramSlotOff ->
cob.aload(0).swap().putfield(CD_this, PARAM_PREFIX + ls.slot(), frame.locals()[ls.slot()].toCD());
case StoreInstruction ls ->
cob.storeLocal(ls.typeKind(), ls.slot() - paramSlotOff + loc_param_off);
default -> cob.with(coe);
}
}
cob.labelBinding(invalid_state);
cob.new_(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString())).dup()
.invokespecial(ClassDesc.ofDescriptor(IllegalStateException.class.descriptorString()), ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
.athrow();
for (var lstate : lstate) {
clb.withField(lstate.name(), lstate.cd(), ClassFile.ACC_PRIVATE);
}
}
}

View file

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

View file

@ -0,0 +1,335 @@
package com.parkertenbroeck.generators.loadtime.future;
import com.parkertenbroeck.future.Future;
import com.parkertenbroeck.future.Waker;
import com.parkertenbroeck.generators.loadtime.*;
import java.lang.classfile.*;
import java.lang.classfile.instruction.SwitchCase;
import java.lang.constant.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
public class FutureSMBuilder extends StateMachineBuilder<FutureSMBuilder> {
public final static ClassDesc CD_Future = ClassDesc.ofDescriptor(Future.class.descriptorString());
public final static ClassDesc CD_Waker = ClassDesc.ofDescriptor(Waker.class.descriptorString());
public final static ClassDesc CD_Pending = ClassDesc.ofDescriptor(Future.Pending.class.descriptorString());
public final static MethodTypeDesc MTD_Future_Obj = MethodTypeDesc.of(CD_Future, ConstantDescs.CD_Object);
public final static MethodTypeDesc MTD_Future = MethodTypeDesc.of(CD_Future);
public final static MethodTypeDesc MTD_Object_Waker = MethodTypeDesc.of(ConstantDescs.CD_Object, CD_Waker);
public final static MethodTypeDesc MTD_Obj = MethodTypeDesc.of(ConstantDescs.CD_Object);
public final static String AWAITING_FIELD_NAME = "awaiting";
private final HashMap<Integer, Consumer<CodeBuilder>> cancellation_behavior = new HashMap<>();
static class AwaitHandler implements SpecialMethodHandler<FutureSMBuilder>{
final StateBuilder.State awaiting;
final Label save_label;
final Label resume_inline;
public AwaitHandler(FutureSMBuilder smb, CodeBuilder cob, Frame frame, StateBuilder sb) {
awaiting = sb.create(cob);
save_label = cob.newLabel();
resume_inline = cob.newLabel();
}
public ReplacementKind replacementKind(){return ReplacementKind.Immediate;}
@Override
public void build_prelude(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.labelBinding(save_label);
var sst = new SavedStateTracker();
frame.save_locals(smb, cob, sst,2);
cob.storeLocal(TypeKind.REFERENCE, 2);
frame.save_stack(smb, cob, sst,1);
cob.loadLocal(TypeKind.REFERENCE, 2);
smb.yielding_state(awaiting, frame, sst);
smb.resumable_return(cob, awaiting, TypeKind.REFERENCE);
sst.restore_all(smb, cob);
cob.goto_(resume_inline);
}
@Override
public void build_inline(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
// [... Future]
var start = cob.newBoundLabel();
cob.dup().aload(1).invokeinterface(CD_Future, "poll", MTD_Object_Waker).dup().instanceOf(CD_Pending);
// [... Future Polled is_pending]
cob.ifThenElse(bcb -> {
awaiting.setState(smb, cob);
// [... Future Polled]
bcb.swap().aload(0).swap().putfield(smb.CD_this, AWAITING_FIELD_NAME, CD_Future);
// [... Polled]
cob.goto_(save_label).labelBinding(resume_inline);
bcb.aload(0).getfield(smb.CD_this, AWAITING_FIELD_NAME, CD_Future);
bcb.aload(0).aconst_null().putfield(smb.CD_this, AWAITING_FIELD_NAME, CD_Future);
bcb.goto_(start);
}, bcb -> {
bcb.swap().pop();
});
// [... Polled]
}
}
static class YieldHandler implements SpecialMethodHandler<FutureSMBuilder> {
final StateBuilder.State resume;
final Label save_ret;
final Label end;
public YieldHandler(FutureSMBuilder smb, CodeBuilder cob, Frame frame, StateBuilder sb) {
resume = sb.create(cob);
save_ret = cob.newLabel();
end = cob.newLabel();
}
public ReplacementKind replacementKind(){return ReplacementKind.Immediate;}
@Override
public void build_prelude(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.labelBinding(save_ret);
var sst = frame.save(smb, cob, 2, 0);
resume.setState(smb, cob);
cob.getstatic(CD_Pending, "INSTANCE", CD_Pending);
smb.yielding_state(resume, frame, sst);
smb.resumable_return(cob, resume, TypeKind.REFERENCE);
sst.restore_all(smb, cob);
cob.goto_(end);
}
@Override
public void build_inline(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.goto_(save_ret);
cob.labelBinding(end);
}
}
static class WakerHandler implements SpecialMethodHandler<FutureSMBuilder>{
protected WakerHandler(FutureSMBuilder smb, CodeBuilder cob, Frame frame, StateBuilder sb) {}
@Override
public void build_prelude(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {}
@Override
public void build_inline(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.aload(1);
}
@Override
public ReplacementKind replacementKind() {
return ReplacementKind.Immediate;
}
}
static class RetHandler implements SpecialMethodHandler<FutureSMBuilder>{
protected RetHandler(FutureSMBuilder smb, CodeBuilder cob, Frame frame, StateBuilder sb) {}
public ReplacementKind replacementKind(){return ReplacementKind.ReplacingNextReturn;}
@Override
public void build_prelude(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {}
@Override
public void build_inline(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.aload(0).loadConstant(-1).putfield(smb.CD_this, STATE_NAME, TypeKind.INT.upperBound());
smb.nonresumable_return(cob, TypeKind.REFERENCE);
}
}
static class RetVoidHandler implements SpecialMethodHandler<FutureSMBuilder>{
protected RetVoidHandler(FutureSMBuilder smb, CodeBuilder cob, Frame frame, StateBuilder sb) {}
public ReplacementKind replacementKind(){return ReplacementKind.ReplacingNextReturn;}
@Override
public void build_prelude(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
}
@Override
public void build_inline(FutureSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.aload(0).loadConstant(-1).putfield(smb.CD_this, STATE_NAME, TypeKind.INT.upperBound()).aconst_null();
smb.nonresumable_return(cob, TypeKind.REFERENCE);
}
}
private String annotationStringValue(AnnotationValue value){
if (value instanceof AnnotationValue.OfString ofString) {
return ofString.stringValue();
}
throw new RuntimeException();
}
private ClassDesc annotationClassValue(AnnotationValue value){
if (value instanceof AnnotationValue.OfClass ofConstant) {
return ofConstant.classSymbol();
}
throw new RuntimeException();
}
private void yielding_state(StateBuilder.State state, Frame frame, SavedStateTracker sst){
if(frame.local_annotations().length==0)return;
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);
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);
});
}));
});
}
}
cancellation_behavior.put(state.id(), cob -> {
for(var fcb : field_cancellation_behavior) fcb.accept(cob);
});
}
private static Method findMethod(Class<?> owner, String name){
try{
return owner.getDeclaredMethod(name);
}catch (Exception ignore){}
if(owner.equals(Object.class))return null;
for(var iface : owner.getInterfaces()){
var method = findMethod(iface, name);
if(method!=null)return method;
}
if(!owner.isInterface()){
Class<?> sup = owner.getSuperclass();
if(sup!=null){
return findMethod(sup, name);
}
}
return findMethod(Object.class, name);
}
@Override
protected void buildStateMachineMethod(ClassBuilder clb){
clb.withInterfaces(List.of(clb.constantPool().classEntry(CD_Future)));
clb.withMethod("poll", MTD_Object_Waker, ClassFile.ACC_PUBLIC, mb -> mb.withCode(cob -> {
cob.localVariable(1, "waker", CD_Waker, cob.startLabel(), cob.endLabel());
buildStateMachineMethodCode(clb, cob, 2);
}));
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.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 -> {
boc.trying(tcob -> {
tcob.aload(0).getfield(CD_this, AWAITING_FIELD_NAME, CD_Future)
.aload(0).aconst_null().putfield(CD_this, AWAITING_FIELD_NAME, CD_Future)
.invokeinterface(CD_Future, "cancel", MethodTypeDesc.of(ConstantDescs.CD_void));
}, cb -> {
cb.catchingAll(ccob -> {
ccob.aload(2).ifThenElse(Opcode.IFNONNULL, bcob -> {
bcob.aload(2).swap();
addSuppressed(bcob);
}, bcob -> {
bcob.astore(2);
});
});
});
});
if(!cancellation_behavior.isEmpty()){
var states = cancellation_behavior.entrySet().stream().toList();
var cases = states.stream().map(v -> SwitchCase.of(v.getKey(), cob.newLabel())).toList();
cob.iload(1);
var end = cob.newLabel();
cob.tableswitch(end, cases);
for(int i = 0; i < states.size(); i ++){
cob.labelBinding(cases.get(i).target());
states.get(i).getValue().accept(cob);
cob.goto_(end);
}
cob.labelBinding(end);
}
this.synchronized_exit(cob);
cob.aload(2).ifThen(Opcode.IFNONNULL, bcob -> {
bcob.aload(2).athrow();
});
cob.return_();
}));
clb.withField(AWAITING_FIELD_NAME, CD_Future, ClassFile.ACC_PRIVATE);
}
private void addSuppressed(CodeBuilder cob) {
cob.invokevirtual(ClassDesc.ofDescriptor(Throwable.class.descriptorString()), "addSuppressed", MethodTypeDesc.of(ConstantDescs.CD_void, ClassDesc.ofDescriptor(Throwable.class.descriptorString())));
}
public FutureSMBuilder(ClassModel src_clm, MethodModel src_mem, CodeModel src_com) {
super(src_clm, src_mem, src_com, "Fut");
smmap.put(new SpecialMethod(CD_Future, "await", MTD_Obj), AwaitHandler::new);
smmap.put(new SpecialMethod(CD_Future, "ret", MTD_Future_Obj), RetHandler::new);
smmap.put(new SpecialMethod(CD_Future, "ret", MTD_Future), RetVoidHandler::new);
smmap.put(new SpecialMethod(CD_Future, "yield", ConstantDescs.MTD_void), YieldHandler::new);
smmap.put(new SpecialMethod(CD_Waker, "waker", MethodTypeDesc.of(CD_Waker)), WakerHandler::new);
}
}

View file

@ -0,0 +1,111 @@
package com.parkertenbroeck.generators.loadtime.gen;
import com.parkertenbroeck.gen.Gen;
import com.parkertenbroeck.generators.loadtime.*;
import java.lang.classfile.*;
import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc;
import java.util.List;
public class GenSMBuilder extends StateMachineBuilder<GenSMBuilder> {
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 final static MethodTypeDesc MTD_Obj = MethodTypeDesc.of(ConstantDescs.CD_Object);
static class YieldHandler implements SpecialMethodHandler<GenSMBuilder> {
final StateBuilder.State yielded;
final Label save;
final Label resume;
final boolean is_void;
public YieldHandler(GenSMBuilder smb, CodeBuilder cob, StateBuilder sb, boolean is_void) {
yielded = sb.create(cob);
save = cob.newLabel();
resume = cob.newLabel();
this.is_void = is_void;
}
public ReplacementKind replacementKind(){return ReplacementKind.ImmediateReplacingPop;}
@Override
public void build_prelude(GenSMBuilder smb, CodeBuilder cob, Frame frame) {
cob.labelBinding(save);
var sst = new SavedStateTracker();
frame.save_locals(smb, cob, sst,1);
cob.storeLocal(TypeKind.REFERENCE, 1);
frame.save_stack(smb, cob, sst,1);
cob.loadLocal(TypeKind.REFERENCE, 1);
smb.resumable_return(cob, yielded, TypeKind.REFERENCE);
sst.restore_all(smb, cob);
cob.goto_(resume);
}
@Override
public void build_inline(GenSMBuilder smb, CodeBuilder cob, Frame frame) {
if(is_void)cob.aconst_null();
yielded.setState(smb, cob);
cob.new_(CD_Yield)
.dup_x1()
.swap()
.invokespecial(CD_Yield, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object))
.goto_(save);
cob.labelBinding(resume);
}
}
static class RetHandler implements SpecialMethodHandler<GenSMBuilder> {
final boolean is_void;
public RetHandler(GenSMBuilder smb, CodeBuilder cob, StateBuilder sb, boolean is_void) {
this.is_void = is_void;
}
public ReplacementKind replacementKind(){return ReplacementKind.ReplacingNextReturn;}
@Override
public void build_prelude(GenSMBuilder smb, CodeBuilder cob, Frame frame) {}
@Override
public void build_inline(GenSMBuilder smb, CodeBuilder cob, Frame frame) {
if(is_void)cob.aconst_null();
cob.aload(0).loadConstant(-1).putfield(smb.CD_this, 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));
smb.nonresumable_return(cob, TypeKind.REFERENCE);
}
}
public GenSMBuilder(ClassModel src_clm, MethodModel src_mem, CodeModel src_com) {
super(src_clm, src_mem, src_com, "Gen");
smmap.put(new SpecialMethod(CD_Gen, "yield", MTD_Gen_Obj),(smb, cob, frame, sb) -> new YieldHandler(smb, cob, sb, false));
smmap.put(new SpecialMethod(CD_Gen, "yield", MTD_Gen),(smb, cob, frame, sb) -> new YieldHandler(smb, cob, sb,true));
smmap.put(new SpecialMethod(CD_Gen, "ret", MTD_Gen_Obj),(smb, cob, frame, sb) -> new RetHandler(smb, cob, sb,false));
smmap.put(new SpecialMethod(CD_Gen, "ret", MTD_Gen),(smb, cob, frame, sb) -> new RetHandler(smb, cob, sb, true));
}
@Override
protected void buildStateMachineMethod(ClassBuilder clb){
clb.withInterfaces(List.of(clb.constantPool().classEntry(CD_Gen)));
clb.withMethod("next", MethodTypeDesc.of(CD_Res), ClassFile.ACC_PUBLIC, mb -> mb.withCode(cob -> {
buildStateMachineMethodCode(clb, cob, 1);
}));
}
}