// The vector memory accesses: `mem.loadv128(p)`, `mem.load8_lane(p, v, 0)`.
//
// These are methods on a MEMORY object, not on a stack value, which is why they
// are a separate registry from the arithmetic intrinsics: the receiver is not
// an operand, and there are trailing `align`/`offset` literals the others do
// not have.
///|
/// The lane width a `load8_lane`-style name carries.
pub(all) enum LaneWidth {
L8
L16
L32
L64
} derive(Eq, Hash, Debug)
///|
pub fn LaneWidth::to_str(self : LaneWidth) -> String {
match self {
L8 => "8"
L16 => "16"
L32 => "32"
L64 => "64"
}
}
///|
/// The alignment such an access naturally has, in bytes.
pub fn LaneWidth::nat_align(self : LaneWidth) -> Int {
match self {
L8 => 1
L16 => 2
L32 => 4
L64 => 8
}
}
///|
pub let lane_widths : Array[LaneWidth] = [L8, L16, L32, L64]
///|
/// The whole-vector loads, each of which reads a differently shaped chunk and
/// widens or zero-fills it.
pub(all) enum VecLoad {
Load128
Load8x8S
Load8x8U
Load16x4S
Load16x4U
Load32x2S
Load32x2U
Load32Zero
Load64Zero
} derive(Eq, Hash, Debug)
///|
pub fn VecLoad::to_str(self : VecLoad) -> String {
match self {
Load128 => "loadv128"
Load8x8S => "load8x8_s"
Load8x8U => "load8x8_u"
Load16x4S => "load16x4_s"
Load16x4U => "load16x4_u"
Load32x2S => "load32x2_s"
Load32x2U => "load32x2_u"
Load32Zero => "load32_zero"
Load64Zero => "load64_zero"
}
}
///|
/// How many bytes the load actually reads -- which is not always 16, and is
/// what its natural alignment follows from.
pub fn VecLoad::nat_align(self : VecLoad) -> Int {
match self {
Load128 => 16
Load8x8S
| Load8x8U
| Load16x4S
| Load16x4U
| Load32x2S
| Load32x2U
| Load64Zero => 8
Load32Zero => 4
}
}
///|
pub let vec_loads : Array[VecLoad] = [
Load128,
Load8x8S,
Load8x8U,
Load16x4S,
Load16x4U,
Load32x2S,
Load32x2U,
Load32Zero,
Load64Zero,
]
///|
pub let store_name : String = "storev128"
///|
pub fn load_splat_name(w : LaneWidth) -> String {
"load" + w.to_str() + "_splat"
}
///|
pub fn load_lane_name(w : LaneWidth) -> String {
"load" + w.to_str() + "_lane"
}
///|
pub fn store_lane_name(w : LaneWidth) -> String {
"store" + w.to_str() + "_lane"
}
///|
/// What a vector memory method takes, gives back, and builds.
pub struct MemIntrinsic {
/// The stack operands: the address first, then the vector for a store or a
/// lane operation. The memory receiver is not among them.
operands : Array[Ty]
result : Ty?
/// Whether a constant lane immediate follows the operands.
lane : Bool
/// The alignment the access naturally has, as a byte count.
nat_align : Int
/// Memory index, alignment exponent, offset, lane -> the instruction. The
/// lane is ignored when `lane` is false.
build : (Int, Int, Int64, Int) -> @wasm_bin.Instruction
}
///|
/// The vector memory method of this name, if there is one.
pub fn mem_method(name : String) -> MemIntrinsic? {
fn load(op : VecLoad) -> MemIntrinsic {
{
operands: [TI32],
result: Some(TV128),
lane: false,
nat_align: op.nat_align(),
build: (m, a, o, _) => vec_load_instr(op, m, a, o),
}
}
fn load_splat(w : LaneWidth) -> MemIntrinsic {
{
operands: [TI32],
result: Some(TV128),
lane: false,
nat_align: w.nat_align(),
build: (m, a, o, _) => load_splat_instr(w, m, a, o),
}
}
fn load_lane(w : LaneWidth) -> MemIntrinsic {
{
// The vector to write the loaded lane into is an operand as well.
operands: [TI32, TV128],
result: Some(TV128),
lane: true,
nat_align: w.nat_align(),
build: (m, a, o, l) => load_lane_instr(w, m, a, o, l),
}
}
fn store_lane(w : LaneWidth) -> MemIntrinsic {
{
operands: [TI32, TV128],
result: None,
lane: true,
nat_align: w.nat_align(),
build: (m, a, o, l) => store_lane_instr(w, m, a, o, l),
}
}
for op in vec_loads {
if name == op.to_str() {
return Some(load(op))
}
}
if name == store_name {
return Some({
operands: [TI32, TV128],
result: None,
lane: false,
nat_align: 16,
build: (m, a, o, _) => V128Store(m, a, o),
})
}
for w in lane_widths {
if name == load_splat_name(w) {
return Some(load_splat(w))
}
if name == load_lane_name(w) {
return Some(load_lane(w))
}
if name == store_lane_name(w) {
return Some(store_lane(w))
}
}
None
}
///|
pub fn is_mem_method(name : String) -> Bool {
mem_method(name) is Some(_)
}
///|
/// Every vector memory method name, for completion after `mem.`. Exactly the
/// set `mem_method` recognises, because both are built from the same lists.
pub let mem_method_names : Array[String] = {
let out = vec_loads.map(op => op.to_str())
out.push(store_name)
for w in lane_widths {
out.push(load_splat_name(w))
}
for w in lane_widths {
out.push(load_lane_name(w))
}
for w in lane_widths {
out.push(store_lane_name(w))
}
out
}
///|
fn vec_load_instr(
op : VecLoad,
m : Int,
align : Int,
offset : Int64,
) -> @wasm_bin.Instruction {
match op {
Load128 => V128Load(m, align, offset)
Load8x8S => V128Load8x8S(m, align, offset)
Load8x8U => V128Load8x8U(m, align, offset)
Load16x4S => V128Load16x4S(m, align, offset)
Load16x4U => V128Load16x4U(m, align, offset)
Load32x2S => V128Load32x2S(m, align, offset)
Load32x2U => V128Load32x2U(m, align, offset)
Load32Zero => V128Load32Zero(m, align, offset)
Load64Zero => V128Load64Zero(m, align, offset)
}
}
///|
fn load_splat_instr(
w : LaneWidth,
m : Int,
align : Int,
offset : Int64,
) -> @wasm_bin.Instruction {
match w {
L8 => V128Load8Splat(m, align, offset)
L16 => V128Load16Splat(m, align, offset)
L32 => V128Load32Splat(m, align, offset)
L64 => V128Load64Splat(m, align, offset)
}
}
///|
fn load_lane_instr(
w : LaneWidth,
m : Int,
align : Int,
offset : Int64,
lane : Int,
) -> @wasm_bin.Instruction {
match w {
L8 => V128Load8Lane(m, align, offset, lane)
L16 => V128Load16Lane(m, align, offset, lane)
L32 => V128Load32Lane(m, align, offset, lane)
L64 => V128Load64Lane(m, align, offset, lane)
}
}
///|
fn store_lane_instr(
w : LaneWidth,
m : Int,
align : Int,
offset : Int64,
lane : Int,
) -> @wasm_bin.Instruction {
match w {
L8 => V128Store8Lane(m, align, offset, lane)
L16 => V128Store16Lane(m, align, offset, lane)
L32 => V128Store32Lane(m, align, offset, lane)
L64 => V128Store64Lane(m, align, offset, lane)
}
}