Coverage Report

Created: 2026-08-15 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/winch/codegen/src/isa/mod.rs
Line
Count
Source
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.
40
5.18k
pub fn lookup(triple: Triple) -> Result<Builder> {
41
5.18k
    match triple.architecture {
42
        Architecture::X86_64 => {
43
5.18k
            isa_builder!(x64, (feature = "x64"), triple)
44
        }
45
        Architecture::Aarch64 { .. } => {
46
0
            isa_builder!(aarch64, (feature = "arm64"), triple)
47
        }
48
49
0
        _ => Err(format_err!(LookupError::Unsupported)),
50
    }
51
5.18k
}
52
53
impl error::Error for LookupError {}
54
impl Display for LookupError {
55
0
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56
0
        match self {
57
0
            LookupError::Unsupported => write!(f, "This target is not supported yet"),
58
0
            LookupError::SupportDisabled => write!(f, "Support for this target was disabled"),
59
        }
60
0
    }
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`.
104
592k
    fn is_fastcall(&self) -> bool {
105
592k
        match &self {
106
0
            CallingConvention::WindowsFastcall => true,
107
592k
            _ => false,
108
        }
109
592k
    }
110
111
    /// Returns true if the current calling convention is `SystemV`.
112
518k
    fn is_systemv(&self) -> bool {
113
518k
        match &self {
114
0
            CallingConvention::SystemV => true,
115
518k
            _ => false,
116
        }
117
518k
    }
118
119
    /// Returns true if the current calling convention is `AppleAarch64`.
120
0
    fn is_apple_aarch64(&self) -> bool {
121
0
        match &self {
122
0
            CallingConvention::AppleAarch64 => true,
123
0
            _ => false,
124
        }
125
0
    }
126
127
    /// Returns true if the current calling convention is `Default`.
128
1.49M
    pub fn is_default(&self) -> bool {
129
1.49M
        match &self {
130
1.49M
            CallingConvention::Default => true,
131
0
            _ => false,
132
        }
133
1.49M
    }
134
}
135
136
impl From<CallingConvention> for CallConv {
137
137k
    fn from(value: CallingConvention) -> Self {
138
137k
        match value {
139
0
            CallingConvention::SystemV => Self::SystemV,
140
0
            CallingConvention::AppleAarch64 => Self::AppleAarch64,
141
137k
            CallingConvention::Default => Self::Winch,
142
0
            CallingConvention::WindowsFastcall => Self::WindowsFastcall,
143
        }
144
137k
    }
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.
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
6.76k
    fn is_branch_protection_enabled(&self) -> bool {
164
6.76k
        false
165
6.76k
    }
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
4.90k
    fn default_call_conv(&self) -> CallConv {
181
4.90k
        CallConv::triple_default(&self.triple())
182
4.90k
    }
183
184
    /// Derive Wasmtime's calling convention from the triple's default
185
    /// calling convention.
186
4.90k
    fn wasmtime_call_conv(&self) -> CallingConvention {
187
4.90k
        match self.default_call_conv() {
188
0
            CallConv::AppleAarch64 => CallingConvention::AppleAarch64,
189
4.90k
            CallConv::SystemV => CallingConvention::SystemV,
190
0
            CallConv::WindowsFastcall => CallingConvention::WindowsFastcall,
191
0
            cc => unimplemented!("calling convention: {:?}", cc),
192
        }
193
4.90k
    }
194
195
    /// Get the endianness of the underlying target triple.
196
0
    fn endianness(&self) -> target_lexicon::Endianness {
197
0
        self.triple().endianness().unwrap()
198
0
    }
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
0
    fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
208
        // By default, an ISA cannot create a System V CIE.
209
0
        None
210
0
    }
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
69.7k
    fn pointer_bytes(&self) -> u8 {
220
69.7k
        let width = self.triple().pointer_width().unwrap();
221
69.7k
        width.bytes()
222
69.7k
    }
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
231
impl Debug for &dyn TargetIsa {
232
0
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
233
0
        write!(
234
0
            f,
235
            "Target ISA {{ triple: {:?}, calling convention: {:?} }}",
236
0
            self.triple(),
237
0
            self.default_call_conv()
238
        )
239
0
    }
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 {
260
74.1k
    fn with_limits_per_class(int: u8, float: u8) -> Self {
261
74.1k
        let int = RegClassEnv {
262
74.1k
            limit: int,
263
74.1k
            index: 0,
264
74.1k
        };
265
266
74.1k
        let float = RegClassEnv {
267
74.1k
            limit: float,
268
74.1k
            index: 0,
269
74.1k
        };
270
271
74.1k
        Self {
272
74.1k
            int,
273
74.1k
            float: Some(float),
274
74.1k
        }
275
74.1k
    }
276
277
386k
    fn with_absolute_limit(limit: u8) -> Self {
278
386k
        let int = RegClassEnv { limit, index: 0 };
279
280
386k
        Self { int, float: None }
281
386k
    }
282
}
283
284
impl RegIndexEnv {
285
939k
    fn next_gpr(&mut self) -> Option<u8> {
286
939k
        (self.int.index < self.int.limit)
287
939k
            .then(|| Self::increment(&mut self.int.index))
288
939k
            .flatten()
289
939k
    }
290
291
349k
    fn next_fpr(&mut self) -> Option<u8> {
292
349k
        if let Some(f) = self.float.as_mut() {
293
177k
            (f.index < f.limit)
294
177k
                .then(|| Self::increment(&mut f.index))
295
177k
                .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
171k
            self.next_gpr()
300
        }
301
349k
    }
302
303
444k
    fn increment(index: &mut u8) -> Option<u8> {
304
444k
        let current = *index;
305
444k
        match index.checked_add(1) {
306
444k
            Some(next) => {
307
444k
                *index = next;
308
444k
                Some(current)
309
            }
310
0
            None => None,
311
        }
312
444k
    }
313
}
314
315
#[cfg(test)]
316
mod 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
}