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, IntValue, 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 global.
69    #[allow(unused)]
70    fn get_g0_ptr_param<'ctx>(&self, func_value: &FunctionValue<'ctx>) -> IntValue<'ctx> {
71        // g0 is always after the vmctx.
72        let vmctx_idx = u32::from(
73            func_value
74                .get_enum_attribute(
75                    AttributeLoc::Param(0),
76                    Attribute::get_named_enum_kind_id("sret"),
77                )
78                .is_some(),
79        );
80
81        let param = func_value.get_nth_param(vmctx_idx + 1).unwrap();
82        param.set_name("g0");
83
84        param.into_int_value()
85    }
86
87    /// Given a function definition, retrieve the parameter that is the pointer to the first --
88    /// number 0 -- local memory.
89    ///
90    /// # Notes
91    /// This function assumes that g0m0 is enabled.
92    fn get_m0_ptr_param<'ctx>(&self, func_value: &FunctionValue<'ctx>) -> PointerValue<'ctx> {
93        // m0 is always after g0.
94        let vmctx_idx = u32::from(
95            func_value
96                .get_enum_attribute(
97                    AttributeLoc::Param(0),
98                    Attribute::get_named_enum_kind_id("sret"),
99                )
100                .is_some(),
101        );
102
103        let param = func_value.get_nth_param(vmctx_idx + 1).unwrap();
104        param.set_name("m0_base_ptr");
105
106        param.into_pointer_value()
107    }
108
109    /// Given a wasm function type, produce an llvm function declaration.
110    ///
111    /// # Notes
112    /// This function assumes that m0 optimization is enabled.
113    fn func_type_to_llvm<'ctx>(
114        &self,
115        context: &'ctx Context,
116        intrinsics: &Intrinsics<'ctx>,
117        offsets: Option<&VMOffsets>,
118        sig: &FuncSig,
119        include_m0_param: bool,
120    ) -> Result<(FunctionType<'ctx>, Vec<(Attribute, AttributeLoc)>), CompileError>;
121
122    /// Marshall wasm stack values into function parameters.
123    #[allow(clippy::too_many_arguments)]
124    fn args_to_call<'ctx>(
125        &self,
126        alloca_builder: &Builder<'ctx>,
127        func_sig: &FuncSig,
128        llvm_fn_ty: &FunctionType<'ctx>,
129        ctx_ptr: PointerValue<'ctx>,
130        values: &[BasicValueEnum<'ctx>],
131        intrinsics: &Intrinsics<'ctx>,
132        m0: Option<PointerValue<'ctx>>,
133    ) -> Result<Vec<BasicValueEnum<'ctx>>, CompileError> {
134        // If it's an sret, allocate the return space.
135        let sret = if llvm_fn_ty.get_return_type().is_none() && func_sig.results().len() > 1 {
136            let llvm_params: Vec<_> = func_sig
137                .results()
138                .iter()
139                .map(|x| type_to_llvm(intrinsics, *x).unwrap())
140                .collect();
141            let llvm_params = llvm_fn_ty
142                .get_context()
143                .struct_type(llvm_params.as_slice(), false);
144            Some(err!(alloca_builder.build_alloca(llvm_params, "sret")))
145        } else {
146            None
147        };
148
149        let mut args = vec![ctx_ptr.as_basic_value_enum()];
150
151        if let Some(m0) = m0 {
152            args.push(m0.into());
153        }
154
155        let args = args.into_iter().chain(values.iter().copied());
156
157        let ret = if let Some(sret) = sret {
158            std::iter::once(sret.as_basic_value_enum())
159                .chain(args)
160                .collect()
161        } else {
162            args.collect()
163        };
164
165        Ok(ret)
166    }
167
168    /// Given a CallSite, extract the returned values and return them in a Vec.
169    fn rets_from_call<'ctx>(
170        &self,
171        builder: &Builder<'ctx>,
172        intrinsics: &Intrinsics<'ctx>,
173        call_site: CallSiteValue<'ctx>,
174        func_sig: &FuncSig,
175    ) -> Result<Vec<BasicValueEnum<'ctx>>, CompileError>;
176
177    /// Whether the llvm equivalent of this wasm function has an `sret` attribute.
178    fn is_sret(&self, func_sig: &FuncSig) -> Result<bool, CompileError> {
179        let func_sig_returns_bitwidths = func_sig
180            .results()
181            .iter()
182            .map(|ty| match ty {
183                Type::I32 | Type::F32 | Type::ExceptionRef => 32,
184                Type::I64 | Type::F64 => 64,
185                Type::V128 => 128,
186                Type::ExternRef | Type::FuncRef => 64, /* pointer */
187            })
188            .collect::<Vec<i32>>();
189
190        Ok(!matches!(
191            func_sig_returns_bitwidths.as_slice(),
192            [] | [_]
193                | [32, 32]
194                | [32, 64]
195                | [64, 32]
196                | [64, 64]
197                | [32, 32, 32]
198                | [32, 32, 64]
199                | [64, 32, 32]
200                | [32, 32, 32, 32]
201        ))
202    }
203
204    /// Pack LLVM IR values representing individual wasm values into the return type for the function.
205    fn pack_values_for_register_return<'ctx>(
206        &self,
207        intrinsics: &Intrinsics<'ctx>,
208        builder: &Builder<'ctx>,
209        values: &[BasicValueEnum<'ctx>],
210        func_type: &FunctionType<'ctx>,
211    ) -> Result<BasicValueEnum<'ctx>, CompileError>;
212}