Path: blob/main/crates/wiggle/generate/src/funcs.rs
1693 views
use crate::codegen_settings::{CodegenSettings, ErrorType};1use crate::lifetimes::anon_lifetime;2use crate::module_trait::passed_by_reference;3use crate::names;4use crate::types::WiggleType;5use proc_macro2::{Ident, Span, TokenStream};6use quote::quote;7use std::mem;8use witx::Instruction;910pub fn define_func(11module: &witx::Module,12func: &witx::InterfaceFunc,13settings: &CodegenSettings,14) -> TokenStream {15let (ts, _bounds) = _define_func(module, func, settings);16ts17}1819pub fn func_bounds(20module: &witx::Module,21func: &witx::InterfaceFunc,22settings: &CodegenSettings,23) -> Vec<Ident> {24let (_ts, bounds) = _define_func(module, func, settings);25bounds26}2728fn _define_func(29module: &witx::Module,30func: &witx::InterfaceFunc,31settings: &CodegenSettings,32) -> (TokenStream, Vec<Ident>) {33let ident = names::func(&func.name);3435let (wasm_params, wasm_results) = func.wasm_signature();36let param_names = (0..wasm_params.len())37.map(|i| Ident::new(&format!("arg{i}"), Span::call_site()))38.collect::<Vec<_>>();39let abi_params = wasm_params.iter().zip(¶m_names).map(|(arg, name)| {40let wasm = names::wasm_type(*arg);41quote!(#name : #wasm)42});4344let abi_ret = match wasm_results.len() {450 => quote!(()),461 => {47let ty = names::wasm_type(wasm_results[0]);48quote!(#ty)49}50_ => unimplemented!(),51};5253let mut body = TokenStream::new();54let mut bounds = vec![names::trait_name(&module.name)];55func.call_interface(56&module.name,57&mut Rust {58src: &mut body,59params: ¶m_names,60block_storage: Vec::new(),61blocks: Vec::new(),62module,63funcname: func.name.as_str(),64settings,65bounds: &mut bounds,66},67);6869let mod_name = &module.name.as_str();70let func_name = &func.name.as_str();71let mk_span = quote!(72let _span = wiggle::tracing::span!(73wiggle::tracing::Level::TRACE,74"wiggle abi",75module = #mod_name,76function = #func_name77);78);79let ctx_type = if settings.mutable {80quote!(&mut)81} else {82quote!(&)83};84if settings.get_async(&module, &func).is_sync() {85let traced_body = if settings.tracing.enabled_for(&mod_name, &func_name) {86quote!(87#mk_span88_span.in_scope(|| {89#body90})91)92} else {93quote!(#body)94};95(96quote!(97#[allow(unreachable_code)] // deals with warnings in noreturn functions98pub fn #ident(99ctx: #ctx_type (impl #(#bounds)+*),100memory: &mut wiggle::GuestMemory<'_>,101#(#abi_params),*102) -> wiggle::anyhow::Result<#abi_ret> {103use std::convert::TryFrom as _;104#traced_body105}106),107bounds,108)109} else {110let traced_body = if settings.tracing.enabled_for(&mod_name, &func_name) {111quote!(112use wiggle::tracing::Instrument as _;113#mk_span114async move {115#body116}.instrument(_span).await117)118} else {119quote!(120#body121)122};123(124quote!(125#[allow(unreachable_code)] // deals with warnings in noreturn functions126pub async fn #ident(127ctx: #ctx_type (impl #(#bounds)+*),128memory: &mut wiggle::GuestMemory<'_>,129#(#abi_params),*130) -> wiggle::anyhow::Result<#abi_ret> {131use std::convert::TryFrom as _;132#traced_body133}134),135bounds,136)137}138}139140struct Rust<'a> {141src: &'a mut TokenStream,142params: &'a [Ident],143block_storage: Vec<TokenStream>,144blocks: Vec<TokenStream>,145module: &'a witx::Module,146funcname: &'a str,147settings: &'a CodegenSettings,148bounds: &'a mut Vec<Ident>,149}150151impl Rust<'_> {152fn bound(&mut self, i: Ident) {153if !self.bounds.contains(&i) {154self.bounds.push(i);155}156}157}158159impl witx::Bindgen for Rust<'_> {160type Operand = TokenStream;161162fn push_block(&mut self) {163let prev = mem::replace(self.src, TokenStream::new());164self.block_storage.push(prev);165}166167fn finish_block(&mut self, operand: Option<TokenStream>) {168let to_restore = self.block_storage.pop().unwrap();169let src = mem::replace(self.src, to_restore);170match operand {171None => self.blocks.push(src),172Some(s) => {173if src.is_empty() {174self.blocks.push(s);175} else {176self.blocks.push(quote!({ #src; #s }));177}178}179}180}181182// This is only used for `call_wasm` at this time.183fn allocate_space(&mut self, _: usize, _: &witx::NamedType) {184unimplemented!()185}186187fn emit(188&mut self,189inst: &Instruction<'_>,190operands: &mut Vec<TokenStream>,191results: &mut Vec<TokenStream>,192) {193let wrap_err = |location: &str| {194let modulename = self.module.name.as_str();195let funcname = self.funcname;196quote! {197|e| {198wiggle::GuestError::InFunc {199modulename: #modulename,200funcname: #funcname,201location: #location,202err: Box::new(wiggle::GuestError::from(e)),203}204}205}206};207208let mut try_from = |ty: TokenStream| {209let val = operands.pop().unwrap();210let wrap_err = wrap_err(&format!("convert {ty}"));211results.push(quote!(#ty::try_from(#val).map_err(#wrap_err)?));212};213214match inst {215Instruction::GetArg { nth } => {216let param = &self.params[*nth];217results.push(quote!(#param));218}219220Instruction::PointerFromI32 { ty } | Instruction::ConstPointerFromI32 { ty } => {221let val = operands.pop().unwrap();222let pointee_type = names::type_ref(ty, anon_lifetime());223results.push(quote! {224wiggle::GuestPtr::<#pointee_type>::new(#val as u32)225});226}227228Instruction::ListFromPointerLength { ty } => {229let ptr = &operands[0];230let len = &operands[1];231let ty = match &**ty.type_() {232witx::Type::Builtin(witx::BuiltinType::Char) => quote!(str),233_ => {234let ty = names::type_ref(ty, anon_lifetime());235quote!([#ty])236}237};238results.push(quote! {239wiggle::GuestPtr::<#ty>::new((#ptr as u32, #len as u32));240})241}242243Instruction::CallInterface { func, .. } => {244// Use the `tracing` crate to log all arguments that are going245// out, and afterwards we call the function with those bindings.246let mut args = Vec::new();247for (i, param) in func.params.iter().enumerate() {248let name = names::func_param(¶m.name);249let val = &operands[i];250self.src.extend(quote!(let #name = #val;));251if passed_by_reference(param.tref.type_()) {252args.push(quote!(&#name));253} else {254args.push(quote!(#name));255}256}257if self258.settings259.tracing260.enabled_for(self.module.name.as_str(), self.funcname)261&& func.params.len() > 0262{263let args = func264.params265.iter()266.map(|param| {267let name = names::func_param(¶m.name);268if param.impls_display() {269quote!( #name = wiggle::tracing::field::display(&#name) )270} else {271quote!( #name = wiggle::tracing::field::debug(&#name) )272}273})274.collect::<Vec<_>>();275self.src.extend(quote! {276wiggle::tracing::event!(wiggle::tracing::Level::TRACE, #(#args),*);277});278}279280let trait_name = names::trait_name(&self.module.name);281let ident = names::func(&func.name);282if self.settings.get_async(&self.module, &func).is_sync() {283self.src.extend(quote! {284let ret = #trait_name::#ident(ctx, memory, #(#args),*);285})286} else {287self.src.extend(quote! {288let ret = #trait_name::#ident(ctx, memory, #(#args),*).await;289})290};291if self292.settings293.tracing294.enabled_for(self.module.name.as_str(), self.funcname)295{296self.src.extend(quote! {297wiggle::tracing::event!(298wiggle::tracing::Level::TRACE,299result = wiggle::tracing::field::debug(&ret),300);301});302}303304if func.results.len() > 0 {305results.push(quote!(ret));306} else if func.noreturn {307self.src.extend(quote!(return Err(ret);));308}309}310311// Lowering an enum is typically simple but if we have an error312// transformation registered for this enum then what we're actually313// doing is lowering from a user-defined error type to the error314// enum, and *then* we lower to an i32.315Instruction::EnumLower { ty } => {316let val = operands.pop().unwrap();317let val = match self.settings.errors.for_name(ty) {318Some(ErrorType::User(custom)) => {319let method = names::user_error_conversion_method(&custom);320self.bound(quote::format_ident!("UserErrorConversion"));321quote!(UserErrorConversion::#method(ctx, #val)?)322}323Some(ErrorType::Generated(_)) => quote!(#val.downcast()?),324None => val,325};326results.push(quote!(#val as i32));327}328329Instruction::ResultLower { err: err_ty, .. } => {330let err = self.blocks.pop().unwrap();331let ok = self.blocks.pop().unwrap();332let val = operands.pop().unwrap();333let err_typename = names::type_ref(err_ty.unwrap(), anon_lifetime());334results.push(quote! {335match #val {336Ok(e) => { #ok; <#err_typename as wiggle::GuestErrorType>::success() as i32 }337Err(e) => { #err }338}339});340}341342Instruction::VariantPayload => results.push(quote!(e)),343344Instruction::Return { amt: 0 } => {345self.src.extend(quote!(return Ok(())));346}347Instruction::Return { amt: 1 } => {348let val = operands.pop().unwrap();349self.src.extend(quote!(return Ok(#val)));350}351Instruction::Return { .. } => unimplemented!(),352353Instruction::TupleLower { amt } => {354let names = (0..*amt)355.map(|i| Ident::new(&format!("t{i}"), Span::call_site()))356.collect::<Vec<_>>();357let val = operands.pop().unwrap();358self.src.extend(quote!( let (#(#names,)*) = #val;));359results.extend(names.iter().map(|i| quote!(#i)));360}361362Instruction::Store { ty } => {363let ptr = operands.pop().unwrap();364let val = operands.pop().unwrap();365let wrap_err = wrap_err(&format!("write {}", ty.name.as_str()));366let pointee_type = names::type_(&ty.name);367self.src.extend(quote! {368memory.write(369wiggle::GuestPtr::<#pointee_type>::new(#ptr as u32),370#val,371)372.map_err(#wrap_err)?;373});374}375376Instruction::Load { ty } => {377let ptr = operands.pop().unwrap();378let wrap_err = wrap_err(&format!("read {}", ty.name.as_str()));379let pointee_type = names::type_(&ty.name);380results.push(quote! {381memory.read(wiggle::GuestPtr::<#pointee_type>::new(#ptr as u32))382.map_err(#wrap_err)?383});384}385386Instruction::HandleFromI32 { ty } => {387let val = operands.pop().unwrap();388let ty = names::type_(&ty.name);389results.push(quote!(#ty::from(#val)));390}391392// Smaller-than-32 numerical conversions are done with `TryFrom` to393// ensure we're not losing bits.394Instruction::U8FromI32 => try_from(quote!(u8)),395Instruction::S8FromI32 => try_from(quote!(i8)),396Instruction::Char8FromI32 => try_from(quote!(u8)),397Instruction::U16FromI32 => try_from(quote!(u16)),398Instruction::S16FromI32 => try_from(quote!(i16)),399400// Conversions with matching bit-widths but different signededness401// use `as` since we're basically just reinterpreting the bits.402Instruction::U32FromI32 | Instruction::UsizeFromI32 => {403let val = operands.pop().unwrap();404results.push(quote!(#val as u32));405}406Instruction::U64FromI64 => {407let val = operands.pop().unwrap();408results.push(quote!(#val as u64));409}410411// Conversions to enums/bitflags use `TryFrom` to ensure that the412// values are valid coming in.413Instruction::EnumLift { ty }414| Instruction::BitflagsFromI64 { ty }415| Instruction::BitflagsFromI32 { ty } => {416let ty = names::type_(&ty.name);417try_from(quote!(#ty))418}419420// No conversions necessary for these, the native wasm type matches421// our own representation.422Instruction::If32FromF32423| Instruction::If64FromF64424| Instruction::S32FromI32425| Instruction::S64FromI64 => results.push(operands.pop().unwrap()),426427// There's a number of other instructions we could implement but428// they're not exercised by WASI at this time. As necessary we can429// add code to implement them.430other => panic!("no implementation for {other:?}"),431}432}433}434435436