simd.nx source
↩ module page · 203 lines · 6623 B
1// simd.nx -- SIMD vector types + intrinsics (spec + soft-fallback).
2//
3// Target: 8-16x speedup on every numeric routine NishiLang ships
4// (sha256, chacha20, base64, CRCs, etc.) via target-native
5// vector instructions (AVX-512, RVV, NEON).
6//
7// Two-phase approach:
8//
9// Phase A (this file): define the SIMD type API + soft-fallback
10// implementation. Every SIMD op is a regular i64 function
11// that manipulates an "8x i64" packed buffer one lane at a
12// time. No speedup yet, but callers can write SIMD-shaped
13// code today.
14//
15// Phase B (riscv.nx backend work, pending): pattern-match these
16// functions during codegen and replace with single vector
17// instructions. Same source code compiles to 8-16x
18// faster machine code.
19//
20// Why this order: API commitment FIRST means library authors
21// can start writing SIMD-shaped routines now. The speedup
22// lands later without breaking any caller.
23//
24// Invariants:
25// V1 Vec64 is 8x i64 = 64 bytes = one AVX-512 register / two
26// AVX2 regs / one RVV VLEN-512 register / four NEON regs.
27// V2 Ops are pure functions of operand lanes -- no side
28// effects, no aliasing concerns.
29// V3 Memory ops (load/store) take aligned pointers; caller
30// ensures 64-byte alignment for max throughput.
31
32import "syscalls.nx"
33
34// 64-byte vector = 8 i64 lanes. Stored as a heap pointer to an
35// 8-element i64 array. Future hardware-backed versions will use
36// register allocation for this.
37struct Vec64 {
38 lane0: i64, lane1: i64, lane2: i64, lane3: i64,
39 lane4: i64, lane5: i64, lane6: i64, lane7: i64,
40}
41
42const VEC64_BYTES: i64 = 64
43
44// ===== construction =================================================
45
46func vec64_zero() -> *Vec64 {
47 let raw: *u8 = sys_mmap(VEC64_BYTES)
48 let v: *Vec64 = raw as *Vec64
49 v.lane0 = 0; v.lane1 = 0; v.lane2 = 0; v.lane3 = 0
50 v.lane4 = 0; v.lane5 = 0; v.lane6 = 0; v.lane7 = 0
51 return v
52}
53
54// Broadcast: all lanes = scalar.
55func vec64_splat(x: i64) -> *Vec64 {
56 let v: *Vec64 = vec64_zero()
57 v.lane0 = x; v.lane1 = x; v.lane2 = x; v.lane3 = x
58 v.lane4 = x; v.lane5 = x; v.lane6 = x; v.lane7 = x
59 return v
60}
61
62// Load from aligned memory. Caller guarantees p is 64-byte aligned.
63// Future: compiles to `vld1q_u64` / `vmovdqa64` / RVV `vle64.v`.
64func vec64_load(p: *i64) -> *Vec64 {
65 let v: *Vec64 = vec64_zero()
66 v.lane0 = p[0]; v.lane1 = p[1]; v.lane2 = p[2]; v.lane3 = p[3]
67 v.lane4 = p[4]; v.lane5 = p[5]; v.lane6 = p[6]; v.lane7 = p[7]
68 return v
69}
70
71// Store to aligned memory. Future: single `vmovdqa64` / `vst1q_u64`.
72func vec64_store(v: *Vec64, p: *i64) -> i64 {
73 p[0] = v.lane0; p[1] = v.lane1; p[2] = v.lane2; p[3] = v.lane3
74 p[4] = v.lane4; p[5] = v.lane5; p[6] = v.lane6; p[7] = v.lane7
75 return 0
76}
77
78// ===== arithmetic ====================================================
79
80// Lane-wise add. a[i] + b[i]. Future: `vpaddq` / `vadd.vv`.
81func vec64_add(a: *Vec64, b: *Vec64) -> *Vec64 {
82 let r: *Vec64 = vec64_zero()
83 r.lane0 = a.lane0 + b.lane0
84 r.lane1 = a.lane1 + b.lane1
85 r.lane2 = a.lane2 + b.lane2
86 r.lane3 = a.lane3 + b.lane3
87 r.lane4 = a.lane4 + b.lane4
88 r.lane5 = a.lane5 + b.lane5
89 r.lane6 = a.lane6 + b.lane6
90 r.lane7 = a.lane7 + b.lane7
91 return r
92}
93
94// Lane-wise subtract.
95func vec64_sub(a: *Vec64, b: *Vec64) -> *Vec64 {
96 let r: *Vec64 = vec64_zero()
97 r.lane0 = a.lane0 - b.lane0
98 r.lane1 = a.lane1 - b.lane1
99 r.lane2 = a.lane2 - b.lane2
100 r.lane3 = a.lane3 - b.lane3
101 r.lane4 = a.lane4 - b.lane4
102 r.lane5 = a.lane5 - b.lane5
103 r.lane6 = a.lane6 - b.lane6
104 r.lane7 = a.lane7 - b.lane7
105 return r
106}
107
108// Lane-wise 64-bit multiply (low result).
109func vec64_mul(a: *Vec64, b: *Vec64) -> *Vec64 {
110 let r: *Vec64 = vec64_zero()
111 r.lane0 = a.lane0 * b.lane0
112 r.lane1 = a.lane1 * b.lane1
113 r.lane2 = a.lane2 * b.lane2
114 r.lane3 = a.lane3 * b.lane3
115 r.lane4 = a.lane4 * b.lane4
116 r.lane5 = a.lane5 * b.lane5
117 r.lane6 = a.lane6 * b.lane6
118 r.lane7 = a.lane7 * b.lane7
119 return r
120}
121
122// ===== bitwise =======================================================
123
124func vec64_and(a: *Vec64, b: *Vec64) -> *Vec64 {
125 let r: *Vec64 = vec64_zero()
126 r.lane0 = a.lane0 & b.lane0
127 r.lane1 = a.lane1 & b.lane1
128 r.lane2 = a.lane2 & b.lane2
129 r.lane3 = a.lane3 & b.lane3
130 r.lane4 = a.lane4 & b.lane4
131 r.lane5 = a.lane5 & b.lane5
132 r.lane6 = a.lane6 & b.lane6
133 r.lane7 = a.lane7 & b.lane7
134 return r
135}
136
137func vec64_or(a: *Vec64, b: *Vec64) -> *Vec64 {
138 let r: *Vec64 = vec64_zero()
139 r.lane0 = a.lane0 | b.lane0
140 r.lane1 = a.lane1 | b.lane1
141 r.lane2 = a.lane2 | b.lane2
142 r.lane3 = a.lane3 | b.lane3
143 r.lane4 = a.lane4 | b.lane4
144 r.lane5 = a.lane5 | b.lane5
145 r.lane6 = a.lane6 | b.lane6
146 r.lane7 = a.lane7 | b.lane7
147 return r
148}
149
150func vec64_xor(a: *Vec64, b: *Vec64) -> *Vec64 {
151 let r: *Vec64 = vec64_zero()
152 r.lane0 = a.lane0 ^ b.lane0
153 r.lane1 = a.lane1 ^ b.lane1
154 r.lane2 = a.lane2 ^ b.lane2
155 r.lane3 = a.lane3 ^ b.lane3
156 r.lane4 = a.lane4 ^ b.lane4
157 r.lane5 = a.lane5 ^ b.lane5
158 r.lane6 = a.lane6 ^ b.lane6
159 r.lane7 = a.lane7 ^ b.lane7
160 return r
161}
162
163// Shift left by constant count (uniform shift across lanes).
164func vec64_shl(v: *Vec64, n: i64) -> *Vec64 {
165 let r: *Vec64 = vec64_zero()
166 r.lane0 = v.lane0 << n
167 r.lane1 = v.lane1 << n
168 r.lane2 = v.lane2 << n
169 r.lane3 = v.lane3 << n
170 r.lane4 = v.lane4 << n
171 r.lane5 = v.lane5 << n
172 r.lane6 = v.lane6 << n
173 r.lane7 = v.lane7 << n
174 return r
175}
176
177// ===== reduction =====================================================
178
179// Horizontal sum of all lanes. Future: log-fold via vector-
180// reduction intrinsics (`vredsum.vs` etc.).
181func vec64_hsum(v: *Vec64) -> i64 {
182 return v.lane0 + v.lane1 + v.lane2 + v.lane3 + v.lane4 + v.lane5 + v.lane6 + v.lane7
183}
184
185// Compile-only smoke.
186func main() -> i64 {
187 // Build [1..8] vs [10..17], verify sums + XOR.
188 let a: *Vec64 = vec64_zero()
189 a.lane0 = 1; a.lane1 = 2; a.lane2 = 3; a.lane3 = 4
190 a.lane4 = 5; a.lane5 = 6; a.lane6 = 7; a.lane7 = 8
191
192 let b: *Vec64 = vec64_splat(10)
193
194 let c: *Vec64 = vec64_add(a, b)
195 if c.lane0 != 11 { return 1 }
196 if c.lane7 != 18 { return 2 }
197 if vec64_hsum(c) != 116 { return 3 } // 11+12+...+18 = 116
198
199 let d: *Vec64 = vec64_xor(a, a)
200 if vec64_hsum(d) != 0 { return 4 }
201
202 return 0
203}