skip to content
==> src/float_guard.c <==
1/* float_check, as in mesh-host/src/wasm.rs: validate twice (every proposal, then without what the
2 * executor never runs), then walk imports, exports and every operator. */
3#include "wasm/float_guard.h"
4
5#include "mesh/abi/wasm.h"
6#include "wasm/decode.h"
7#include "wasm/instr.h"
8#include "wasm/validate.h"
9
10#include <stdarg.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14
15bool wasm_flushing_f32(uint32_t op) {
16    switch (op) {
17    case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: case 0x90: case 0x91: /* abs neg ceil floor trunc nearest sqrt */
18    case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: /* add sub mul div min max copysign */
19    case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60:             /* eq ne lt gt le ge */
20    case 0xb6: case 0xbb:                                                         /* demote, promote */
21        return true;
22    default: return false;
23    }
24}
25
26static bool say(char *err, size_t errlen, const char *fmt, ...) {
27    if (err && errlen) {
28        va_list ap;
29        va_start(ap, fmt);
30        vsnprintf(err, errlen, fmt, ap);
31        va_end(ap);
32    }
33    return false;
34}
35
36static bool starts_with(const uint8_t *s, uint32_t n, const char *prefix) {
37    size_t k = strlen(prefix);
38    return n >= k && memcmp(s, prefix, k) == 0;
39}
40
41bool wasm_float_check(const uint8_t *wasm, size_t len, bool ftz, wasm_float_report *rep, char *err, size_t errlen) {
42    char msg[384];
43    wasm_module m;
44    wasm_float_report r = {0};
45    /* Well-formed under every proposal we know (else it is the compiler's refusal, `jit`) ... */
46    if (!wasm_decode(wasm, len, &m, msg, sizeof msg)) return say(err, errlen, "jit: invalid wasm: %s", msg);
47    bool ok = false;
48    bool *helpers = NULL;
49    if (!wasm_validate(&m, WASM_F_ALL, NULL, msg, sizeof msg)) {
50        say(err, errlen, "jit: invalid wasm: %s", msg);
51        goto out;
52    }
53    /* ... and without what this executor never runs: threads, shared memory and relaxed SIMD
54     * anywhere; any SIMD in an FTZ image (its f32x4 ops have no flush). */
55    wasm_features f = WASM_F_ALL & ~(wasm_features)(WASM_F_THREADS | WASM_F_RELAXED_SIMD);
56    if (ftz) f &= ~(wasm_features)WASM_F_SIMD;
57    if (!wasm_validate(&m, f, NULL, msg, sizeof msg)) {
58        say(err, errlen, "float: %s (a CPU program uses no threads, shared memory or relaxed SIMD%s)", msg,
59            ftz ? "; an FTZ image no SIMD" : "");
60        goto out;
61    }
62    for (uint32_t i = 0; i < m.nimports; i++) {
63        const wasm_import *im = &m.imports[i];
64        if (!(im->module_len == strlen(MESH_WASM_MODULE) && memcmp(im->module, MESH_WASM_MODULE, im->module_len) == 0) ||
65            im->kind != WASM_EXTERN_FUNC) {
66            say(err, errlen, "load: import %.*s::%.*s: a CPU program imports only `%s` functions (mesh-abi v2::wasm)",
67                (int)im->module_len, im->module, (int)im->name_len, im->name, MESH_WASM_MODULE);
68            goto out;
69        }
70    }
71    if (!(helpers = calloc(m.nfuncs ? m.nfuncs : 1, sizeof *helpers))) {
72        say(err, errlen, "load: out of memory");
73        goto out;
74    }
75    for (uint32_t i = 0; i < m.nexports; i++) {
76        const wasm_export *e = &m.exports[i];
77        if (e->kind == WASM_EXTERN_FUNC && starts_with(e->name, e->name_len, MESH_WASM_FTZ_PREFIX) && !helpers[e->index]) {
78            helpers[e->index] = true;
79            r.helpers++;
80        }
81    }
82    for (uint32_t b = 0; b < m.ncodes; b++) {
83        uint32_t func = m.nfunc_imports + b;
84        const wasm_expr *body = &m.codes[b].body;
85        wasm_reader rd = wasm_reader_at(m.bytes, body->start, body->start + body->len, msg, sizeof msg);
86        wasm_instr in;
87        while (!wasm_rd_eof(&rd)) {
88            if (!wasm_rd_instr(&rd, &in)) {
89                say(err, errlen, "jit: %s", msg);
90                goto out;
91            }
92            if (!wasm_flushing_f32(in.op)) continue;
93            r.f32_ops++;
94            if (ftz && !helpers[func]) {
95                say(err, errlen,
96                    "float: %s in function %u, outside the image's %s* helpers: an FTZ image (manifest float = \"ftz\", "
97                    "the default) computes every f32 op through mesh-ref's Z (crates/mesh-rt-wasm); declare float = "
98                    "\"ieee\" for plain f32 (not lockstep)",
99                    in.info.name, func, MESH_WASM_FTZ_PREFIX);
100                goto out;
101            }
102        }
103    }
104    r.funcs = m.ncodes;
105    if (rep) *rep = r;
106    ok = true;
107out:
108    free(helpers);
109    wasm_module_free(&m);
110    return ok;
111}