skip to content
==> include/wasm/numeric.h <==
1/* @ajax/wasm-numeric: WebAssembly's numeric instructions (spec 4.3), deterministic.
2 *
3 * Floats follow IEEE 754 in the default rounding mode, with no fused multiply-add (build with
4 * -ffp-contract=off, on a target with FLT_EVAL_METHOD 0) and, as wasmtime's engine in meshd
5 * (`cranelift_nan_canonicalization(true)`), every NaN an arithmetic operation produces is the
6 * canonical one: add, sub, mul, div, sqrt, min, max, ceil, floor, trunc, nearest, promote, demote.
7 * neg, abs and copysign are bit operations and keep a NaN's payload, as do loads, stores, selects
8 * and reinterprets.
9 *
10 * Operations that can trap return a trap code (WASM_NUM_OK when they didn't) and write the result
11 * through a pointer. Header-only. */
12#ifndef WASM_NUMERIC_H
13#define WASM_NUMERIC_H
14
15#include <float.h>
16#include <math.h>
17#include <stdbool.h>
18#include <stdint.h>
19#include <string.h>
20
21_Static_assert(FLT_EVAL_METHOD == 0, "wasm floats need float and double evaluated in their own precision");
22
23/* Trap codes, numbered as wasmtime's `Trap` (what meshd's FAULT_TRAP word carries). */
24enum {
25    WASM_NUM_OK = -1,
26    WASM_TRAP_STACK_OVERFLOW = 0,
27    WASM_TRAP_MEMORY_OUT_OF_BOUNDS = 1,
28    WASM_TRAP_HEAP_MISALIGNED = 2,
29    WASM_TRAP_TABLE_OUT_OF_BOUNDS = 3,
30    WASM_TRAP_INDIRECT_CALL_TO_NULL = 4,
31    WASM_TRAP_BAD_SIGNATURE = 5,
32    WASM_TRAP_INTEGER_OVERFLOW = 6,
33    WASM_TRAP_INTEGER_DIVISION_BY_ZERO = 7,
34    WASM_TRAP_BAD_CONVERSION_TO_INTEGER = 8,
35    WASM_TRAP_UNREACHABLE = 9,
36    WASM_TRAP_INTERRUPT = 10,
37    WASM_TRAP_OUT_OF_FUEL = 11,
38    WASM_TRAP_NULL_REFERENCE = 13,
39};
40
41/* wasmtime's message for a trap ("wasm trap: " + this). */
42static inline const char *wasm_trap_message(int trap) {
43    switch (trap) {
44    case WASM_TRAP_STACK_OVERFLOW: return "call stack exhausted";
45    case WASM_TRAP_MEMORY_OUT_OF_BOUNDS: return "out of bounds memory access";
46    case WASM_TRAP_HEAP_MISALIGNED: return "unaligned atomic";
47    case WASM_TRAP_TABLE_OUT_OF_BOUNDS: return "undefined element: out of bounds table access";
48    case WASM_TRAP_INDIRECT_CALL_TO_NULL: return "uninitialized element";
49    case WASM_TRAP_BAD_SIGNATURE: return "indirect call type mismatch";
50    case WASM_TRAP_INTEGER_OVERFLOW: return "integer overflow";
51    case WASM_TRAP_INTEGER_DIVISION_BY_ZERO: return "integer divide by zero";
52    case WASM_TRAP_BAD_CONVERSION_TO_INTEGER: return "invalid conversion to integer";
53    case WASM_TRAP_UNREACHABLE: return "wasm `unreachable` instruction executed";
54    case WASM_TRAP_INTERRUPT: return "interrupt";
55    case WASM_TRAP_OUT_OF_FUEL: return "all fuel consumed by WebAssembly";
56    case WASM_TRAP_NULL_REFERENCE: return "null reference";
57    default: return "trap";
58    }
59}
60
61#define WASM_CANON_F32 0x7fc00000u
62#define WASM_CANON_F64 0x7ff8000000000000ull
63
64static inline uint32_t wasm_f32_bits(float f) {
65    uint32_t u;
66    memcpy(&u, &f, 4);
67    return u;
68}
69static inline uint64_t wasm_f64_bits(double d) {
70    uint64_t u;
71    memcpy(&u, &d, 8);
72    return u;
73}
74static inline float wasm_f32_from(uint32_t u) {
75    float f;
76    memcpy(&f, &u, 4);
77    return f;
78}
79static inline double wasm_f64_from(uint64_t u) {
80    double d;
81    memcpy(&d, &u, 8);
82    return d;
83}
84
85static inline float wasm_canon32(float f) { return f != f ? wasm_f32_from(WASM_CANON_F32) : f; }
86static inline double wasm_canon64(double d) { return d != d ? wasm_f64_from(WASM_CANON_F64) : d; }
87
88/* ---- integers ---- */
89
90static inline uint32_t wasm_i32_clz(uint32_t x) { return x ? (uint32_t)__builtin_clz(x) : 32; }
91static inline uint32_t wasm_i32_ctz(uint32_t x) { return x ? (uint32_t)__builtin_ctz(x) : 32; }
92static inline uint32_t wasm_i32_popcnt(uint32_t x) { return (uint32_t)__builtin_popcount(x); }
93static inline uint64_t wasm_i64_clz(uint64_t x) { return x ? (uint64_t)__builtin_clzll(x) : 64; }
94static inline uint64_t wasm_i64_ctz(uint64_t x) { return x ? (uint64_t)__builtin_ctzll(x) : 64; }
95static inline uint64_t wasm_i64_popcnt(uint64_t x) { return (uint64_t)__builtin_popcountll(x); }
96static inline uint32_t wasm_i32_rotl(uint32_t x, uint32_t k) { k &= 31; return k ? x << k | x >> (32 - k) : x; }
97static inline uint32_t wasm_i32_rotr(uint32_t x, uint32_t k) { k &= 31; return k ? x >> k | x << (32 - k) : x; }
98static inline uint64_t wasm_i64_rotl(uint64_t x, uint64_t k) { k &= 63; return k ? x << k | x >> (64 - k) : x; }
99static inline uint64_t wasm_i64_rotr(uint64_t x, uint64_t k) { k &= 63; return k ? x >> k | x << (64 - k) : x; }
100static inline uint32_t wasm_i32_shr_s(uint32_t x, uint32_t k) {
101    k &= 31;
102    return (x & 0x80000000u) ? ~(~x >> k) : x >> k;
103}
104static inline uint64_t wasm_i64_shr_s(uint64_t x, uint64_t k) {
105    k &= 63;
106    return (x & 0x8000000000000000ull) ? ~(~x >> k) : x >> k;
107}
108
109static inline int wasm_i32_div_s(uint32_t a, uint32_t b, uint32_t *r) {
110    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
111    if (a == 0x80000000u && b == 0xffffffffu) return WASM_TRAP_INTEGER_OVERFLOW;
112    *r = (uint32_t)((int32_t)a / (int32_t)b);
113    return WASM_NUM_OK;
114}
115static inline int wasm_i32_div_u(uint32_t a, uint32_t b, uint32_t *r) {
116    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
117    *r = a / b;
118    return WASM_NUM_OK;
119}
120static inline int wasm_i32_rem_s(uint32_t a, uint32_t b, uint32_t *r) {
121    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
122    *r = b == 0xffffffffu ? 0 : (uint32_t)((int32_t)a % (int32_t)b);
123    return WASM_NUM_OK;
124}
125static inline int wasm_i32_rem_u(uint32_t a, uint32_t b, uint32_t *r) {
126    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
127    *r = a % b;
128    return WASM_NUM_OK;
129}
130static inline int wasm_i64_div_s(uint64_t a, uint64_t b, uint64_t *r) {
131    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
132    if (a == 0x8000000000000000ull && b == ~0ull) return WASM_TRAP_INTEGER_OVERFLOW;
133    *r = (uint64_t)((int64_t)a / (int64_t)b);
134    return WASM_NUM_OK;
135}
136static inline int wasm_i64_div_u(uint64_t a, uint64_t b, uint64_t *r) {
137    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
138    *r = a / b;
139    return WASM_NUM_OK;
140}
141static inline int wasm_i64_rem_s(uint64_t a, uint64_t b, uint64_t *r) {
142    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
143    *r = b == ~0ull ? 0 : (uint64_t)((int64_t)a % (int64_t)b);
144    return WASM_NUM_OK;
145}
146static inline int wasm_i64_rem_u(uint64_t a, uint64_t b, uint64_t *r) {
147    if (b == 0) return WASM_TRAP_INTEGER_DIVISION_BY_ZERO;
148    *r = a % b;
149    return WASM_NUM_OK;
150}
151
152/* ---- floats ---- */
153
154static inline float wasm_f32_min(float a, float b) {
155    if (a != a || b != b) return wasm_f32_from(WASM_CANON_F32);
156    if (a == b) return wasm_f32_from(wasm_f32_bits(a) | wasm_f32_bits(b)); /* min(-0, +0) = -0 */
157    return a < b ? a : b;
158}
159static inline float wasm_f32_max(float a, float b) {
160    if (a != a || b != b) return wasm_f32_from(WASM_CANON_F32);
161    if (a == b) return wasm_f32_from(wasm_f32_bits(a) & wasm_f32_bits(b)); /* max(-0, +0) = +0 */
162    return a > b ? a : b;
163}
164static inline double wasm_f64_min(double a, double b) {
165    if (a != a || b != b) return wasm_f64_from(WASM_CANON_F64);
166    if (a == b) return wasm_f64_from(wasm_f64_bits(a) | wasm_f64_bits(b));
167    return a < b ? a : b;
168}
169static inline double wasm_f64_max(double a, double b) {
170    if (a != a || b != b) return wasm_f64_from(WASM_CANON_F64);
171    if (a == b) return wasm_f64_from(wasm_f64_bits(a) & wasm_f64_bits(b));
172    return a > b ? a : b;
173}
174
175static inline float wasm_f32_add(float a, float b) { return wasm_canon32(a + b); }
176static inline float wasm_f32_sub(float a, float b) { return wasm_canon32(a - b); }
177static inline float wasm_f32_mul(float a, float b) { return wasm_canon32(a * b); }
178static inline float wasm_f32_div(float a, float b) { return wasm_canon32(a / b); }
179static inline float wasm_f32_sqrt(float a) { return wasm_canon32(sqrtf(a)); }
180static inline float wasm_f32_ceil(float a) { return wasm_canon32(ceilf(a)); }
181static inline float wasm_f32_floor(float a) { return wasm_canon32(floorf(a)); }
182static inline float wasm_f32_trunc(float a) { return wasm_canon32(truncf(a)); }
183/* Round to nearest, ties to even (the default rounding mode, which meshd never changes). */
184static inline float wasm_f32_nearest(float a) { return wasm_canon32(nearbyintf(a)); }
185static inline double wasm_f64_add(double a, double b) { return wasm_canon64(a + b); }
186static inline double wasm_f64_sub(double a, double b) { return wasm_canon64(a - b); }
187static inline double wasm_f64_mul(double a, double b) { return wasm_canon64(a * b); }
188static inline double wasm_f64_div(double a, double b) { return wasm_canon64(a / b); }
189static inline double wasm_f64_sqrt(double a) { return wasm_canon64(sqrt(a)); }
190static inline double wasm_f64_ceil(double a) { return wasm_canon64(ceil(a)); }
191static inline double wasm_f64_floor(double a) { return wasm_canon64(floor(a)); }
192static inline double wasm_f64_trunc(double a) { return wasm_canon64(trunc(a)); }
193static inline double wasm_f64_nearest(double a) { return wasm_canon64(nearbyint(a)); }
194static inline float wasm_f32_demote(double a) { return wasm_canon32((float)a); }
195static inline double wasm_f64_promote(float a) { return wasm_canon64((double)a); }
196
197static inline uint32_t wasm_f32_neg(uint32_t a) { return a ^ 0x80000000u; }
198static inline uint32_t wasm_f32_abs(uint32_t a) { return a & 0x7fffffffu; }
199static inline uint32_t wasm_f32_copysign(uint32_t a, uint32_t b) { return (a & 0x7fffffffu) | (b & 0x80000000u); }
200static inline uint64_t wasm_f64_neg(uint64_t a) { return a ^ 0x8000000000000000ull; }
201static inline uint64_t wasm_f64_abs(uint64_t a) { return a & 0x7fffffffffffffffull; }
202static inline uint64_t wasm_f64_copysign(uint64_t a, uint64_t b) {
203    return (a & 0x7fffffffffffffffull) | (b & 0x8000000000000000ull);
204}
205
206/* ---- float to integer: trapping (NaN: bad conversion; out of range: overflow) ---- */
207
208#define WASM_TRUNC(name, F, T, lo_ok, hi_ok)                                                                 \
209    static inline int name(F x, T *r) {                                                                       \
210        if (x != x) return WASM_TRAP_BAD_CONVERSION_TO_INTEGER;                                               \
211        if (!(lo_ok) || !(hi_ok)) return WASM_TRAP_INTEGER_OVERFLOW;                                          \
212        *r = (T)x;                                                                                            \
213        return WASM_NUM_OK;                                                                                   \
214    }
215WASM_TRUNC(wasm_i32_trunc_f32_s_, float, int32_t, x >= -2147483648.0f, x < 2147483648.0f)
216WASM_TRUNC(wasm_i32_trunc_f32_u_, float, uint32_t, x > -1.0f, x < 4294967296.0f)
217WASM_TRUNC(wasm_i32_trunc_f64_s_, double, int32_t, x > -2147483649.0, x < 2147483648.0)
218WASM_TRUNC(wasm_i32_trunc_f64_u_, double, uint32_t, x > -1.0, x < 4294967296.0)
219WASM_TRUNC(wasm_i64_trunc_f32_s_, float, int64_t, x >= -9223372036854775808.0f, x < 9223372036854775808.0f)
220WASM_TRUNC(wasm_i64_trunc_f32_u_, float, uint64_t, x > -1.0f, x < 18446744073709551616.0f)
221WASM_TRUNC(wasm_i64_trunc_f64_s_, double, int64_t, x >= -9223372036854775808.0, x < 9223372036854775808.0)
222WASM_TRUNC(wasm_i64_trunc_f64_u_, double, uint64_t, x > -1.0, x < 18446744073709551616.0)
223#undef WASM_TRUNC
224
225static inline int wasm_i32_trunc_f32_s(float x, uint32_t *r) {
226    int32_t v = 0;
227    int t = wasm_i32_trunc_f32_s_(x, &v);
228    *r = (uint32_t)v;
229    return t;
230}
231static inline int wasm_i32_trunc_f64_s(double x, uint32_t *r) {
232    int32_t v = 0;
233    int t = wasm_i32_trunc_f64_s_(x, &v);
234    *r = (uint32_t)v;
235    return t;
236}
237static inline int wasm_i64_trunc_f32_s(float x, uint64_t *r) {
238    int64_t v = 0;
239    int t = wasm_i64_trunc_f32_s_(x, &v);
240    *r = (uint64_t)v;
241    return t;
242}
243static inline int wasm_i64_trunc_f64_s(double x, uint64_t *r) {
244    int64_t v = 0;
245    int t = wasm_i64_trunc_f64_s_(x, &v);
246    *r = (uint64_t)v;
247    return t;
248}
249#define wasm_i32_trunc_f32_u wasm_i32_trunc_f32_u_
250#define wasm_i32_trunc_f64_u wasm_i32_trunc_f64_u_
251#define wasm_i64_trunc_f32_u wasm_i64_trunc_f32_u_
252#define wasm_i64_trunc_f64_u wasm_i64_trunc_f64_u_
253
254/* ---- float to integer: saturating (NaN: 0; out of range: the nearest bound) ---- */
255
256static inline uint32_t wasm_i32_trunc_sat_f32_s(float x) {
257    if (x != x) return 0;
258    if (x < -2147483648.0f) return 0x80000000u;
259    if (x >= 2147483648.0f) return 0x7fffffffu;
260    return (uint32_t)(int32_t)x;
261}
262static inline uint32_t wasm_i32_trunc_sat_f32_u(float x) {
263    if (x != x || x <= -1.0f) return 0;
264    if (x >= 4294967296.0f) return 0xffffffffu;
265    return (uint32_t)x;
266}
267static inline uint32_t wasm_i32_trunc_sat_f64_s(double x) {
268    if (x != x) return 0;
269    if (x <= -2147483649.0) return 0x80000000u;
270    if (x >= 2147483648.0) return 0x7fffffffu;
271    return (uint32_t)(int32_t)x;
272}
273static inline uint32_t wasm_i32_trunc_sat_f64_u(double x) {
274    if (x != x || x <= -1.0) return 0;
275    if (x >= 4294967296.0) return 0xffffffffu;
276    return (uint32_t)x;
277}
278static inline uint64_t wasm_i64_trunc_sat_f32_s(float x) {
279    if (x != x) return 0;
280    if (x < -9223372036854775808.0f) return 0x8000000000000000ull;
281    if (x >= 9223372036854775808.0f) return 0x7fffffffffffffffull;
282    return (uint64_t)(int64_t)x;
283}
284static inline uint64_t wasm_i64_trunc_sat_f32_u(float x) {
285    if (x != x || x <= -1.0f) return 0;
286    if (x >= 18446744073709551616.0f) return ~0ull;
287    return (uint64_t)x;
288}
289static inline uint64_t wasm_i64_trunc_sat_f64_s(double x) {
290    if (x != x) return 0;
291    if (x < -9223372036854775808.0) return 0x8000000000000000ull;
292    if (x >= 9223372036854775808.0) return 0x7fffffffffffffffull;
293    return (uint64_t)(int64_t)x;
294}
295static inline uint64_t wasm_i64_trunc_sat_f64_u(double x) {
296    if (x != x || x <= -1.0) return 0;
297    if (x >= 18446744073709551616.0) return ~0ull;
298    return (uint64_t)x;
299}
300
301#endif