vendor/agent/ is a package like any other - agent.flan declares three calls, flan_agent.c implements them, link asks for -lpthread. start listens on a unix socket, poll installs whatever arrived and says how many, wait does the same after waiting for something. The split between poll and the listener is the whole design. dlopen relocates a module and takes the loader lock, which is milliseconds and unbounded, so it happens on the listener thread. flan_reload_install is one store per function and must not land while a redefined function is on the stack, so it happens on the game thread at the top of the frame, when the program asks. A ring and two atomics connect them; the game thread never blocks on the loader. wait exists for tests. A test that races the frame rate fails on a loaded machine, so test/programs/agent.flan waits for the reload rather than sleeping past it. It also sends a junk path first: the daemon is a separate process and can send anything, and a bad path must be refused rather than take down the program it was sent to. Two things came out of running it. The reply goes out before the module is queued, because the other way round the game thread can install and the program can exit between the two, and the answer reaches the sender as a connection reset instead of as ok. And ok means queued, not installed - the sender does not get to know when the swap happened, since only the program knows when it is between frames. sand.flan now polls at the top of its loop, which is what this step was for. Under Xvfb, one line on the socket and 455 consecutive frames drew from a game-draw that did not exist when the process started. Building without --dev still works: there are no cells, so a module is refused on the listener thread and the loop never notices. flan reload builds one module the way the daemon will. --new names what the host was not built with, which is the one thing the command cannot work out for itself and exactly what the session will track.
166 lines
6.4 KiB
OCaml
166 lines
6.4 KiB
OCaml
(* flan — milestone 2 driver. *)
|
|
|
|
let with_errors path f =
|
|
try f () with
|
|
| Flan.Loc.Error (loc, msg) ->
|
|
Printf.eprintf "%s: %s\n" (Flan.Loc.to_string loc) msg;
|
|
ignore path;
|
|
exit 1
|
|
|
|
let summarise (d : Flan.Ast.decl) =
|
|
let open Flan.Ast in
|
|
match d.d with
|
|
| Package n -> Printf.sprintf "package %s" n
|
|
| Import (a, p) -> Printf.sprintf "import %s %S" a p
|
|
| Defalias (n, _) -> Printf.sprintf "defalias %s" n
|
|
| Defstruct (n, fs) -> Printf.sprintf "defstruct %s (%d fields)" n (List.length fs)
|
|
| Defunion (n, vs) -> Printf.sprintf "defunion %s (%d cases)" n (List.length vs)
|
|
| Defvar (n, _, _) -> Printf.sprintf "defvar %s" n
|
|
| Defconst (n, _, _) -> Printf.sprintf "defconst %s" n
|
|
| Declare (fn, csym) ->
|
|
Printf.sprintf "declare %s (%d params) = %s" fn.name (List.length fn.params)
|
|
csym
|
|
| Defenum (n, ms) -> Printf.sprintf "defenum %s (%d members)" n (List.length ms)
|
|
| Defn fn ->
|
|
Printf.sprintf "defn %s (%d params, %s return, %d body forms)"
|
|
fn.name (List.length fn.params)
|
|
(match fn.ret with None -> "Unit" | Some _ -> "explicit")
|
|
(List.length fn.fbody)
|
|
|
|
(* Every path past [parse] goes through [Load]: an import is resolved into the
|
|
declarations it stands for, and the package's C shim and linker arguments
|
|
come back with them. *)
|
|
let load path : Flan.Load.t =
|
|
Flan.Load.program ~file:path (Flan.Parse.program (Flan.Reader.read_file path))
|
|
|
|
let checked path = Flan.Check.program (load path).decls
|
|
|
|
(* Bounds checks are on unless a build asks for them off — the release
|
|
decision, not the optimisation level (NEXT.md, Bounds checks). *)
|
|
let no_checks_flag = "--no-bounds-checks"
|
|
|
|
(* A dev build is the one a REPL can attach to: every call goes through a cell
|
|
so a redefinition can be installed, and the cells and globals are exported
|
|
so a loaded module can reach them (NEXT.md, the dev loop). *)
|
|
let dev_flag = "--dev"
|
|
|
|
let flags = [ no_checks_flag; dev_flag ]
|
|
|
|
let () =
|
|
match Array.to_list Sys.argv with
|
|
| _ :: "read" :: files when files <> [] ->
|
|
List.iter
|
|
(fun path ->
|
|
with_errors path (fun () ->
|
|
Flan.Reader.read_file path
|
|
|> List.iter (fun f -> print_endline (Flan.Form.to_string f))))
|
|
files
|
|
| _ :: "parse" :: files when files <> [] ->
|
|
List.iter
|
|
(fun path ->
|
|
with_errors path (fun () ->
|
|
Flan.Reader.read_file path
|
|
|> Flan.Parse.program
|
|
|> List.iter (fun d -> print_endline (summarise d))))
|
|
files
|
|
| _ :: "check" :: files when files <> [] ->
|
|
List.iter
|
|
(fun path ->
|
|
with_errors path (fun () ->
|
|
let p = checked path in
|
|
List.iter
|
|
(fun (g : Flan.Tast.global) ->
|
|
Printf.printf "%s %s %s\n"
|
|
(if g.gconst then "defconst" else "defvar")
|
|
g.gname (Flan.Types.to_string g.gty))
|
|
p.globals;
|
|
List.iter
|
|
(fun (f : Flan.Tast.fn) ->
|
|
Printf.printf "defn %s : (Fn [%s] %s) %d slots\n" f.name
|
|
(String.concat " "
|
|
(List.map Flan.Types.to_string f.params))
|
|
(Flan.Types.to_string f.ret) (Array.length f.slots))
|
|
p.fns))
|
|
files
|
|
| _ :: "emit" :: args when List.exists (fun a -> not (List.mem a flags)) args ->
|
|
let checks = not (List.mem no_checks_flag args) in
|
|
let dev = List.mem dev_flag args in
|
|
let files = List.filter (fun a -> not (List.mem a flags)) args in
|
|
List.iter
|
|
(fun path ->
|
|
with_errors path (fun () ->
|
|
checked path |> Flan.Emit.program ~checks ~dev |> print_string))
|
|
files
|
|
| _ :: "build" :: path :: rest ->
|
|
let checks = not (List.mem no_checks_flag rest) in
|
|
let dev = List.mem dev_flag rest in
|
|
let out =
|
|
match List.filter (fun a -> not (List.mem a flags)) rest with
|
|
| [ "-o"; o ] -> o
|
|
| [] -> Filename.remove_extension (Filename.basename path)
|
|
| _ ->
|
|
prerr_endline
|
|
"usage: flan build <file.flan> [-o out] [--no-bounds-checks] [--dev]";
|
|
exit 2
|
|
in
|
|
with_errors path (fun () ->
|
|
let l = load path in
|
|
let p = Flan.Check.program l.decls in
|
|
ignore (Flan.Build.executable
|
|
~opts:{ Flan.Build.default with checks; dev }
|
|
~csrcs:l.csrcs ~lflags:l.lflags p ~out))
|
|
(* One redefinition, built the way the daemon will build it: the forms named
|
|
become a module the running process can install. [--new] is the names the
|
|
host was *not* built with, which is the one thing this command cannot work
|
|
out for itself — a session tracks it, a CLI has to be told. *)
|
|
| _ :: "reload" :: path :: rest when rest <> [] ->
|
|
let rec split acc out news = function
|
|
| "-o" :: o :: r -> split acc (Some o) news r
|
|
| "--new" :: n :: r ->
|
|
split acc out (news @ String.split_on_char ',' n) r
|
|
| f :: r -> split (acc @ [ f ]) out news r
|
|
| [] -> (acc, out, news)
|
|
in
|
|
let fns, out, news = split [] None [] rest in
|
|
let out =
|
|
match out with
|
|
| Some o -> o
|
|
| None -> Filename.remove_extension (Filename.basename path) ^ ".so"
|
|
in
|
|
if fns = [] then begin
|
|
prerr_endline
|
|
"usage: flan reload <file.flan> <fn>... [-o out.so] [--new name,...]";
|
|
exit 2
|
|
end;
|
|
with_errors path (fun () ->
|
|
let p = Flan.Check.program (load path).decls in
|
|
let known n = not (List.exists (String.equal n) news) in
|
|
let opts = { Flan.Build.default with dev = true } in
|
|
let t =
|
|
Flan.Build.shared ~opts
|
|
~ir:(Flan.Emit.redefinition ~dev:true ~known p ~fns) ~out ()
|
|
in
|
|
Printf.eprintf "%s llc %.1fms ld %.1fms\n" out t.Flan.Build.llc_ms
|
|
t.Flan.Build.link_ms)
|
|
| _ :: "run" :: path :: args ->
|
|
with_errors path (fun () ->
|
|
let exe =
|
|
Filename.concat (Filename.get_temp_dir_name ())
|
|
(Printf.sprintf "flan-run-%d" (Unix.getpid ()))
|
|
in
|
|
let l = load path in
|
|
let p = Flan.Check.program l.decls in
|
|
ignore (Flan.Build.executable ~csrcs:l.csrcs ~lflags:l.lflags p ~out:exe);
|
|
let code =
|
|
Sys.command (String.concat " " (List.map Filename.quote (exe :: args)))
|
|
in
|
|
(try Sys.remove exe with Sys_error _ -> ());
|
|
exit code)
|
|
| _ ->
|
|
prerr_endline
|
|
"usage: flan (read|parse|check|emit) <file.flan>...\n\
|
|
\ flan build <file.flan> [-o out] [--no-bounds-checks] [--dev]\n\
|
|
\ flan run <file.flan> [args...]\n\
|
|
\ flan reload <file.flan> <fn>... [-o out.so] [--new name,...]";
|
|
exit 2
|