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