mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
b1f65b3513
- `difftest --digest-dir DIR [--digest-debug]` runs only `run_rust` and writes the DIFFTEST_RUST_DIGEST-style per-case digest (and optionally the Debug output). - On wasm32, solidcore-ffi links no C++: shims stay `env` imports, served by the stub module from scripts/wasm_stub.py (SC_ERR_UNLINKED + call counter -> `needs-cpp`). - scripts/wasm-digest.sh builds both targets, runs native and under wasmtime, compares. - solidcore-ffi: MAX_LOOP_INDEX `1 << 40` overflowed on 32-bit targets (E0080). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9QwJFBTKLZ9FFK3nVd1X4
123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Writes the C++ stub module for a wasm32 `difftest` (see scripts/wasm-digest.sh).
|
|
|
|
python3 scripts/wasm_stub.py <difftest.wasm> <out.wat>
|
|
|
|
On wasm32 the `solidcore-ffi` shims are not linked: every `sc_*` function stays an
|
|
import from module `env`. This reads those imports (with their exact signatures) and
|
|
writes a WAT module, preloaded as `env`, in which each one counts the call and returns
|
|
`SC_ERR_UNLINKED` (-1009; nothing for a void shim). `sc_wasm_unlinked_calls()` returns
|
|
the count, so `difftest --digest-dir` marks a case whose Rust side reached C++ as
|
|
`needs-cpp` instead of digesting a stub result. Standard library only.
|
|
"""
|
|
|
|
import sys
|
|
|
|
SC_ERR_UNLINKED = -1009
|
|
COUNTER = "sc_wasm_unlinked_calls"
|
|
VALTYPES = {0x7F: "i32", 0x7E: "i64", 0x7D: "f32", 0x7C: "f64"}
|
|
|
|
|
|
def leb(buf, pos):
|
|
"""Unsigned LEB128 at `pos`: (value, next position)."""
|
|
val = shift = 0
|
|
while True:
|
|
b = buf[pos]
|
|
pos += 1
|
|
val |= (b & 0x7F) << shift
|
|
shift += 7
|
|
if b < 0x80:
|
|
return val, pos
|
|
|
|
|
|
def name(buf, pos):
|
|
n, pos = leb(buf, pos)
|
|
return buf[pos : pos + n].decode("utf-8"), pos + n
|
|
|
|
|
|
def env_imports(buf):
|
|
"""[(field, params, results)] of the function imports from module `env`."""
|
|
if buf[:8] != b"\0asm\1\0\0\0":
|
|
sys.exit("wasm_stub: not a wasm module (version 1)")
|
|
types, imports = [], []
|
|
pos = 8
|
|
while pos < len(buf):
|
|
sid = buf[pos]
|
|
size, pos = leb(buf, pos + 1)
|
|
end = pos + size
|
|
if sid == 1: # type section
|
|
count, p = leb(buf, pos)
|
|
for _ in range(count):
|
|
if buf[p] != 0x60:
|
|
sys.exit("wasm_stub: unsupported type form")
|
|
sig = []
|
|
p += 1
|
|
for _ in range(2):
|
|
n, p = leb(buf, p)
|
|
sig.append([VALTYPES.get(t) for t in buf[p : p + n]])
|
|
p += n
|
|
types.append(sig)
|
|
elif sid == 2: # import section
|
|
count, p = leb(buf, pos)
|
|
for _ in range(count):
|
|
module, p = name(buf, p)
|
|
field, p = name(buf, p)
|
|
kind = buf[p]
|
|
p += 1
|
|
if kind == 0:
|
|
idx, p = leb(buf, p)
|
|
if module == "env":
|
|
imports.append((field, *types[idx]))
|
|
elif kind == 1: # table: reftype + limits
|
|
p += 1
|
|
flags, p = leb(buf, p)
|
|
_, p = leb(buf, p)
|
|
if flags & 1:
|
|
_, p = leb(buf, p)
|
|
elif kind == 2: # memory: limits
|
|
flags, p = leb(buf, p)
|
|
_, p = leb(buf, p)
|
|
if flags & 1:
|
|
_, p = leb(buf, p)
|
|
elif kind == 3: # global: valtype + mutability
|
|
p += 2
|
|
else:
|
|
sys.exit(f"wasm_stub: unsupported import kind {kind}")
|
|
break
|
|
pos = end
|
|
return imports
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 3:
|
|
sys.exit(__doc__)
|
|
with open(sys.argv[1], "rb") as f:
|
|
imports = env_imports(f.read())
|
|
out = [
|
|
"(module",
|
|
" (global $n (mut i64) (i64.const 0))",
|
|
f' (func (export "{COUNTER}") (result i64) global.get $n)',
|
|
]
|
|
for field, params, results in imports:
|
|
if field == COUNTER:
|
|
continue
|
|
if not field.startswith("sc_"):
|
|
sys.exit(f"wasm_stub: unexpected env import `{field}` (not a shim)")
|
|
if None in params or None in results or len(results) > 1:
|
|
sys.exit(f"wasm_stub: unsupported signature of `{field}`")
|
|
sig = "".join(f" (param {t})" for t in params)
|
|
sig += "".join(f" (result {t})" for t in results)
|
|
ret = f" {results[0]}.const {SC_ERR_UNLINKED}" if results else ""
|
|
out.append(
|
|
f' (func (export "{field}"){sig}'
|
|
f" global.get $n i64.const 1 i64.add global.set $n{ret})"
|
|
)
|
|
out.append(")")
|
|
with open(sys.argv[2], "w") as f:
|
|
f.write("\n".join(out) + "\n")
|
|
print(f"wasm_stub: {len(imports)} env imports -> {sys.argv[2]}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|