xref: /wasmtime-44.0.1/winch/codegen/src/isa/mod.rs (revision cccc4e64)
1 use crate::BuiltinFunctions;
2 use anyhow::{anyhow, Result};
3 use core::fmt::Formatter;
4 use cranelift_codegen::isa::unwind::{UnwindInfo, UnwindInfoKind};
5 use cranelift_codegen::isa::{CallConv, IsaBuilder};
6 use cranelift_codegen::settings;
7 use cranelift_codegen::{Final, MachBufferFinalized, TextSectionBuilder};
8 use std::{
9     error,
10     fmt::{self, Debug, Display},
11 };
12 use target_lexicon::{Architecture, Triple};
13 use wasmparser::{FuncValidator, FunctionBody, ValidatorResources};
14 use wasmtime_cranelift::CompiledFunction;
15 use wasmtime_environ::{ModuleTranslation, ModuleTypesBuilder, Tunables, WasmFuncType};
16 
17 #[cfg(feature = "x64")]
18 pub(crate) mod x64;
19 
20 #[cfg(feature = "arm64")]
21 pub(crate) mod aarch64;
22 
23 pub(crate) mod reg;
24 
25 macro_rules! isa_builder {
26     ($name: ident, $cfg_terms: tt, $triple: ident) => {{
27         #[cfg $cfg_terms]
28         {
29             Ok($name::isa_builder($triple))
30         }
31         #[cfg(not $cfg_terms)]
32         {
33             Err(anyhow!(LookupError::SupportDisabled))
34         }
35     }};
36 }
37 
38 pub type Builder = IsaBuilder<Result<Box<dyn TargetIsa>>>;
39 
40 /// Look for an ISA builder for the given target triple.
41 pub fn lookup(triple: Triple) -> Result<Builder> {
42     match triple.architecture {
43         Architecture::X86_64 => {
44             isa_builder!(x64, (feature = "x64"), triple)
45         }
46         Architecture::Aarch64 { .. } => {
47             isa_builder!(aarch64, (feature = "arm64"), triple)
48         }
49 
50         _ => Err(anyhow!(LookupError::Unsupported)),
51     }
52 }
53 
54 impl error::Error for LookupError {}
55 impl Display for LookupError {
56     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57         match self {
58             LookupError::Unsupported => write!(f, "This target is not supported yet"),
59             LookupError::SupportDisabled => write!(f, "Support for this target was disabled"),
60         }
61     }
62 }
63 
64 #[derive(Debug)]
65 pub(crate) enum LookupError {
66     Unsupported,
67     // This directive covers the case in which the consumer
68     // enables the `all-arch` feature; in such case, this variant
69     // will never be used. This is most likely going to change
70     // in the future; this is one of the simplest options for now.
71     #[allow(dead_code)]
72     SupportDisabled,
73 }
74 
75 /// Calling conventions supported by Winch. Winch supports a variation of
76 /// the calling conventions defined in this enum plus an internal default
77 /// calling convention.
78 ///
79 /// This enum is a reduced subset of the calling conventions defined in
80 /// [cranelift_codegen::isa::CallConv]. Introducing this enum makes it easier
81 /// to enforce the invariant of all the calling conventions supported by Winch.
82 ///
83 /// The main difference between the system calling conventions defined in
84 /// this enum and their native counterparts is how multiple returns are handled.
85 /// Given that Winch is not meant to be a standalone code generator, the code
86 /// it generates is tightly coupled to how Wasmtime expects multiple returns
87 /// to be handled: the first return in a register, dictated by the calling
88 /// convention and the rest, if any, via a return pointer.
89 #[derive(Copy, Clone, Debug)]
90 pub enum CallingConvention {
91     /// See [cranelift_codegen::isa::CallConv::SystemV]
92     SystemV,
93     /// See [cranelift_codegen::isa::CallConv::WindowsFastcall]
94     WindowsFastcall,
95     /// See [cranelift_codegen::isa::CallConv::AppleAarch64]
96     AppleAarch64,
97     /// The default calling convention for Winch. It largely follows SystemV
98     /// for parameter and result handling. This calling convention is part of
99     /// Winch's default ABI `crate::abi::ABI`.
100     Default,
101 }
102 
103 impl CallingConvention {
104     /// Returns true if the current calling convention is `WindowsFastcall`.
105     fn is_fastcall(&self) -> bool {
106         match &self {
107             CallingConvention::WindowsFastcall => true,
108             _ => false,
109         }
110     }
111 
112     /// Returns true if the current calling convention is `SystemV`.
113     fn is_systemv(&self) -> bool {
114         match &self {
115             CallingConvention::SystemV => true,
116             _ => false,
117         }
118     }
119 
120     /// Returns true if the current calling convention is `AppleAarch64`.
121     fn is_apple_aarch64(&self) -> bool {
122         match &self {
123             CallingConvention::AppleAarch64 => true,
124             _ => false,
125         }
126     }
127 
128     /// Returns true if the current calling convention is `Default`.
129     pub fn is_default(&self) -> bool {
130         match &self {
131             CallingConvention::Default => true,
132             _ => false,
133         }
134     }
135 }
136 
137 impl From<CallingConvention> for CallConv {
138     fn from(value: CallingConvention) -> Self {
139         match value {
140             CallingConvention::SystemV => Self::SystemV,
141             CallingConvention::AppleAarch64 => Self::AppleAarch64,
142             CallingConvention::Default => Self::Winch,
143             CallingConvention::WindowsFastcall => Self::WindowsFastcall,
144         }
145     }
146 }
147 
148 /// A trait representing commonalities between the supported
149 /// instruction set architectures.
150 pub trait TargetIsa: Send + Sync {
151     /// Get the name of the ISA.
152     fn name(&self) -> &'static str;
153 
154     /// Get the target triple of the ISA.
155     fn triple(&self) -> &Triple;
156 
157     /// Get the ISA-independent flags that were used to make this trait object.
158     fn flags(&self) -> &settings::Flags;
159 
160     /// Get the ISA-dependent flag values that were used to make this trait object.
161     fn isa_flags(&self) -> Vec<settings::Value>;
162 
163     /// Get a flag indicating whether branch protection is enabled.
164     fn is_branch_protection_enabled(&self) -> bool {
165         false
166     }
167 
168     /// Compile a function.
169     fn compile_function(
170         &self,
171         sig: &WasmFuncType,
172         body: &FunctionBody,
173         translation: &ModuleTranslation,
174         types: &ModuleTypesBuilder,
175         builtins: &mut BuiltinFunctions,
176         validator: &mut FuncValidator<ValidatorResources>,
177         tunables: &Tunables,
178     ) -> Result<CompiledFunction>;
179 
180     /// Get the default calling convention of the underlying target triple.
181     fn default_call_conv(&self) -> CallConv {
182         CallConv::triple_default(&self.triple())
183     }
184 
185     /// Derive Wasmtime's calling convention from the triple's default
186     /// calling convention.
187     fn wasmtime_call_conv(&self) -> CallingConvention {
188         match self.default_call_conv() {
189             CallConv::AppleAarch64 => CallingConvention::AppleAarch64,
190             CallConv::SystemV => CallingConvention::SystemV,
191             CallConv::WindowsFastcall => CallingConvention::WindowsFastcall,
192             cc => unimplemented!("calling convention: {:?}", cc),
193         }
194     }
195 
196     /// Get the endianness of the underlying target triple.
197     fn endianness(&self) -> target_lexicon::Endianness {
198         self.triple().endianness().unwrap()
199     }
200 
201     fn emit_unwind_info(
202         &self,
203         _result: &MachBufferFinalized<Final>,
204         _kind: UnwindInfoKind,
205     ) -> Result<Option<UnwindInfo>>;
206 
207     /// See `cranelift_codegen::isa::TargetIsa::create_systemv_cie`.
208     fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
209         // By default, an ISA cannot create a System V CIE.
210         None
211     }
212 
213     /// See `cranelift_codegen::isa::TargetIsa::text_section_builder`.
214     fn text_section_builder(&self, num_labeled_funcs: usize) -> Box<dyn TextSectionBuilder>;
215 
216     /// See `cranelift_codegen::isa::TargetIsa::function_alignment`.
217     fn function_alignment(&self) -> u32;
218 
219     /// Returns the pointer width of the ISA in bytes.
220     fn pointer_bytes(&self) -> u8 {
221         let width = self.triple().pointer_width().unwrap();
222         width.bytes()
223     }
224 
225     /// The log2 of the target's page size and alignment.
226     ///
227     /// Note that this may be an upper-bound that is larger than necessary for
228     /// some platforms since it may depend on runtime configuration.
229     fn page_size_align_log2(&self) -> u8;
230 }
231 
232 impl Debug for &dyn TargetIsa {
233     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
234         write!(
235             f,
236             "Target ISA {{ triple: {:?}, calling convention: {:?} }}",
237             self.triple(),
238             self.default_call_conv()
239         )
240     }
241 }
242 
243 /// Per-class register environment.
244 pub(crate) struct RegClassEnv {
245     /// Float register class limit.
246     limit: u8,
247     /// Float register class index.
248     index: u8,
249 }
250 
251 /// Helper environment to track register assignment for Winch's default calling
252 /// convention.
253 pub(crate) struct RegIndexEnv {
254     /// Int register environment.
255     int: RegClassEnv,
256     /// Float register environment.
257     float: Option<RegClassEnv>,
258 }
259 
260 impl RegIndexEnv {
261     fn with_limits_per_class(int: u8, float: u8) -> Self {
262         let int = RegClassEnv {
263             limit: int,
264             index: 0,
265         };
266 
267         let float = RegClassEnv {
268             limit: float,
269             index: 0,
270         };
271 
272         Self {
273             int,
274             float: Some(float),
275         }
276     }
277 
278     fn with_absolute_limit(limit: u8) -> Self {
279         let int = RegClassEnv { limit, index: 0 };
280 
281         Self { int, float: None }
282     }
283 }
284 
285 impl RegIndexEnv {
286     fn next_gpr(&mut self) -> Option<u8> {
287         (self.int.index < self.int.limit)
288             .then(|| Self::increment(&mut self.int.index))
289             .flatten()
290     }
291 
292     fn next_fpr(&mut self) -> Option<u8> {
293         if let Some(f) = self.float.as_mut() {
294             (f.index < f.limit)
295                 .then(|| Self::increment(&mut f.index))
296                 .flatten()
297         } else {
298             // If a single `RegClassEnv` is used, it means that the count is
299             // absolute, so we default to calling `next_gpr`.
300             self.next_gpr()
301         }
302     }
303 
304     fn increment(index: &mut u8) -> Option<u8> {
305         let current = *index;
306         match index.checked_add(1) {
307             Some(next) => {
308                 *index = next;
309                 Some(current)
310             }
311             None => None,
312         }
313     }
314 }
315 
316 #[cfg(test)]
317 mod tests {
318     use super::RegIndexEnv;
319     #[test]
320     fn test_get_next_reg_index() {
321         let mut index_env = RegIndexEnv::with_limits_per_class(3, 3);
322         assert_eq!(index_env.next_fpr(), Some(0));
323         assert_eq!(index_env.next_gpr(), Some(0));
324         assert_eq!(index_env.next_fpr(), Some(1));
325         assert_eq!(index_env.next_gpr(), Some(1));
326         assert_eq!(index_env.next_fpr(), Some(2));
327         assert_eq!(index_env.next_gpr(), Some(2));
328     }
329 
330     #[test]
331     fn test_reg_index_env_absolute_count() {
332         let mut e = RegIndexEnv::with_absolute_limit(4);
333         assert!(e.next_gpr() == Some(0));
334         assert!(e.next_fpr() == Some(1));
335         assert!(e.next_gpr() == Some(2));
336         assert!(e.next_fpr() == Some(3));
337     }
338 }
339