Skip to content

Commit 03ea439

Browse files
committed
feat(host): infer optimized bindings from signatures
1 parent 052090e commit 03ea439

4 files changed

Lines changed: 325 additions & 46 deletions

File tree

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ windows-sys = { version = "0.59", features = ["Win32_System_Diagnostics_Debug",
7777
libc = "0.2"
7878

7979
[dev-dependencies]
80+
syn = { version = "2", features = ["full"] }
8081
tokio = { version = "1", features = ["macros", "rt", "time", "sync"] }
8182

83+
[[test]]
84+
name = "host_binding_generation_tests"
85+
path = "tests/host_binding_generation_tests.rs"
86+
8287
[build-dependencies]
8388
syn = { version = "2", features = ["full"] }

build.rs

Lines changed: 139 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ enum WrapperParamKind {
4141
SliceArgs,
4242
}
4343

44+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45+
pub(crate) enum HostBindingKind {
46+
StaticStack,
47+
StaticArgs,
48+
StaticNonYieldingArgs,
49+
}
50+
51+
impl HostBindingKind {
52+
pub(crate) fn render_bind_static_call(&self, name: &str, function_name: &str) -> String {
53+
let method = match self {
54+
Self::StaticStack => "bind_static_stack_function",
55+
Self::StaticNonYieldingArgs => "bind_static_non_yielding_args_function",
56+
Self::StaticArgs => "bind_static_args_function",
57+
};
58+
format!("vm.{method}({name:?}, {function_name});")
59+
}
60+
}
61+
4462
#[derive(Clone, Debug)]
4563
struct CallableDecl {
4664
rust_ident: String,
@@ -51,6 +69,7 @@ struct CallableDecl {
5169
return_label: String,
5270
static_return_type: String,
5371
wrapper: Option<WrapperDecl>,
72+
host_binding_kind: HostBindingKind,
5473
}
5574

5675
#[derive(Clone, Debug)]
@@ -184,6 +203,110 @@ fn parse_sources(
184203
out
185204
}
186205

206+
pub(crate) fn classify_host_binding(function: &ItemFn) -> HostBindingKind {
207+
if function.sig.inputs.iter().any(|input| match input {
208+
FnArg::Typed(pat_type) => is_vm_context_type(&pat_type.ty),
209+
_ => false,
210+
}) {
211+
return HostBindingKind::StaticStack;
212+
}
213+
let return_type = normalized_return_type(&function.sig.output);
214+
if matches!(
215+
plain_path_type(&return_type).as_deref(),
216+
Some("CallOutcome")
217+
) {
218+
return HostBindingKind::StaticArgs;
219+
}
220+
if let Some(inner) = sole_type_argument(&return_type, "VmResult")
221+
.or_else(|| sole_type_argument(&return_type, "HostResult"))
222+
{
223+
if matches!(plain_path_type(&inner).as_deref(), Some("CallOutcome")) {
224+
return HostBindingKind::StaticArgs;
225+
}
226+
}
227+
if is_supported_ordinary_return_type(&return_type) {
228+
return HostBindingKind::StaticNonYieldingArgs;
229+
}
230+
HostBindingKind::StaticArgs
231+
}
232+
233+
fn is_supported_ordinary_return_type(ty: &Type) -> bool {
234+
match ty {
235+
Type::Group(group) => is_supported_ordinary_return_type(&group.elem),
236+
Type::Paren(paren) => is_supported_ordinary_return_type(&paren.elem),
237+
Type::Reference(reference) => is_supported_ordinary_return_type(&reference.elem),
238+
Type::Path(path) => {
239+
path.path
240+
.segments
241+
.last()
242+
.is_some_and(|seg| match seg.ident.to_string().as_str() {
243+
"Option" | "VmResult" | "HostResult" => {
244+
let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
245+
return false;
246+
};
247+
args.args
248+
.first()
249+
.and_then(|arg| match arg {
250+
syn::GenericArgument::Type(inner) => Some(inner),
251+
_ => None,
252+
})
253+
.is_some_and(|inner| is_supported_ordinary_return_type(inner))
254+
}
255+
_ => true,
256+
})
257+
}
258+
Type::Tuple(tuple) => tuple.elems.is_empty(),
259+
_ => false,
260+
}
261+
}
262+
263+
fn normalized_return_type(output: &ReturnType) -> Type {
264+
match output {
265+
ReturnType::Default => syn::parse_quote!(()),
266+
ReturnType::Type(_, ty) => unwrap_surface_type(ty),
267+
}
268+
}
269+
270+
fn unwrap_surface_type(ty: &Type) -> Type {
271+
match ty {
272+
Type::Group(group) => unwrap_surface_type(&group.elem),
273+
Type::Paren(paren) => unwrap_surface_type(&paren.elem),
274+
Type::Reference(reference) => unwrap_surface_type(&reference.elem),
275+
_ => ty.clone(),
276+
}
277+
}
278+
279+
fn plain_path_type(ty: &Type) -> Option<String> {
280+
match ty {
281+
Type::Group(group) => plain_path_type(&group.elem),
282+
Type::Paren(paren) => plain_path_type(&paren.elem),
283+
Type::Reference(reference) => plain_path_type(&reference.elem),
284+
Type::Path(path) => path.path.segments.last().map(|seg| seg.ident.to_string()),
285+
_ => None,
286+
}
287+
}
288+
289+
fn sole_type_argument(ty: &Type, wrapper: &str) -> Option<Type> {
290+
let ty = unwrap_surface_type(ty);
291+
match &ty {
292+
Type::Path(path) => {
293+
let segment = path.path.segments.last()?;
294+
if segment.ident != wrapper {
295+
return None;
296+
}
297+
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
298+
return None;
299+
};
300+
let arg = args.args.first()?;
301+
match arg {
302+
syn::GenericArgument::Type(inner) => Some(inner.clone()),
303+
_ => None,
304+
}
305+
}
306+
_ => None,
307+
}
308+
}
309+
187310
fn parse_source_file(path: &Path, spec: &SourceSpec, _order_offset: usize) -> Vec<CallableDecl> {
188311
let source = fs::read_to_string(path)
189312
.unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()));
@@ -214,6 +337,7 @@ fn parse_source_file(path: &Path, spec: &SourceSpec, _order_offset: usize) -> Ve
214337
return_label: return_type_label(&function.sig.output),
215338
static_return_type: static_return_type_label(&function.sig.output),
216339
wrapper,
340+
host_binding_kind: classify_host_binding(function),
217341
});
218342
}
219343
out
@@ -765,7 +889,7 @@ fn render_builtin_runtime_dispatch(
765889
.as_ref()
766890
.expect("host wrappers should exist");
767891
let adapter_name = host_wrapper_adapter_name(callable);
768-
if wrapper_uses_vm(wrapper) {
892+
if callable.host_binding_kind == HostBindingKind::StaticStack {
769893
writeln!(
770894
&mut out,
771895
"fn {adapter_name}(vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome> {{"
@@ -799,11 +923,7 @@ fn render_builtin_runtime_dispatch(
799923
)
800924
.unwrap();
801925
for callable in host_callables {
802-
let wrapper = callable
803-
.wrapper
804-
.as_ref()
805-
.expect("host wrappers should exist");
806-
if wrapper_uses_vm(wrapper) {
926+
if callable.host_binding_kind == HostBindingKind::StaticStack {
807927
writeln!(
808928
&mut out,
809929
" registry.register_static_stack({:?}, {}, {});",
@@ -812,6 +932,15 @@ fn render_builtin_runtime_dispatch(
812932
host_wrapper_adapter_name(callable)
813933
)
814934
.unwrap();
935+
} else if callable.host_binding_kind == HostBindingKind::StaticNonYieldingArgs {
936+
writeln!(
937+
&mut out,
938+
" registry.register_static_non_yielding_args({:?}, {}, {});",
939+
callable.name,
940+
callable.params.len(),
941+
host_wrapper_adapter_name(callable)
942+
)
943+
.unwrap();
815944
} else {
816945
writeln!(
817946
&mut out,
@@ -833,28 +962,11 @@ fn render_builtin_runtime_dispatch(
833962
.unwrap();
834963
writeln!(&mut out, " match name {{").unwrap();
835964
for callable in host_callables {
836-
let wrapper = callable
837-
.wrapper
838-
.as_ref()
839-
.expect("host wrappers should exist");
965+
let bind_call = callable
966+
.host_binding_kind
967+
.render_bind_static_call(&callable.name, &host_wrapper_adapter_name(callable));
840968
writeln!(&mut out, " {:?} => {{", callable.name).unwrap();
841-
if wrapper_uses_vm(wrapper) {
842-
writeln!(
843-
&mut out,
844-
" vm.bind_static_stack_function({:?}, {});",
845-
callable.name,
846-
host_wrapper_adapter_name(callable)
847-
)
848-
.unwrap();
849-
} else {
850-
writeln!(
851-
&mut out,
852-
" vm.bind_static_args_function({:?}, {});",
853-
callable.name,
854-
host_wrapper_adapter_name(callable)
855-
)
856-
.unwrap();
857-
}
969+
writeln!(&mut out, " {bind_call}").unwrap();
858970
writeln!(&mut out, " true").unwrap();
859971
writeln!(&mut out, " }}").unwrap();
860972
}
@@ -1606,13 +1718,6 @@ fn host_wrapper_adapter_name(callable: &CallableDecl) -> String {
16061718
format!("__pd_host_adapter_{}", callable.rust_ident)
16071719
}
16081720

1609-
fn wrapper_uses_vm(wrapper: &WrapperDecl) -> bool {
1610-
wrapper
1611-
.params
1612-
.iter()
1613-
.any(|param| matches!(param, WrapperParamKind::Vm))
1614-
}
1615-
16161721
fn generated_wrapper_decl(function: &ItemFn) -> WrapperDecl {
16171722
let mut params = Vec::new();
16181723
for input in &function.sig.inputs {

src/builtins/runtime/host.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn sleep_duration(millis: i64) -> VmResult<Duration> {
4747

4848
/// Sleeps for the requested milliseconds.
4949
#[pd_host_function(name = "runtime::sleep")]
50-
fn runtime_sleep_impl(_vm: &mut Vm, ms: i64) -> VmResult<bool> {
50+
fn runtime_sleep_impl(ms: i64) -> VmResult<bool> {
5151
let duration = sleep_duration(ms)?;
5252
#[cfg(not(target_arch = "wasm32"))]
5353
std::thread::sleep(duration);
@@ -58,7 +58,7 @@ fn runtime_sleep_impl(_vm: &mut Vm, ms: i64) -> VmResult<bool> {
5858

5959
/// Halts the current VM invocation immediately.
6060
#[pd_host_function(name = "runtime::exit")]
61-
fn runtime_exit_impl(_vm: &mut Vm) -> VmResult<CallOutcome> {
61+
fn runtime_exit_impl() -> VmResult<CallOutcome> {
6262
Ok(CallOutcome::Halt)
6363
}
6464

@@ -94,11 +94,7 @@ mod tests {
9494

9595
#[test]
9696
fn runtime_sleep_rejects_negative_milliseconds() {
97-
let mut vm = Vm::new(Program::new(
98-
Vec::new(),
99-
vec![crate::bytecode::OpCode::Ret as u8],
100-
));
101-
let err = runtime_sleep_impl(&mut vm, -1).expect_err("negative sleep should fail");
97+
let err = runtime_sleep_impl(-1).expect_err("negative sleep should fail");
10298
assert!(
10399
err.to_string()
104100
.contains("runtime::sleep expects non-negative milliseconds"),
@@ -118,12 +114,8 @@ mod tests {
118114

119115
#[test]
120116
fn runtime_exit_returns_halt_outcome() {
121-
let mut vm = Vm::new(Program::new(
122-
Vec::new(),
123-
vec![crate::bytecode::OpCode::Ret as u8],
124-
));
125117
assert_eq!(
126-
runtime_exit_impl(&mut vm).expect("runtime::exit should halt"),
118+
runtime_exit_impl().expect("runtime::exit should halt"),
127119
CallOutcome::Halt
128120
);
129121
}

0 commit comments

Comments
 (0)