1use askama::Template;
40use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
41use serde::{Deserialize, Serialize};
42use std::env;
43use std::error::Error;
44use std::fs;
45use std::path::{Path, PathBuf};
46
47const ABI_VERSION: u32 = 1;
48
49fn default_abi_version() -> u32 {
50 ABI_VERSION
51}
52
53#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
65pub struct Manifest {
66 #[serde(default = "default_abi_version")]
69 pub abi_version: u32,
70 pub extension: Extension,
72 #[serde(default)]
74 pub functions: Vec<Function>,
75}
76
77#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
79pub struct Extension {
80 pub name: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
87 pub wasm_module: Option<String>,
88 #[serde(default)]
90 pub prewarm: Vec<String>,
91}
92
93#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
95pub struct Function {
96 pub name: String,
99 #[serde(default)]
101 pub params: Vec<Param>,
102 #[serde(default)]
104 pub returns: Option<Type>,
105 #[serde(default)]
109 pub r#async: bool,
110}
111
112#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
114pub struct Param {
115 pub name: String,
117 #[serde(rename = "type")]
119 pub ty: Type,
120}
121
122#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
133#[serde(rename_all = "lowercase")]
134pub enum Type {
135 String,
139 Int,
141 Float,
143 Bytes,
147}
148
149#[derive(Clone, Debug)]
155pub struct ExtensionSpec {
156 manifest: Manifest,
157}
158
159impl ExtensionSpec {
160 pub fn new(name: impl Into<String>) -> Self {
164 Self {
165 manifest: Manifest {
166 abi_version: ABI_VERSION,
167 extension: Extension {
168 name: name.into(),
169 wasm_module: None,
170 prewarm: Vec::new(),
171 },
172 functions: Vec::new(),
173 },
174 }
175 }
176
177 pub fn wasm_module(mut self, wasm_module: impl Into<String>) -> Self {
180 self.manifest.extension.wasm_module = Some(wasm_module.into());
181 self
182 }
183
184 pub fn prewarm<I, S>(mut self, modules: I) -> Self
187 where
188 I: IntoIterator<Item = S>,
189 S: Into<String>,
190 {
191 self.manifest.extension.prewarm = modules.into_iter().map(Into::into).collect();
192 self
193 }
194
195 pub fn function<F>(mut self, name: impl Into<String>, configure: F) -> Self
199 where
200 F: FnOnce(FunctionSpec) -> FunctionSpec,
201 {
202 self.manifest
203 .functions
204 .push(configure(FunctionSpec::new(name)).build());
205 self
206 }
207
208 pub fn manifest(&self) -> &Manifest {
210 &self.manifest
211 }
212
213 pub fn into_manifest(self) -> Manifest {
215 self.manifest
216 }
217}
218
219#[derive(Clone, Debug)]
222pub struct FunctionSpec {
223 function: Function,
224}
225
226impl FunctionSpec {
227 fn new(name: impl Into<String>) -> Self {
228 Self {
229 function: Function {
230 name: name.into(),
231 params: Vec::new(),
232 returns: None,
233 r#async: false,
234 },
235 }
236 }
237
238 pub fn param(mut self, name: impl Into<String>, ty: Type) -> Self {
241 self.function.params.push(Param {
242 name: name.into(),
243 ty,
244 });
245 self
246 }
247
248 pub fn returns(mut self, ty: Type) -> Self {
250 self.function.returns = Some(ty);
251 self
252 }
253
254 pub fn r#async(mut self) -> Self {
259 self.function.r#async = true;
260 self
261 }
262
263 fn build(self) -> Function {
264 self.function
265 }
266}
267
268pub struct Build {
274 manifest: Manifest,
275 rust_guest: bool,
276 abi_json: bool,
277 abi_json_to: Option<PathBuf>,
278 java_host: Option<(PathBuf, String)>,
279 rust_host: Option<PathBuf>,
280}
281
282impl Build {
283 pub fn new(extension: ExtensionSpec) -> Self {
285 Self {
286 manifest: extension.into_manifest(),
287 rust_guest: false,
288 abi_json: false,
289 abi_json_to: None,
290 java_host: None,
291 rust_host: None,
292 }
293 }
294
295 pub fn emit(self) -> Self {
299 self.emit_rust_guest().emit_abi_json()
300 }
301
302 pub fn emit_defaults(self) -> Self {
304 self.emit()
305 }
306
307 pub fn emit_rust_guest(mut self) -> Self {
310 self.rust_guest = true;
311 self
312 }
313
314 pub fn emit_abi_json(mut self) -> Self {
319 self.abi_json = true;
320 self
321 }
322
323 pub fn emit_abi_json_to(mut self, path: impl Into<PathBuf>) -> Self {
327 self.abi_json_to = Some(path.into());
328 self
329 }
330
331 pub fn emit_java_host(
337 mut self,
338 out_dir: impl Into<PathBuf>,
339 package: impl Into<String>,
340 ) -> Self {
341 self.java_host = Some((out_dir.into(), package.into()));
342 self
343 }
344
345 pub fn emit_rust_host(mut self, out_dir: impl Into<PathBuf>) -> Self {
350 self.rust_host = Some(out_dir.into());
351 self
352 }
353
354 pub fn generate(self) -> Result<(), Box<dyn Error>> {
362 validate_manifest(&self.manifest)?;
363
364 if self.rust_guest {
365 let out_dir = out_dir()?;
366 write_rust_guest(&self.manifest, &out_dir)?;
367 }
368
369 if self.abi_json {
370 let out_dir = out_dir()?;
371 write_abi_json(
372 &self.manifest,
373 &out_dir.join(format!("{}.abi.json", self.manifest.extension.name)),
374 )?;
375 }
376
377 if let Some(path) = &self.abi_json_to {
378 write_abi_json(&self.manifest, path)?;
379 }
380
381 if let Some((out_dir, package)) = &self.java_host {
382 write_java_host(&self.manifest, out_dir, package)?;
383 }
384
385 if let Some(out_dir) = &self.rust_host {
386 write_rust_host(&self.manifest, out_dir)?;
387 }
388
389 Ok(())
390 }
391}
392
393fn out_dir() -> Result<PathBuf, Box<dyn Error>> {
394 Ok(PathBuf::from(env::var("OUT_DIR")?))
395}
396
397fn write_rust_guest(manifest: &Manifest, out_dir: &Path) -> Result<(), Box<dyn Error>> {
398 let filename = format!("ext_{}.rs", manifest.extension.name);
399 fs::write(out_dir.join(filename), generate_rust_code(manifest))?;
400 Ok(())
401}
402
403fn write_abi_json(manifest: &Manifest, path: &Path) -> Result<(), Box<dyn Error>> {
404 if let Some(parent) = path
405 .parent()
406 .filter(|parent| !parent.as_os_str().is_empty())
407 {
408 fs::create_dir_all(parent)?;
409 }
410 fs::write(path, serde_json::to_string_pretty(manifest)? + "\n")?;
411 Ok(())
412}
413
414fn write_java_host(
415 manifest: &Manifest,
416 out_dir: &Path,
417 package: &str,
418) -> Result<(), Box<dyn Error>> {
419 let package_dir = out_dir.join(package.replace('.', "/"));
420 fs::create_dir_all(&package_dir)?;
421 let code = generate_java_code(&manifest, package);
422 let classname = format!(
423 "{}HostFunctions",
424 manifest.extension.name.to_upper_camel_case()
425 );
426 fs::write(package_dir.join(format!("{}.java", classname)), code)?;
427 Ok(())
428}
429
430fn write_rust_host(manifest: &Manifest, out_dir: &Path) -> Result<(), Box<dyn Error>> {
431 fs::create_dir_all(out_dir)?;
432 let filename = format!("host_{}.rs", manifest.extension.name);
433 fs::write(out_dir.join(filename), generate_rust_host_code(manifest)?)?;
434 Ok(())
435}
436
437pub fn read_abi(path: &Path) -> Result<Manifest, Box<dyn Error>> {
440 let content = fs::read_to_string(path)?;
441 let manifest: Manifest = serde_json::from_str(&content)?;
442 validate_manifest(&manifest)?;
443 Ok(manifest)
444}
445
446pub fn generate_java(abi_path: &str, out_dir: &str, package: &str) -> Result<(), Box<dyn Error>> {
454 let manifest = read_abi(Path::new(abi_path))?;
455 write_java_host(&manifest, Path::new(out_dir), package)
456}
457
458pub fn generate_rust_host(abi_path: &str, out_dir: &str) -> Result<(), Box<dyn Error>> {
464 let manifest = read_abi(Path::new(abi_path))?;
465 write_rust_host(&manifest, Path::new(out_dir))
466}
467
468fn validate_manifest(manifest: &Manifest) -> Result<(), Box<dyn Error>> {
469 if manifest.abi_version != ABI_VERSION {
470 return Err(format!(
471 "unsupported ABI version {}; expected {}",
472 manifest.abi_version, ABI_VERSION
473 )
474 .into());
475 }
476 if manifest.extension.name.trim().is_empty() {
477 return Err("extension name is required".into());
478 }
479 validate_identifier("extension name", &manifest.extension.name)?;
480 for function in &manifest.functions {
481 if function.name.trim().is_empty() {
482 return Err("function name is required".into());
483 }
484 validate_identifier("function name", &function.name)?;
485 if is_reserved_async_control_name(&function.name) {
486 return Err(format!("function name '{}' is reserved", function.name).into());
487 }
488 if function.r#async && function.returns != Some(Type::String) {
489 return Err(format!(
490 "async function '{}' must currently return string",
491 function.name
492 )
493 .into());
494 }
495 for param in &function.params {
496 if param.name.trim().is_empty() {
497 return Err(format!("parameter name is required for {}", function.name).into());
498 }
499 validate_identifier(
500 &format!("parameter name for function '{}'", function.name),
501 ¶m.name,
502 )?;
503 }
504 }
505 Ok(())
506}
507
508fn validate_identifier(kind: &str, name: &str) -> Result<(), Box<dyn Error>> {
509 if !is_valid_identifier(name) {
510 return Err(format!(
511 "{} '{}' must be an ASCII Rust/Java identifier: start with a letter or underscore, continue with letters, digits, or underscores, and avoid reserved keywords",
512 kind, name
513 )
514 .into());
515 }
516 Ok(())
517}
518
519fn is_valid_identifier(name: &str) -> bool {
520 let mut chars = name.chars();
521 let Some(first) = chars.next() else {
522 return false;
523 };
524 if !(first == '_' || first.is_ascii_alphabetic()) {
525 return false;
526 }
527 if !chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric()) {
528 return false;
529 }
530 !is_reserved_identifier(name)
531}
532
533fn is_reserved_identifier(name: &str) -> bool {
534 const RESERVED: &[&str] = &[
535 "abstract",
536 "as",
537 "assert",
538 "async",
539 "await",
540 "become",
541 "boolean",
542 "box",
543 "break",
544 "byte",
545 "case",
546 "catch",
547 "char",
548 "class",
549 "const",
550 "continue",
551 "crate",
552 "default",
553 "do",
554 "double",
555 "dyn",
556 "else",
557 "enum",
558 "extends",
559 "extern",
560 "false",
561 "final",
562 "finally",
563 "float",
564 "fn",
565 "for",
566 "gen",
567 "goto",
568 "if",
569 "impl",
570 "implements",
571 "import",
572 "in",
573 "instanceof",
574 "int",
575 "interface",
576 "let",
577 "long",
578 "loop",
579 "macro",
580 "match",
581 "mod",
582 "move",
583 "mut",
584 "native",
585 "new",
586 "null",
587 "override",
588 "package",
589 "priv",
590 "private",
591 "protected",
592 "pub",
593 "public",
594 "ref",
595 "return",
596 "self",
597 "Self",
598 "short",
599 "static",
600 "strictfp",
601 "struct",
602 "super",
603 "switch",
604 "synchronized",
605 "this",
606 "throw",
607 "throws",
608 "trait",
609 "transient",
610 "true",
611 "try",
612 "type",
613 "typeof",
614 "union",
615 "unsafe",
616 "unsized",
617 "use",
618 "virtual",
619 "void",
620 "volatile",
621 "where",
622 "while",
623 "yield",
624 ];
625 RESERVED.contains(&name)
626}
627
628fn is_reserved_async_control_name(name: &str) -> bool {
629 matches!(
630 name,
631 "__async_protocol__"
632 | "__async_start__"
633 | "__async_poll__"
634 | "__async_result__"
635 | "__async_cancel__"
636 )
637}
638
639pub fn generate_rust_code(m: &Manifest) -> String {
647 let mod_name = &m.extension.name;
648 let wasm_mod = m.extension.wasm_module.as_deref().unwrap_or(mod_name);
649
650 let mut out = String::new();
651 out.push_str("use pyo3::prelude::*;\n\n");
652
653 if !m.functions.is_empty() {
655 out.push_str(&format!("#[link(wasm_import_module = \"{}\")]\n", wasm_mod));
656 out.push_str("unsafe extern \"C\" {\n");
657 for f in &m.functions {
658 out.push_str(&format!(" fn {}(\n", wasm_import_name(f)));
659 for p in &f.params {
660 for (wn, wt) in wasm_params(&p.name, p.ty) {
661 out.push_str(&format!(" {}: {},\n", wn, wt));
662 }
663 }
664 if !f.r#async && (f.returns == Some(Type::String) || f.returns == Some(Type::Bytes)) {
665 out.push_str(" result_ptr: *mut u8,\n");
666 out.push_str(" result_max_len: i32,\n");
667 }
668 let ret = wasm_return_type(f);
669 out.push_str(&format!(" ) -> {};\n", ret));
670 }
671 out.push_str("}\n\n");
672 }
673
674 out.push_str("const MAX_RESULT: i32 = 1024 * 1024;\n");
675 out.push_str("/// Host ABI sentinel: the result exceeded the caller-provided result buffer.\n");
676 out.push_str("const HOST_CALL_RESULT_TOO_LARGE: i32 = -2;\n\n");
677
678 for f in &m.functions {
680 out.push_str(&generate_rust_pyo3_wrapper(f));
681 out.push_str("\n");
682 }
683
684 let py_mod_name = format!("_{}", mod_name);
686 out.push_str(&format!(
687 "unsafe extern \"C\" {{\n #[allow(non_snake_case)]\n fn PyInit_{}() -> *mut pyo3::ffi::PyObject;\n}}\n\n",
688 py_mod_name
689 ));
690
691 out.push_str(&format!(
693 "pub fn register() {{\n unsafe {{\n pyo3::ffi::PyImport_AppendInittab(\n b\"{}\\0\".as_ptr() as *const i8,\n Some(PyInit_{}),\n );\n }}\n}}\n\n",
694 py_mod_name, py_mod_name
695 ));
696
697 out.push_str(&format!(
698 "pub fn prewarm(py: Python) {{\n let modules = {:?};\n for name in modules {{\n match py.import(name) {{\n Ok(_) => eprintln!(\"[prewarm] OK: {{}}\", name),\n Err(e) => eprintln!(\"[prewarm] FAILED: {{}} - {{:?}}\", name, e),\n }}\n }}\n}}\n\n",
699 m.extension.prewarm
700 ));
701
702 out.push_str(&format!(
704 "#[pymodule]\nfn {}(m: &Bound<'_, PyModule>) -> PyResult<()> {{\n",
705 py_mod_name
706 ));
707 for f in &m.functions {
708 out.push_str(&format!(
709 " m.add_function(wrap_pyfunction!(py_{}, m)?)?;\n",
710 f.name
711 ));
712 }
713 out.push_str(" Ok(())\n}\n");
714
715 out
716}
717
718fn buffer_return_error_handling() -> String {
725 let mut out = String::new();
726 out.push_str(" if ret == HOST_CALL_RESULT_TOO_LARGE {\n");
727 out.push_str(" return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(\n");
728 out.push_str(
729 " format!(\"host call result exceeded the {MAX_RESULT}-byte limit\")));\n",
730 );
731 out.push_str(" }\n");
732 out.push_str(" if ret < 0 {\n");
733 out.push_str(" return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(\"host call failed\"));\n");
734 out.push_str(" }\n");
735 out
736}
737
738fn generate_rust_pyo3_wrapper(f: &Function) -> String {
739 if f.r#async {
740 return generate_rust_async_pyo3_wrapper(f);
741 }
742
743 let mut out = String::new();
744
745 out.push_str(&format!("#[pyfunction]\n#[pyo3(name = \"{}\")]\n", f.name));
746 let ret_type = match f.returns {
747 Some(Type::String) => "PyResult<String>",
748 Some(Type::Int) => "PyResult<i32>",
749 Some(Type::Float) => "PyResult<f64>",
750 Some(Type::Bytes) => "PyResult<Vec<u8>>",
751 None => "PyResult<()>",
752 };
753 out.push_str(&format!("fn py_{}(", f.name));
754 let py_params: Vec<String> = f
755 .params
756 .iter()
757 .map(|p| format!("{}: {}", &p.name, rust_py_type(p.ty)))
758 .collect();
759 out.push_str(&py_params.join(", "));
760 out.push_str(&format!(") -> {} {{\n", ret_type));
761 out.push_str(" unsafe {\n");
762
763 for p in &f.params {
764 if p.ty == Type::String {
765 out.push_str(&format!(
766 " let {name}_bytes = {name}.as_bytes();\n",
767 name = p.name
768 ));
769 } else if p.ty == Type::Bytes {
770 out.push_str(&format!(
771 " let {name}_bytes = {name};\n",
772 name = p.name
773 ));
774 }
775 }
776
777 if f.returns == Some(Type::String) || f.returns == Some(Type::Bytes) {
778 out.push_str(" let mut result_buf = vec![0u8; MAX_RESULT as usize];\n");
779 }
780
781 if f.returns.is_some() {
782 out.push_str(&format!(" let ret = {}(\n", wasm_import_name(f)));
783 } else {
784 out.push_str(&format!(" {}(\n", wasm_import_name(f)));
785 }
786 for p in &f.params {
787 match p.ty {
788 Type::String | Type::Bytes => {
789 out.push_str(&format!(
790 " {name}_bytes.as_ptr(),\n {name}_bytes.len() as i32,\n",
791 name = p.name
792 ));
793 }
794 Type::Int | Type::Float => {
795 out.push_str(&format!(" {},\n", p.name));
796 }
797 }
798 }
799 if f.returns == Some(Type::String) || f.returns == Some(Type::Bytes) {
800 out.push_str(" result_buf.as_mut_ptr(),\n");
801 out.push_str(" MAX_RESULT,\n");
802 }
803 out.push_str(" );\n");
804
805 match f.returns {
806 Some(Type::String) => {
807 out.push_str(&buffer_return_error_handling());
808 out.push_str(
809 " Ok(String::from_utf8_lossy(&result_buf[..ret as usize]).into_owned())\n",
810 );
811 }
812 Some(Type::Bytes) => {
813 out.push_str(&buffer_return_error_handling());
814 out.push_str(" Ok(result_buf[..ret as usize].to_vec())\n");
815 }
816 Some(Type::Int) => out.push_str(" Ok(ret)\n"),
817 None => out.push_str(" Ok(())\n"),
818 Some(Type::Float) => out.push_str(" Ok(ret)\n"),
819 }
820
821 out.push_str(" }\n}\n");
822 out
823}
824
825fn generate_rust_async_pyo3_wrapper(f: &Function) -> String {
826 validate_async_function(f);
827 let mut out = String::new();
828 out.push_str(&format!("#[pyfunction]\n#[pyo3(name = \"{}\")]\n", f.name));
829 out.push_str(&format!("fn py_{}(py: Python", f.name));
830 for p in &f.params {
831 out.push_str(&format!(", {}: {}", p.name, rust_py_type(p.ty)));
832 }
833 out.push_str(") -> PyResult<Py<PyAny>> {\n");
834 out.push_str(" unsafe {\n");
835 for p in &f.params {
836 if p.ty == Type::String {
837 out.push_str(&format!(
838 " let {name}_bytes = {name}.as_bytes();\n",
839 name = p.name
840 ));
841 } else if p.ty == Type::Bytes {
842 out.push_str(&format!(
843 " let {name}_bytes = {name};\n",
844 name = p.name
845 ));
846 }
847 }
848 out.push_str(&format!(" let token = {}(\n", wasm_import_name(f)));
849 for p in &f.params {
850 match p.ty {
851 Type::String | Type::Bytes => {
852 out.push_str(&format!(
853 " {name}_bytes.as_ptr(),\n {name}_bytes.len() as i32,\n",
854 name = p.name
855 ));
856 }
857 Type::Int | Type::Float => {
858 out.push_str(&format!(" {},\n", p.name));
859 }
860 }
861 }
862 out.push_str(" );\n");
863 out.push_str(" if token < 0 {\n");
867 out.push_str(" return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(\"async host call failed\"));\n");
868 out.push_str(" }\n");
869 out.push_str(" let asyncio = py.import(\"boomslang_host.asyncio\")?;\n");
870 out.push_str(" let from_host_token = asyncio.getattr(\"from_host_token\")?;\n");
871 out.push_str(" let future = from_host_token.call1((token,))?;\n");
872 out.push_str(" Ok(future.unbind())\n");
873 out.push_str(" }\n}\n");
874 out
875}
876
877fn validate_async_function(f: &Function) {
878 if f.returns != Some(Type::String) {
879 panic!("Async function '{}' must currently return string", f.name);
880 }
881}
882
883fn wasm_import_name(f: &Function) -> String {
884 f.name.clone()
885}
886
887fn wasm_params(name: &str, ty: Type) -> Vec<(String, &'static str)> {
888 match ty {
889 Type::String | Type::Bytes => vec![
890 (format!("{}_ptr", name), "*const u8"),
891 (format!("{}_len", name), "i32"),
892 ],
893 Type::Int => vec![(name.to_string(), "i32")],
894 Type::Float => vec![(name.to_string(), "f64")],
895 }
896}
897
898fn wasm_return_type(f: &Function) -> &'static str {
899 if f.r#async {
900 validate_async_function(f);
901 return "i64";
902 }
903 match f.returns {
904 Some(Type::String) | Some(Type::Bytes) | Some(Type::Int) => "i32",
905 Some(Type::Float) => "f64",
906 None => "()",
907 }
908}
909
910fn rust_py_type(ty: Type) -> &'static str {
911 match ty {
912 Type::String => "&str",
913 Type::Int => "i32",
914 Type::Float => "f64",
915 Type::Bytes => "&[u8]",
916 }
917}
918
919pub fn generate_rust_host_code(m: &Manifest) -> Result<String, Box<dyn Error>> {
925 validate_manifest(m)?;
926
927 let ext_name = &m.extension.name;
928 let struct_name = rust_host_struct_name(ext_name);
929 let builder_name = format!("{}Builder", struct_name);
930 let template = RustHostTemplate {
931 struct_name,
932 builder_name,
933 extension_name: rust_string_literal(ext_name),
934 wasm_module: rust_string_literal(m.extension.wasm_module.as_deref().unwrap_or(ext_name)),
935 imports: rust_host_imports(m),
936 functions: m
937 .functions
938 .iter()
939 .map(rust_host_function_template)
940 .collect(),
941 };
942
943 Ok(template.render().map(normalize_template_code)?)
944}
945
946#[derive(Template)]
947#[template(path = "rust_host.rs", escape = "none")]
948struct RustHostTemplate {
949 struct_name: String,
950 builder_name: String,
951 extension_name: String,
952 wasm_module: String,
953 imports: String,
954 functions: Vec<RustHostFunctionTemplate>,
955}
956
957struct RustHostFunctionTemplate {
958 name: String,
959 import_name: String,
960 field: String,
961 arc_handler_type: String,
962 fn_bound: String,
963 with_method: String,
964 register_method: String,
965 wasm_params: String,
966 wasm_returns: String,
967 results_name: &'static str,
968 param_reads: String,
969 return_handling: String,
970 error_handling: String,
971 needs_memory: bool,
972}
973
974fn rust_host_function_template(f: &Function) -> RustHostFunctionTemplate {
975 RustHostFunctionTemplate {
976 name: f.name.clone(),
977 import_name: rust_string_literal(&f.name),
978 field: rust_host_field_name(f),
979 arc_handler_type: rust_host_arc_handler_type(f),
980 fn_bound: rust_host_fn_bound(f),
981 with_method: rust_host_with_method(f),
982 register_method: rust_host_register_method(f),
983 wasm_params: rust_host_valtype_vec(rust_host_wasmtime_params(f)),
984 wasm_returns: rust_host_valtype_vec(rust_host_wasmtime_returns(f)),
985 results_name: if f.r#async || f.returns.is_some() {
986 "results"
987 } else {
988 "_results"
989 },
990 param_reads: rust_host_param_reads(f),
991 return_handling: rust_host_return_handling(f),
992 error_handling: rust_host_error_handling(f),
993 needs_memory: needs_memory(f),
994 }
995}
996
997fn rust_host_imports(m: &Manifest) -> String {
998 let ext_name = &m.extension.name;
999 let wasm_module = m.extension.wasm_module.as_deref().unwrap_or(ext_name);
1000 let imports = m
1001 .functions
1002 .iter()
1003 .map(|f| rust_string_literal(&format!("{}::{}", wasm_module, f.name)))
1004 .collect::<Vec<_>>()
1005 .join(", ");
1006 format!("vec![{}]", imports)
1007}
1008
1009fn rust_host_param_reads(f: &Function) -> String {
1010 let mut out = String::new();
1011 let mut arg_idx = 0;
1012 for (param_idx, p) in f.params.iter().enumerate() {
1013 let rust_name = format!("param{}", param_idx);
1014 match p.ty {
1015 Type::String => {
1016 out.push_str(&format!(
1017 " let {name} = read_string(&caller, memory, expect_i32(params, {ptr_idx}, {ptr_name})?, expect_i32(params, {len_idx}, {len_name})?)?;\n",
1018 name = rust_name,
1019 ptr_idx = arg_idx,
1020 ptr_name = rust_string_literal(&format!("{}_ptr", p.name)),
1021 len_idx = arg_idx + 1,
1022 len_name = rust_string_literal(&format!("{}_len", p.name))
1023 ));
1024 arg_idx += 2;
1025 }
1026 Type::Bytes => {
1027 out.push_str(&format!(
1028 " let {name} = read_bytes(&caller, memory, expect_i32(params, {ptr_idx}, {ptr_name})?, expect_i32(params, {len_idx}, {len_name})?)?;\n",
1029 name = rust_name,
1030 ptr_idx = arg_idx,
1031 ptr_name = rust_string_literal(&format!("{}_ptr", p.name)),
1032 len_idx = arg_idx + 1,
1033 len_name = rust_string_literal(&format!("{}_len", p.name))
1034 ));
1035 arg_idx += 2;
1036 }
1037 Type::Int => {
1038 out.push_str(&format!(
1039 " let {} = expect_i32(params, {}, {})?;\n",
1040 rust_name,
1041 arg_idx,
1042 rust_string_literal(&p.name)
1043 ));
1044 arg_idx += 1;
1045 }
1046 Type::Float => {
1047 out.push_str(&format!(
1048 " let {} = expect_f64(params, {}, {})?;\n",
1049 rust_name,
1050 arg_idx,
1051 rust_string_literal(&p.name)
1052 ));
1053 arg_idx += 1;
1054 }
1055 }
1056 }
1057
1058 if !f.r#async && is_buffer_return(f.returns) {
1059 out.push_str(&format!(
1060 " let result_ptr = expect_i32(params, {}, \"result_ptr\")?;\n",
1061 arg_idx
1062 ));
1063 out.push_str(&format!(
1064 " let result_max_len = expect_i32(params, {}, \"result_max_len\")?;\n",
1065 arg_idx + 1
1066 ));
1067 }
1068
1069 out
1070}
1071
1072fn rust_host_return_handling(f: &Function) -> String {
1073 let call_args = (0..f.params.len())
1074 .map(|idx| format!("param{}", idx))
1075 .collect::<Vec<_>>()
1076 .join(", ");
1077 let call = format!("handler({})", call_args);
1078
1079 if f.r#async {
1080 validate_async_function(f);
1081 return format!(
1082 " let token = {}?;\n results[0] = Val::I64(token);\n",
1083 call
1084 );
1085 }
1086
1087 match f.returns {
1088 Some(Type::String) => format!(
1089 " let result = {}?;\n write_buffer_result(&mut caller, memory, result_ptr, result_max_len, result.as_bytes(), results)?;\n",
1090 call
1091 ),
1092 Some(Type::Bytes) => format!(
1093 " let result = {}?;\n write_buffer_result(&mut caller, memory, result_ptr, result_max_len, &result, results)?;\n",
1094 call
1095 ),
1096 Some(Type::Int) => format!(
1097 " let result = {}?;\n results[0] = Val::I32(result);\n",
1098 call
1099 ),
1100 Some(Type::Float) => format!(
1101 " let result = {}?;\n results[0] = Val::F64(result.to_bits());\n",
1102 call
1103 ),
1104 None => format!(" {}?;\n", call),
1105 }
1106}
1107
1108fn rust_host_error_handling(f: &Function) -> String {
1109 if f.r#async {
1110 format!(
1111 " if let Err(error) = result {{\n eprintln!(\"async host function {{}}::{} failed: {{error:#}}\", Self::MODULE);\n results[0] = Val::I64(-1);\n }}\n Ok(())\n",
1112 f.name
1113 )
1114 } else if is_buffer_return(f.returns) {
1115 format!(
1116 " if let Err(error) = result {{\n eprintln!(\"host function {{}}::{} failed: {{error:#}}\", Self::MODULE);\n results[0] = Val::I32(-1);\n }}\n Ok(())\n",
1117 f.name
1118 )
1119 } else {
1120 " result.map_err(wasmtime::Error::msg)\n".to_string()
1121 }
1122}
1123
1124fn rust_host_struct_name(ext_name: &str) -> String {
1125 format!("{}HostFunctions", ext_name.to_upper_camel_case())
1126}
1127
1128fn rust_host_field_name(f: &Function) -> String {
1129 format!("{}_handler", f.name.to_snake_case())
1130}
1131
1132fn rust_host_with_method(f: &Function) -> String {
1133 format!("with_{}", f.name.to_snake_case())
1134}
1135
1136fn rust_host_register_method(f: &Function) -> String {
1137 format!("register_{}", f.name.to_snake_case())
1138}
1139
1140fn rust_host_arc_handler_type(f: &Function) -> String {
1141 format!("Arc<dyn {} + Send + Sync>", rust_host_fn_bound(f))
1142}
1143
1144fn rust_host_fn_bound(f: &Function) -> String {
1145 let params = f
1146 .params
1147 .iter()
1148 .map(|p| rust_host_value_type(p.ty))
1149 .collect::<Vec<_>>()
1150 .join(", ");
1151 format!("Fn({}) -> Result<{}>", params, rust_host_return_type(f))
1152}
1153
1154fn rust_host_value_type(ty: Type) -> &'static str {
1155 match ty {
1156 Type::String => "String",
1157 Type::Int => "i32",
1158 Type::Float => "f64",
1159 Type::Bytes => "Vec<u8>",
1160 }
1161}
1162
1163fn rust_host_return_type(f: &Function) -> &'static str {
1164 if f.r#async {
1165 validate_async_function(f);
1166 return "i64";
1167 }
1168 match f.returns {
1169 Some(Type::String) => "String",
1170 Some(Type::Int) => "i32",
1171 Some(Type::Float) => "f64",
1172 Some(Type::Bytes) => "Vec<u8>",
1173 None => "()",
1174 }
1175}
1176
1177fn rust_host_wasmtime_params(f: &Function) -> Vec<&'static str> {
1178 let mut params = Vec::new();
1179 for p in &f.params {
1180 match p.ty {
1181 Type::String | Type::Bytes => {
1182 params.push("ValType::I32");
1183 params.push("ValType::I32");
1184 }
1185 Type::Int => params.push("ValType::I32"),
1186 Type::Float => params.push("ValType::F64"),
1187 }
1188 }
1189 if !f.r#async && is_buffer_return(f.returns) {
1190 params.push("ValType::I32");
1191 params.push("ValType::I32");
1192 }
1193 params
1194}
1195
1196fn rust_host_wasmtime_returns(f: &Function) -> Vec<&'static str> {
1197 if f.r#async {
1198 validate_async_function(f);
1199 return vec!["ValType::I64"];
1200 }
1201 match f.returns {
1202 Some(Type::String) | Some(Type::Bytes) | Some(Type::Int) => vec!["ValType::I32"],
1203 Some(Type::Float) => vec!["ValType::F64"],
1204 None => vec![],
1205 }
1206}
1207
1208fn rust_host_valtype_vec(types: Vec<&'static str>) -> String {
1209 if types.is_empty() {
1210 return "vec![]".to_string();
1211 }
1212 if types.len() <= 3 {
1213 return format!("vec![{}]", types.join(", "));
1214 }
1215 format!(
1216 "vec![\n {},\n ]",
1217 types.join(",\n ")
1218 )
1219}
1220
1221fn rust_string_literal(value: &str) -> String {
1222 format!("{:?}", value)
1223}
1224
1225#[derive(Template)]
1228#[template(path = "java_host_functions.java", escape = "none")]
1229struct JavaHostFunctionsTemplate {
1230 package: String,
1231 class_name: String,
1232 extension_name: String,
1233 wasm_module: String,
1234 has_async: bool,
1235 functions: Vec<JavaFunctionTemplate>,
1236}
1237
1238struct JavaFunctionTemplate {
1239 name: String,
1240 upper_name: String,
1241 field: String,
1242 handler_type: String,
1243 with_method: String,
1244 return_type: String,
1245 interface_params: String,
1246 wasm_params: String,
1247 wasm_returns: String,
1248 param_reads: String,
1249 return_handling: String,
1250 error_handling: &'static str,
1251 #[allow(dead_code)]
1252 is_async: bool,
1253 needs_memory: bool,
1254}
1255
1256pub fn generate_java_code(m: &Manifest, package: &str) -> String {
1261 let ext_name = &m.extension.name;
1262 let template = JavaHostFunctionsTemplate {
1263 package: package.to_string(),
1264 class_name: format!("{}HostFunctions", ext_name.to_upper_camel_case()),
1265 extension_name: ext_name.to_string(),
1266 wasm_module: m
1267 .extension
1268 .wasm_module
1269 .as_deref()
1270 .unwrap_or(ext_name)
1271 .to_string(),
1272 has_async: m.functions.iter().any(|f| f.r#async),
1273 functions: m.functions.iter().map(java_function_template).collect(),
1274 };
1275
1276 template
1277 .render()
1278 .map(normalize_template_code)
1279 .expect("failed to render Java host functions template")
1280}
1281
1282fn normalize_template_code(code: String) -> String {
1283 let mut out = Vec::new();
1284 let mut previous_blank = false;
1285
1286 for line in code.lines() {
1287 let line = line.trim_end();
1288 if line.is_empty() {
1289 if !previous_blank {
1290 out.push(String::new());
1291 }
1292 previous_blank = true;
1293 continue;
1294 }
1295
1296 out.push(line.to_string());
1297 previous_blank = false;
1298 }
1299
1300 let mut idx = 1;
1301 while idx < out.len() {
1302 if out[idx - 1].is_empty() && out[idx].trim() == "}" {
1303 out.remove(idx - 1);
1304 } else {
1305 idx += 1;
1306 }
1307 }
1308
1309 out.join("\n") + "\n"
1310}
1311
1312fn java_function_template(f: &Function) -> JavaFunctionTemplate {
1313 let field = f.name.to_lower_camel_case();
1314 JavaFunctionTemplate {
1315 name: f.name.clone(),
1316 upper_name: f.name.to_upper_camel_case(),
1317 field: field.clone(),
1318 handler_type: format!("{}Handler", f.name.to_upper_camel_case()),
1319 with_method: format!("with{}", f.name.to_upper_camel_case()),
1320 return_type: java_handler_return_type(f),
1321 interface_params: java_interface_params(f),
1322 wasm_params: java_value_type_list(java_wasm_params(f)),
1323 wasm_returns: java_value_type_list(java_wasm_returns(f)),
1324 param_reads: java_param_reads(f),
1325 return_handling: java_return_handling(f, &field),
1326 error_handling: java_error_handling(f),
1327 is_async: f.r#async,
1328 needs_memory: needs_memory(f),
1329 }
1330}
1331
1332fn needs_memory(f: &Function) -> bool {
1337 let reads_buffer = f
1338 .params
1339 .iter()
1340 .any(|p| p.ty == Type::String || p.ty == Type::Bytes);
1341 let writes_buffer = !f.r#async && is_buffer_return(f.returns);
1342 reads_buffer || writes_buffer
1343}
1344
1345fn java_interface_params(f: &Function) -> String {
1346 f.params
1347 .iter()
1348 .map(|p| format!("{} {}", java_type(p.ty), p.name.to_lower_camel_case()))
1349 .collect::<Vec<_>>()
1350 .join(", ")
1351}
1352
1353fn java_wasm_params(f: &Function) -> Vec<&'static str> {
1354 let mut params = Vec::new();
1355 for p in &f.params {
1356 match p.ty {
1357 Type::String | Type::Bytes => {
1358 params.push("ValueType.I32");
1359 params.push("ValueType.I32");
1360 }
1361 Type::Int => params.push("ValueType.I32"),
1362 Type::Float => params.push("ValueType.F64"),
1363 }
1364 }
1365 if !f.r#async && is_buffer_return(f.returns) {
1366 params.push("ValueType.I32");
1367 params.push("ValueType.I32");
1368 }
1369 params
1370}
1371
1372fn java_value_type_list(types: Vec<&'static str>) -> String {
1373 if types.is_empty() {
1374 return "List.of()".to_string();
1375 }
1376 if types.len() <= 3 {
1377 return format!("List.of({})", types.join(", "));
1378 }
1379 format!(
1380 "List.of(\n {}\n )",
1381 types.join(",\n ")
1382 )
1383}
1384
1385fn java_wasm_returns(f: &Function) -> Vec<&'static str> {
1386 if f.r#async {
1387 validate_async_function(f);
1388 return vec!["ValueType.I64"];
1389 }
1390 match f.returns {
1391 Some(Type::String) | Some(Type::Bytes) | Some(Type::Int) => vec!["ValueType.I32"],
1392 Some(Type::Float) => vec!["ValueType.F64"],
1393 None => vec![],
1394 }
1395}
1396
1397fn java_param_reads(f: &Function) -> String {
1398 let mut out = String::new();
1399 let mut arg_idx = 0;
1400 for (param_idx, p) in f.params.iter().enumerate() {
1401 let java_name = format!("param{}", param_idx);
1402 match p.ty {
1403 Type::String => {
1404 out.push_str(&format!(
1405 " int {name}Ptr = Math.toIntExact(wasmArgs[{i}]);\n int {name}Len = Math.toIntExact(wasmArgs[{j}]);\n String {name} = memory.readString({name}Ptr, {name}Len, StandardCharsets.UTF_8);\n",
1406 name = java_name,
1407 i = arg_idx,
1408 j = arg_idx + 1
1409 ));
1410 arg_idx += 2;
1411 }
1412 Type::Bytes => {
1413 out.push_str(&format!(
1414 " int {name}Ptr = Math.toIntExact(wasmArgs[{i}]);\n int {name}Len = Math.toIntExact(wasmArgs[{j}]);\n byte[] {name} = memory.readBytes({name}Ptr, {name}Len);\n",
1415 name = java_name,
1416 i = arg_idx,
1417 j = arg_idx + 1
1418 ));
1419 arg_idx += 2;
1420 }
1421 Type::Int => {
1422 out.push_str(&format!(
1423 " int {} = Math.toIntExact(wasmArgs[{}]);\n",
1424 java_name, arg_idx
1425 ));
1426 arg_idx += 1;
1427 }
1428 Type::Float => {
1429 out.push_str(&format!(
1430 " double {} = Double.longBitsToDouble(wasmArgs[{}]);\n",
1431 java_name, arg_idx
1432 ));
1433 arg_idx += 1;
1434 }
1435 }
1436 }
1437
1438 if !f.r#async && is_buffer_return(f.returns) {
1439 out.push_str(&format!(
1440 " int resultPtr = Math.toIntExact(wasmArgs[{}]);\n int resultMaxLen = Math.toIntExact(wasmArgs[{}]);\n",
1441 arg_idx,
1442 arg_idx + 1
1443 ));
1444 }
1445
1446 out
1447}
1448
1449fn java_return_handling(f: &Function, field: &str) -> String {
1450 let call_expr = java_handler_call(f, field);
1451 if f.r#async {
1452 validate_async_function(f);
1453 return format!(
1454 " if (asyncRegistry == null) {{\n throw new IllegalStateException(\n \"AsyncHostRegistry is required for async host function \" + MODULE + \"::{name}\"\n );\n }}\n CompletionStage<String> stage = {call_expr};\n if (stage == null) {{\n throw new IllegalStateException(\n \"Host function returned null: \" + MODULE + \"::{name}\"\n );\n }}\n return new long[] {{ asyncRegistry.start(stage) }};",
1455 call_expr = call_expr,
1456 name = f.name
1457 );
1458 }
1459 match f.returns {
1460 Some(Type::String) => format!(
1461 " String result = {call_expr};\n if (result == null) {{\n throw new IllegalStateException(\n \"Host function returned null: \" + MODULE + \"::{name}\"\n );\n }}\n byte[] resultBytes = result.getBytes(StandardCharsets.UTF_8);\n if (resultBytes.length > resultMaxLen) {{\n return new long[] {{ -2 }};\n }}\n memory.write(resultPtr, resultBytes);\n return new long[] {{ resultBytes.length }};",
1462 call_expr = call_expr,
1463 name = f.name
1464 ),
1465 Some(Type::Bytes) => format!(
1466 " byte[] resultBytes = {call_expr};\n if (resultBytes == null) {{\n throw new IllegalStateException(\n \"Host function returned null: \" + MODULE + \"::{name}\"\n );\n }}\n if (resultBytes.length > resultMaxLen) {{\n return new long[] {{ -2 }};\n }}\n memory.write(resultPtr, resultBytes);\n return new long[] {{ resultBytes.length }};",
1467 call_expr = call_expr,
1468 name = f.name
1469 ),
1470 Some(Type::Int) => format!(
1471 " int result = {};\n return new long[] {{ result }};",
1472 call_expr
1473 ),
1474 Some(Type::Float) => format!(
1475 " double result = {};\n return new long[] {{ Double.doubleToRawLongBits(result) }};",
1476 call_expr
1477 ),
1478 None => format!(
1479 " {};\n return null;",
1480 call_expr
1481 ),
1482 }
1483}
1484
1485fn java_handler_call(f: &Function, field: &str) -> String {
1486 let call_args = f
1487 .params
1488 .iter()
1489 .enumerate()
1490 .map(|(idx, _)| format!("param{}", idx))
1491 .collect::<Vec<_>>()
1492 .join(", ");
1493 format!("{}.handle({})", field, call_args)
1494}
1495
1496fn java_error_handling(f: &Function) -> &'static str {
1497 if f.r#async {
1498 " if (asyncRegistry == null) {\n return new long[] { -1 };\n }\n return new long[] { asyncRegistry.startFailed(e) };"
1502 } else if is_buffer_return(f.returns) {
1503 " return new long[] { -1 };"
1504 } else {
1505 " throw e;"
1506 }
1507}
1508
1509fn is_buffer_return(ty: Option<Type>) -> bool {
1510 matches!(ty, Some(Type::String) | Some(Type::Bytes))
1511}
1512
1513fn java_type(ty: Type) -> &'static str {
1514 match ty {
1515 Type::String => "String",
1516 Type::Int => "int",
1517 Type::Float => "double",
1518 Type::Bytes => "byte[]",
1519 }
1520}
1521
1522fn java_handler_return_type(f: &Function) -> String {
1523 if f.r#async {
1524 validate_async_function(f);
1525 "CompletionStage<String>".to_string()
1526 } else {
1527 java_return_type(f.returns).to_string()
1528 }
1529}
1530
1531fn java_return_type(ty: Option<Type>) -> &'static str {
1532 match ty {
1533 Some(Type::String) => "String",
1534 Some(Type::Int) => "int",
1535 Some(Type::Float) => "double",
1536 Some(Type::Bytes) => "byte[]",
1537 None => "void",
1538 }
1539}
1540
1541#[cfg(test)]
1542mod tests;