gobley_wasm_transformer/
lib.rs1pub mod import;
8pub mod stack;
9
10use std::collections::BTreeSet;
11
12use askama::Template;
13use base64::Engine;
14use walrus::{
15 ir::Value, ConstExpr, ElementItems, ElementKind, Export, ExportItem, Function, Global,
16 GlobalKind, Import, ImportKind, Module, ModuleGlobals, ValType,
17};
18
19use self::import::WasmFunctionImport;
20
21#[derive(Debug)]
22pub struct Transformer {
23 module: Module,
24 function_imports: Vec<WasmFunctionImport>,
25}
26
27#[derive(Template)]
28#[template(syntax = "kt", escape = "none", path = "js.kt")]
29pub struct KotlinJsRenderer<'a> {
30 package_name: Option<&'a str>,
31 base64: &'a str,
32 module: &'a Module,
33}
34
35impl<'a> KotlinJsRenderer<'a> {
36 fn import_modules(&self) -> Vec<String> {
37 let import_modules = self
38 .module
39 .imports
40 .iter()
41 .map(|i| &i.module)
42 .collect::<BTreeSet<_>>();
43
44 import_modules.iter().map(|i| i.to_string()).collect()
45 }
46
47 fn imports_from_module<'b>(
48 &'b self,
49 module: impl AsRef<str> + 'b,
50 ) -> impl Iterator<Item = &'a Import> + 'b {
51 self.module
52 .imports
53 .iter()
54 .filter(move |i| i.module == module.as_ref())
55 }
56
57 fn import_to_kt_signature(&self, import: &Import) -> String {
58 match import.kind {
59 ImportKind::Function(id) => self.function_to_kt_signature(self.module.funcs.get(id)),
60 ImportKind::Table(_) => "WebAssembly.Table".to_string(),
61 ImportKind::Memory(_) => "WebAssembly.Memory".to_string(),
62 ImportKind::Global(id) => Self::global_to_kt_signature(self.module.globals.get(id)),
63 }
64 }
65
66 fn import_to_function_table_entry_idx(&self, import: &Import) -> Option<usize> {
67 let ImportKind::Function(function_id) = import.kind else {
68 return None;
69 };
70
71 let Ok(Some(main_function_table)) = self.module.tables.main_function_table() else {
72 return None;
73 };
74
75 for element in self.module.elements.iter() {
76 let ElementItems::Functions(function_ids) = &element.items else {
77 continue;
78 };
79 let Some(offset) = function_ids.iter().position(|id| *id == function_id) else {
80 continue;
81 };
82 let ElementKind::Active {
83 table,
84 offset: element_offset,
85 } = &element.kind
86 else {
87 continue;
88 };
89 if main_function_table != *table {
90 continue;
91 }
92
93 fn get_usize_from_constexpr(
94 globals: &ModuleGlobals,
95 expr: &ConstExpr,
96 ) -> Option<usize> {
97 Some(match expr {
98 ConstExpr::Value(value) => match value {
99 Value::I32(i32) => *i32 as usize,
100 Value::I64(i64) => *i64 as usize,
101 Value::F32(f32) => *f32 as usize,
102 Value::F64(f64) => *f64 as usize,
103 Value::V128(v128) => *v128 as usize,
104 },
105 ConstExpr::Global(id) => {
106 return match &globals.get(*id).kind {
107 GlobalKind::Local(expr) => get_usize_from_constexpr(globals, expr),
108 _ => None,
109 }
110 }
111 _ => return None,
112 })
113 }
114
115 let Some(element_offset) =
116 get_usize_from_constexpr(&self.module.globals, element_offset)
117 else {
118 continue;
119 };
120
121 return Some(offset + element_offset);
122 }
123
124 None
125 }
126
127 fn exports(&self) -> impl Iterator<Item = &Export> {
128 self.module.exports.iter()
129 }
130
131 fn export_to_kt_signature(&self, export: &Export) -> String {
132 match export.item {
133 ExportItem::Function(id) => self.function_to_kt_signature(self.module.funcs.get(id)),
134 ExportItem::Table(_) => "WebAssembly.Table".to_string(),
135 ExportItem::Memory(_) => "WebAssembly.Memory".to_string(),
136 ExportItem::Global(id) => Self::global_to_kt_signature(self.module.globals.get(id)),
137 }
138 }
139
140 fn function_to_kt_signature(&self, function: &Function) -> String {
141 let ty = self.module.types.get(function.ty());
142 let mut output = String::new();
143 let mut first = true;
144 output.push('(');
145
146 for param_str in ty.params().iter().map(Self::map_val_type_to_kt) {
147 if !first {
148 output.push_str(", ");
149 }
150 first = false;
151 output.push_str(param_str);
152 }
153
154 output.push_str(") -> ");
155
156 if let Some(result) = ty.results().first() {
157 output.push_str(Self::map_val_type_to_kt(result));
158 } else {
159 output.push_str("Unit");
160 }
161
162 output
163 }
164
165 fn global_to_kt_signature(global: &Global) -> String {
166 let inner_ty = Self::map_val_type_to_kt(&global.ty);
167 format!("WebAssembly.Global<{inner_ty}>")
168 }
169
170 fn map_val_type_to_kt(ty: &ValType) -> &'static str {
171 match ty {
172 ValType::I32 => "Int",
173 ValType::F32 => "Float",
174 ValType::F64 => "Double",
175 _ => "Any",
176 }
177 }
178}
179
180impl Transformer {
181 pub fn new(input: &[u8], function_imports: Vec<WasmFunctionImport>) -> anyhow::Result<Self> {
182 Ok(Self {
183 module: Module::from_buffer(input)?,
184 function_imports,
185 })
186 }
187
188 fn transform(&mut self) -> anyhow::Result<()> {
189 self.inject_stack_pointer_shim()?;
190 self.inject_function_imports();
191 Ok(())
192 }
193
194 pub fn render_into_kt(mut self, package_name: Option<&str>) -> anyhow::Result<String> {
195 use base64::prelude::BASE64_STANDARD;
196
197 self.transform()?;
198
199 let wasm = self.module.emit_wasm();
200 let wasm_base64 = BASE64_STANDARD.encode(&wasm);
201 let module = Module::from_buffer(&wasm)?;
202 let renderer = KotlinJsRenderer {
203 package_name,
204 base64: &wasm_base64,
205 module: &module,
206 };
207 Ok(renderer.render()?)
208 }
209}