The browser is a third target, and emcc is its driver
flan build --target=web produces a page, its JS and a .wasm. The two wasm
targets share the word and almost nothing else, so is_wasi and is_web are
separate predicates and is_wasm is their union — the union is exactly the
facts about the machine, 32-bit pointers and no dlopen, which is what the
refusals are about.
Everything the wasi target has to find by hand is what emcc already is: no
sysroot, no builtins archive, no shadow resource directory, and no
__main_argc_argv shim, because emscripten's start code calls main under that
name. target_flags for web is empty and the only thing checked is that emcc
exists. The one fact this rests on is that emcc takes a .ll on its command
line, so Emit's output needs no change.
The main loop is -sASYNCIFY rather than emscripten_set_main_loop, which
BUILT.md predicted. The prediction had the browser right and the cost wrong:
set_main_loop wants the loop body as a callback, so every example that writes
(until (rl/window-should-close?) ...) would be split by hand into an init and
a tick and would stop being the native program. raylib's web platform is built
for asyncify instead — WindowShouldClose on PLATFORM_WEB is an
emscripten_sleep(16) that returns false — so the loop yields at a call it
already makes and no example changed a character. Asyncify goes on every web
link, because whether a program blocks is not a question Build can answer and
a per-program flag set is a per-program cache key.
A link line may now be addressed to one target — @native, @wasi, @web — and
${NAME} expands from the environment. The selection is here and not in Load,
which reads the file, because Load resolves imports before a target is chosen.
The object cache now keys on whichever compiler the target uses, so an emcc
object and a clang one of the same source cannot collide. The refusals name
the target that was asked for; --sanitize on web says the weaker truth, that
emscripten ships an ASan and nothing here has ever run it.
This commit is contained in:
parent
3afce2aeac
commit
c2dc4d4244
11
bin/main.ml
11
bin/main.ml
@ -81,7 +81,8 @@ let sanitize_flag = "--sanitize"
|
||||
|
||||
let flags = [ no_checks_flag; dev_flag; debug_flag; sanitize_flag ]
|
||||
|
||||
(* [--target=wasm32-wasi], the one cross target. Unlike the flags above it
|
||||
(* [--target=wasm32-wasi] and [--target=web], the two cross targets. Unlike
|
||||
the flags above, a target
|
||||
carries a value, so it is matched by prefix and stripped from the residual
|
||||
arguments by the same test — otherwise [-o out --target=X] falls into the
|
||||
usage error. *)
|
||||
@ -187,13 +188,17 @@ let () =
|
||||
let base = Filename.remove_extension (Filename.basename path) in
|
||||
(* A wasm module is not an executable and must not be named like one:
|
||||
the extension is what tells a runtime, and a reader, what it is. *)
|
||||
(* A web build is three files — the page, its JS and the module — and
|
||||
the page is the one named here: emcc derives the other two from it,
|
||||
and it is the one a browser opens. *)
|
||||
(match target with
|
||||
| Some t when Flan.Build.is_web t -> base ^ ".html"
|
||||
| Some t when String.starts_with ~prefix:"wasm32" t -> base ^ ".wasm"
|
||||
| _ -> base)
|
||||
| _ ->
|
||||
prerr_endline
|
||||
"usage: flan build <file.flan> [-o out] [--no-bounds-checks] \
|
||||
[--dev] [--debug] [--sanitize] [--target=wasm32-wasi]";
|
||||
[--dev] [--debug] [--sanitize] [--target=wasm32-wasi|web]";
|
||||
exit 2
|
||||
in
|
||||
with_errors path (fun () ->
|
||||
@ -285,7 +290,7 @@ let () =
|
||||
prerr_endline
|
||||
"usage: flan (read|parse|check|emit|shim) <file.flan>...\n\
|
||||
\ flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] \
|
||||
[--debug] [--sanitize] [--target=wasm32-wasi]\n\
|
||||
[--debug] [--sanitize] [--target=wasm32-wasi|web]\n\
|
||||
\ flan run <file.flan> [args...]\n\
|
||||
\ flan reload <program.flan> <forms.flan> [-o out.so]\n\
|
||||
\ flan dev <program.flan> [-s socket]";
|
||||
|
||||
321
lib/build.ml
321
lib/build.ml
@ -4,12 +4,24 @@
|
||||
|
||||
{v flan → typed IR → .ll → clang --target={native,wasm32} v}
|
||||
|
||||
Three targets now, and the third takes a different driver: the browser is
|
||||
built by emcc, which accepts the same .ll. See [is_web].
|
||||
|
||||
Not the dev path — that one never invokes the clang driver, because the
|
||||
driver *is* the cost, and goes llc + ld -shared + dlopen instead. That is
|
||||
[shared], at the bottom of this file, measured at ~19ms. *)
|
||||
|
||||
let clang = try Sys.getenv "FLAN_CLANG" with Not_found -> "clang"
|
||||
|
||||
(* The browser target's compiler. emcc is a clang driver with a sysroot, a
|
||||
builtins archive, a JS runtime and an HTML shell already attached, so the
|
||||
whole of [wasm_sysroot], [wasm_builtins] and [wasm_resource_dir] below —
|
||||
everything the wasi target has to find by hand — is what emcc *is*. It also
|
||||
accepts a .ll on its command line, which is the one thing that had to be
|
||||
true for this target to exist at all: [Emit] writes IR text and nothing
|
||||
else. *)
|
||||
let emcc = try Sys.getenv "FLAN_EMCC" with Not_found -> "emcc"
|
||||
|
||||
let write path contents =
|
||||
let ch = open_out path in
|
||||
output_string ch contents;
|
||||
@ -50,7 +62,10 @@ let cachedir () =
|
||||
d
|
||||
|
||||
type opts = {
|
||||
target : string option; (* None is the host; "wasm32-wasi" is the other *)
|
||||
(* None is the host. "wasm32-wasi" is the headless one, run by a WASI
|
||||
runtime; "web" is the browser one, built by emscripten. They share the
|
||||
word wasm and almost nothing else — see [is_wasi] and [is_web]. *)
|
||||
target : string option;
|
||||
opt : string;
|
||||
keep : bool; (* leave the .ll behind *)
|
||||
checks : bool; (* bounds-check [at] and [slice] *)
|
||||
@ -134,11 +149,38 @@ let cflags opts =
|
||||
|
||||
let getenv name = try Some (Sys.getenv name) with Not_found -> None
|
||||
|
||||
let is_wasm t = String.starts_with ~prefix:"wasm32" t
|
||||
(* ── Two wasm targets, spelled apart ─────────────────────────────────
|
||||
[web] is emscripten's: a browser, a GL context, a JS runtime, and a clang
|
||||
whose sysroot and builtins come with it. [wasm32-wasi] is the headless one:
|
||||
a WASI runtime, no GL, no browser, and a sysroot this file has to find.
|
||||
"wasm32-unknown-emscripten" is accepted as a synonym for [web] because that
|
||||
is the triple, and someone will write it.
|
||||
|
||||
What the two share is the machine — 32-bit pointers, no dlopen — which is
|
||||
exactly the set of things [is_wasm] guards: the three refusals below are
|
||||
about the machine and so they apply to both. *)
|
||||
let is_web t =
|
||||
t = "web" || t = "emscripten"
|
||||
|| String.starts_with ~prefix:"wasm32-unknown-emscripten" t
|
||||
|| String.starts_with ~prefix:"wasm32-emscripten" t
|
||||
|
||||
let is_wasi t = String.starts_with ~prefix:"wasm32" t && not (is_web t)
|
||||
let is_wasm t = is_web t || is_wasi t
|
||||
|
||||
let wasm_target opts =
|
||||
match opts.target with Some t when is_wasm t -> true | _ -> false
|
||||
|
||||
let web_target opts =
|
||||
match opts.target with Some t when is_web t -> true | _ -> false
|
||||
|
||||
let wasi_target opts =
|
||||
match opts.target with Some t when is_wasi t -> true | _ -> false
|
||||
|
||||
(* Which compiler a target is built by. This is not a flag difference: emcc is
|
||||
a different program with a different driver, and the object cache key below
|
||||
carries it for the same reason it carries clang's mtime. *)
|
||||
let compiler opts = if web_target opts then emcc else clang
|
||||
|
||||
(* Where on PATH a program is, or None. *)
|
||||
let on_path prog =
|
||||
let dirs = String.split_on_char ':' (try Sys.getenv "PATH" with Not_found -> "") in
|
||||
@ -256,6 +298,19 @@ let wasm_resource_dir () =
|
||||
let target_flags opts =
|
||||
match opts.target with
|
||||
| None -> []
|
||||
(* The browser target adds no flags to a compile at all: emcc already is the
|
||||
triple, the sysroot and the builtins. What it does need is to exist, and
|
||||
an emcc that is not there must be refused here — where the reason can name
|
||||
it — and not at the first "command not found" from a subshell. *)
|
||||
| Some t when is_web t ->
|
||||
if on_path emcc = None && not (Sys.file_exists emcc) then
|
||||
failwith
|
||||
(Printf.sprintf
|
||||
"web: no %s on PATH. The browser target is built by emscripten: \
|
||||
install an emsdk and source its emsdk_env.sh, or set FLAN_EMCC to \
|
||||
the emcc to use."
|
||||
emcc);
|
||||
[]
|
||||
| Some t when not (is_wasm t) -> [ "--target=" ^ t ]
|
||||
| Some t ->
|
||||
let sysroot = wasm_sysroot () in
|
||||
@ -285,23 +340,202 @@ let wasm_main_source =
|
||||
"int flan_entry(int argc, char **argv) __asm__(\"main\");\n\
|
||||
int __main_argc_argv(int argc, char **argv) { return flan_entry(argc, argv); }\n"
|
||||
|
||||
(* ── The browser: the main loop, the shell, and the link ─────────────
|
||||
The mechanism, and why it is not the one the old note predicted.
|
||||
|
||||
BUILT.md says a web build "drives the loop with [emscripten_set_main_loop]
|
||||
instead of a [while]. That is a different [main], not a different program."
|
||||
The first half is right about the browser and wrong about what it costs
|
||||
here: [emscripten_set_main_loop] wants the loop body as a callback, so every
|
||||
example that spells
|
||||
|
||||
(until (rl/window-should-close?) ...)
|
||||
|
||||
would have to be cut in half — an init and a tick — by hand, in every file,
|
||||
and the resulting program would no longer be the one that runs natively.
|
||||
|
||||
[-sASYNCIFY] is the other half of the same browser fact and costs none of
|
||||
that. It rewrites the module so a call can suspend and resume across a
|
||||
return to the event loop, and raylib's web platform is built for it:
|
||||
[WindowShouldClose] on PLATFORM_WEB is an [emscripten_sleep(16)] that then
|
||||
returns false (raylib 5.5, platforms/rcore_web.c). So the Flan [until] loop
|
||||
yields to the browser once a frame, at the call it already makes, and not
|
||||
one example changes. Same program, same [main], same source on both targets.
|
||||
|
||||
What it costs is real and worth writing down: asyncify instruments the whole
|
||||
module, which is roughly a doubling of code size and a measurable slowdown
|
||||
on the instrumented paths. It is applied to every web build rather than to
|
||||
the ones that block, because "does this program block" is not a question
|
||||
this file can answer and a flag set that varies per program is a cache key
|
||||
that varies per program. *)
|
||||
|
||||
let web_shell_default =
|
||||
{html|<!doctype html>
|
||||
<!-- The smallest page that makes a Flan web build openable: a canvas for
|
||||
raylib's GL context, and stdout on the page rather than only in the
|
||||
console, because a headless check and a human reader want the same text.
|
||||
emcc substitutes the module's JS for {{{ SCRIPT }}}; nothing else here is
|
||||
emscripten's. Replace it with FLAN_WEB_SHELL. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>flan</title>
|
||||
<style>
|
||||
html, body { margin: 0; background: #14161a; color: #d8dee9;
|
||||
font: 13px/1.5 ui-monospace, monospace; }
|
||||
#canvas { display: block; margin: 0 auto; background: #000;
|
||||
outline: none; }
|
||||
#out { white-space: pre-wrap; padding: 8px 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" tabindex="-1"
|
||||
oncontextmenu="event.preventDefault()"></canvas>
|
||||
<pre id="out"></pre>
|
||||
<script>
|
||||
var out = document.getElementById('out');
|
||||
var Module = {
|
||||
canvas: document.getElementById('canvas'),
|
||||
print: function (t) { out.textContent += t + '\n'; },
|
||||
printErr: function (t) { out.textContent += t + '\n'; },
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
||||
|html}
|
||||
|
||||
(* The shell is a string here rather than a file in the tree for the same
|
||||
reason [Runtime_src] is: it has to be present wherever the compiler is, and
|
||||
a build that cannot find its own shell is a build that fails for a reason
|
||||
nobody spelled. FLAN_WEB_SHELL replaces it. *)
|
||||
let web_shell_file () =
|
||||
match getenv "FLAN_WEB_SHELL" with
|
||||
| Some p when Sys.file_exists p -> p
|
||||
| Some p ->
|
||||
failwith (Printf.sprintf "web: FLAN_WEB_SHELL is %s, which does not exist" p)
|
||||
| None ->
|
||||
let p = Filename.concat (workdir ()) "flan-shell.html" in
|
||||
write p web_shell_default;
|
||||
p
|
||||
|
||||
(* The flags the browser target adds at the link, and only at the link.
|
||||
|
||||
ALLOW_MEMORY_GROWTH because the default heap is 16MB and a texture is not
|
||||
small. EXPORT_ES6=0 and the default MODULARIZE are left alone so that the
|
||||
.js is a plain script both the shell and node can run — which is what makes
|
||||
the test headless.
|
||||
|
||||
--shell-file only when the output is a page. Asking emcc for a .js and
|
||||
handing it an HTML shell is accepted and ignored, which is the kind of
|
||||
silence this file tries not to produce. *)
|
||||
let web_link_flags ~out =
|
||||
[ "-sASYNCIFY"; "-sALLOW_MEMORY_GROWTH=1" ]
|
||||
@ (if Filename.extension out = ".html" then
|
||||
[ "--shell-file"; Filename.quote (web_shell_file ()) ]
|
||||
else [])
|
||||
|
||||
(* ── A [link] line may be addressed to one target ────────────────────
|
||||
`vendor/raylib/link` names Fedora's libraylib.so.550, which exists on the
|
||||
host and nowhere else; the browser wants a static archive built by
|
||||
emscripten and three -s flags besides. So a line may carry a tag:
|
||||
|
||||
@native -l:libraylib.so.550
|
||||
@web ${FLAN_RAYLIB_WEB}
|
||||
|
||||
and an untagged line applies to every target, which is what every existing
|
||||
`link` file is.
|
||||
|
||||
The selection happens *here* and not in [Load], which is where the file is
|
||||
read, because [Load] resolves imports before a target is chosen — the same
|
||||
program is built for both — and a package's linker arguments are carried to
|
||||
this function as a flat list of strings. [Load] passing the lines through
|
||||
untouched is the whole of its part in this.
|
||||
|
||||
${NAME} expands from the environment. An unset one is refused by name: the
|
||||
archive a web build needs is built once by vendor/raylib/build-web.sh and
|
||||
lives at a path only that machine knows, and the alternative to naming it
|
||||
here is a linker error about GLFW symbols. *)
|
||||
|
||||
let link_tags = [ "native"; "wasi"; "web" ]
|
||||
|
||||
let target_tag opts =
|
||||
if web_target opts then "web"
|
||||
else if wasi_target opts then "wasi"
|
||||
else "native"
|
||||
|
||||
(* ${NAME} → the environment's NAME. Nothing else is substituted: this is not a
|
||||
shell, and a linker argument containing a $ that is not a ${ is left alone
|
||||
rather than guessed at. *)
|
||||
let expand_vars ~where s =
|
||||
let b = Buffer.create (String.length s) in
|
||||
let n = String.length s in
|
||||
let rec go i =
|
||||
if i >= n then ()
|
||||
else if i + 1 < n && s.[i] = '$' && s.[i + 1] = '{' then
|
||||
match String.index_from_opt s (i + 2) '}' with
|
||||
| None -> Buffer.add_char b s.[i]; go (i + 1)
|
||||
| Some j ->
|
||||
let name = String.sub s (i + 2) (j - i - 2) in
|
||||
(match getenv name with
|
||||
| Some v -> Buffer.add_string b v
|
||||
| None ->
|
||||
failwith
|
||||
(Printf.sprintf
|
||||
"%s: the linker argument %s wants %s, which is not set in the \
|
||||
environment"
|
||||
where s name));
|
||||
go (j + 1)
|
||||
else (Buffer.add_char b s.[i]; go (i + 1))
|
||||
in
|
||||
go 0;
|
||||
Buffer.contents b
|
||||
|
||||
let select_lflags opts flags =
|
||||
let want = target_tag opts in
|
||||
List.filter_map
|
||||
(fun f ->
|
||||
if String.length f > 0 && f.[0] = '@' then begin
|
||||
let tag, rest =
|
||||
match String.index_opt f ' ' with
|
||||
| Some i ->
|
||||
(String.sub f 1 (i - 1),
|
||||
String.trim (String.sub f (i + 1) (String.length f - i - 1)))
|
||||
| None -> (String.sub f 1 (String.length f - 1), "")
|
||||
in
|
||||
if not (List.mem tag link_tags) then
|
||||
failwith
|
||||
(Printf.sprintf
|
||||
"link: @%s is not a target — the tags are %s, and an untagged \
|
||||
line applies to all of them"
|
||||
tag
|
||||
(String.concat ", " (List.map (fun t -> "@" ^ t) link_tags)));
|
||||
if tag = want && rest <> "" then
|
||||
Some (expand_vars ~where:("link: @" ^ tag) rest)
|
||||
else None
|
||||
end
|
||||
else Some (expand_vars ~where:"link" f))
|
||||
flags
|
||||
|
||||
(* What the compiler itself is, cheaply: its path, size and mtime. A clang
|
||||
upgrade changes one of those, so the key changes with it — without paying a
|
||||
[clang --version] subprocess on every build, which would cost most of what
|
||||
the cache buys. *)
|
||||
let clang_stamp =
|
||||
lazy
|
||||
(let path =
|
||||
if Filename.is_relative clang then
|
||||
let dirs = String.split_on_char ':' (try Sys.getenv "PATH" with Not_found -> "") in
|
||||
(try List.find (fun d -> Sys.file_exists (Filename.concat d clang))
|
||||
dirs |> fun d -> Filename.concat d clang
|
||||
with Not_found -> clang)
|
||||
else clang
|
||||
in
|
||||
match Unix.stat path with
|
||||
| st -> Printf.sprintf "%s:%d:%f" path st.Unix.st_size st.Unix.st_mtime
|
||||
| exception Unix.Unix_error _ -> path)
|
||||
the cache buys. Taken of whichever compiler the target uses, so an emcc
|
||||
object and a clang object of the same source cannot collide. *)
|
||||
let stamp_of prog =
|
||||
let path =
|
||||
if Filename.is_relative prog then
|
||||
let dirs = String.split_on_char ':' (try Sys.getenv "PATH" with Not_found -> "") in
|
||||
(try List.find (fun d -> Sys.file_exists (Filename.concat d prog))
|
||||
dirs |> fun d -> Filename.concat d prog
|
||||
with Not_found -> prog)
|
||||
else prog
|
||||
in
|
||||
match Unix.stat path with
|
||||
| st -> Printf.sprintf "%s:%d:%f" path st.Unix.st_size st.Unix.st_mtime
|
||||
| exception Unix.Unix_error _ -> path
|
||||
|
||||
let clang_stamp = lazy (stamp_of clang)
|
||||
|
||||
(* Compile one C translation unit to an object file, reusing a cached one when
|
||||
the source text, the compiler and the flags are all unchanged. The key has
|
||||
@ -312,11 +546,12 @@ let compile_c ~opts ?tflags ~src ~name () =
|
||||
resource directory decide which headers and which builtins an object was
|
||||
built against, so repointing either must not serve a stale .o. *)
|
||||
let tflags = match tflags with Some f -> f | None -> target_flags opts in
|
||||
let cc = compiler opts in
|
||||
let key =
|
||||
Digest.to_hex
|
||||
(Digest.string
|
||||
(String.concat "\000"
|
||||
[ name; src; Lazy.force clang_stamp; opts.opt;
|
||||
[ name; src; stamp_of cc; opts.opt;
|
||||
String.concat " " (cflags opts);
|
||||
String.concat " " tflags ]))
|
||||
in
|
||||
@ -330,14 +565,14 @@ let compile_c ~opts ?tflags ~src ~name () =
|
||||
let tmp = Printf.sprintf "%s.%d.tmp" obj (Unix.getpid ()) in
|
||||
let cmd =
|
||||
String.concat " "
|
||||
([ Filename.quote clang; opts.opt ]
|
||||
([ Filename.quote cc; opts.opt ]
|
||||
@ cflags opts
|
||||
@ [ "-c" ] @ tflags
|
||||
@ [ Filename.quote c; "-o"; Filename.quote tmp ])
|
||||
in
|
||||
let code = Sys.command cmd in
|
||||
if code <> 0 then
|
||||
failwith (Printf.sprintf "%s failed (exit %d) on %s" clang code name);
|
||||
failwith (Printf.sprintf "%s failed (exit %d) on %s" cc code name);
|
||||
(try Unix.rename tmp obj with Unix.Unix_error _ -> ());
|
||||
(try Sys.remove c with Sys_error _ -> ())
|
||||
end;
|
||||
@ -351,10 +586,15 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
(* A dev build is the REPL's, and the REPL reaches a running process through
|
||||
[-rdynamic] and [dlopen]. Neither exists on wasm32, so the combination is
|
||||
refused rather than quietly producing a module nothing can attach to. *)
|
||||
(* Named for the target that was asked for, since two of them answer to
|
||||
[wasm_target] now and "wasm32:" on a --target=web build reads as a
|
||||
compiler that did not hear the question. *)
|
||||
let tname = if web_target opts then "web" else "wasm32" in
|
||||
if wasm_target opts && opts.dev then
|
||||
failwith
|
||||
"wasm32: --dev is native only — the reload path is dlopen, which wasm32 \
|
||||
has no equivalent of";
|
||||
(tname
|
||||
^ ": --dev is native only — the reload path is dlopen, which wasm has \
|
||||
no equivalent of");
|
||||
(* Refused rather than emitted-and-hoped-for. The member offsets in the DWARF
|
||||
are computed for the host's layout — [ptr] 8 bytes — and wasm32's pointer
|
||||
is 4, so a slice's [len] is at byte 8 there and at byte 16 here. Emitting
|
||||
@ -363,16 +603,26 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
keeps meeting at the FFI boundary. *)
|
||||
if wasm_target opts && opts.debug then
|
||||
failwith
|
||||
"wasm32: --debug is native only — the DWARF member offsets are computed \
|
||||
for the host's layout, and wasm32's 32-bit pointer moves every one of \
|
||||
them";
|
||||
(tname
|
||||
^ ": --debug is native only — the DWARF member offsets are computed \
|
||||
for the host's layout, and wasm32's 32-bit pointer moves every one \
|
||||
of them");
|
||||
(* There is no wasm32 sanitizer runtime to link against: clang accepts
|
||||
-fsanitize=address for the triple and the link fails on
|
||||
__asan_report_load4. Refused by name rather than met at the linker. *)
|
||||
(* emscripten does ship an ASan, so the web half of this refusal is weaker
|
||||
than the wasi half: it is untested here rather than known to be
|
||||
impossible. Refused all the same, because a sanitizer that has never been
|
||||
run is a sanitizer whose silence means nothing, and the sweep this project
|
||||
runs (@sanitize) is native. *)
|
||||
if wasm_target opts && opts.sanitize then
|
||||
failwith
|
||||
"wasm32: --sanitize is native only — there is no libclang_rt.asan for \
|
||||
wasm32-wasi to link against";
|
||||
(if web_target opts then
|
||||
"web: --sanitize is native only — emscripten has its own ASan, and \
|
||||
nothing here has ever run it; the sanitizer sweep is the native one"
|
||||
else
|
||||
"wasm32: --sanitize is native only — there is no libclang_rt.asan for \
|
||||
wasm32-wasi to link against");
|
||||
(* -O0 is not a choice a debug build offers: [llvm.dbg.declare] describes an
|
||||
alloca, and at -O2 mem2reg deletes the alloca. [sanitize] deliberately
|
||||
does not do this: see [opts]. *)
|
||||
@ -399,8 +649,11 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
let objs =
|
||||
cc Runtime_src.source "flan_rt.c"
|
||||
:: [ cc Runtime_src.dev_source "flan_dev.c" ]
|
||||
(* wasi-libc's entry point, which is not [main]. See [wasm_main_source]. *)
|
||||
@ (if wasm_target opts then [ cc wasm_main_source "flan_wasm_main.c" ] else [])
|
||||
(* wasi-libc's entry point, which is not [main]. See [wasm_main_source].
|
||||
Not the browser's: emscripten's start code calls [main] under that name,
|
||||
so the .ll's @main is already the entry point and the shim would be a
|
||||
second definition of it. *)
|
||||
@ (if wasi_target opts then [ cc wasm_main_source "flan_wasm_main.c" ] else [])
|
||||
(* The generated half of the FFI: one translation unit holding a typedef
|
||||
per struct that crosses and a wrapper per (declare-c ...), compiled
|
||||
exactly like a package's hand-written .c. It rides on the program
|
||||
@ -414,7 +667,7 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
in
|
||||
let cmd =
|
||||
String.concat " "
|
||||
([ Filename.quote clang; opts.opt; "-Wno-override-module" ]
|
||||
([ Filename.quote (compiler opts); opts.opt; "-Wno-override-module" ]
|
||||
(* -g at the link so clang does not strip, and keeps the object files'
|
||||
debug sections; the .ll carries its own. The sanitizer flags have to
|
||||
be here too — they are what pulls in libclang_rt.asan and the UBSan
|
||||
@ -424,9 +677,12 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
@ cflags opts
|
||||
@ (if opts.dev then [ "-rdynamic" ] else [])
|
||||
@ tflags
|
||||
@ (if web_target opts then web_link_flags ~out else [])
|
||||
@ [ Filename.quote ll ]
|
||||
@ List.map Filename.quote objs
|
||||
@ lflags
|
||||
(* A package's linker arguments, with the lines addressed to another
|
||||
target dropped and ${VAR} expanded. See [select_lflags]. *)
|
||||
@ select_lflags opts lflags
|
||||
(* The prelude declares sqrtf, so every link needs libm. It goes here
|
||||
and not in the leading flags: the default --as-needed drops a
|
||||
library named before the object that wants it, so at -O2 this would
|
||||
@ -480,8 +736,9 @@ let shared ?(opts = default) ~ir ~out () : timing =
|
||||
let opts = if opts.debug then { opts with opt = "-O0" } else opts in
|
||||
if wasm_target opts then
|
||||
failwith
|
||||
"wasm32: the reload path is native only — it is llc + ld -shared + \
|
||||
dlopen, and wasm32 has no dlopen";
|
||||
((if web_target opts then "web" else "wasm32")
|
||||
^ ": the reload path is native only — it is llc + ld -shared + dlopen, \
|
||||
and wasm has no dlopen");
|
||||
let dir = workdir () in
|
||||
let base = Filename.remove_extension (Filename.basename out) in
|
||||
let ll = Filename.concat dir (base ^ ".ll") in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user