Skip to content

Commit 3cef41f

Browse files
committed
feat(io): split blocking and host-driven async backends
1 parent db15f14 commit 3cef41f

23 files changed

Lines changed: 1183 additions & 101 deletions

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
6767
rusqlite = { version = "0.32", default-features = false, features = ["bundled", "hooks", "limits"], optional = true }
6868
url = { version = "2", optional = true }
6969
futures-util = { version = "0.3", optional = true }
70-
tokio = { version = "1", features = ["rt-multi-thread", "net", "time", "sync"], optional = true }
70+
tokio = { version = "1", features = ["rt-multi-thread", "net", "time", "sync", "fs", "io-util", "process"], optional = true }
7171
edge_abi = { package = "pd-edge-abi", version = "0.1.1", default-features = false, optional = true }
7272
futures-channel = "0.3"
7373
paste = "1"

build.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,21 @@ fn write_generated_file(path: &Path, contents: &str) {
251251
fn builtin_source_specs(namespaces: &[NamespaceDecl]) -> Vec<SourceSpec> {
252252
namespaces
253253
.iter()
254-
.map(|namespace| SourceSpec {
255-
path: format!("src/builtins/runtime/{}.rs", namespace.module),
256-
module: namespace.module.clone(),
257-
category: SourceCategory::NamespacedBuiltin,
254+
.map(|namespace| {
255+
let path = if namespace.module == "io" {
256+
if cfg!(feature = "async") {
257+
"src/builtins/runtime/io/async_io.rs".to_string()
258+
} else {
259+
"src/builtins/runtime/io/blocking.rs".to_string()
260+
}
261+
} else {
262+
format!("src/builtins/runtime/{}.rs", namespace.module)
263+
};
264+
SourceSpec {
265+
path,
266+
module: namespace.module.clone(),
267+
category: SourceCategory::NamespacedBuiltin,
268+
}
258269
})
259270
.collect()
260271
}
@@ -2113,7 +2124,8 @@ fn type_label(ty: &Type) -> String {
21132124
};
21142125
format!("{} | null", type_label(inner))
21152126
}
2116-
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult" => {
2127+
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult"
2128+
| "HostFutureOutput" => {
21172129
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
21182130
panic!("{ident}<T> requires one generic argument");
21192131
};

pd-host-function/src/lib.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -308,13 +308,13 @@ fn generate_vm_wrapper(
308308

309309
Ok(quote! {
310310
#[allow(dead_code)]
311-
pub(super) fn #wrapper_name(#(#imm_wrapper_params),*) -> #wrapper_output {
311+
pub(crate) fn #wrapper_name(#(#imm_wrapper_params),*) -> #wrapper_output {
312312
#(#imm_extract_stmts)*
313313
#call_expr
314314
}
315315

316316
#[allow(dead_code)]
317-
pub(super) fn #mutable_wrapper_name(#(#mut_wrapper_params),*) -> #wrapper_output {
317+
pub(crate) fn #mutable_wrapper_name(#(#mut_wrapper_params),*) -> #wrapper_output {
318318
#(#mut_extract_stmts)*
319319
#call_expr
320320
}
@@ -344,7 +344,7 @@ fn generate_async_vm_wrapper(
344344
let ty = &pat_type.ty;
345345
if is_host_context_param(input) {
346346
extract_stmts.push(quote! {
347-
let #ident = <#ty as super::CaptureAsyncHostContext>::capture(vm)?;
347+
let #ident = <#ty as super::CaptureAsyncHostContext>::capture_with_args(vm, args)?;
348348
});
349349
call_args.push(quote!(#ident));
350350
continue;
@@ -366,12 +366,14 @@ fn generate_async_vm_wrapper(
366366
} else {
367367
quote!(#impl_name(#(#call_args),*).await)
368368
};
369-
let body = quote! {
370-
#(#extract_stmts)*
371-
vm.submit_host_future(Box::pin(async move {
372-
let value = #await_value;
369+
let future_result = if return_is_host_future_output(&item.sig.output) {
370+
quote!(Ok(value.map(super::return_one)))
371+
} else {
372+
quote! {
373373
match super::IntoHostCallOutcome::into_host_call_outcome(value) {
374-
super::CallOutcome::Return(values) => Ok(values),
374+
super::CallOutcome::Return(values) => {
375+
Ok(super::HostFutureOutput::returning(values))
376+
}
375377
super::CallOutcome::Pending(op_id) => Err(super::VmError::HostError(
376378
format!("async host function returned nested pending operation {op_id}"),
377379
)),
@@ -381,20 +383,27 @@ fn generate_async_vm_wrapper(
381383
),
382384
),
383385
}
386+
}
387+
};
388+
let body = quote! {
389+
#(#extract_stmts)*
390+
vm.submit_host_future(Box::pin(async move {
391+
let value = #await_value;
392+
#future_result
384393
}))
385394
};
386395

387396
Ok(quote! {
388397
#[allow(dead_code)]
389-
pub(super) fn #wrapper_name(
398+
pub(crate) fn #wrapper_name(
390399
vm: &mut super::super::Vm,
391400
args: &[super::super::Value],
392401
) -> super::super::VmResult<super::CallOutcome> {
393402
#body
394403
}
395404

396405
#[allow(dead_code)]
397-
pub(super) fn #mutable_wrapper_name(
406+
pub(crate) fn #mutable_wrapper_name(
398407
vm: &mut super::super::Vm,
399408
args: &mut [super::super::Value],
400409
) -> super::super::VmResult<super::CallOutcome> {
@@ -468,6 +477,20 @@ fn unwrap_vm_result_type(ty: &Type) -> Result<Option<Type>, Error> {
468477
}
469478
}
470479

480+
fn return_is_host_future_output(output: &ReturnType) -> bool {
481+
vm_result_inner_type(output)
482+
.expect("pd_host_function return type should already be validated")
483+
.and_then(|ty| match ty {
484+
Type::Path(path) => path
485+
.path
486+
.segments
487+
.last()
488+
.map(|segment| segment.ident.clone()),
489+
_ => None,
490+
})
491+
.is_some_and(|ident| ident == "HostFutureOutput")
492+
}
493+
471494
fn return_is_vm_result(output: &ReturnType) -> bool {
472495
vm_result_inner_type(output)
473496
.expect("pd_host_function return type should already be validated")
@@ -529,7 +552,8 @@ fn type_label(ty: &Type) -> Result<String, Error> {
529552
let inner_label = type_label(inner)?;
530553
Ok(format!("{inner_label} | null"))
531554
}
532-
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult" => {
555+
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult"
556+
| "HostFutureOutput" => {
533557
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
534558
return Err(Error::new_spanned(
535559
&segment.arguments,
@@ -690,9 +714,26 @@ mod tests {
690714
assert!(expanded.contains("async move"));
691715
assert!(expanded.contains("borrow_arg"));
692716
assert!(expanded.contains("CaptureAsyncHostContext"));
717+
assert!(expanded.contains("capture_with_args"));
693718
assert!(!expanded.contains("pd_host_context"));
694719
}
695720

721+
#[test]
722+
fn async_host_future_output_maps_its_inner_value_to_call_return() {
723+
let attr: Punctuated<Meta, Token![,]> = parse_quote!(name = "test::completion");
724+
let item: ItemFn = parse_quote! {
725+
/// Completes after mutating VM-owned state.
726+
async fn completion() -> VmResult<HostFutureOutput<i64>> {
727+
todo!()
728+
}
729+
};
730+
731+
let expanded = expand_pd_host_function(attr, item)
732+
.expect("host future output should be accepted")
733+
.to_string();
734+
assert!(expanded.contains("value . map (super :: return_one)"));
735+
}
736+
696737
#[test]
697738
fn async_signature_rejects_borrowed_parameters() {
698739
let attr: Punctuated<Meta, Token![,]> = parse_quote!(name = "test::borrowed");

src/builtins/runtime/cancellation.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ impl OperationState {
410410
self.core.status()
411411
}
412412

413+
#[cfg_attr(feature = "async", allow(dead_code))]
413414
pub fn set_payload(&self, payload: ResourceHandle) {
414415
self.core
415416
.inner
@@ -453,6 +454,7 @@ impl OperationState {
453454
.payload
454455
}
455456

457+
#[cfg_attr(feature = "async", allow(dead_code))]
456458
pub fn set_resource(&self, resource: ResourceHandle) {
457459
self.core
458460
.inner
@@ -560,6 +562,7 @@ impl OperationRegistry {
560562
Ok(id)
561563
}
562564

565+
#[cfg_attr(feature = "async", allow(dead_code))]
563566
pub fn start_owned(
564567
&mut self,
565568
owner: OperationOwner,

0 commit comments

Comments
 (0)