mirror of
https://github.com/ParkerTenBroeck/coroutines.git
synced 2026-06-06 21:00:35 -04:00
migrated to gradle
This commit is contained in:
parent
0dd6fb237d
commit
ecb18b417e
43 changed files with 619 additions and 177 deletions
24
lib/build.gradle.kts
Normal file
24
lib/build.gradle.kts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Use JUnit Jupiter for testing.
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(24)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
// Use JUnit Platform for unit tests.
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
136
lib/src/main/java/com/parkertenbroeck/async_runtime/Jokio.java
Normal file
136
lib/src/main/java/com/parkertenbroeck/async_runtime/Jokio.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
122
lib/src/main/java/com/parkertenbroeck/async_runtime/Util.java
Normal file
122
lib/src/main/java/com/parkertenbroeck/async_runtime/Util.java
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
33
lib/src/main/java/com/parkertenbroeck/future/Future.java
Normal file
33
lib/src/main/java/com/parkertenbroeck/future/Future.java
Normal 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(){}
|
||||
}
|
||||
}
|
||||
10
lib/src/main/java/com/parkertenbroeck/future/Waker.java
Normal file
10
lib/src/main/java/com/parkertenbroeck/future/Waker.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.parkertenbroeck.future;
|
||||
|
||||
public interface Waker {
|
||||
|
||||
static Waker waker() {
|
||||
throw new RuntimeException("NO!");
|
||||
}
|
||||
|
||||
void wake();
|
||||
}
|
||||
27
lib/src/main/java/com/parkertenbroeck/gen/Gen.java
Normal file
27
lib/src/main/java/com/parkertenbroeck/gen/Gen.java
Normal 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>{}
|
||||
}
|
||||
15
lib/src/main/java/com/parkertenbroeck/generators/RT.java
Normal file
15
lib/src/main/java/com/parkertenbroeck/generators/RT.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.parkertenbroeck.generators.loadtime;
|
||||
|
||||
public enum ReplacementKind {
|
||||
ImmediateReplacingPop,
|
||||
Immediate,
|
||||
ReplacingNextReturn,
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue