281 lines
9.3 KiB
Rust
281 lines
9.3 KiB
Rust
//! Actual LLVM coroutine object linked with the runtime-only archive.
|
|
|
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
|
#[path = "dever-tests/tests/support/process.rs"]
|
|
mod process;
|
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
|
#[path = "dever-tests/tests/support/temp.rs"]
|
|
mod temp;
|
|
|
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
|
#[test]
|
|
#[ignore = "build runtime-abi-only staticlib in target/native-runtime-abi before running"]
|
|
fn switched_resume_coroutine_suspends_resumes_and_destroys() {
|
|
use dever_backend_bridge::{Target, emit_object};
|
|
use std::path::Path;
|
|
use std::process::{Command, Stdio};
|
|
use std::time::Duration;
|
|
|
|
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
let archive = root.join("target/native-runtime-abi/debug/libdever_backend_bridge.a");
|
|
assert!(
|
|
archive.is_file(),
|
|
"missing explicit runtime ABI fixture archive"
|
|
);
|
|
let directory = temp::TemporaryDirectory::new();
|
|
let object = directory.path().join("coroutine.o");
|
|
std::fs::write(
|
|
&object,
|
|
emit_object(COROUTINE, Target::LinuxX86_64).unwrap(),
|
|
)
|
|
.unwrap();
|
|
let executable = directory.path().join("coroutine");
|
|
let compiler = Command::new("/usr/bin/cc")
|
|
.env_clear()
|
|
.args(["-std=c11", "-Wall", "-Wextra", "-Werror", "-O2"])
|
|
.arg(root.join("test/native-runtime-abi/async-driver.c"))
|
|
.arg(object)
|
|
.arg(archive)
|
|
.args(["-ldl", "-lpthread", "-lm", "-o"])
|
|
.arg(&executable)
|
|
.arg("-B/usr/bin/")
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
compiler.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&compiler.stderr)
|
|
);
|
|
let status = process::status(
|
|
Command::new(executable)
|
|
.env_clear()
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::inherit()),
|
|
Duration::from_secs(5),
|
|
)
|
|
.unwrap();
|
|
assert!(status.success(), "coroutine driver failed: {status}");
|
|
}
|
|
|
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
|
#[test]
|
|
#[ignore = "build runtime-abi-only staticlib in target/native-runtime-abi before running"]
|
|
fn async_operations_preserve_scope_and_backpressure() {
|
|
use std::path::Path;
|
|
use std::process::{Command, Stdio};
|
|
use std::time::Duration;
|
|
|
|
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
let archive = root.join("target/native-runtime-abi/debug/libdever_backend_bridge.a");
|
|
assert!(
|
|
archive.is_file(),
|
|
"missing explicit runtime ABI fixture archive"
|
|
);
|
|
let directory = temp::TemporaryDirectory::new();
|
|
let executable = directory.path().join("async-operations");
|
|
let compiler = Command::new("/usr/bin/cc")
|
|
.env_clear()
|
|
.args(["-std=c11", "-Wall", "-Wextra", "-Werror", "-O2"])
|
|
.arg("-I")
|
|
.arg(root.join("crates/dever-backend-bridge/include"))
|
|
.arg(root.join("test/native-runtime-abi/async-operations-driver.c"))
|
|
.arg(archive)
|
|
.args(["-ldl", "-lpthread", "-lm", "-o"])
|
|
.arg(&executable)
|
|
.arg("-B/usr/bin/")
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
compiler.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&compiler.stderr)
|
|
);
|
|
let status = process::status(
|
|
Command::new(executable)
|
|
.env_clear()
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::inherit()),
|
|
Duration::from_secs(5),
|
|
)
|
|
.unwrap();
|
|
assert!(status.success(), "async operation driver failed: {status}");
|
|
}
|
|
|
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
|
const COROUTINE: &str = r#"
|
|
@progress = global i32 0
|
|
declare ptr @malloc(i64)
|
|
declare void @free(ptr)
|
|
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
|
|
declare i64 @llvm.coro.size.i64()
|
|
declare ptr @llvm.coro.begin(token, ptr)
|
|
declare i8 @llvm.coro.suspend(token, i1)
|
|
declare ptr @llvm.coro.free(token, ptr)
|
|
declare i1 @llvm.coro.end(ptr, i1, token)
|
|
declare void @llvm.coro.resume(ptr)
|
|
declare void @llvm.coro.destroy(ptr)
|
|
declare i1 @llvm.coro.done(ptr)
|
|
declare i64 @llvm.coro.align.i64()
|
|
declare ptr @dever_rt_v1_async_frame_alloc(i64, i64)
|
|
declare void @dever_rt_v1_async_frame_free(ptr, i64, i64)
|
|
declare void @dever_rt_v1_async_complete(ptr, i32)
|
|
declare i32 @dever_rt_v1_async_root(ptr, ptr, ptr, ptr, ptr)
|
|
declare ptr @dever_rt_v1_async_sleep(i64)
|
|
declare i32 @dever_rt_v1_async_op_poll(ptr, ptr, ptr, ptr, ptr, ptr)
|
|
declare void @dever_rt_v1_async_op_release(ptr)
|
|
@root_polls = global i32 0
|
|
|
|
@probe_type = private constant { i64, i64, ptr, ptr, i8 } { i64 8, i64 8, ptr @probe_move, ptr @probe_drop, i8 1 }
|
|
@probe_function = private constant { ptr, ptr, ptr, ptr, ptr, ptr, i8 } {
|
|
ptr @probe_type, ptr @probe_type, ptr @dever_coro_root_create,
|
|
ptr @dever_coro_root_resume, ptr @dever_coro_root_destroy, ptr @dever_coro_root_done, i8 1
|
|
}
|
|
|
|
define void @probe_move(ptr %source, ptr %destination) {
|
|
entry:
|
|
%value = load i64, ptr %source
|
|
store i64 %value, ptr %destination
|
|
store i64 0, ptr %source
|
|
ret void
|
|
}
|
|
|
|
define void @probe_drop(ptr %value) {
|
|
entry:
|
|
ret void
|
|
}
|
|
|
|
define ptr @dever_coro_probe_create() presplitcoroutine {
|
|
entry:
|
|
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
|
|
%size = call i64 @llvm.coro.size.i64()
|
|
%memory = call ptr @malloc(i64 %size)
|
|
%frame = call ptr @llvm.coro.begin(token %id, ptr %memory)
|
|
%initial = call i8 @llvm.coro.suspend(token none, i1 false)
|
|
switch i8 %initial, label %suspend [i8 0, label %body i8 1, label %cleanup]
|
|
body:
|
|
store i32 42, ptr @progress
|
|
%final = call i8 @llvm.coro.suspend(token none, i1 true)
|
|
switch i8 %final, label %suspend [i8 1, label %cleanup]
|
|
cleanup:
|
|
%allocation = call ptr @llvm.coro.free(token %id, ptr %frame)
|
|
call void @free(ptr %allocation)
|
|
br label %suspend
|
|
suspend:
|
|
call i1 @llvm.coro.end(ptr %frame, i1 false, token none)
|
|
ret ptr %frame
|
|
}
|
|
|
|
define i32 @dever_coro_probe_progress() {
|
|
entry:
|
|
%value = load i32, ptr @progress
|
|
ret i32 %value
|
|
}
|
|
|
|
define void @dever_coro_probe_resume(ptr %frame) {
|
|
entry:
|
|
call void @llvm.coro.resume(ptr %frame)
|
|
ret void
|
|
}
|
|
|
|
define i1 @dever_coro_probe_done(ptr %frame) {
|
|
entry:
|
|
%done = call i1 @llvm.coro.done(ptr %frame)
|
|
ret i1 %done
|
|
}
|
|
|
|
define void @dever_coro_probe_destroy(ptr %frame) {
|
|
entry:
|
|
call void @llvm.coro.destroy(ptr %frame)
|
|
ret void
|
|
}
|
|
|
|
define ptr @dever_coro_root_create(ptr %state, ptr %input, ptr %output, ptr %fault) presplitcoroutine {
|
|
entry:
|
|
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
|
|
%size = call i64 @llvm.coro.size.i64()
|
|
%align = call i64 @llvm.coro.align.i64()
|
|
%memory = call ptr @dever_rt_v1_async_frame_alloc(i64 %size, i64 %align)
|
|
%frame = call ptr @llvm.coro.begin(token %id, ptr %memory)
|
|
%operation = alloca ptr
|
|
store ptr null, ptr %operation
|
|
%error = alloca { ptr, i64 }
|
|
%initial = call i8 @llvm.coro.suspend(token none, i1 false)
|
|
switch i8 %initial, label %suspend [i8 0, label %body i8 1, label %cleanup]
|
|
body:
|
|
%sleep = call ptr @dever_rt_v1_async_sleep(i64 1)
|
|
store ptr %sleep, ptr %operation
|
|
br label %poll_sleep
|
|
poll_sleep:
|
|
%polls = load i32, ptr @root_polls
|
|
%next_polls = add i32 %polls, 1
|
|
store i32 %next_polls, ptr @root_polls
|
|
%active = load ptr, ptr %operation
|
|
%sleep_status = call i32 @dever_rt_v1_async_op_poll(ptr %state, ptr %active, ptr null, ptr null, ptr null, ptr %error)
|
|
%pending = icmp eq i32 %sleep_status, 0
|
|
br i1 %pending, label %wait_sleep, label %finished_sleep
|
|
wait_sleep:
|
|
%waiting = call i8 @llvm.coro.suspend(token none, i1 false)
|
|
switch i8 %waiting, label %suspend [i8 0, label %poll_sleep i8 1, label %cleanup]
|
|
finished_sleep:
|
|
%completed_op = load ptr, ptr %operation
|
|
call void @dever_rt_v1_async_op_release(ptr %completed_op)
|
|
store ptr null, ptr %operation
|
|
store i64 42, ptr %output
|
|
call void @dever_rt_v1_async_complete(ptr %state, i32 1)
|
|
%final = call i8 @llvm.coro.suspend(token none, i1 true)
|
|
switch i8 %final, label %suspend [i8 1, label %cleanup]
|
|
cleanup:
|
|
%remaining = load ptr, ptr %operation
|
|
%has_op = icmp ne ptr %remaining, null
|
|
br i1 %has_op, label %release_op, label %free_frame
|
|
release_op:
|
|
call void @dever_rt_v1_async_op_release(ptr %remaining)
|
|
br label %free_frame
|
|
free_frame:
|
|
%allocation = call ptr @llvm.coro.free(token %id, ptr %frame)
|
|
call void @dever_rt_v1_async_frame_free(ptr %allocation, i64 %size, i64 %align)
|
|
br label %suspend
|
|
suspend:
|
|
call i1 @llvm.coro.end(ptr %frame, i1 false, token none)
|
|
ret ptr %frame
|
|
}
|
|
|
|
define void @dever_coro_root_resume(ptr %frame) {
|
|
entry:
|
|
call void @llvm.coro.resume(ptr %frame)
|
|
ret void
|
|
}
|
|
|
|
define i8 @dever_coro_root_done(ptr %frame) {
|
|
entry:
|
|
%done = call i1 @llvm.coro.done(ptr %frame)
|
|
%result = zext i1 %done to i8
|
|
ret i8 %result
|
|
}
|
|
|
|
define void @dever_coro_root_destroy(ptr %frame) {
|
|
entry:
|
|
call void @llvm.coro.destroy(ptr %frame)
|
|
ret void
|
|
}
|
|
|
|
define i32 @dever_coro_root_run() {
|
|
entry:
|
|
%output = alloca i64
|
|
%fault = alloca i64
|
|
%error = alloca { ptr, i64 }
|
|
%status = call i32 @dever_rt_v1_async_root(ptr @probe_function, ptr null, ptr %output, ptr %fault, ptr %error)
|
|
%value = load i64, ptr %output
|
|
%value_ok = icmp eq i64 %value, 42
|
|
%status_ok = icmp eq i32 %status, 1
|
|
%polls = load i32, ptr @root_polls
|
|
%suspended = icmp uge i32 %polls, 2
|
|
%result_ok = and i1 %value_ok, %status_ok
|
|
%passed = and i1 %result_ok, %suspended
|
|
%code = select i1 %passed, i32 0, i32 1
|
|
ret i32 %code
|
|
}
|
|
"#;
|