use heck::*; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::mem; use wit_bindgen_gen_core::wit_parser::abi::{ AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, }; use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Ns}; pub mod dependencies; pub mod source; use dependencies::Dependencies; use source::Source; #[derive(Default)] pub struct WasmtimePy { src: Source, in_import: bool, opts: Opts, guest_imports: HashMap, guest_exports: HashMap, sizes: SizeAlign, /// Tracks the intrinsics and Python imports needed deps: Dependencies, /// Whether the Python Union being emited will wrap its cases with dataclasses union_representation: HashMap, } #[derive(Debug, Clone, Copy)] enum PyUnionRepresentation { /// A union whose inner types are used directly Raw, /// A union whose inner types have been wrapped in dataclasses Wrapped, } #[derive(Default)] struct Imports { freestanding_funcs: Vec, resource_funcs: BTreeMap>, } struct Import { name: String, src: Source, wasm_ty: String, pysig: String, } #[derive(Default)] struct Exports { freestanding_funcs: Vec, resource_funcs: BTreeMap>, fields: BTreeMap, } #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] pub struct Opts { #[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))] pub no_typescript: bool, } impl Opts { pub fn build(self) -> WasmtimePy { let mut r = WasmtimePy::new(); r.opts = self; r } } impl WasmtimePy { pub fn new() -> WasmtimePy { WasmtimePy::default() } fn abi_variant(dir: Direction) -> AbiVariant { // This generator uses a reversed mapping! In the Wasmtime-py host-side // bindings, we don't use any extra adapter layer between guest wasm // modules and the host. When the guest imports functions using the // `GuestImport` ABI, the host directly implements the `GuestImport` // ABI, even though the host is *exporting* functions. Similarly, when // the guest exports functions using the `GuestExport` ABI, the host // directly imports them with the `GuestExport` ABI, even though the // host is *importing* functions. match dir { Direction::Import => AbiVariant::GuestExport, Direction::Export => AbiVariant::GuestImport, } } /// Creates a `Source` with all of the required intrinsics fn intrinsics(&mut self, iface: &Interface) -> Source { if iface.resources.len() > 0 { self.deps.needs_resources = true; self.deps.pyimport("typing", "runtime_checkable"); } self.deps.intrinsics() } } fn array_ty(iface: &Interface, ty: &Type) -> Option<&'static str> { match ty { Type::Unit | Type::Bool => None, Type::U8 => Some("c_uint8"), Type::S8 => Some("c_int8"), Type::U16 => Some("c_uint16"), Type::S16 => Some("c_int16"), Type::U32 => Some("c_uint32"), Type::S32 => Some("c_int32"), Type::U64 => Some("c_uint64"), Type::S64 => Some("c_int64"), Type::Float32 => Some("c_float"), Type::Float64 => Some("c_double"), Type::Char => None, Type::Handle(_) => None, Type::String => None, Type::Id(id) => match &iface.types[*id].kind { TypeDefKind::Type(t) => array_ty(iface, t), _ => None, }, } } impl Generator for WasmtimePy { fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { let variant = Self::abi_variant(dir); self.sizes.fill(iface); self.in_import = variant == AbiVariant::GuestImport; } fn type_record( &mut self, iface: &Interface, _id: TypeId, name: &str, record: &Record, docs: &Docs, ) { let mut builder = self.src.builder(&mut self.deps, iface); builder.pyimport("dataclasses", "dataclass"); builder.push_str("@dataclass\n"); builder.push_str(&format!("class {}:\n", name.to_camel_case())); builder.indent(); builder.docstring(docs); for field in record.fields.iter() { builder.comment(&field.docs); let field_name = field.name.to_snake_case(); builder.push_str(&format!("{field_name}: ")); builder.print_ty(&field.ty, true); builder.push_str("\n"); } if record.fields.is_empty() { builder.push_str("pass\n"); } builder.dedent(); builder.push_str("\n"); } fn type_tuple( &mut self, iface: &Interface, _id: TypeId, name: &str, tuple: &Tuple, docs: &Docs, ) { let mut builder = self.src.builder(&mut self.deps, iface); builder.comment(docs); builder.push_str(&format!("{} = ", name.to_camel_case())); builder.print_tuple(tuple); builder.push_str("\n"); } fn type_flags( &mut self, iface: &Interface, _id: TypeId, name: &str, flags: &Flags, docs: &Docs, ) { let mut builder = self.src.builder(&mut self.deps, iface); builder.pyimport("enum", "Flag"); builder.pyimport("enum", "auto"); builder.push_str(&format!("class {}(Flag):\n", name.to_camel_case())); builder.indent(); builder.docstring(docs); for flag in flags.flags.iter() { let flag_name = flag.name.to_shouty_snake_case(); builder.comment(&flag.docs); builder.push_str(&format!("{flag_name} = auto()\n")); } if flags.flags.is_empty() { builder.push_str("pass\n"); } builder.dedent(); builder.push_str("\n"); } fn type_variant( &mut self, iface: &Interface, _id: TypeId, name: &str, variant: &Variant, docs: &Docs, ) { let mut builder = self.src.builder(&mut self.deps, iface); builder.pyimport("dataclasses", "dataclass"); let mut cases = Vec::new(); for case in variant.cases.iter() { builder.docstring(&case.docs); builder.push_str("@dataclass\n"); let case_name = format!("{}{}", name.to_camel_case(), case.name.to_camel_case()); builder.push_str(&format!("class {case_name}:\n")); builder.indent(); builder.push_str("value: "); builder.print_ty(&case.ty, true); builder.push_str("\n"); builder.dedent(); builder.push_str("\n"); cases.push(case_name); } builder.deps.pyimport("typing", "Union"); builder.comment(docs); builder.push_str(&format!( "{} = Union[{}]\n", name.to_camel_case(), cases.join(", "), )); builder.push_str("\n"); } /// Appends a Python definition for the provided Union to the current `Source`. /// e.g. `MyUnion = Union[float, str, int]` fn type_union( &mut self, iface: &Interface, _id: TypeId, name: &str, union: &Union, docs: &Docs, ) { let mut py_type_classes = BTreeSet::new(); for case in union.cases.iter() { py_type_classes.insert(py_type_class_of(&case.ty)); } let mut builder = self.src.builder(&mut self.deps, iface); if py_type_classes.len() != union.cases.len() { // Some of the cases are not distinguishable self.union_representation .insert(name.to_string(), PyUnionRepresentation::Wrapped); builder.print_union_wrapped(name, union, docs); } else { // All of the cases are distinguishable self.union_representation .insert(name.to_string(), PyUnionRepresentation::Raw); builder.print_union_raw(name, union, docs); } } fn type_option( &mut self, iface: &Interface, _id: TypeId, name: &str, payload: &Type, docs: &Docs, ) { let mut builder = self.src.builder(&mut self.deps, iface); builder.pyimport("typing", "Optional"); builder.comment(docs); builder.push_str(&name.to_camel_case()); builder.push_str(" = Optional["); builder.print_ty(payload, true); builder.push_str("]\n\n"); } fn type_expected( &mut self, iface: &Interface, _id: TypeId, name: &str, expected: &Expected, docs: &Docs, ) { self.deps.needs_expected = true; let mut builder = self.src.builder(&mut self.deps, iface); builder.comment(docs); builder.push_str(&format!("{} = Expected[", name.to_camel_case())); builder.print_ty(&expected.ok, true); builder.push_str(", "); builder.print_ty(&expected.err, true); builder.push_str("]\n\n"); } fn type_enum(&mut self, iface: &Interface, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { let mut builder = self.src.builder(&mut self.deps, iface); builder.pyimport("enum", "Enum"); builder.push_str(&format!("class {}(Enum):\n", name.to_camel_case())); builder.indent(); builder.docstring(docs); for (i, case) in enum_.cases.iter().enumerate() { builder.comment(&case.docs); // TODO this handling of digits should be more general and // shouldn't be here just to fix the one case in wasi where an // enum variant is "2big" and doesn't generate valid Python. We // should probably apply this to all generated Python // identifiers. let mut name = case.name.to_shouty_snake_case(); if name.chars().next().unwrap().is_digit(10) { name = format!("_{}", name); } builder.push_str(&format!("{} = {}\n", name, i)); } builder.dedent(); builder.push_str("\n"); } fn type_resource(&mut self, _iface: &Interface, _ty: ResourceId) { // if !self.in_import { // self.exported_resources.insert(ty); // } } fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { let mut builder = self.src.builder(&mut self.deps, iface); builder.comment(docs); builder.push_str(&format!("{} = ", name.to_camel_case())); builder.print_ty(ty, false); builder.push_str("\n"); } fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { let mut builder = self.src.builder(&mut self.deps, iface); builder.comment(docs); builder.push_str(&format!("{} = ", name.to_camel_case())); builder.print_list(ty); builder.push_str("\n"); } fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { self.type_alias(iface, id, name, ty, docs); } // As with `abi_variant` above, we're generating host-side bindings here // so a user "export" uses the "guest import" ABI variant on the inside of // this `Generator` implementation. fn export(&mut self, iface: &Interface, func: &Function) { assert!(!func.is_async, "async not supported yet"); let mut pysig = Source::default(); let mut builder = pysig.builder(&mut self.deps, iface); builder.print_sig(func, self.in_import); let pysig = pysig.to_string(); let mut func_body = Source::default(); let mut builder = func_body.builder(&mut self.deps, iface); let sig = iface.wasm_signature(AbiVariant::GuestImport, func); builder.push_str(&format!( "def {}(caller: wasmtime.Caller", func.name.to_snake_case(), )); let mut params = Vec::new(); for (i, param) in sig.params.iter().enumerate() { builder.push_str(", "); let name = format!("arg{}", i); builder.push_str(&name); builder.push_str(": "); builder.push_str(wasm_ty_typing(*param)); params.push(name); } builder.push_str(") -> "); match sig.results.len() { 0 => builder.push_str("None"), 1 => builder.push_str(wasm_ty_typing(sig.results[0])), _ => unimplemented!(), } builder.push_str(":\n"); builder.indent(); drop(builder); let mut f = FunctionBindgen::new(self, params); iface.call( AbiVariant::GuestImport, LiftLower::LiftArgsLowerResults, func, &mut f, ); let FunctionBindgen { src, needs_memory, needs_realloc, needs_free, mut locals, .. } = f; let mut builder = func_body.builder(&mut self.deps, iface); if needs_memory { // TODO: hardcoding "memory" builder.push_str("m = caller[\"memory\"]\n"); builder.push_str("assert(isinstance(m, wasmtime.Memory))\n"); builder.deps.pyimport("typing", "cast"); builder.push_str("memory = cast(wasmtime.Memory, m)\n"); locals.insert("memory").unwrap(); } if let Some(name) = needs_realloc { builder.push_str(&format!("realloc = caller[\"{}\"]\n", name)); builder.push_str("assert(isinstance(realloc, wasmtime.Func))\n"); locals.insert("realloc").unwrap(); } if let Some(name) = needs_free { builder.push_str(&format!("free = caller[\"{}\"]\n", name)); builder.push_str("assert(isinstance(free, wasmtime.Func))\n"); locals.insert("free").unwrap(); } builder.push_str(&src); builder.dedent(); let mut wasm_ty = String::from("wasmtime.FuncType(["); wasm_ty.push_str( &sig.params .iter() .map(|t| wasm_ty_ctor(*t)) .collect::>() .join(", "), ); wasm_ty.push_str("], ["); wasm_ty.push_str( &sig.results .iter() .map(|t| wasm_ty_ctor(*t)) .collect::>() .join(", "), ); wasm_ty.push_str("])"); let import = Import { name: func.name.clone(), src: func_body, wasm_ty, pysig, }; let imports = self .guest_imports .entry(iface.name.to_string()) .or_insert(Imports::default()); let dst = match &func.kind { FunctionKind::Freestanding | FunctionKind::Static { .. } => { &mut imports.freestanding_funcs } FunctionKind::Method { resource, .. } => imports .resource_funcs .entry(*resource) .or_insert(Vec::new()), }; dst.push(import); } // As with `abi_variant` above, we're generating host-side bindings here // so a user "import" uses the "export" ABI variant on the inside of // this `Generator` implementation. fn import(&mut self, iface: &Interface, func: &Function) { assert!(!func.is_async, "async not supported yet"); let mut func_body = Source::default(); let mut builder = func_body.builder(&mut self.deps, iface); // Print the function signature let params = builder.print_sig(func, self.in_import); builder.push_str(":\n"); builder.indent(); drop(builder); // Use FunctionBindgen call let src_object = match &func.kind { FunctionKind::Freestanding => "self".to_string(), FunctionKind::Static { .. } => "obj".to_string(), FunctionKind::Method { .. } => "self._obj".to_string(), }; let mut f = FunctionBindgen::new(self, params); f.src_object = src_object; iface.call( AbiVariant::GuestExport, LiftLower::LowerArgsLiftResults, func, &mut f, ); let FunctionBindgen { src, needs_memory, needs_realloc, needs_free, src_object, .. } = f; let mut builder = func_body.builder(&mut self.deps, iface); if needs_memory { // TODO: hardcoding "memory" builder.push_str(&format!("memory = {}._memory;\n", src_object)); } if let Some(name) = &needs_realloc { builder.push_str(&format!( "realloc = {}._{}\n", src_object, name.to_snake_case(), )); } if let Some(name) = &needs_free { builder.push_str(&format!( "free = {}._{}\n", src_object, name.to_snake_case(), )); } builder.push_str(&src); builder.dedent(); let exports = self .guest_exports .entry(iface.name.to_string()) .or_insert_with(Exports::default); if needs_memory { exports .fields .insert("memory".to_string(), "wasmtime.Memory"); } if let Some(name) = &needs_realloc { exports.fields.insert(name.clone(), "wasmtime.Func"); } if let Some(name) = &needs_free { exports.fields.insert(name.clone(), "wasmtime.Func"); } exports.fields.insert(func.name.clone(), "wasmtime.Func"); let dst = match &func.kind { FunctionKind::Freestanding => &mut exports.freestanding_funcs, FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { exports .resource_funcs .entry(*resource) .or_insert(Vec::new()) } }; dst.push(func_body); } fn finish_one(&mut self, iface: &Interface, files: &mut Files) { self.deps.pyimport("typing", "Any"); self.deps.pyimport("abc", "abstractmethod"); let types = mem::take(&mut self.src); let intrinsics = self.intrinsics(iface); for (k, v) in self.deps.pyimports.iter() { match v { Some(list) => { let list = list.iter().cloned().collect::>().join(", "); self.src.push_str(&format!("from {} import {}\n", k, list)); } None => { self.src.push_str(&format!("import {}\n", k)); } } } self.src.push_str("import wasmtime\n"); self.src.push_str( " try: from typing import Protocol except ImportError: class Protocol: # type: ignore pass ", ); self.src.push_str("\n"); if self.deps.needs_t_typevar { self.src.push_str("T = TypeVar('T')\n"); } self.src.push_str(&intrinsics); for (id, r) in iface.resources.iter() { let name = r.name.to_camel_case(); if self.in_import { self.src.push_str("@runtime_checkable\n"); self.src.push_str(&format!("class {}(Protocol):\n", name)); self.src.indent(); self.src.push_str("def drop(self) -> None:\n"); self.src.indent(); self.src.push_str("pass\n"); self.src.dedent(); for (_, funcs) in self.guest_imports.iter() { if let Some(funcs) = funcs.resource_funcs.get(&id) { for func in funcs { self.src.push_str("@abstractmethod\n"); self.src.push_str(&func.pysig); self.src.push_str(":\n"); self.src.indent(); self.src.push_str("raise NotImplementedError\n"); self.src.dedent(); } } } self.src.dedent(); } else { self.src.push_str(&format!("class {}:\n", name)); self.src.indent(); self.src.push_str(&format!( " _wasm_val: int _refcnt: int _obj: '{iface}' _destroyed: bool def __init__(self, val: int, obj: '{iface}') -> None: self._wasm_val = val self._refcnt = 1 self._obj = obj self._destroyed = False def clone(self) -> '{name}': self._refcnt += 1 return self def drop(self, store: wasmtime.Storelike) -> None: self._refcnt -= 1; if self._refcnt != 0: return assert(not self._destroyed) self._destroyed = True self._obj._canonical_abi_drop_{drop}(store, self._wasm_val) def __del__(self) -> None: if not self._destroyed: raise RuntimeError('wasm object not dropped') ", name = name, iface = iface.name.to_camel_case(), drop = name.to_snake_case(), )); for (_, exports) in self.guest_exports.iter() { if let Some(funcs) = exports.resource_funcs.get(&id) { for func in funcs { self.src.push_str(func); } } } self.src.dedent(); } } self.src.push_str(&types); for (module, funcs) in mem::take(&mut self.guest_imports) { self.src .push_str(&format!("class {}(Protocol):\n", module.to_camel_case())); self.src.indent(); for func in funcs.freestanding_funcs.iter() { self.src.push_str("@abstractmethod\n"); self.src.push_str(&func.pysig); self.src.push_str(":\n"); self.src.indent(); self.src.push_str("raise NotImplementedError\n"); self.src.dedent(); } self.src.dedent(); self.src.push_str("\n"); self.src.push_str(&format!( "def add_{}_to_linker(linker: wasmtime.Linker, store: wasmtime.Store, host: {}) -> None:\n", module.to_snake_case(), module.to_camel_case(), )); self.src.indent(); for (id, r) in iface.resources.iter() { self.src.push_str(&format!( "_resources{}: Slab[{}] = Slab()\n", id.index(), r.name.to_camel_case() )); } for func in funcs .freestanding_funcs .iter() .chain(funcs.resource_funcs.values().flat_map(|v| v)) { self.src.push_str(&format!("ty = {}\n", func.wasm_ty)); self.src.push_str(&func.src); self.src.push_str(&format!( "linker.define('{}', '{}', wasmtime.Func(store, ty, {}, access_caller = True))\n", iface.name, func.name, func.name.to_snake_case(), )); } for (id, resource) in iface.resources.iter() { let snake = resource.name.to_snake_case(); self.src.push_str(&format!( "def resource_drop_{}(i: int) -> None:\n _resources{}.remove(i).drop()\n", snake, id.index(), )); self.src .push_str("ty = wasmtime.FuncType([wasmtime.ValType.i32()], [])\n"); self.src.push_str(&format!( "linker.define(\ 'canonical_abi', \ 'resource_drop_{}', \ wasmtime.Func(store, ty, resource_drop_{})\ )\n", resource.name, snake, )); } self.src.dedent(); } // This is exculsively here to get mypy to not complain about empty // modules, this probably won't really get triggered much in practice if !self.in_import && self.guest_exports.is_empty() { self.src .push_str(&format!("class {}:\n", iface.name.to_camel_case())); self.src.indent(); if iface.resources.len() == 0 { self.src.push_str("pass\n"); } else { for (_, r) in iface.resources.iter() { self.src.push_str(&format!( "_canonical_abi_drop_{}: wasmtime.Func\n", r.name.to_snake_case(), )); } } self.src.dedent(); } for (module, exports) in mem::take(&mut self.guest_exports) { let module = module.to_camel_case(); self.src.push_str(&format!("class {}:\n", module)); self.src.indent(); self.src.push_str("instance: wasmtime.Instance\n"); for (name, ty) in exports.fields.iter() { self.src .push_str(&format!("_{}: {}\n", name.to_snake_case(), ty)); } for (id, r) in iface.resources.iter() { self.src.push_str(&format!( "_resource{}_slab: Slab[{}]\n", id.index(), r.name.to_camel_case(), )); self.src.push_str(&format!( "_canonical_abi_drop_{}: wasmtime.Func\n", r.name.to_snake_case(), )); } self.src.push_str("def __init__(self, store: wasmtime.Store, linker: wasmtime.Linker, module: wasmtime.Module):\n"); self.src.indent(); for (id, r) in iface.resources.iter() { self.src.push_str(&format!( " ty1 = wasmtime.FuncType([wasmtime.ValType.i32()], []) ty2 = wasmtime.FuncType([wasmtime.ValType.i32()], [wasmtime.ValType.i32()]) def drop_{snake}(caller: wasmtime.Caller, idx: int) -> None: self._resource{idx}_slab.remove(idx).drop(caller); linker.define('canonical_abi', 'resource_drop_{name}', wasmtime.Func(store, ty1, drop_{snake}, access_caller = True)) def clone_{snake}(idx: int) -> int: obj = self._resource{idx}_slab.get(idx) return self._resource{idx}_slab.insert(obj.clone()) linker.define('canonical_abi', 'resource_clone_{name}', wasmtime.Func(store, ty2, clone_{snake})) def get_{snake}(idx: int) -> int: obj = self._resource{idx}_slab.get(idx) return obj._wasm_val linker.define('canonical_abi', 'resource_get_{name}', wasmtime.Func(store, ty2, get_{snake})) def new_{snake}(val: int) -> int: return self._resource{idx}_slab.insert({camel}(val, self)) linker.define('canonical_abi', 'resource_new_{name}', wasmtime.Func(store, ty2, new_{snake})) ", name = r.name, camel = r.name.to_camel_case(), snake = r.name.to_snake_case(), idx = id.index(), )); } self.src .push_str("self.instance = linker.instantiate(store, module)\n"); self.src .push_str("exports = self.instance.exports(store)\n"); for (name, ty) in exports.fields.iter() { self.src.push_str(&format!( " {snake} = exports['{name}'] assert(isinstance({snake}, {ty})) self._{snake} = {snake} ", name = name, snake = name.to_snake_case(), ty = ty, )); } for (id, r) in iface.resources.iter() { self.src.push_str(&format!( " self._resource{idx}_slab = Slab() canon_drop_{snake} = exports['canonical_abi_drop_{name}'] assert(isinstance(canon_drop_{snake}, wasmtime.Func)) self._canonical_abi_drop_{snake} = canon_drop_{snake} ", idx = id.index(), name = r.name, snake = r.name.to_snake_case(), )); } self.src.dedent(); for func in exports.freestanding_funcs.iter() { self.src.push_str(&func); } self.src.dedent(); } files.push("bindings.py", self.src.as_bytes()); } } struct FunctionBindgen<'a> { gen: &'a mut WasmtimePy, locals: Ns, src: Source, block_storage: Vec, blocks: Vec<(String, Vec)>, needs_memory: bool, needs_realloc: Option, needs_free: Option, params: Vec, payloads: Vec, src_object: String, } impl FunctionBindgen<'_> { fn new(gen: &mut WasmtimePy, params: Vec) -> FunctionBindgen<'_> { let mut locals = Ns::default(); locals.insert("len").unwrap(); // python built-in locals.insert("base").unwrap(); // may be used as loop var locals.insert("i").unwrap(); // may be used as loop var for param in params.iter() { locals.insert(param).unwrap(); } FunctionBindgen { gen, locals, src: Source::default(), block_storage: Vec::new(), blocks: Vec::new(), needs_memory: false, needs_realloc: None, needs_free: None, params, payloads: Vec::new(), src_object: "self".to_string(), } } fn clamp(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) where T: std::fmt::Display, { self.gen.deps.needs_clamp = true; results.push(format!("_clamp({}, {}, {})", operands[0], min, max)); } fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { self.needs_memory = true; self.gen.deps.needs_load = true; let tmp = self.locals.tmp("load"); self.src.push_str(&format!( "{} = _load(ctypes.{}, memory, caller, {}, {})\n", tmp, ty, operands[0], offset, )); results.push(tmp); } fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { self.needs_memory = true; self.gen.deps.needs_store = true; self.src.push_str(&format!( "_store(ctypes.{}, memory, caller, {}, {}, {})\n", ty, operands[1], offset, operands[0] )); } } impl Bindgen for FunctionBindgen<'_> { type Operand = String; fn sizes(&self) -> &SizeAlign { &self.gen.sizes } fn push_block(&mut self) { let prev = mem::take(&mut self.src); self.block_storage.push(prev); } fn finish_block(&mut self, operands: &mut Vec) { let to_restore = self.block_storage.pop().unwrap(); let src = mem::replace(&mut self.src, to_restore); self.blocks.push((src.into(), mem::take(operands))); } fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { unimplemented!() } fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { array_ty(iface, ty).is_some() } fn emit( &mut self, iface: &Interface, inst: &Instruction<'_>, operands: &mut Vec, results: &mut Vec, ) { let mut builder = self.src.builder(&mut self.gen.deps, iface); match inst { Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), Instruction::I32Const { val } => results.push(val.to_string()), Instruction::ConstZero { tys } => { for t in tys.iter() { match t { WasmType::I32 | WasmType::I64 => results.push("0".to_string()), WasmType::F32 | WasmType::F64 => results.push("0.0".to_string()), } } } // The representation of i32 in Python is a number, so 8/16-bit // values get further clamped to ensure that the upper bits aren't // set when we pass the value, ensuring that only the right number // of bits are transferred. Instruction::U8FromI32 => self.clamp(results, operands, u8::MIN, u8::MAX), Instruction::S8FromI32 => self.clamp(results, operands, i8::MIN, i8::MAX), Instruction::U16FromI32 => self.clamp(results, operands, u16::MIN, u16::MAX), Instruction::S16FromI32 => self.clamp(results, operands, i16::MIN, i16::MAX), // Ensure the bits of the number are treated as unsigned. Instruction::U32FromI32 => { results.push(format!("{} & 0xffffffff", operands[0])); } // All bigints coming from wasm are treated as signed, so convert // it to ensure it's treated as unsigned. Instruction::U64FromI64 => { results.push(format!("{} & 0xffffffffffffffff", operands[0])); } // Nothing to do signed->signed where the representations are the // same. Instruction::S32FromI32 | Instruction::S64FromI64 => { results.push(operands.pop().unwrap()) } // All values coming from the host and going to wasm need to have // their ranges validated, since the host could give us any value. Instruction::I32FromU8 => self.clamp(results, operands, u8::MIN, u8::MAX), Instruction::I32FromS8 => self.clamp(results, operands, i8::MIN, i8::MAX), Instruction::I32FromU16 => self.clamp(results, operands, u16::MIN, u16::MAX), Instruction::I32FromS16 => self.clamp(results, operands, i16::MIN, i16::MAX), // TODO: need to do something to get this to be represented as signed? Instruction::I32FromU32 => { self.clamp(results, operands, u32::MIN, u32::MAX); } Instruction::I32FromS32 => self.clamp(results, operands, i32::MIN, i32::MAX), // TODO: need to do something to get this to be represented as signed? Instruction::I64FromU64 => self.clamp(results, operands, u64::MIN, u64::MAX), Instruction::I64FromS64 => self.clamp(results, operands, i64::MIN, i64::MAX), // Python uses `float` for f32/f64, so everything is equivalent // here. Instruction::Float32FromF32 | Instruction::Float64FromF64 | Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => results.push(operands.pop().unwrap()), // Validate that i32 values coming from wasm are indeed valid code // points. Instruction::CharFromI32 => { builder.deps.needs_validate_guest_char = true; results.push(format!("_validate_guest_char({})", operands[0])); } Instruction::I32FromChar => { results.push(format!("ord({})", operands[0])); } Instruction::Bitcasts { casts } => { for (cast, op) in casts.iter().zip(operands) { match cast { Bitcast::I32ToF32 => { builder.deps.needs_i32_to_f32 = true; results.push(format!("_i32_to_f32({})", op)); } Bitcast::F32ToI32 => { builder.deps.needs_f32_to_i32 = true; results.push(format!("_f32_to_i32({})", op)); } Bitcast::I64ToF64 => { builder.deps.needs_i64_to_f64 = true; results.push(format!("_i64_to_f64({})", op)); } Bitcast::F64ToI64 => { builder.deps.needs_f64_to_i64 = true; results.push(format!("_f64_to_i64({})", op)); } Bitcast::I64ToF32 => { builder.deps.needs_i32_to_f32 = true; results.push(format!("_i32_to_f32(({}) & 0xffffffff)", op)); } Bitcast::F32ToI64 => { builder.deps.needs_f32_to_i32 = true; results.push(format!("_f32_to_i32({})", op)); } Bitcast::I32ToI64 | Bitcast::I64ToI32 | Bitcast::None => { results.push(op.clone()) } } } } Instruction::UnitLower => {} Instruction::UnitLift => { results.push("None".to_string()); } Instruction::BoolFromI32 => { let op = self.locals.tmp("operand"); let ret = self.locals.tmp("boolean"); builder.push_str(&format!( " {op} = {} if {op} == 0: {ret} = False elif {op} == 1: {ret} = True else: raise TypeError(\"invalid variant discriminant for bool\") ", operands[0] )); results.push(ret); } Instruction::I32FromBool => { results.push(format!("int({})", operands[0])); } // These instructions are used with handles when we're implementing // imports. This means we interact with the `resources` slabs to // translate the wasm-provided index into a Python value. Instruction::I32FromOwnedHandle { ty } => { results.push(format!("_resources{}.insert({})", ty.index(), operands[0])); } Instruction::HandleBorrowedFromI32 { ty } => { results.push(format!("_resources{}.get({})", ty.index(), operands[0])); } // These instructions are used for handles to objects owned in wasm. // This means that they're interacting with a wrapper class defined // in Python. Instruction::I32FromBorrowedHandle { ty } => { let obj = self.locals.tmp("obj"); builder.push_str(&format!("{} = {}\n", obj, operands[0])); results.push(format!( "{}._resource{}_slab.insert({}.clone())", self.src_object, ty.index(), obj, )); } Instruction::HandleOwnedFromI32 { ty } => { results.push(format!( "{}._resource{}_slab.remove({})", self.src_object, ty.index(), operands[0], )); } Instruction::RecordLower { record, .. } => { if record.fields.is_empty() { return; } let tmp = self.locals.tmp("record"); builder.push_str(&format!("{} = {}\n", tmp, operands[0])); for field in record.fields.iter() { let name = self.locals.tmp("field"); builder.push_str(&format!( "{} = {}.{}\n", name, tmp, field.name.to_snake_case(), )); results.push(name); } } Instruction::RecordLift { name, .. } => { results.push(format!("{}({})", name.to_camel_case(), operands.join(", "))); } Instruction::TupleLower { tuple, .. } => { if tuple.types.is_empty() { return; } builder.push_str("("); for _ in 0..tuple.types.len() { let name = self.locals.tmp("tuplei"); builder.push_str(&name); builder.push_str(","); results.push(name); } builder.push_str(") = "); builder.push_str(&operands[0]); builder.push_str("\n"); } Instruction::TupleLift { .. } => { if operands.is_empty() { results.push("None".to_string()); } else { results.push(format!("({},)", operands.join(", "))); } } Instruction::FlagsLift { name, .. } => { let operand = match operands.len() { 1 => operands[0].clone(), _ => { let tmp = self.locals.tmp("flags"); builder.push_str(&format!("{tmp} = 0\n")); for (i, op) in operands.iter().enumerate() { let i = 32 * i; builder.push_str(&format!("{tmp} |= {op} << {i}\n")); } tmp } }; results.push(format!("{}({})", name.to_camel_case(), operand)); } Instruction::FlagsLower { flags, .. } => match flags.repr().count() { 1 => results.push(format!("({}).value", operands[0])), n => { let tmp = self.locals.tmp("flags"); self.src .push_str(&format!("{tmp} = ({}).value\n", operands[0])); for i in 0..n { let i = 32 * i; results.push(format!("({tmp} >> {i}) & 0xffffffff")); } } }, Instruction::VariantPayloadName => { let name = self.locals.tmp("payload"); results.push(name.clone()); self.payloads.push(name); } Instruction::VariantLower { variant, results: result_types, name, .. } => { let blocks = self .blocks .drain(self.blocks.len() - variant.cases.len()..) .collect::>(); let payloads = self .payloads .drain(self.payloads.len() - variant.cases.len()..) .collect::>(); for _ in 0..result_types.len() { results.push(self.locals.tmp("variant")); } for (i, ((case, (block, block_results)), payload)) in variant.cases.iter().zip(blocks).zip(payloads).enumerate() { if i == 0 { builder.push_str("if "); } else { builder.push_str("elif "); } builder.push_str(&format!( "isinstance({}, {}{}):\n", operands[0], name.to_camel_case(), case.name.to_camel_case() )); builder.indent(); builder.push_str(&format!("{} = {}.value\n", payload, operands[0])); builder.push_str(&block); for (i, result) in block_results.iter().enumerate() { builder.push_str(&format!("{} = {}\n", results[i], result)); } builder.dedent(); } let variant_name = name.to_camel_case(); builder.push_str("else:\n"); builder.indent(); builder.push_str(&format!( "raise TypeError(\"invalid variant specified for {}\")\n", variant_name )); builder.dedent(); } Instruction::VariantLift { variant, name, ty, .. } => { let blocks = self .blocks .drain(self.blocks.len() - variant.cases.len()..) .collect::>(); let result = self.locals.tmp("variant"); builder.print_var_declaration(&result, &Type::Id(*ty)); for (i, (case, (block, block_results))) in variant.cases.iter().zip(blocks).enumerate() { if i == 0 { builder.push_str("if "); } else { builder.push_str("elif "); } builder.push_str(&format!("{} == {}:\n", operands[0], i)); builder.indent(); builder.push_str(&block); builder.push_str(&format!( "{} = {}{}(", result, name.to_camel_case(), case.name.to_camel_case() )); assert!(block_results.len() == 1); builder.push_str(&block_results[0]); builder.push_str(")\n"); builder.dedent(); } builder.push_str("else:\n"); builder.indent(); let variant_name = name.to_camel_case(); builder.push_str(&format!( "raise TypeError(\"invalid variant discriminant for {}\")\n", variant_name )); builder.dedent(); results.push(result); } Instruction::UnionLower { union, results: result_types, name, .. } => { let blocks = self .blocks .drain(self.blocks.len() - union.cases.len()..) .collect::>(); let payloads = self .payloads .drain(self.payloads.len() - union.cases.len()..) .collect::>(); for _ in 0..result_types.len() { results.push(self.locals.tmp("variant")); } // Assumes that type_union has been called for this union let union_representation = *self .gen .union_representation .get(&name.to_string()) .unwrap(); let name = name.to_camel_case(); let op0 = &operands[0]; for (i, ((case, (block, block_results)), payload)) in union.cases.iter().zip(blocks).zip(payloads).enumerate() { builder.push_str(if i == 0 { "if " } else { "elif " }); builder.push_str(&format!("isinstance({op0}, ")); match union_representation { // Prints the Python type for this union case PyUnionRepresentation::Raw => { builder.print_ty(&case.ty, false); } // Prints the name of this union cases dataclass PyUnionRepresentation::Wrapped => { builder.push_str(&format!("{name}{i}")); } } builder.push_str(&format!("):\n")); builder.indent(); match union_representation { // Uses the value directly PyUnionRepresentation::Raw => { builder.push_str(&format!("{payload} = {op0}\n")) } // Uses this union case dataclass's inner value PyUnionRepresentation::Wrapped => { builder.push_str(&format!("{payload} = {op0}.value\n")) } } builder.push_str(&block); for (i, result) in block_results.iter().enumerate() { builder.push_str(&format!("{} = {result}\n", results[i])); } builder.dedent(); } builder.push_str("else:\n"); builder.indent(); builder.push_str(&format!( "raise TypeError(\"invalid variant specified for {name}\")\n" )); builder.dedent(); } Instruction::UnionLift { union, name, ty, .. } => { let blocks = self .blocks .drain(self.blocks.len() - union.cases.len()..) .collect::>(); let result = self.locals.tmp("variant"); builder.print_var_declaration(&result, &Type::Id(*ty)); // Assumes that type_union has been called for this union let union_representation = *self .gen .union_representation .get(&name.to_string()) .unwrap(); let name = name.to_camel_case(); let op0 = &operands[0]; for (i, (_case, (block, block_results))) in union.cases.iter().zip(blocks).enumerate() { builder.push_str(if i == 0 { "if " } else { "elif " }); builder.push_str(&format!("{op0} == {i}:\n")); builder.indent(); builder.push_str(&block); assert!(block_results.len() == 1); let block_result = &block_results[0]; builder.push_str(&format!("{result} = ")); match union_representation { // Uses the passed value directly PyUnionRepresentation::Raw => builder.push_str(block_result), // Constructs an instance of the union cases dataclass PyUnionRepresentation::Wrapped => { builder.push_str(&format!("{name}{i}({block_result})")) } } builder.newline(); builder.dedent(); } builder.push_str("else:\n"); builder.indent(); builder.push_str(&format!( "raise TypeError(\"invalid variant discriminant for {name}\")\n", )); builder.dedent(); results.push(result); } Instruction::OptionLower { results: result_types, .. } => { let (some, some_results) = self.blocks.pop().unwrap(); let (none, none_results) = self.blocks.pop().unwrap(); let some_payload = self.payloads.pop().unwrap(); let _none_payload = self.payloads.pop().unwrap(); for _ in 0..result_types.len() { results.push(self.locals.tmp("variant")); } let op0 = &operands[0]; builder.push_str(&format!("if {op0} is None:\n")); builder.indent(); builder.push_str(&none); for (dst, result) in results.iter().zip(&none_results) { builder.push_str(&format!("{dst} = {result}\n")); } builder.dedent(); builder.push_str("else:\n"); builder.indent(); builder.push_str(&format!("{some_payload} = {op0}\n")); builder.push_str(&some); for (dst, result) in results.iter().zip(&some_results) { builder.push_str(&format!("{dst} = {result}\n")); } builder.dedent(); } Instruction::OptionLift { ty, .. } => { let (some, some_results) = self.blocks.pop().unwrap(); let (none, none_results) = self.blocks.pop().unwrap(); assert!(none_results.len() == 1); assert!(some_results.len() == 1); let some_result = &some_results[0]; assert_eq!(none_results[0], "None"); let result = self.locals.tmp("option"); builder.print_var_declaration(&result, &Type::Id(*ty)); let op0 = &operands[0]; builder.push_str(&format!("if {op0} == 0:\n")); builder.indent(); builder.push_str(&none); builder.push_str(&format!("{result} = None\n")); builder.dedent(); builder.push_str(&format!("elif {op0} == 1:\n")); builder.indent(); builder.push_str(&some); builder.push_str(&format!("{result} = {some_result}\n")); builder.dedent(); builder.push_str("else:\n"); builder.indent(); builder.push_str("raise TypeError(\"invalid variant discriminant for option\")\n"); builder.dedent(); results.push(result); } Instruction::ExpectedLower { results: result_types, .. } => { let (err, err_results) = self.blocks.pop().unwrap(); let (ok, ok_results) = self.blocks.pop().unwrap(); let err_payload = self.payloads.pop().unwrap(); let ok_payload = self.payloads.pop().unwrap(); for _ in 0..result_types.len() { results.push(self.locals.tmp("variant")); } let op0 = &operands[0]; builder.push_str(&format!("if isinstance({op0}, Ok):\n")); builder.indent(); builder.push_str(&format!("{ok_payload} = {op0}.value\n")); builder.push_str(&ok); for (dst, result) in results.iter().zip(&ok_results) { builder.push_str(&format!("{dst} = {result}\n")); } builder.dedent(); builder.push_str(&format!("elif isinstance({op0}, Err):\n")); builder.indent(); builder.push_str(&format!("{err_payload} = {op0}.value\n")); builder.push_str(&err); for (dst, result) in results.iter().zip(&err_results) { builder.push_str(&format!("{dst} = {result}\n")); } builder.dedent(); builder.push_str("else:\n"); builder.indent(); builder.push_str(&format!( "raise TypeError(\"invalid variant specified for expected\")\n", )); builder.dedent(); } Instruction::ExpectedLift { ty, .. } => { let (err, err_results) = self.blocks.pop().unwrap(); let (ok, ok_results) = self.blocks.pop().unwrap(); assert!(err_results.len() == 1); let err_result = &err_results[0]; assert!(ok_results.len() == 1); let ok_result = &ok_results[0]; let result = self.locals.tmp("expected"); builder.print_var_declaration(&result, &Type::Id(*ty)); let op0 = &operands[0]; builder.push_str(&format!("if {op0} == 0:\n")); builder.indent(); builder.push_str(&ok); builder.push_str(&format!("{result} = Ok({ok_result})\n")); builder.dedent(); builder.push_str(&format!("elif {op0} == 1:\n")); builder.indent(); builder.push_str(&err); builder.push_str(&format!("{result} = Err({err_result})\n")); builder.dedent(); builder.push_str("else:\n"); builder.indent(); builder .push_str("raise TypeError(\"invalid variant discriminant for expected\")\n"); builder.dedent(); results.push(result); } Instruction::EnumLower { .. } => results.push(format!("({}).value", operands[0])), Instruction::EnumLift { name, .. } => { results.push(format!("{}({})", name.to_camel_case(), operands[0])); } Instruction::ListCanonLower { element, realloc } => { // Lowering only happens when we're passing lists into wasm, // which forces us to always allocate, so this should always be // `Some`. let realloc = realloc.unwrap(); self.needs_memory = true; self.needs_realloc = Some(realloc.to_string()); let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); let array_ty = array_ty(iface, element).unwrap(); builder.deps.needs_list_canon_lower = true; let size = self.gen.sizes.size(element); let align = self.gen.sizes.align(element); builder.push_str(&format!( "{}, {} = _list_canon_lower({}, ctypes.{}, {}, {}, realloc, memory, caller)\n", ptr, len, operands[0], array_ty, size, align, )); results.push(ptr); results.push(len); } Instruction::ListCanonLift { element, free, .. } => { self.needs_memory = true; let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); builder.push_str(&format!("{} = {}\n", ptr, operands[0])); builder.push_str(&format!("{} = {}\n", len, operands[1])); let array_ty = array_ty(iface, element).unwrap(); builder.deps.needs_list_canon_lift = true; let lift = format!( "_list_canon_lift({}, {}, {}, ctypes.{}, memory, caller)", ptr, len, self.gen.sizes.size(element), array_ty, ); builder.deps.pyimport("typing", "cast"); let align = self.gen.sizes.align(element); match free { Some(free) => { self.needs_free = Some(free.to_string()); let list = self.locals.tmp("list"); builder.push_str(&list); builder.push_str(" = cast("); builder.print_list(element); builder.push_str(", "); builder.push_str(&lift); builder.push_str(")\n"); builder.push_str(&format!("free(caller, {}, {}, {})\n", ptr, len, align)); results.push(list); } None => { let mut result_src = Source::default(); drop(builder); let mut builder = result_src.builder(&mut self.gen.deps, iface); builder.push_str("cast("); builder.print_list(element); builder.push_str(", "); builder.push_str(&lift); builder.push_str(")"); results.push(result_src.to_string()); } } } Instruction::StringLower { realloc } => { // Lowering only happens when we're passing strings into wasm, // which forces us to always allocate, so this should always be // `Some`. let realloc = realloc.unwrap(); self.needs_memory = true; self.needs_realloc = Some(realloc.to_string()); let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); builder.deps.needs_encode_utf8 = true; builder.push_str(&format!( "{}, {} = _encode_utf8({}, realloc, memory, caller)\n", ptr, len, operands[0], )); results.push(ptr); results.push(len); } Instruction::StringLift { free, .. } => { self.needs_memory = true; let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); builder.push_str(&format!("{} = {}\n", ptr, operands[0])); builder.push_str(&format!("{} = {}\n", len, operands[1])); builder.deps.needs_decode_utf8 = true; let result = format!("_decode_utf8(memory, caller, {}, {})", ptr, len); match free { Some(free) => { self.needs_free = Some(free.to_string()); let list = self.locals.tmp("list"); builder.push_str(&format!("{} = {}\n", list, result)); self.src .push_str(&format!("free(caller, {}, {}, 1)\n", ptr, len)); results.push(list); } None => results.push(result), } } Instruction::ListLower { element, realloc } => { let base = self.payloads.pop().unwrap(); let e = self.payloads.pop().unwrap(); let realloc = realloc.unwrap(); let (body, body_results) = self.blocks.pop().unwrap(); assert!(body_results.is_empty()); let vec = self.locals.tmp("vec"); let result = self.locals.tmp("result"); let len = self.locals.tmp("len"); self.needs_realloc = Some(realloc.to_string()); let size = self.gen.sizes.size(element); let align = self.gen.sizes.align(element); // first store our vec-to-lower in a temporary since we'll // reference it multiple times. builder.push_str(&format!("{} = {}\n", vec, operands[0])); builder.push_str(&format!("{} = len({})\n", len, vec)); // ... then realloc space for the result in the guest module builder.push_str(&format!( "{} = realloc(caller, 0, 0, {}, {} * {})\n", result, align, len, size, )); builder.push_str(&format!("assert(isinstance({}, int))\n", result)); // ... then consume the vector and use the block to lower the // result. let i = self.locals.tmp("i"); builder.push_str(&format!("for {} in range(0, {}):\n", i, len)); builder.indent(); builder.push_str(&format!("{} = {}[{}]\n", e, vec, i)); builder.push_str(&format!("{} = {} + {} * {}\n", base, result, i, size)); builder.push_str(&body); builder.dedent(); results.push(result); results.push(len); } Instruction::ListLift { element, free, .. } => { let (body, body_results) = self.blocks.pop().unwrap(); let base = self.payloads.pop().unwrap(); let size = self.gen.sizes.size(element); let align = self.gen.sizes.align(element); let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); builder.push_str(&format!("{} = {}\n", ptr, operands[0])); builder.push_str(&format!("{} = {}\n", len, operands[1])); let result = self.locals.tmp("result"); builder.push_str(&format!("{}: List[", result)); builder.print_ty(element, true); builder.push_str("] = []\n"); let i = self.locals.tmp("i"); builder.push_str(&format!("for {} in range(0, {}):\n", i, len)); builder.indent(); builder.push_str(&format!("{} = {} + {} * {}\n", base, ptr, i, size)); builder.push_str(&body); assert_eq!(body_results.len(), 1); builder.push_str(&format!("{}.append({})\n", result, body_results[0])); builder.dedent(); if let Some(free) = free { self.needs_free = Some(free.to_string()); builder.push_str(&format!( "free(caller, {}, {} * {}, {})\n", ptr, len, size, align, )); } results.push(result); } Instruction::IterElem { .. } => { let name = self.locals.tmp("e"); results.push(name.clone()); self.payloads.push(name); } Instruction::IterBasePointer => { let name = self.locals.tmp("base"); results.push(name.clone()); self.payloads.push(name); } Instruction::CallWasm { iface: _, name, sig, } => { if sig.results.len() > 0 { for i in 0..sig.results.len() { if i > 0 { builder.push_str(", "); } let ret = self.locals.tmp("ret"); builder.push_str(&ret); results.push(ret); } builder.push_str(" = "); } builder.push_str(&self.src_object); builder.push_str("._"); builder.push_str(&name.to_snake_case()); builder.push_str("(caller"); if operands.len() > 0 { builder.push_str(", "); } builder.push_str(&operands.join(", ")); builder.push_str(")\n"); for (ty, name) in sig.results.iter().zip(results.iter()) { let ty = match ty { WasmType::I32 | WasmType::I64 => "int", WasmType::F32 | WasmType::F64 => "float", }; self.src .push_str(&format!("assert(isinstance({}, {}))\n", name, ty)); } } Instruction::CallInterface { module: _, func } => { match &func.result { Type::Unit => { results.push("".to_string()); } _ => { let result = self.locals.tmp("ret"); builder.push_str(&result); results.push(result); builder.push_str(" = "); } } match &func.kind { FunctionKind::Freestanding | FunctionKind::Static { .. } => { builder.push_str(&format!( "host.{}({})", func.name.to_snake_case(), operands.join(", "), )); } FunctionKind::Method { name, .. } => { builder.push_str(&format!( "{}.{}({})", operands[0], name.to_snake_case(), operands[1..].join(", "), )); } } builder.push_str("\n"); } Instruction::Return { amt, .. } => match amt { 0 => {} 1 => builder.push_str(&format!("return {}\n", operands[0])), _ => { self.src .push_str(&format!("return ({})\n", operands.join(", "))); } }, Instruction::I32Load { offset } => self.load("c_int32", *offset, operands, results), Instruction::I64Load { offset } => self.load("c_int64", *offset, operands, results), Instruction::F32Load { offset } => self.load("c_float", *offset, operands, results), Instruction::F64Load { offset } => self.load("c_double", *offset, operands, results), Instruction::I32Load8U { offset } => self.load("c_uint8", *offset, operands, results), Instruction::I32Load8S { offset } => self.load("c_int8", *offset, operands, results), Instruction::I32Load16U { offset } => self.load("c_uint16", *offset, operands, results), Instruction::I32Load16S { offset } => self.load("c_int16", *offset, operands, results), Instruction::I32Store { offset } => self.store("c_uint32", *offset, operands), Instruction::I64Store { offset } => self.store("c_uint64", *offset, operands), Instruction::F32Store { offset } => self.store("c_float", *offset, operands), Instruction::F64Store { offset } => self.store("c_double", *offset, operands), Instruction::I32Store8 { offset } => self.store("c_uint8", *offset, operands), Instruction::I32Store16 { offset } => self.store("c_uint16", *offset, operands), Instruction::Malloc { realloc, size, align, } => { self.needs_realloc = Some(realloc.to_string()); let ptr = self.locals.tmp("ptr"); builder.push_str(&format!( " {ptr} = realloc(caller, 0, 0, {align}, {size}) assert(isinstance({ptr}, int)) ", )); results.push(ptr); } i => unimplemented!("{:?}", i), } } } fn py_type_class_of(ty: &Type) -> PyTypeClass { match ty { Type::Unit => PyTypeClass::None, Type::Bool | Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::S8 | Type::S16 | Type::S32 | Type::S64 => PyTypeClass::Int, Type::Float32 | Type::Float64 => PyTypeClass::Float, Type::Char | Type::String => PyTypeClass::Str, Type::Handle(_) | Type::Id(_) => PyTypeClass::Custom, } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum PyTypeClass { None, Int, Str, Float, Custom, } fn wasm_ty_ctor(ty: WasmType) -> &'static str { match ty { WasmType::I32 => "wasmtime.ValType.i32()", WasmType::I64 => "wasmtime.ValType.i64()", WasmType::F32 => "wasmtime.ValType.f32()", WasmType::F64 => "wasmtime.ValType.f64()", } } fn wasm_ty_typing(ty: WasmType) -> &'static str { match ty { WasmType::I32 => "int", WasmType::I64 => "int", WasmType::F32 => "float", WasmType::F64 => "float", } }