summaryrefslogtreecommitdiffstats
path: root/library/stdarch/crates/stdarch-verify/tests/mips.rs
blob: 1eb86dc29c63334f8a2bc3ad940d0c56f7e65130 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
//! Verification of MIPS MSA intrinsics
#![allow(bad_style, unused)]

// This file is obtained from
// https://gcc.gnu.org/onlinedocs//gcc/MIPS-SIMD-Architecture-Built-in-Functions.html
static HEADER: &str = include_str!("../mips-msa.h");

stdarch_verify::mips_functions!(static FUNCTIONS);

struct Function {
    name: &'static str,
    arguments: &'static [&'static Type],
    ret: Option<&'static Type>,
    target_feature: Option<&'static str>,
    instrs: &'static [&'static str],
    file: &'static str,
    required_const: &'static [usize],
    has_test: bool,
}

static F16: Type = Type::PrimFloat(16);
static F32: Type = Type::PrimFloat(32);
static F64: Type = Type::PrimFloat(64);
static I8: Type = Type::PrimSigned(8);
static I16: Type = Type::PrimSigned(16);
static I32: Type = Type::PrimSigned(32);
static I64: Type = Type::PrimSigned(64);
static U8: Type = Type::PrimUnsigned(8);
static U16: Type = Type::PrimUnsigned(16);
static U32: Type = Type::PrimUnsigned(32);
static U64: Type = Type::PrimUnsigned(64);
static NEVER: Type = Type::Never;
static TUPLE: Type = Type::Tuple;
static v16i8: Type = Type::I(8, 16, 1);
static v8i16: Type = Type::I(16, 8, 1);
static v4i32: Type = Type::I(32, 4, 1);
static v2i64: Type = Type::I(64, 2, 1);
static v16u8: Type = Type::U(8, 16, 1);
static v8u16: Type = Type::U(16, 8, 1);
static v4u32: Type = Type::U(32, 4, 1);
static v2u64: Type = Type::U(64, 2, 1);
static v8f16: Type = Type::F(16, 8, 1);
static v4f32: Type = Type::F(32, 4, 1);
static v2f64: Type = Type::F(64, 2, 1);

#[derive(Debug, Copy, Clone, PartialEq)]
enum Type {
    PrimFloat(u8),
    PrimSigned(u8),
    PrimUnsigned(u8),
    PrimPoly(u8),
    MutPtr(&'static Type),
    ConstPtr(&'static Type),
    Tuple,
    I(u8, u8, u8),
    U(u8, u8, u8),
    P(u8, u8, u8),
    F(u8, u8, u8),
    Never,
}

#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(non_camel_case_types)]
enum MsaTy {
    v16i8,
    v8i16,
    v4i32,
    v2i64,
    v16u8,
    v8u16,
    v4u32,
    v2u64,
    v8f16,
    v4f32,
    v2f64,
    imm0_1,
    imm0_3,
    imm0_7,
    imm0_15,
    imm0_31,
    imm0_63,
    imm0_255,
    imm_n16_15,
    imm_n512_511,
    imm_n1024_1022,
    imm_n2048_2044,
    imm_n4096_4088,
    i32,
    u32,
    i64,
    u64,
    Void,
    MutVoidPtr,
}

impl<'a> From<&'a str> for MsaTy {
    fn from(s: &'a str) -> MsaTy {
        match s {
            "v16i8" => MsaTy::v16i8,
            "v8i16" => MsaTy::v8i16,
            "v4i32" => MsaTy::v4i32,
            "v2i64" => MsaTy::v2i64,
            "v16u8" => MsaTy::v16u8,
            "v8u16" => MsaTy::v8u16,
            "v4u32" => MsaTy::v4u32,
            "v2u64" => MsaTy::v2u64,
            "v8f16" => MsaTy::v8f16,
            "v4f32" => MsaTy::v4f32,
            "v2f64" => MsaTy::v2f64,
            "imm0_1" => MsaTy::imm0_1,
            "imm0_3" => MsaTy::imm0_3,
            "imm0_7" => MsaTy::imm0_7,
            "imm0_15" => MsaTy::imm0_15,
            "imm0_31" => MsaTy::imm0_31,
            "imm0_63" => MsaTy::imm0_63,
            "imm0_255" => MsaTy::imm0_255,
            "imm_n16_15" => MsaTy::imm_n16_15,
            "imm_n512_511" => MsaTy::imm_n512_511,
            "imm_n1024_1022" => MsaTy::imm_n1024_1022,
            "imm_n2048_2044" => MsaTy::imm_n2048_2044,
            "imm_n4096_4088" => MsaTy::imm_n4096_4088,
            "i32" => MsaTy::i32,
            "u32" => MsaTy::u32,
            "i64" => MsaTy::i64,
            "u64" => MsaTy::u64,
            "void" => MsaTy::Void,
            "void *" => MsaTy::MutVoidPtr,
            v => panic!("unknown ty: \"{}\"", v),
        }
    }
}

#[derive(Debug, Clone)]
struct MsaIntrinsic {
    id: String,
    arg_tys: Vec<MsaTy>,
    ret_ty: MsaTy,
    instruction: String,
}

struct NoneError;

impl std::convert::TryFrom<&'static str> for MsaIntrinsic {
    // The intrinsics are just C function declarations of the form:
    // $ret_ty __builtin_${fn_id}($($arg_ty),*);
    type Error = NoneError;
    fn try_from(line: &'static str) -> Result<Self, Self::Error> {
        return inner(line).ok_or(NoneError);

        fn inner(line: &'static str) -> Option<MsaIntrinsic> {
            let first_whitespace = line.find(char::is_whitespace)?;
            let ret_ty = &line[0..first_whitespace];
            let ret_ty = MsaTy::from(ret_ty);

            let first_parentheses = line.find('(')?;
            assert!(first_parentheses > first_whitespace);
            let id = &line[first_whitespace + 1..first_parentheses].trim();
            assert!(id.starts_with("__builtin"));
            let mut id_str = "_".to_string();
            id_str += &id[9..];
            let id = id_str;

            let mut arg_tys = Vec::new();

            let last_parentheses = line.find(')')?;
            for arg in (&line[first_parentheses + 1..last_parentheses]).split(',') {
                let arg = arg.trim();
                arg_tys.push(MsaTy::from(arg));
            }

            // The instruction is the intrinsic name without the __msa_ prefix.
            let instruction = &id[6..];
            let mut instruction = instruction.to_string();
            // With all underscores but the first one replaced with a `.`
            if let Some(first_underscore) = instruction.find('_') {
                let postfix = instruction[first_underscore + 1..].replace('_', ".");
                instruction = instruction[0..=first_underscore].to_string();
                instruction += &postfix;
            }

            Some(MsaIntrinsic {
                id,
                ret_ty,
                arg_tys,
                instruction,
            })
        }
    }
}

#[test]
fn verify_all_signatures() {
    // Parse the C intrinsic header file:
    let mut intrinsics = std::collections::HashMap::<String, MsaIntrinsic>::new();
    for line in HEADER.lines() {
        if line.is_empty() {
            continue;
        }

        use std::convert::TryFrom;
        let intrinsic: MsaIntrinsic = TryFrom::try_from(line)
            .unwrap_or_else(|_| panic!("failed to parse line: \"{}\"", line));
        assert!(!intrinsics.contains_key(&intrinsic.id));
        intrinsics.insert(intrinsic.id.clone(), intrinsic);
    }

    let mut all_valid = true;
    for rust in FUNCTIONS {
        if !rust.has_test {
            let skip = [
                "__msa_ceqi_d",
                "__msa_cfcmsa",
                "__msa_clei_s_d",
                "__msa_clti_s_d",
                "__msa_ctcmsa",
                "__msa_ldi_d",
                "__msa_maxi_s_d",
                "__msa_mini_s_d",
                "break_",
            ];
            if !skip.contains(&rust.name) {
                println!(
                    "missing run-time test named `test_{}` for `{}`",
                    {
                        let mut id = rust.name;
                        while id.starts_with('_') {
                            id = &id[1..];
                        }
                        id
                    },
                    rust.name
                );
                all_valid = false;
            }
        }

        // Skip some intrinsics that aren't part of MSA
        match rust.name {
            "break_" => continue,
            _ => {}
        }
        let mips = match intrinsics.get(rust.name) {
            Some(i) => i,
            None => {
                eprintln!(
                    "missing mips definition for {:?} in {}",
                    rust.name, rust.file
                );
                all_valid = false;
                continue;
            }
        };

        if let Err(e) = matches(rust, mips) {
            println!("failed to verify `{}`", rust.name);
            println!("  * {}", e);
            all_valid = false;
        }
    }
    assert!(all_valid);
}

fn matches(rust: &Function, mips: &MsaIntrinsic) -> Result<(), String> {
    macro_rules! bail {
        ($($t:tt)*) => (return Err(format!($($t)*)))
    }

    if rust.ret.is_none() && mips.ret_ty != MsaTy::Void {
        bail!("mismatched return value")
    }

    if rust.arguments.len() != mips.arg_tys.len() {
        bail!("mismatched argument lengths");
    }

    let mut nconst = 0;
    for (i, (rust_arg, mips_arg)) in rust.arguments.iter().zip(mips.arg_tys.iter()).enumerate() {
        match mips_arg {
            MsaTy::v16i8 if **rust_arg == v16i8 => (),
            MsaTy::v8i16 if **rust_arg == v8i16 => (),
            MsaTy::v4i32 if **rust_arg == v4i32 => (),
            MsaTy::v2i64 if **rust_arg == v2i64 => (),
            MsaTy::v16u8 if **rust_arg == v16u8 => (),
            MsaTy::v8u16 if **rust_arg == v8u16 => (),
            MsaTy::v4u32 if **rust_arg == v4u32 => (),
            MsaTy::v2u64 if **rust_arg == v2u64 => (),
            MsaTy::v4f32 if **rust_arg == v4f32 => (),
            MsaTy::v2f64 if **rust_arg == v2f64 => (),
            MsaTy::imm0_1
            | MsaTy::imm0_3
            | MsaTy::imm0_7
            | MsaTy::imm0_15
            | MsaTy::imm0_31
            | MsaTy::imm0_63
            | MsaTy::imm0_255
            | MsaTy::imm_n16_15
            | MsaTy::imm_n512_511
            | MsaTy::imm_n1024_1022
            | MsaTy::imm_n2048_2044
            | MsaTy::imm_n4096_4088
                if **rust_arg == I32 => {}
            MsaTy::i32 if **rust_arg == I32 => (),
            MsaTy::i64 if **rust_arg == I64 => (),
            MsaTy::u32 if **rust_arg == U32 => (),
            MsaTy::u64 if **rust_arg == U64 => (),
            MsaTy::MutVoidPtr if **rust_arg == Type::MutPtr(&U8) => (),
            m => bail!(
                "mismatched argument \"{}\"= \"{:?}\" != \"{:?}\"",
                i,
                m,
                *rust_arg
            ),
        }

        let is_const = matches!(
            mips_arg,
            MsaTy::imm0_1
                | MsaTy::imm0_3
                | MsaTy::imm0_7
                | MsaTy::imm0_15
                | MsaTy::imm0_31
                | MsaTy::imm0_63
                | MsaTy::imm0_255
                | MsaTy::imm_n16_15
                | MsaTy::imm_n512_511
                | MsaTy::imm_n1024_1022
                | MsaTy::imm_n2048_2044
                | MsaTy::imm_n4096_4088
        );
        if is_const {
            nconst += 1;
            if !rust.required_const.contains(&i) {
                bail!("argument const mismatch");
            }
        }
    }

    if nconst != rust.required_const.len() {
        bail!("wrong number of const arguments");
    }

    if rust.target_feature != Some("msa") {
        bail!("wrong target_feature");
    }

    if !rust.instrs.is_empty() {
        // Normalize slightly to get rid of assembler differences
        let actual = rust.instrs[0].replace(".", "_");
        let expected = mips.instruction.replace(".", "_");
        if actual != expected {
            bail!(
                "wrong instruction: \"{}\" != \"{}\"",
                rust.instrs[0],
                mips.instruction
            );
        }
    } else {
        bail!(
            "missing assert_instr for \"{}\" (should be \"{}\")",
            mips.id,
            mips.instruction
        );
    }

    Ok(())
}