wasmer_compiler_llvm/abi/
mod.rs

1// LLVM implements part of the ABI lowering internally, but also requires that
2// the user pack and unpack values themselves sometimes. This can help the LLVM
3// optimizer by exposing operations to the optimizer, but it requires that the
4// frontend know exactly what IR to produce in order to get the right ABI.
5
6#![deny(missing_docs)]
7
8use crate::error::err;
9use crate::translator::intrinsics::{Intrinsics, type_to_llvm};
10use inkwell::values::BasicValue;
11use inkwell::{
12    attributes::{Attribute, AttributeLoc},
13    builder::Builder,
14    context::Context,
15    targets::TargetMachine,
16    types::FunctionType,
17    values::{BasicValueEnum, CallSiteValue, FunctionValue, PointerValue},
18};
19use wasmer_types::{CompileError, FunctionType as FuncSig, Type};
20use wasmer_vm::VMOffsets;
21
22mod aarch64_systemv;
23mod riscv_systemv;
24mod x86_64_systemv;
25
26use aarch64_systemv::Aarch64SystemV;
27use riscv_systemv::RiscvSystemV;
28use x86_64_systemv::X86_64SystemV;
29
30pub fn get_abi(target_machine: &TargetMachine) -> Box<dyn Abi> {
31    let target_name = target_machine.get_triple();
32    let target_name = target_name.as_str().to_string_lossy();
33
34    if target_name.starts_with("aarch64") {
35        Box::new(Aarch64SystemV {})
36    } else if target_name.starts_with("riscv") {
37        Box::new(RiscvSystemV {
38            is_riscv64: target_name.starts_with("riscv64"),
39        })
40    } else {
41        Box::new(X86_64SystemV {})
42    }
43}
44
45/// We need to produce different LLVM IR for different platforms. (Contrary to
46/// popular knowledge LLVM IR is not intended to be portable in that way.) This
47/// trait deals with differences between function signatures on different
48/// targets.
49pub trait Abi {
50    /// Given a function definition, retrieve the parameter that is the vmctx pointer.
51    fn get_vmctx_ptr_param<'ctx>(&self, func_value: &FunctionValue<'ctx>) -> PointerValue<'ctx> {
52        let param = func_value
53            .get_nth_param(u32::from(
54                func_value
55                    .get_enum_attribute(
56                        AttributeLoc::Param(0),
57                        Attribute::get_named_enum_kind_id("sret"),
58                    )
59                    .is_some(),
60            ))
61            .unwrap();
62        param.set_name("vmctx");
63
64        param.into_pointer_value()
65    }
66
67    /// Given a function definition, retrieve the parameter that is the pointer to the first --
68    /// number 0 -- local memory.
69    fn get_m0_ptr_param<'ctx>(&self, func_value: &FunctionValue<'ctx>) -> PointerValue<'ctx> {
70        let vmctx_idx = u32::from(
71            func_value
72                .get_enum_attribute(
73                    AttributeLoc::Param(0),
74                    Attribute::get_named_enum_kind_id("sret"),
75                )
76                .is_some(),
77        );
78
79        let param = func_value.get_nth_param(vmctx_idx + 1).unwrap();
80        param.set_name("m0_base_ptr");
81
82        param.into_pointer_value()
83    }
84
85    /// Given a wasm function type, produce an llvm function declaration.
86    ///
87    /// # Notes
88    /// This function assumes that m0 optimization is enabled.
89    fn func_type_to_llvm<'ctx>(
90        &self,
91        context: &'ctx Context,
92        intrinsics: &Intrinsics<'ctx>,
93        offsets: Option<&VMOffsets>,
94        sig: &FuncSig,
95        include_m0_param: bool,
96    ) -> Result<(FunctionType<'ctx>, Vec<(Attribute, AttributeLoc)>), CompileError>;
97
98    /// Marshall wasm stack values into function parameters.
99    #[allow(clippy::too_many_arguments)]
100    fn args_to_call<'ctx>(
101        &self,
102        alloca_builder: &Builder<'ctx>,
103        func_sig: &FuncSig,
104        llvm_fn_ty: &FunctionType<'ctx>,
105        ctx_ptr: PointerValue<'ctx>,
106        values: &[BasicValueEnum<'ctx>],
107        intrinsics: &Intrinsics<'ctx>,
108        m0: Option<PointerValue<'ctx>>,
109        sret_ptr: Option<PointerValue<'ctx>>,
110    ) -> Result<Vec<BasicValueEnum<'ctx>>, CompileError> {
111        // If it's an sret, allocate the return space.
112        let sret = if self.llvm_fn_uses_sret(llvm_fn_ty, func_sig) {
113            let llvm_params: Vec<_> = func_sig
114                .results()
115                .iter()
116                .map(|x| type_to_llvm(intrinsics, *x).unwrap())
117                .collect();
118            let llvm_params = llvm_fn_ty
119                .get_context()
120                .struct_type(llvm_params.as_slice(), false);
121            // If return_call is used, we pass existing sret pointer instead a newly created one.
122            Some(match sret_ptr {
123                Some(sret_ptr) => sret_ptr,
124                None => err!(alloca_builder.build_alloca(llvm_params, "sret")),
125            })
126        } else {
127            None
128        };
129
130        let mut args = vec![ctx_ptr.as_basic_value_enum()];
131
132        if let Some(m0) = m0 {
133            args.push(m0.into());
134        }
135
136        let args = args.into_iter().chain(values.iter().copied());
137
138        let ret = if let Some(sret) = sret {
139            std::iter::once(sret.as_basic_value_enum())
140                .chain(args)
141                .collect()
142        } else {
143            args.collect()
144        };
145
146        Ok(ret)
147    }
148
149    /// Whether a concrete LLVM function type uses an `sret` parameter for the given wasm signature.
150    fn llvm_fn_uses_sret<'ctx>(&self, llvm_fn_ty: &FunctionType<'ctx>, func_sig: &FuncSig) -> bool {
151        llvm_fn_ty.get_return_type().is_none() && func_sig.results().len() > 1
152    }
153
154    /// Given a CallSite, extract the returned values and return them in a Vec.
155    fn rets_from_call<'ctx>(
156        &self,
157        builder: &Builder<'ctx>,
158        intrinsics: &Intrinsics<'ctx>,
159        call_site: CallSiteValue<'ctx>,
160        func_sig: &FuncSig,
161    ) -> Result<Vec<BasicValueEnum<'ctx>>, CompileError>;
162
163    /// Whether the llvm equivalent of this wasm function has an `sret` attribute.
164    fn is_sret(&self, func_sig: &FuncSig) -> Result<bool, CompileError> {
165        let func_sig_returns_bitwidths = func_sig
166            .results()
167            .iter()
168            .map(|ty| match ty {
169                Type::I32 | Type::F32 | Type::ExceptionRef => 32,
170                Type::I64 | Type::F64 => 64,
171                Type::V128 => 128,
172                Type::ExternRef | Type::FuncRef => 64, /* pointer */
173            })
174            .collect::<Vec<i32>>();
175
176        Ok(!matches!(
177            func_sig_returns_bitwidths.as_slice(),
178            [] | [_]
179                | [32, 32]
180                | [32, 64]
181                | [64, 32]
182                | [64, 64]
183                | [32, 32, 32]
184                | [32, 32, 64]
185                | [64, 32, 32]
186                | [32, 32, 32, 32]
187        ))
188    }
189
190    /// Pack LLVM IR values representing individual wasm values into the return type for the function.
191    fn pack_values_for_register_return<'ctx>(
192        &self,
193        intrinsics: &Intrinsics<'ctx>,
194        builder: &Builder<'ctx>,
195        values: &[BasicValueEnum<'ctx>],
196        func_type: &FunctionType<'ctx>,
197    ) -> Result<BasicValueEnum<'ctx>, CompileError>;
198}