Skip to main content

winch_codegen/isa/
mod.rs

1use crate::{BuiltinFunctions, Result, format_err};
2use core::fmt::Formatter;
3use cranelift_codegen::isa::unwind::{UnwindInfo, UnwindInfoKind};
4use cranelift_codegen::isa::{CallConv, IsaBuilder};
5use cranelift_codegen::settings;
6use cranelift_codegen::{Final, MachBufferFinalized, TextSectionBuilder};
7use std::{
8    error,
9    fmt::{self, Debug, Display},
10};
11use target_lexicon::{Architecture, Triple};
12use wasmparser::{FuncValidator, FunctionBody, ValidatorResources};
13use wasmtime_cranelift::CompiledFunction;
14use wasmtime_environ::{ModuleTranslation, ModuleTypesBuilder, Tunables, WasmFuncType};
15
16#[cfg(feature = "x64")]
17pub(crate) mod x64;
18
19#[cfg(feature = "arm64")]
20pub(crate) mod aarch64;
21
22pub(crate) mod reg;
23
24macro_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
37pub type Builder = IsaBuilder<Result<Box<dyn TargetIsa>>>;
38
39/// Look for an ISA builder for the given target triple.
40pub 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
53impl error::Error for LookupError {}
54impl Display for LookupError {
55    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)]
64pub(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)]
89pub 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
102impl CallingConvention {
103    /// Returns true if the current calling convention is `WindowsFastcall`.
104    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`.
112    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`.
120    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`.
128    pub fn is_default(&self) -> bool {
129        match &self {
130            CallingConvention::Default => true,
131            _ => false,
132        }
133    }
134}
135
136impl From<CallingConvention> for CallConv {
137    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.
149pub trait TargetIsa: Send + Sync {
150    /// Get the name of the ISA.
151    fn name(&self) -> &'static str;
152
153    /// Get the target triple of the ISA.
154    fn triple(&self) -> &Triple;
155
156    /// Get the ISA-independent flags that were used to make this trait object.
157    fn flags(&self) -> &settings::Flags;
158
159    /// Get the ISA-dependent flag values that were used to make this trait object.
160    fn isa_flags(&self) -> Vec<settings::Value>;
161
162    /// Get a flag indicating whether branch protection is enabled.
163    fn is_branch_protection_enabled(&self) -> bool {
164        false
165    }
166
167    /// Compile a function.
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.
180    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.
186    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.
196    fn endianness(&self) -> target_lexicon::Endianness {
197        self.triple().endianness().unwrap()
198    }
199
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`.
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`.
213    fn text_section_builder(&self, num_labeled_funcs: usize) -> Box<dyn TextSectionBuilder>;
214
215    /// See `cranelift_codegen::isa::TargetIsa::function_alignment`.
216    fn function_alignment(&self) -> u32;
217
218    /// Returns the pointer width of the ISA in bytes.
219    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.
228    fn page_size_align_log2(&self) -> u8;
229}
230
231impl Debug for &dyn TargetIsa {
232    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.
243pub(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.
252pub(crate) struct RegIndexEnv {
253    /// Int register environment.
254    int: RegClassEnv,
255    /// Float register environment.
256    float: Option<RegClassEnv>,
257}
258
259impl RegIndexEnv {
260    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
277    fn with_absolute_limit(limit: u8) -> Self {
278        let int = RegClassEnv { limit, index: 0 };
279
280        Self { int, float: None }
281    }
282}
283
284impl RegIndexEnv {
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
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
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)]
316mod tests {
317    use super::RegIndexEnv;
318    #[test]
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]
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}