Skip to content

Commit 88b3761

Browse files
davidtwcoadamgemmellJamieCunliffejacobbramleySevenarth
committed
stdarch-verify: support sve
Co-authored-by: Adam Gemmell <Adam.Gemmell@arm.com> Co-authored-by: Jamie Cunliffe <Jamie.Cunliffe@arm.com> Co-authored-by: Jacob Bramley <jacob.bramley@arm.com> Co-authored-by: Luca Vizzarro <Luca.Vizzarro@arm.com>
1 parent caef1bf commit 88b3761

2 files changed

Lines changed: 209 additions & 37 deletions

File tree

crates/stdarch-verify/src/lib.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ fn functions(input: TokenStream, dirs: &[&str]) -> TokenStream {
120120
);
121121
}
122122

123+
// Newer intrinsics don't have `rustc_legacy_const_generics` - assume they belong at
124+
// the end of the argument list
125+
if required_const.is_empty() && legacy_const_generics.is_empty() {
126+
legacy_const_generics =
127+
(arguments.len()..(arguments.len() + const_arguments.len())).collect();
128+
}
129+
123130
// The list of required consts, used to verify the arguments, comes from either the
124131
// `rustc_args_required_const` or the `rustc_legacy_const_generics` attribute.
125132
let required_const = if required_const.is_empty() {
@@ -136,14 +143,14 @@ fn functions(input: TokenStream, dirs: &[&str]) -> TokenStream {
136143
arguments.insert(idx, ty);
137144
}
138145

139-
// strip leading underscore from fn name when building a test
140-
// _mm_foo -> mm_foo such that the test name is test_mm_foo.
141-
let test_name_string = format!("{name}");
142-
let mut test_name_id = test_name_string.as_str();
143-
while test_name_id.starts_with('_') {
144-
test_name_id = &test_name_id[1..];
145-
}
146-
let has_test = tests.contains(&format!("test_{test_name_id}"));
146+
// Strip leading underscore from fn name when building a test
147+
// `_mm_foo` -> `mm_foo` such that the test name is `test_mm_foo`.
148+
let test_name = name.to_string();
149+
let test_name = test_name.trim_start_matches('_');
150+
let has_test = tests.contains(&format!("test_{test_name}"))
151+
// SVE load/store tests start with `test` or `_with_`
152+
|| tests.iter().any(|t| t.starts_with(&format!("test_{test_name}"))
153+
|| t.ends_with(&format!("_with_{test_name}")));
147154

148155
let doc = find_doc(&f.attrs);
149156

@@ -347,6 +354,50 @@ fn to_type(t: &syn::Type) -> proc_macro2::TokenStream {
347354
"v4f32" => quote! { &v4f32 },
348355
"v2f64" => quote! { &v2f64 },
349356

357+
"svbool_t" => quote! { &SVBOOL },
358+
"svint8_t" => quote! { &SVI8 },
359+
"svint8x2_t" => quote! { &SVI8X2 },
360+
"svint8x3_t" => quote! { &SVI8X3 },
361+
"svint8x4_t" => quote! { &SVI8X4 },
362+
"svint16_t" => quote! { &SVI16 },
363+
"svint16x2_t" => quote! { &SVI16X2 },
364+
"svint16x3_t" => quote! { &SVI16X3 },
365+
"svint16x4_t" => quote! { &SVI16X4 },
366+
"svint32_t" => quote! { &SVI32 },
367+
"svint32x2_t" => quote! { &SVI32X2 },
368+
"svint32x3_t" => quote! { &SVI32X3 },
369+
"svint32x4_t" => quote! { &SVI32X4 },
370+
"svint64_t" => quote! { &SVI64 },
371+
"svint64x2_t" => quote! { &SVI64X2 },
372+
"svint64x3_t" => quote! { &SVI64X3 },
373+
"svint64x4_t" => quote! { &SVI64X4 },
374+
"svuint8_t" => quote! { &SVU8 },
375+
"svuint8x2_t" => quote! { &SVU8X2 },
376+
"svuint8x3_t" => quote! { &SVU8X3 },
377+
"svuint8x4_t" => quote! { &SVU8X4 },
378+
"svuint16_t" => quote! { &SVU16 },
379+
"svuint16x2_t" => quote! { &SVU16X2 },
380+
"svuint16x3_t" => quote! { &SVU16X3 },
381+
"svuint16x4_t" => quote! { &SVU16X4 },
382+
"svuint32_t" => quote! { &SVU32 },
383+
"svuint32x2_t" => quote! { &SVU32X2 },
384+
"svuint32x3_t" => quote! { &SVU32X3 },
385+
"svuint32x4_t" => quote! { &SVU32X4 },
386+
"svuint64_t" => quote! { &SVU64 },
387+
"svuint64x2_t" => quote! { &SVU64X2 },
388+
"svuint64x3_t" => quote! { &SVU64X3 },
389+
"svuint64x4_t" => quote! { &SVU64X4 },
390+
"svfloat32_t" => quote! { &SVF32 },
391+
"svfloat32x2_t" => quote! { &SVF32X2 },
392+
"svfloat32x3_t" => quote! { &SVF32X3 },
393+
"svfloat32x4_t" => quote! { &SVF32X4 },
394+
"svfloat64_t" => quote! { &SVF64 },
395+
"svfloat64x2_t" => quote! { &SVF64X2 },
396+
"svfloat64x3_t" => quote! { &SVF64X3 },
397+
"svfloat64x4_t" => quote! { &SVF64X4 },
398+
"svprfop" => quote! { &SVPRFOP },
399+
"svpattern" => quote! { &SVPATTERN },
400+
350401
// Generic types
351402
"T" => quote! { &GENERICT },
352403
"U" => quote! { &GENERICU },

crates/stdarch-verify/tests/arm.rs

Lines changed: 150 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct Function {
1616
doc: &'static str,
1717
}
1818

19+
static BOOL: Type = Type::PrimBool;
1920
static F16: Type = Type::PrimFloat(16);
2021
static F32: Type = Type::PrimFloat(32);
2122
static F64: Type = Type::PrimFloat(64);
@@ -28,6 +29,7 @@ static U32: Type = Type::PrimUnsigned(32);
2829
static U64: Type = Type::PrimUnsigned(64);
2930
static U8: Type = Type::PrimUnsigned(8);
3031
static NEVER: Type = Type::Never;
32+
static VOID: Type = Type::Void;
3133
static GENERICT: Type = Type::GenericParam("T");
3234
static GENERICU: Type = Type::GenericParam("U");
3335

@@ -151,19 +153,70 @@ static U8X8X2: Type = Type::U(8, 8, 2);
151153
static U8X8X3: Type = Type::U(8, 8, 3);
152154
static U8X8X4: Type = Type::U(8, 8, 4);
153155

156+
static SVBOOL: Type = Type::Pred;
157+
static SVF32: Type = Type::SVF(32, 1);
158+
static SVF32X2: Type = Type::SVF(32, 2);
159+
static SVF32X3: Type = Type::SVF(32, 3);
160+
static SVF32X4: Type = Type::SVF(32, 4);
161+
static SVF64: Type = Type::SVF(64, 1);
162+
static SVF64X2: Type = Type::SVF(64, 2);
163+
static SVF64X3: Type = Type::SVF(64, 3);
164+
static SVF64X4: Type = Type::SVF(64, 4);
165+
static SVI8: Type = Type::SVI(8, 1);
166+
static SVI8X2: Type = Type::SVI(8, 2);
167+
static SVI8X3: Type = Type::SVI(8, 3);
168+
static SVI8X4: Type = Type::SVI(8, 4);
169+
static SVI16: Type = Type::SVI(16, 1);
170+
static SVI16X2: Type = Type::SVI(16, 2);
171+
static SVI16X3: Type = Type::SVI(16, 3);
172+
static SVI16X4: Type = Type::SVI(16, 4);
173+
static SVI32: Type = Type::SVI(32, 1);
174+
static SVI32X2: Type = Type::SVI(32, 2);
175+
static SVI32X3: Type = Type::SVI(32, 3);
176+
static SVI32X4: Type = Type::SVI(32, 4);
177+
static SVI64: Type = Type::SVI(64, 1);
178+
static SVI64X2: Type = Type::SVI(64, 2);
179+
static SVI64X3: Type = Type::SVI(64, 3);
180+
static SVI64X4: Type = Type::SVI(64, 4);
181+
static SVU8: Type = Type::SVU(8, 1);
182+
static SVU8X2: Type = Type::SVU(8, 2);
183+
static SVU8X3: Type = Type::SVU(8, 3);
184+
static SVU8X4: Type = Type::SVU(8, 4);
185+
static SVU16: Type = Type::SVU(16, 1);
186+
static SVU16X2: Type = Type::SVU(16, 2);
187+
static SVU16X3: Type = Type::SVU(16, 3);
188+
static SVU16X4: Type = Type::SVU(16, 4);
189+
static SVU32: Type = Type::SVU(32, 1);
190+
static SVU32X2: Type = Type::SVU(32, 2);
191+
static SVU32X3: Type = Type::SVU(32, 3);
192+
static SVU32X4: Type = Type::SVU(32, 4);
193+
static SVU64: Type = Type::SVU(64, 1);
194+
static SVU64X2: Type = Type::SVU(64, 2);
195+
static SVU64X3: Type = Type::SVU(64, 3);
196+
static SVU64X4: Type = Type::SVU(64, 4);
197+
static SVPRFOP: Type = Type::Enum("svprfop");
198+
static SVPATTERN: Type = Type::Enum("svpattern");
199+
154200
#[derive(Debug, Copy, Clone, PartialEq)]
155201
enum Type {
202+
Void,
203+
PrimBool,
156204
PrimFloat(u8),
157205
PrimSigned(u8),
158206
PrimUnsigned(u8),
159207
PrimPoly(u8),
160208
MutPtr(&'static Type),
161209
ConstPtr(&'static Type),
210+
Enum(&'static str),
162211
GenericParam(&'static str),
163212
I(u8, u8, u8),
164213
U(u8, u8, u8),
165214
P(u8, u8, u8),
166215
F(u8, u8, u8),
216+
Pred,
217+
SVI(u8, u8),
218+
SVU(u8, u8),
219+
SVF(u8, u8),
167220
Never,
168221
}
169222

@@ -182,19 +235,18 @@ fn verify_all_signatures() {
182235

183236
let mut all_valid = true;
184237
for rust in FUNCTIONS {
238+
// Most SVE intrinsics just rely on the intrinsics test tool for validation
185239
if !rust.has_test {
186-
if !SKIP_RUNTIME_TESTS.contains(&rust.name) {
187-
println!(
188-
"missing run-time test named `test_{}` for `{}`",
189-
{
190-
let mut id = rust.name;
191-
while id.starts_with('_') {
192-
id = &id[1..];
193-
}
194-
id
195-
},
196-
rust.name
197-
);
240+
if !SKIP_RUNTIME_TESTS.contains(&rust.name)
241+
// Most run-time tests are handled by the intrinsic-test tool, except for
242+
// load/stores (which have generated tests)
243+
&& (!rust.name.starts_with("sv") || rust.name.starts_with("svld")
244+
|| rust.name.starts_with("svst"))
245+
// The load/store test generator can't handle these cases yet
246+
&& (!rust.name.contains("_u32base_") || rust.name.contains("index") || rust.name.contains("offset"))
247+
&& !(rust.name.starts_with("svldff1") && rust.name.contains("gather"))
248+
{
249+
println!("missing run-time test for `{}`", rust.name);
198250
all_valid = false;
199251
}
200252
}
@@ -269,12 +321,21 @@ fn matches(rust: &Function, arm: &Intrinsic) -> Result<(), String> {
269321
let mut nconst = 0;
270322
let iter = rust.arguments.iter().zip(&arm.arguments).enumerate();
271323
for (i, (rust_ty, (arm, arm_const))) in iter {
272-
if *rust_ty != arm {
273-
bail!("mismatched arguments: {rust_ty:?} != {arm:?}")
324+
match (*rust_ty, arm) {
325+
// SVE uses generic type parameters to handle void pointers
326+
(Type::ConstPtr(Type::GenericParam("T")), Type::ConstPtr(Type::Void)) => (),
327+
// SVE const generics use i32 over u64 for usability reasons
328+
(Type::PrimSigned(32), Type::PrimUnsigned(64)) if rust.required_const.contains(&i) => {
329+
()
330+
}
331+
// svset doesn't have its const argument last as we assumed when building the Function
332+
_ if rust.name.starts_with("svset") => (),
333+
(x, y) if x == y => (),
334+
_ => bail!("mismatched arguments: {rust_ty:?} != {arm:?}"),
274335
}
275336
if *arm_const {
276337
nconst += 1;
277-
if !rust.required_const.contains(&i) {
338+
if !rust.required_const.contains(&i) && !rust.name.starts_with("svset") {
278339
bail!("argument const mismatch");
279340
}
280341
}
@@ -283,7 +344,7 @@ fn matches(rust: &Function, arm: &Intrinsic) -> Result<(), String> {
283344
bail!("wrong number of const arguments");
284345
}
285346

286-
if rust.instrs.is_empty() {
347+
if rust.instrs.is_empty() && arm.instruction != "" {
287348
bail!(
288349
"instruction not listed for `{}`, but arm lists {:?}",
289350
rust.name,
@@ -322,7 +383,7 @@ fn matches(rust: &Function, arm: &Intrinsic) -> Result<(), String> {
322383
Ok(())
323384
}
324385

325-
#[derive(PartialEq)]
386+
#[derive(Debug, PartialEq)]
326387
struct Intrinsic {
327388
name: String,
328389
ret: Option<Type>,
@@ -337,7 +398,7 @@ struct JsonIntrinsic {
337398
arguments: Vec<String>,
338399
return_type: ReturnType,
339400
#[serde(default)]
340-
instructions: Vec<Vec<String>>,
401+
instructions: Option<Vec<Vec<String>>>,
341402
}
342403

343404
#[derive(Deserialize, Debug)]
@@ -356,6 +417,8 @@ fn parse_intrinsics(intrinsics: Vec<JsonIntrinsic>) -> HashMap<String, Intrinsic
356417

357418
fn parse_intrinsic(mut intr: JsonIntrinsic) -> Intrinsic {
358419
let name = intr.name;
420+
// Remove '[' and ']' so that intrinsics of the form `svwhilerw[_s16]` becomes `svwhilerw_s16`.
421+
let name = name.replace('[', "").replace(']', "");
359422
let ret = if intr.return_type.value == "void" {
360423
None
361424
} else {
@@ -364,18 +427,24 @@ fn parse_intrinsic(mut intr: JsonIntrinsic) -> Intrinsic {
364427

365428
// This ignores multiple instructions and different optional sequences for now to mimic
366429
// the old HTML scraping behaviour
367-
let instruction = intr.instructions.swap_remove(0).swap_remove(0);
430+
let instruction = intr
431+
.instructions
432+
.map_or(String::new(), |mut i| i.swap_remove(0).swap_remove(0));
368433

369434
let arguments = intr
370435
.arguments
371436
.iter()
372437
.map(|s| {
373-
let (ty, konst) = match s.strip_prefix("const") {
374-
Some(stripped) => (stripped.trim_start(), true),
375-
None => (s.as_str(), false),
438+
let ty = if let Some(i) = s.find('*') {
439+
&s[..i + 1]
440+
} else {
441+
s.rsplit_once(' ').unwrap().0.trim_start_matches("const ")
376442
};
377-
let ty = ty.rsplit_once(' ').unwrap().0;
378-
(parse_ty(ty), konst)
443+
let ty = parse_ty(ty);
444+
let konst = s.contains("const") && !matches!(ty, Type::ConstPtr(_))
445+
|| s.starts_with("enum")
446+
|| s.rsplit_once(" ").unwrap().1.starts_with("imm");
447+
(ty, konst)
379448
})
380449
.collect::<Vec<_>>();
381450

@@ -388,18 +457,27 @@ fn parse_intrinsic(mut intr: JsonIntrinsic) -> Intrinsic {
388457
}
389458

390459
fn parse_ty(s: &str) -> Type {
391-
let suffix = " const *";
392-
if let Some(base) = s.strip_suffix(suffix) {
393-
Type::ConstPtr(parse_ty_base(base))
394-
} else if let Some(base) = s.strip_suffix(" *") {
395-
Type::MutPtr(parse_ty_base(base))
460+
if let Some(ty) = s.strip_suffix("*") {
461+
let ty = ty.trim();
462+
if let Some(ty) = ty.strip_prefix("const") {
463+
// SVE intrinsics are west-const (`const int8_t *`)
464+
Type::ConstPtr(parse_ty_base(ty))
465+
} else if let Some(ty) = ty.strip_suffix("const") {
466+
// Neon intrinsics are east-const (`int8_t const *`)
467+
Type::ConstPtr(parse_ty_base(ty))
468+
} else {
469+
Type::MutPtr(parse_ty_base(ty))
470+
}
396471
} else {
397472
*parse_ty_base(s)
398473
}
399474
}
400475

401476
fn parse_ty_base(s: &str) -> &'static Type {
477+
let s = s.trim();
402478
match s {
479+
"bool" => &BOOL,
480+
"void" => &VOID,
403481
"float16_t" => &F16,
404482
"float16x4_t" => &F16X4,
405483
"float16x4x2_t" => &F16X4X2,
@@ -529,6 +607,49 @@ fn parse_ty_base(s: &str) -> &'static Type {
529607
"uint8x8x2_t" => &U8X8X2,
530608
"uint8x8x3_t" => &U8X8X3,
531609
"uint8x8x4_t" => &U8X8X4,
610+
"svbool_t" => &SVBOOL,
611+
"svfloat32_t" => &SVF32,
612+
"svfloat32x2_t" => &SVF32X2,
613+
"svfloat32x3_t" => &SVF32X3,
614+
"svfloat32x4_t" => &SVF32X4,
615+
"svfloat64_t" => &SVF64,
616+
"svfloat64x2_t" => &SVF64X2,
617+
"svfloat64x3_t" => &SVF64X3,
618+
"svfloat64x4_t" => &SVF64X4,
619+
"svint8_t" => &SVI8,
620+
"svint8x2_t" => &SVI8X2,
621+
"svint8x3_t" => &SVI8X3,
622+
"svint8x4_t" => &SVI8X4,
623+
"svint16_t" => &SVI16,
624+
"svint16x2_t" => &SVI16X2,
625+
"svint16x3_t" => &SVI16X3,
626+
"svint16x4_t" => &SVI16X4,
627+
"svint32_t" => &SVI32,
628+
"svint32x2_t" => &SVI32X2,
629+
"svint32x3_t" => &SVI32X3,
630+
"svint32x4_t" => &SVI32X4,
631+
"svint64_t" => &SVI64,
632+
"svint64x2_t" => &SVI64X2,
633+
"svint64x3_t" => &SVI64X3,
634+
"svint64x4_t" => &SVI64X4,
635+
"svuint8_t" => &SVU8,
636+
"svuint8x2_t" => &SVU8X2,
637+
"svuint8x3_t" => &SVU8X3,
638+
"svuint8x4_t" => &SVU8X4,
639+
"svuint16_t" => &SVU16,
640+
"svuint16x2_t" => &SVU16X2,
641+
"svuint16x3_t" => &SVU16X3,
642+
"svuint16x4_t" => &SVU16X4,
643+
"svuint32_t" => &SVU32,
644+
"svuint32x2_t" => &SVU32X2,
645+
"svuint32x3_t" => &SVU32X3,
646+
"svuint32x4_t" => &SVU32X4,
647+
"svuint64_t" => &SVU64,
648+
"svuint64x2_t" => &SVU64X2,
649+
"svuint64x3_t" => &SVU64X3,
650+
"svuint64x4_t" => &SVU64X4,
651+
"enum svprfop" => &SVPRFOP,
652+
"enum svpattern" => &SVPATTERN,
532653

533654
_ => panic!("failed to parse json type {s:?}"),
534655
}

0 commit comments

Comments
 (0)