tree: 755629e564da6432b38a245b003e72f873637df4 [path history] [tgz]
  1. spec/
  2. src/
  3. Cargo.toml
  4. README.md
library/stdarch/crates/stdarch-gen-arm/README.md

stdarch-gen-arm generator guide

Running the generator

  • Run: cargo run --bin=stdarch-gen-arm -- crates/stdarch-gen-arm/spec
$ cargo run --bin=stdarch-gen-arm -- crates/stdarch-gen-arm/spec
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/stdarch-gen-arm crates/stdarch-gen-arm/spec`

Input/Output

Input files (intrinsic YAML definitions)

  • crates/stdarch-gen-arm/spec/<feature>/*.spec.yml

Output files

  • Generated intrinsics:
    • crates/core_arch/src/<arch>/<feature>/generated.rs
  • Generated load/store tests:
    • crates/core_arch/src/<arch>/<feature>/ld_st_tests_<arch>.rs
    • Only generated when test: { load: <idx> } or test: { store: <idx> } is set for SVE/SVE2 intrinsics.

.spec.yml file anatomy

---
Configs
---
Variable definitions
---

Intrinsic definitions

---
  • If you're new to YAML syntax, consider reviewing some of the less obvious syntax and features.
  • For example, mapping an attribute to a sequence can be done in two different ways:
attribute: [item_a, item_b, item_c]

or

attribute:
    - item_a
    - item_b
    - item_c

Configs

  • Mappings defining top-level settings applied to all intrinsics:
  • arch_cfgs
    • Sequence of mappings specifying arch_name, target_feature (sequence), and llvm_prefix.
  • uses_neon_types(Optional)
    • A boolean specifying whether to emit NEON type imports in generated code.
  • auto_big_endian(Optional)
    • A boolean specifying whether to auto-generate big-endian shuffles when possible.
  • auto_llvm_sign_conversion(Optional)
    • A boolean specifying whether to auto-convert LLVM wrapper args to signed types.

Variable definitions

  • Defines YAML anchors/variables to avoid repetition.
  • Commonly used for stability attributes, cfgs and target features.

Intrinsic definitions

Example

    - name: "vtst{neon_type[0].no}"
      doc: "Signed compare bitwise Test bits nonzero"
      arguments: ["a: {neon_type[0]}", "b: {neon_type[0]}"]
      return_type: "{neon_type[1]}"
      attr:
        - FnCall: [cfg_attr, [test, {FnCall: [assert_instr, [cmtst]]}]]
        - FnCall: [stable, ['feature = "neon_intrinsics"', 'since = "1.59.0"']]
      safety: safe
      types:
        - [int64x1_t, uint64x1_t, 'i64x1', 'i64x1::new(0)']
        - [int64x2_t, uint64x2_t, 'i64x2', 'i64x2::new(0, 0)']
        - [poly64x1_t, uint64x1_t, 'i64x1', 'i64x1::new(0)']
        - [poly64x2_t, uint64x2_t, 'i64x2', 'i64x2::new(0, 0)']
      compose:
        - Let: [c, "{neon_type[0]}", {FnCall: [simd_and, [a, b]]}]
        - Let: [d, "{type[2]}", "{type[3]}"]
        - FnCall: [simd_ne, [c, {FnCall: [transmute, [d]]}]]

Explanation of fields

  • name
    • The name of the intrinsic
    • Often built from a base name followed by a type suffix
  • doc (Optional)
    • A string explaining the purpose of the intrinsic
  • static_defs (Optional)
    • A sequence of const generics of the format "const <NAME>: <type>"
  • arguments
    • A sequence of strings in the format "<argname>: <argtype>"
  • return_type (Optional)
    • A string specifying the return type. If omitted, the intrinsic returns ().
  • attr (Optional)
    • A sequence of items defining the attributes to be applied to the intrinsic. Often stability attributes, target features, or assert_instr tests. At least one of attr or assert_instr must be set.
  • target_features (Optional)
    • A sequence of target features to enable for this intrinsic (merged with any global arch_cfgs settings).
  • assert_instr (Optional)
    • A sequence of strings expected to be found in the assembly. Required if attr is not set.
  • safety (Optional)
    • Use safe, or map unsafe: to a sequence of unsafety comments:
      • custom: "<string>"
      • uninitialized
      • pointer_offset, pointer_offset_vnum, or dereference (optionally qualified with predicated, predicated_non_faulting, or predicated_first_faulting)
      • unpredictable_on_fault
      • non_temporal
      • neon
      • no_provenance: "<string>"
  • substitutions (Optional)
    • Mappings of custom wildcard names to either MatchSize or MatchKind expressions
  • types
    • A sequence or sequence of sequences specifying the types to use when producing each intrinsic variant. These sequences can then be indexed by wildcards.
  • constraints (Optional)
    • A sequence of mappings. Each specifies a variable and a constraint. The available mappings are:
    • Assert a variable‘s value exists in a sequence of i32’s
      • Usage: { variable: <name>, any_values: [<i32>,...] }
    • Assert a variable's value exists in a range (inclusive)
      • Usage: { variable: <name>, range: [<i32>, <i32>] }
    • Assert a variable's value exists in a range via a match (inclusive)
      • Usage: { variable: <name>, range: <MatchSize returning [i32,i32]> }
    • Assert a variable's value does not exceed the number of elements in a SVE type <type>.
      • Usage: { variable: <name>, sve_max_elems_type: <type> }
    • Assert a variable's value does not exceed the number of elements in a vector type <type>.
      • Usage: { variable: <name>, vec_max_elems_type: <type> }
  • predication_methods (Optional)
    • Configuration for predicate-form variants. Only used when the intrinsic name includes an _m*_ wildcard (e.g., {_mx}, {_mxz}).
    • zeroing_method: Required when requesting _z; either { drop: <arg> } to remove an argument and replace it with a zero initialiser, or { select: <predicate_var> } to select zeros into a predicate.
    • dont_care_method: How _x should be implemented (inferred, as_zeroing, or as_merging).
  • compose
    • A sequence of expressions that make up the body of the intrinsic
  • big_endian_inverse (Optional)
    • A boolean, default false. If true, generates two implementations of each intrinsic variant, one for each endianness, and attempts to automatically generate the required bit swizzles
  • visibility (Optional)
    • Function visibility. One of public (default) or private.
  • n_variant_op (Optional)
    • Enables generation of an _n variant when the intrinsic name includes the {_n} wildcard. Set to the operand name that should be splattered for the _n form.
  • test (Optional)
    • When set, load/store tests are automatically generated.
    • A mapping of either load or store to a number that indexes types to specify the type that the test should be addressing in memory.

Expressions

Common

  • Let
    • Defines a variable
    • Usage: Let: [<variable>, <type(optional)>, <expression>]
  • Const
    • Defines a const
    • Usage: Const: [<variable>, <type>, <expression>]
  • Assign
    • Performs variable assignment
    • Usage: Assign: [<variable>, <expression>]
  • FnCall
    • Performs a function call
    • Usage: FnCall: [<function pointer: expression>, [<argument: expression>, ... ], [<turbofish argument: expression>, ...](optional), <unsafe wrapper(optional): bool>]
  • MacroCall
    • Performs a macro call
    • Usage: MacroCall: [<macro name>, <token stream>]
  • MethodCall
    • Performs a method call
    • Usage: MethodCall: [<object: expression>, <method name>, [<argument: expression>, ... ]]
  • LLVMLink
    • Creates an LLVM link and stores the function's name in the wildcard {llvm_link} for later use in subsequent expressions.
    • If left unset, the arguments and return type inherit from the intrinsic's signature by default. The links will also be set automatically if unset.
    • Usage:
LLVMLink:
    name: <name>
    arguments: [<expression>, ... ](optional)
    return_type: <return type>(optional)
    links: (optional)
        - link: <link>
          arch: <arch>
        - ...
  • Identifier
    • Emits a symbol. Prepend with a $ to treat it as a scope variable, which engages variable tracking and enables inference. For example, my_function_name for a generic symbol or $my_variable for a variable.
    • Usage Identifier: [<symbol name>, <Variable|Symbol>]
  • CastAs
    • Casts an expression to an unchecked type
    • Usage: CastAs: [<expression>, <type>]
  • MatchSize
    • Allows for conditional generation depending on the size of a specified type
    • Usage:
MatchSize:
    - <type>
    - default: <expression>
      byte(optional): <expression>
      halfword(optional): <expression>
      doubleword(optional): <expression>
  • MatchKind
    • Allows for conditional generation depending on the kind of a specified type
MatchKind:
    - <type>
    - default: <expression>
      float(optional): <expression>
      unsigned(optional): <expression>

Rarely Used

  • IntConstant
    • Constant signed integer expression
    • Usage: IntConstant: <i32>
  • FloatConstant
    • Constant floating-point expression
    • Usage: FloatConstant: <f32>
  • BoolConstant
    • Constant boolean expression
    • Usage: BoolConstant: <bool>
  • Array
    • An array of expressions
    • Usage: Array: [<expression>, ...]
  • SvUndef
    • Returns the LLVM undef symbol
    • Usage: SvUndef
  • Multiply
    • Simply *
    • Usage: Multiply: [<expression>, <expression>]
  • Xor
    • Simply ^
    • Usage: Xor: [<expression>, <expression>]
  • ConvertConst
    • Converts the specified constant to the specified type's kind
    • Usage: ConvertConst: [<type>, <i32>]
  • Type
    • Yields the given type in the Rust representation
    • Usage: Type: [<type>]

Wildstrings

  • Wildstrings let you take advantage of wildcards.
  • For example, they are often used in intrinsic names name: "vtst{neon_type[0].no}"
  • As shown above, wildcards are identified by the surrounding curly brackets.
  • Double curly brackets can be used to escape wildcard functionality if you need literal curly brackets in the generated intrinsic.

Wildcards

Wildcards are heavily used in the spec. They let you write generalised definitions for a group of intrinsics that generate multiple variants. The wildcard itself is replaced with the relevant string in each variant. Ignoring endianness, for each row in the types field of an intrinsic in the spec, a variant of the intrinsic will be generated. That row's contents can be indexed by the wildcards. Below is the behaviour of each wildcard.

  • type[<index: usize>]
    • Replaced in each variant with the value in the indexed position in the relevant row of the types field.
    • For unnested sequences of types (i.e., types is a sequence where each element is a single item, not another sequence), the square brackets can be omitted. Simply: type
  • neon_type[<index: usize>]
    • Extends the behaviour of type with some NEON-specific features and inference.
    • Tuples: This wildcard can also be written as neon_type_x<n> where n is in the set {2,3,4}. This generates the n-tuple variant of the (inferred) NEON type.
    • Suffixes: These modify the behaviour of the wildcard from simple substitution.
      • no - normal behaviour. Tries to do as much work as it can for you, inferring when to emit:
        • Regular type-size suffixes: _s8, _u16, _f32, ...
        • q variants for double-width (128b) vector types: q_s8, q_u16, q_f32, ...
        • _x<n> variants for tuple vector types: _s8_x2, _u32_x3, _f64_x4, ...
        • As well as any combination of the above: q_s16_x16 ...
    • Most of the other suffixes modify the normal behaviour by disabling features or adding new ones. (See table below)
  • sve_type[<index: usize>]
    • Similar to neon_type, but without the suffixes.
  • size[<index: usize>]
    • The size (in bits) of the indexed type.
  • size_minus_one[<index: usize>]
    • Emits the size (in bits) of the indexed type minus one.
  • size_literal[<index: usize>]
    • The literal representation of the indexed type.
    • b: byte, h: halfword, w: word, or d: double.
  • type_kind[<index: usize>]
    • The literal representation of the indexed type's kind.
    • f: float, s: signed, u: unsigned, p: polynomial, b: boolean.
  • size_in_bytes_log2[<index: usize>]
    • Log2 of the size of the indexed type in bytes.
  • predicate[<index: usize>]
    • SVE predicate vector type inferred from the indexed type.
  • max_predicate
    • The same as predicate, but uses the largest type in the relevant types sequence/row.
  • _n
    • Emits the current N-variant suffix when n_variant_op is configured.
  • <wildcard> as <type>
    • If <wildcard> evaluates to a vector, it produces a vector of the same shape, but with <type> as the base type.
  • llvm_link
    • If the LLVMLink mapping has been set for an intrinsic, this will give the name of the link.
  • _m*
    • Predicate form masks. Use wildcards such as {_mx} or {_mxz} to expand merging/don't-care/zeroing variants according to the mask.
  • <custom>
    • You may simply call upon wildcards defined under substitutions.

neon_type suffixes

suffiximplication
.noNormal
.noqNever include qs
.noxNever include _x<n>s
.NInclude _n_, e.g., _n_s8
.noq_NInclude _n_, but never qs
.dupInclude _dup_, e.g., _dup_s8
.dup_noxInclude _dup_ but never _x<n>s
.laneInclude _lane_, e.g., _lane_s8
.lane_noxInclude _lane_, but never _x<n>s
.rot90Include _rot90_, e.g., _rot90_s8
.rot180Include _rot180_, e.g., _rot180_s8
.rot270Include _rot270_, e.g., _rot270_s8
.rot90_laneInclude _rot90_lane_
.rot180_laneInclude _rot180_lane_
.rot270_laneInclude _rot270_lane_
.rot90_laneqInclude _rot90_laneq_
.rot180_laneqInclude _rot180_laneq_
.rot270_laneqInclude _rot270_laneq_
.baseProduce only the size, e.g., 8, 16
.uProduce the type's unsigned equivalent
.laneq_noxInclude _laneq_, but never _x<n>s
.tupleProduce only the size of the tuple, e.g., 3
.base_byte_sizeProduce only the size in bytes.