winch_codegen/isa/
mod.rs

1use crate::BuiltinFunctions;
2use anyhow::{anyhow, Result};
3use core::fmt::Formatter;
4use cranelift_codegen::isa::unwind::{UnwindInfo, UnwindInfoKind};
5use cranelift_codegen::isa::{CallConv, IsaBuilder};
6use cranelift_codegen::settings;
7use cranelift_codegen::{Final, MachBufferFinalized, TextSectionBuilder};
8use std::{
9    error,
10    fmt::{self, Debug, Display},
11};
12use target_lexicon::{Architecture, Triple};
13use wasmparser::{FuncValidator, FunctionBody, ValidatorResources};
14use wasmtime_cranelift::CompiledFunction;
15use wasmtime_environ::{ModuleTranslation, ModuleTypesBuilder, Tunables, WasmFuncType};
16
17#[cfg(feature = "x64")]
18pub(crate) mod x64;
19
20#[cfg(feature = "arm64")]
21pub(crate) mod aarch64;
22
23pub(crate) mod reg;
24
25macro_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
38pub type Builder = IsaBuilder<Result<Box<dyn TargetIsa>>>;
39
40/// Look for an ISA builder for the given target triple.
41pub 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
54impl error::Error for LookupError {}
55impl 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)]
65pub(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)]
90pub 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
103impl 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
137impl 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.
150pub 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
232impl 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.
244pub(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.
253pub(crate) struct RegIndexEnv {
254    /// Int register environment.
255    int: RegClassEnv,
256    /// Float register environment.
257    float: Option<RegClassEnv>,
258}
259
260impl 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
285impl 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)]
317mod 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}