Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,031 changes: 1,016 additions & 15 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ name = "vm"
[features]
default = ["runtime", "cli", "cranelift-jit"]
runtime = []
http-client = ["dep:reqwest", "dep:url"]
edge-abi = [
"dep:edge_abi",
"edge_abi/console",
Expand Down Expand Up @@ -60,6 +61,8 @@ cranelift-jit = { version = "0.129.1", optional = true }
cranelift-module = { version = "0.129.1", optional = true }
cranelift-native = { version = "0.129.1", optional = true }
pd-host-function = { path = "./pd-host-function", version = "0.1.0" }
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"], optional = true }
url = { version = "2", optional = true }
edge_abi = { package = "pd-edge-abi", version = "0.1.1", default-features = false, optional = true }
futures-channel = "0.3"
paste = "1"
Expand All @@ -85,5 +88,10 @@ name = "host_binding_generation_tests"
path = "tests/host_binding_generation_tests.rs"
required-features = ["cranelift-jit"]

[[test]]
name = "http_host_tests"
path = "tests/vm/http_host_tests.rs"
required-features = ["http-client"]

[build-dependencies]
syn = { version = "2", features = ["full"] }
53 changes: 45 additions & 8 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ pub(crate) enum HostBindingKind {
StaticNonYieldingArgs,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HostExecutionKind {
Sync,
MaySuspend,
}

impl HostBindingKind {
pub(crate) fn render_bind_static_call(&self, name: &str, function_name: &str) -> String {
let method = match self {
Expand All @@ -71,6 +77,7 @@ struct CallableDecl {
static_return_type: String,
wrapper: Option<WrapperDecl>,
host_binding_kind: HostBindingKind,
host_execution: HostExecutionKind,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -101,11 +108,18 @@ fn main() {
println!("cargo:rerun-if-changed={}", namespace_manifest.display());
let namespaces = parse_namespace_manifest(&namespace_manifest);

let host_sources = [SourceSpec {
path: "src/builtins/runtime/host.rs".to_string(),
module: "host".to_string(),
category: SourceCategory::DefaultHost,
}];
let host_sources = [
SourceSpec {
path: "src/builtins/runtime/host.rs".to_string(),
module: "host".to_string(),
category: SourceCategory::DefaultHost,
},
SourceSpec {
path: "src/builtins/runtime/http.rs".to_string(),
module: "http".to_string(),
category: SourceCategory::DefaultHost,
},
];
let builtin_sources = builtin_source_specs(&namespaces);
let core_sources = [SourceSpec {
path: "src/builtins/runtime/core.rs".to_string(),
Expand Down Expand Up @@ -230,6 +244,24 @@ pub(crate) fn classify_host_binding(function: &ItemFn) -> HostBindingKind {
HostBindingKind::StaticArgs
}

pub(crate) fn infer_host_execution(function: &ItemFn) -> HostExecutionKind {
let return_type = normalized_return_type(&function.sig.output);
if contains_host_call_result(&return_type) {
HostExecutionKind::MaySuspend
} else {
HostExecutionKind::Sync
}
}

fn contains_host_call_result(ty: &Type) -> bool {
if sole_type_argument(ty, "HostCallResult").is_some() {
return true;
}
sole_type_argument(ty, "VmResult")
.or_else(|| sole_type_argument(ty, "HostResult"))
.is_some_and(|inner| contains_host_call_result(&inner))
}

fn is_supported_ordinary_return_type(ty: &Type) -> bool {
match ty {
Type::Group(group) => is_supported_ordinary_return_type(&group.elem),
Expand Down Expand Up @@ -362,6 +394,7 @@ fn parse_source_file(path: &Path, spec: &SourceSpec, _order_offset: usize) -> Ve
static_return_type: static_return_type_label(&function.sig.output),
wrapper,
host_binding_kind: classify_host_binding(function),
host_execution: infer_host_execution(function),
});
}
out
Expand Down Expand Up @@ -1086,9 +1119,13 @@ fn render_callable_consts(callables: &[&CallableDecl]) -> String {
.unwrap();
writeln!(
&mut out,
"#[allow(dead_code)]\nconst {base}_DEF: CallableDef = CallableDef {{ name: {:?}, docs: {:?}, signature: {base}_SIGNATURE }};",
"#[allow(dead_code)]\nconst {base}_DEF: CallableDef = CallableDef {{ name: {:?}, docs: {:?}, signature: {base}_SIGNATURE, host_execution: HostExecution::{} }};",
callable.name,
callable.docs
callable.docs,
match callable.host_execution {
HostExecutionKind::Sync => "Sync",
HostExecutionKind::MaySuspend => "MaySuspend",
}
)
.unwrap();
writeln!(&mut out).unwrap();
Expand Down Expand Up @@ -1965,7 +2002,7 @@ fn type_label(ty: &Type) -> String {
};
format!("{} | null", type_label(inner))
}
"VmResult" | "BuiltinResult" | "HostResult" => {
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult" => {
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
panic!("{ident}<T> requires one generic argument");
};
Expand Down
51 changes: 50 additions & 1 deletion pd-host-function/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ fn parse_name_arg(args: &Punctuated<Meta, Token![,]>) -> Result<LitStr, Error> {
"expected #[pd_host_function(name = \"...\")]",
));
};
if args.len() != 1 {
let extra = args
.iter()
.nth(1)
.expect("a non-empty attribute with more than one argument has an extra argument");
return Err(Error::new_spanned(
extra,
"#[pd_host_function] only supports name = \"...\"",
));
}
if !name_value.path.is_ident("name") {
return Err(Error::new_spanned(
&name_value.path,
Expand Down Expand Up @@ -352,7 +362,7 @@ fn type_label(ty: &Type) -> Result<String, Error> {
let inner_label = type_label(inner)?;
Ok(format!("{inner_label} | null"))
}
"VmResult" | "BuiltinResult" | "HostResult" => {
"VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult" => {
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return Err(Error::new_spanned(
&segment.arguments,
Expand Down Expand Up @@ -472,3 +482,42 @@ fn uses_taken_extractor(ty: &Type) -> bool {
_ => false,
}
}

#[cfg(test)]
mod tests {
use super::expand_pd_host_function;
use syn::{ItemFn, Meta, Token, parse_quote, punctuated::Punctuated};

#[test]
fn accepts_host_call_result_from_the_function_signature() {
let attr: Punctuated<Meta, Token![,]> = parse_quote!(name = "test::suspend");
let item: ItemFn = parse_quote! {
/// Returns a value after a host operation completes.
#[pd_host_function(name = "test::suspend")]
fn suspend() -> VmResult<HostCallResult<Value>> {
todo!()
}
};

let expanded = expand_pd_host_function(attr, item)
.expect("HostCallResult should be accepted from the return signature");
assert!(expanded.to_string().contains("HostCallResult"));
}

#[test]
fn rejects_async_attribute_instead_of_treating_it_as_a_host_contract() {
let attr: Punctuated<Meta, Token![,]> =
parse_quote!(name = "test::suspend", r#async = true);
let item: ItemFn = parse_quote! {
/// Returns a value after a host operation completes.
#[pd_host_function(name = "test::suspend")]
fn suspend() -> VmResult<HostCallResult<Value>> {
todo!()
}
};

let error = expand_pd_host_function(attr, item)
.expect_err("the pd-host-function macro must not accept an async attribute");
assert!(error.to_string().contains("only supports name"));
}
}
7 changes: 7 additions & 0 deletions src/builtins/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ pub struct CallableSignature {
pub return_type: &'static str,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HostExecution {
Sync,
MaySuspend,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CallableDef {
pub name: &'static str,
pub docs: &'static str,
pub signature: CallableSignature,
pub host_execution: HostExecution,
}

#[allow(dead_code)]
Expand Down
4 changes: 3 additions & 1 deletion src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod metadata;
#[cfg(feature = "runtime")]
pub(crate) mod runtime;

pub use self::metadata::{CallableDef, CallableParam, CallableParamType, CallableSignature};
pub use self::metadata::{
CallableDef, CallableParam, CallableParamType, CallableSignature, HostExecution,
};
use crate::ValueType;
#[cfg(feature = "runtime")]
pub(crate) use crate::vm::{HostFunctionRegistry, Value, Vm, VmResult};
Expand Down
Loading
Loading