Read wasi-sdk's version instead of guessing it, and pin the one ABI path left

The wasi-sdk candidate had an LLVM version in it, which moves release to
release — so the path advertised as the proper article would have matched only
by coincidence, while the emscripten one beside it was derived. Both are
derived now.

calc-me on wasm32 covers what the other three cases cannot: flan_argv hands
Flan an array of flan_slice built in C, so what it pins is the element stride
of a ptr+len pair — 16 bytes native, 12 on wasm32 — rather than a field
offset. It is also the claim in this file's own header, that the table runs on
the second target, honoured for the first time.

flan emit refuses --target rather than stripping it. The IR really is
target-free, so ignoring it is correct and silence about it is not.
This commit is contained in:
Joseph Ferano 2026-09-11 19:48:04 +07:00
parent 046593acf1
commit 8a175ebec5
4 changed files with 40 additions and 10 deletions

View File

@ -100,6 +100,15 @@ let () =
(Flan.Types.to_string f.ret) (Array.length f.slots))
p.fns))
files
(* The IR is target-independent — [Emit] writes no triple and no datalayout,
which is what lets one .ll serve both targets so there is nothing for a
target to change here. Refused rather than accepted and ignored: silently
swallowing a flag is the shape the house rule exists to prevent. *)
| _ :: "emit" :: args when target_of args <> None ->
prerr_endline
"flan emit: --target is refused — the emitted IR carries no triple and \
no datalayout, and the target is chosen at build.";
exit 2
| _ :: "emit" :: args when List.exists (fun a -> not (is_flag a)) args ->
let checks = not (List.mem no_checks_flag args) in
let dev = List.mem dev_flag args in

View File

@ -107,7 +107,18 @@ let wasm_sysroot () =
links and runs, and the sand hash matches native byte for byte, but a
session reading this should know the joint is glued. *)
let wasm_builtins_candidates () =
[ "/opt/wasi-sdk/lib/clang/20/lib/wasm32-unknown-wasi/libclang_rt.builtins.a" ]
(* wasi-sdk's own resource directory, whichever LLVM that release bundled —
the version is in the path and moves release to release, so it is read
rather than guessed. Same rule as [clang_resource_dir]. *)
(let root = "/opt/wasi-sdk/lib/clang" in
match Sys.readdir root with
| vs ->
Array.sort compare vs;
Array.to_list vs
|> List.map (fun v ->
Filename.concat root
(Filename.concat v "lib/wasm32-unknown-wasi/libclang_rt.builtins.a"))
| exception Sys_error _ -> [])
@ (match on_path "emcc" with
| None -> []
| Some e ->

View File

@ -15,10 +15,10 @@
(glob_files programs/*.flan)
; The reload primitive's host: a C main that dlopens what Build.shared made.
(file reload_host.c)
; The WASI host the wasm32 case runs its module under, when no wasmtime or
; wasmer is installed.
(file wasm-run.mjs)
; test_dev runs the compiler itself: flan dev launches and owns a program.
(file %{workspace_root}/bin/main.exe)
; The Emacs client, which test_emacs drives against a real daemon.
(glob_files %{workspace_root}/emacs/*.el)))
(glob_files %{workspace_root}/emacs/*.el)
; The WASI host the wasm32 case runs its module under, when no wasmtime or
; wasmer is installed.
(file wasm-run.mjs)))

View File

@ -356,11 +356,12 @@ let () =
~opts:{ Build.default with opt; target = Some "wasm32-wasi" }
~csrcs:l.Load.csrcs ~lflags:l.Load.lflags p ~out)
in
let wasm_run runner wasm =
let wasm_run ?arg runner wasm =
let out = Filename.concat scratch "flan-acceptance-wasm.out" in
let code =
Sys.command
(Printf.sprintf "%s %s > %s 2>&1" runner (Filename.quote wasm)
(Printf.sprintf "%s %s %s > %s 2>&1" runner (Filename.quote wasm)
(match arg with None -> "" | Some a -> Filename.quote a)
(Filename.quote out))
in
let text = In_channel.with_open_bin out In_channel.input_all in
@ -389,14 +390,14 @@ let () =
| Error why ->
Printf.printf "acceptance: skipping the wasm32 case (%s)\n" why
| Ok () ->
let wasm_case name ?opt path expected =
let wasm_case name ?opt ?arg path expected =
let wasm =
Filename.concat scratch
("flan-w-" ^ Filename.remove_extension (Filename.basename path)
^ ".wasm")
in
wasm_build ?opt path wasm;
let code, text = wasm_run runner wasm in
let code, text = wasm_run ?arg runner wasm in
if text <> expected || code <> 0 then begin
incr failures;
Printf.printf
@ -418,7 +419,16 @@ let () =
likely to go wrong and these are where it would show. *)
wasm_case "value semantics, wasm32" "programs/values.flan" values_out;
wasm_case "machine surface, wasm32" "programs/machine.flan"
machine_out));
machine_out;
(* calc-me, for the one host-ABI path the three above do not touch:
[flan_argv] builds an array of flan_slice in C and Flan indexes it
as [string], so what is pinned here is the element *stride* of a
ptr+len pair, which is 16 bytes native and 12 on wasm32 not a
field offset, and nothing else in the table reaches it. This is
also the file header's own claim, that the table runs on wasm32
too, honoured for the first time. *)
wasm_case "calc-me, wasm32" "../calc-me.flan"
~arg:"1 + 2 * (3 - 0.5) / 2" "3.5\n"));
if !failures = 0 then print_endline "acceptance: all tests passed"
else begin