diff --git a/lib/x86.ml b/lib/x86.ml index 904bfe3..fc07916 100644 --- a/lib/x86.ml +++ b/lib/x86.ml @@ -611,9 +611,8 @@ let addr_into f ~reg (l : loc) = load_int f.b ~dst:reg ~mm:(Frame p) ~size:8 ~signed:false; if a <> 0 then add_imm f.b ~dst:reg a -(* The three spellings of "name a symbol", each picking the pc-relative form +(* The spellings of "name a symbol", each picking the pc-relative form for a symbol this object defines and the GOT form for one it does not. *) -let sym_mem f s = if f.ext s then Got s else Sym (s, 0) let sym_loc f s = if f.ext s then Lgot (s, 0) else Lg (s, 0) (* [lea] of a symbol is an address; out of the GOT the address is already @@ -622,6 +621,19 @@ let addr_sym f ~dst s = if f.ext s then load_int f.b ~dst ~mm:(Got s) ~size:8 ~signed:false else lea f.b ~dst ~mm:(Sym (s, 0)) +(* Read what a global holds, as opposed to where it is -- an indirection cell + is the only caller. Out of the GOT that is two loads, not one: the slot + holds the cell's *address*. Collapsing them was this lane's one real bug, + and it looked exactly right in the disassembly: [mov r11, cell@GOTPCREL(%rip)] + beside [call *%r11] reads as "call through the cell" and in fact calls the + cell. DISCUSS.md item 15 said this is how hand-encoding fails. *) +let load_sym f ~dst s = + if f.ext s then begin + load_int f.b ~dst ~mm:(Got s) ~size:8 ~signed:false; + load_int f.b ~dst ~mm:(Reg (dst, 0)) ~size:8 ~signed:false + end + else load_int f.b ~dst ~mm:(Sym (s, 0)) ~size:8 ~signed:false + let scalar_size f (t : Types.t) = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) @@ -1005,7 +1017,7 @@ and lower_at f (e : Tast.expr) (dst : loc) : unit = address is in a slot there is nothing left to re-resolve. *) | Tast.FnAddr (Tast.Fnval n) -> if f.md.Emit.dev then - load_int f.b ~dst:rax ~mm:(sym_mem f (csym n)) ~size:8 ~signed:false + load_sym f ~dst:rax (csym n) else addr_sym f ~dst:rax (fsym n); store_int f.b ~src:rax ~mm:(lmem f dst ~scratch:r11) ~size:8 | Tast.FnAddr (Tast.Rtfn n) -> @@ -1816,7 +1828,7 @@ and call_flan f ~target ~args ~rty dst = (match callee with | `Sym s -> call_sym f.b s | `Cell s -> - load_int f.b ~dst:r11 ~mm:(sym_mem f s) ~size:8 ~signed:false; + load_sym f ~dst:r11 s; call_r f.b r11 | `Loc o -> load_int f.b ~dst:r11 ~mm:(Frame o) ~size:8 ~signed:false; @@ -2607,7 +2619,11 @@ let program ~checks ?(dev = false) (p : Tast.program) : string = Buffer.add_string rodata gr; (match List.find_opt (fun (f : Tast.fn) -> f.Tast.name = "main") p.Tast.fns with | Some fn -> Buffer.add_string text (emit_main md fn) - | None -> unsupported "no main"); + (* No [main] is not an error, and [emit.ml] treats it the same way: a + program can be linked against a C host that brings its own entry point, + which is what [reload_host.c] is. Refusing here made a --x86 host for the + reload tests impossible to build. *) + | None -> ()); let out = Buffer.create 65536 in Buffer.add_buffer out text; (* The globals' initialiser runs before main, through the same constructor diff --git a/test/test_reload.ml b/test/test_reload.ml index acf64fa..626f0a3 100644 --- a/test/test_reload.ml +++ b/test/test_reload.ml @@ -200,6 +200,58 @@ let () = if code <> 0 || text <> want then fail "reload\n got: %S (exit %d)\n wanted: %S" text code want; + (* The same thing again, compiled by the dev backend end to end (x86.ml's + header, HANDOFF-x86-rt.md item 1). Both halves, host and module, because + the two backends' conventions agree on every scalar and disagree on + every aggregate: an LLVM-built module dlopened into an --x86 host would + be correct until the first redefined function took or returned a struct. + So an --x86 host gets --x86 modules and the two never meet. + + Only v1 and v2, which is the whole of what X86.redefinition compiles: + every name they touch is one the host was built with. v3 and v4 + introduce a function and a global at run time, and the flan_dev_cell / + flan_dev_global lookups that needs are refused there by name. + + Read by running, not by reading. A disassembly reads correctly beside a + wrong answer often enough (DISCUSS.md item 15) that only the printed + transcript settles it: [outer] is compiled once into the host and never + rebuilt, so "after2 1204" can only mean its call site followed a body + that this backend emitted, published through a cell it reached via the + GOT. *) + let x86 = { dev with Build.x86 = true } in + let xhost = tmp "xhost" in + ignore + (Build.executable ~opts:x86 ~csrcs:[ "reload_host.c" ] + ~lflags:[ "-ldl" ] p1 ~out:xhost); + let xmodule q fns name = + let o = tmp name in + let asm = X86.redefinition ~checks:true ~dev:true ~known q ~fns in + ignore (Build.shared_x86 ~opts:x86 ~asm ~out:o ()); + o + in + let xso1 = xmodule p1 [ "bump" ] "xv1.so" in + let xso2 = xmodule p2 [ "bump" ] "xv2.so" in + let xout = tmp "xout" in + let xcode = + Sys.command + (Printf.sprintf "%s %s %s > %s 2> %s" (Filename.quote xhost) + (Filename.quote xso1) (Filename.quote xso2) (Filename.quote xout) + (Filename.quote (tmp "xerr"))) + in + let xtext = In_channel.with_open_bin xout In_channel.input_all in + let xwant = + "v1\nhost 2\nv1\nafter1 4\n" ^ v2s ^ "after2 1204\ncounter 102\n" + in + if xcode <> 0 || xtext <> xwant then + fail "x86 reload\n got: %S (exit %d)\n wanted: %S" xtext xcode + xwant; + (* A name the host was never built with has no symbol to bind to, and the + registry path is not built here. It has to refuse rather than emit + something that links and then stores through a null. *) + (match X86.redefinition ~checks:true ~dev:true ~known p3 ~fns:[ "added" ] with + | _ -> fail "x86 redefinition accepted a name the host does not have" + | exception X86.Unsupported _ -> ()); + (* The layout-drift guard, which needs a process of its own because what it does is abort one. [extra] does not exist in the host: v3 introduced it at run time, so flan_dev.c allocated its storage and recorded its size,