A whole program goes through the hand-written backend and runs
x86.ml was an encoder and a frame model with nothing calling it. It now lowers a whole Tast.program to an assembly file, and `flan build --x86` hands that file to the same clang invocation the LLVM path uses, against the same runtime objects. The flag is off by default; LLVM stays the release backend and the default one. Three programs, built both ways and compared by what they print and what they exit with rather than by reading bytes: exit 0; a dotimes that prints; and a fizz over a call, an if, a remainder and two string literals. All three agree with the LLVM build. The measurement decided the target. hist.ml over the fizz program shows no Signal, no Handled, no RestartCase — a loop that prints does not drag conditions in. What does is the bounds check and the allocator, and neither is in the reachable set of a program that prints a number. That is why there is no transfer guard here, and check_no_transfer is what makes the omission sound rather than hopeful: if nothing reachable can write the channel, no call can return with it set. It is a whole-program property, so it is checked once per build and the build stops with the node's name when it fails.
This commit is contained in:
parent
68625a535e
commit
2155c41465
15
bin/main.ml
15
bin/main.ml
@ -100,8 +100,16 @@ let sanitize_flag = "--sanitize"
|
||||
preference, and it goes away with the transport it drives. *)
|
||||
let two_process_flag = "--two-process"
|
||||
|
||||
(* The hand-written x86-64 backend (lib/x86.ml) instead of LLVM. The dev
|
||||
backend from DISCUSS.md item 15, off by default and named explicitly:
|
||||
LLVM stays the release path and the default one. It covers a subset of the
|
||||
IR and refuses the rest by name, so a build that succeeds is one it really
|
||||
compiled. *)
|
||||
let x86_flag = "--x86"
|
||||
|
||||
let flags =
|
||||
[ no_checks_flag; dev_flag; debug_flag; sanitize_flag; two_process_flag ]
|
||||
[ no_checks_flag; dev_flag; debug_flag; sanitize_flag; two_process_flag;
|
||||
x86_flag ]
|
||||
|
||||
(* [--target=wasm32-wasi] and [--target=web], the two cross targets. Unlike
|
||||
the flags above, a target
|
||||
@ -422,6 +430,7 @@ let () =
|
||||
let dev = List.mem dev_flag rest in
|
||||
let debug = List.mem debug_flag rest in
|
||||
let sanitize = List.mem sanitize_flag rest in
|
||||
let x86 = List.mem x86_flag rest in
|
||||
let target = target_of rest in
|
||||
let out =
|
||||
match List.filter (fun a -> not (is_flag a)) rest with
|
||||
@ -453,7 +462,7 @@ let () =
|
||||
let p, csrcs, lflags = Flan.Reach.link ~dev l p in
|
||||
ignore (Flan.Build.executable
|
||||
~opts:{ Flan.Build.default with checks; dev; debug; sanitize;
|
||||
target }
|
||||
target; x86 }
|
||||
~csrcs ~lflags ~pnames:(if debug then param_names l else [])
|
||||
p ~out))
|
||||
(* The daemon an editor talks to: one session, the program it belongs to
|
||||
@ -536,7 +545,7 @@ let () =
|
||||
\ flan import-c <header.h> [package.flan...] [clang flags...]\n\
|
||||
\ flan generate-c <package-dir>\n\
|
||||
\ flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] \
|
||||
[--debug] [--sanitize] [--target=wasm32-wasi|web]\n\
|
||||
[--debug] [--sanitize] [--x86] [--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]";
|
||||
|
||||
30
lib/build.ml
30
lib/build.ml
@ -147,6 +147,13 @@ type opts = {
|
||||
language's arithmetic means; without the exclusion every program
|
||||
trips on its first [+]. Nothing else is excluded. *)
|
||||
sanitize : bool;
|
||||
(* The dev backend: lower the typed IR to x86-64 assembly here instead of
|
||||
handing LLVM IR to clang. Off by default and off everywhere but the one
|
||||
flag that asks for it — LLVM stays the release backend and the default
|
||||
one. It refuses rather than degrades: a program holding a node [x86.ml]
|
||||
does not lower yet stops the build with that node's name, so a build that
|
||||
succeeds is one this backend really compiled. *)
|
||||
x86 : bool;
|
||||
}
|
||||
|
||||
(* Checks are deliberately independent of [opt]: the acceptance table runs the
|
||||
@ -155,7 +162,7 @@ type opts = {
|
||||
checks. Dropping them is a release decision, not an optimisation one. *)
|
||||
let default =
|
||||
{ target = None; opt = "-O2"; keep = false; checks = true; dev = false;
|
||||
debug = false; sanitize = false }
|
||||
debug = false; sanitize = false; x86 = false }
|
||||
|
||||
(* The flags that are neither [opt] nor the target, spelled once so that the
|
||||
compile command and the object-cache key cannot disagree. They did before:
|
||||
@ -725,11 +732,26 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
|
||||
does not do this: see [opts]. *)
|
||||
let opts = if opts.debug then { opts with opt = "-O0" } else opts in
|
||||
let tflags = target_flags opts in
|
||||
if opts.x86 && (wasm_target opts || opts.dev || opts.debug || opts.sanitize)
|
||||
then
|
||||
failwith
|
||||
"--x86 is the native dev backend on its own: it emits no DWARF, has no \
|
||||
indirection cells for a REPL to redefine through, and there is no \
|
||||
sanitizer pass over hand-written assembly";
|
||||
let dir = workdir () in
|
||||
let ll = Filename.concat dir (Filename.basename out ^ ".ll") in
|
||||
(* The one fork in this function. The x86 backend hands clang an assembly
|
||||
file where LLVM hands it IR text; clang takes either on its command line,
|
||||
so everything past this point — the runtime objects, the shim, the
|
||||
package C, the linker arguments — is the same build. *)
|
||||
let ll =
|
||||
Filename.concat dir
|
||||
(Filename.basename out ^ if opts.x86 then ".s" else ".ll")
|
||||
in
|
||||
write ll
|
||||
(Emit.program ~checks:opts.checks ~dev:opts.dev ~debug:opts.debug ~pnames
|
||||
~sanitize:opts.sanitize p);
|
||||
(if opts.x86 then X86.program p
|
||||
else
|
||||
Emit.program ~checks:opts.checks ~dev:opts.dev ~debug:opts.debug ~pnames
|
||||
~sanitize:opts.sanitize p);
|
||||
(* [flan_dev.c] is compiled into every build, not only a dev one. Nothing in
|
||||
a release build calls into it — the compiler only emits a registry lookup
|
||||
for a name the host was not built with, which cannot arise without cells —
|
||||
|
||||
1095
lib/x86.ml
1095
lib/x86.ml
File diff suppressed because it is too large
Load Diff
2
spike/x86/p1-exit.flan
Normal file
2
spike/x86/p1-exit.flan
Normal file
@ -0,0 +1,2 @@
|
||||
(defn main [] i32
|
||||
0)
|
||||
5
spike/x86/p2-loop-print.flan
Normal file
5
spike/x86/p2-loop-print.flan
Normal file
@ -0,0 +1,5 @@
|
||||
(defn main [] i32
|
||||
(dotimes [i 5]
|
||||
(print i)
|
||||
(println ""))
|
||||
0)
|
||||
11
spike/x86/p3-fizz.flan
Normal file
11
spike/x86/p3-fizz.flan
Normal file
@ -0,0 +1,11 @@
|
||||
(defn fizz? [n i32] bool
|
||||
(= 0 (% n 3)))
|
||||
|
||||
(defn main [] i32
|
||||
(dotimes [i 15]
|
||||
(let [n (+ i 1)]
|
||||
(if (fizz? n)
|
||||
(print "fizz")
|
||||
(print n))
|
||||
(println "")))
|
||||
0)
|
||||
Loading…
x
Reference in New Issue
Block a user