Retyping a global across a reload, which nothing had ever done
flan_dev_global hands back the allocation it made the first time a name was asked for, and compares the size it recorded against the size it is asked for. Nothing exercised the comparison: v5 is v4 with extra as an i32, loaded on top of v3, and what it does is abort the process — so it gets a host run of its own. The message is asserted alongside the exit status, because a process that died for some other reason is not this guard firing and the status alone cannot tell them apart.
This commit is contained in:
parent
3ace7c262f
commit
aa0b799bb6
29
test/programs/reload-v5.flan
Normal file
29
test/programs/reload-v5.flan
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
;;;; v5 is v4 with one difference: [extra] is an i32 rather than an i64.
|
||||||
|
;;;;
|
||||||
|
;;;; [extra] does not exist in the host — v3 introduced it at run time, so its
|
||||||
|
;;;; storage was allocated by runtime/flan_dev.c the first time a module asked
|
||||||
|
;;;; for it, and every later module that mentions the name is handed back that
|
||||||
|
;;;; same allocation. Handing it back for a differently shaped type is layout
|
||||||
|
;;;; drift: the new body would read and write at offsets the old allocation was
|
||||||
|
;;;; never laid out for, and nothing downstream could ever say so. The registry
|
||||||
|
;;;; compares the size it recorded against the size it is asked for and aborts
|
||||||
|
;;;; instead — retyping a var needs a restart, and this is the file that says
|
||||||
|
;;;; the guard is real.
|
||||||
|
;;;;
|
||||||
|
;;;; Loaded on top of v3, and never alongside v4: the process it aborts is the
|
||||||
|
;;;; whole of the test.
|
||||||
|
(defvar counter i64)
|
||||||
|
(defvar extra i32)
|
||||||
|
|
||||||
|
(defn helper [x i64] i64 (* x 2))
|
||||||
|
|
||||||
|
(defn added [] i32
|
||||||
|
(set extra (+ extra 100))
|
||||||
|
extra)
|
||||||
|
|
||||||
|
(defn bump [] i64
|
||||||
|
(println "v5")
|
||||||
|
(set counter (+ counter (i64 (added))))
|
||||||
|
(helper counter))
|
||||||
|
|
||||||
|
(defn outer [] i64 (bump))
|
||||||
@ -82,6 +82,10 @@ let () =
|
|||||||
not exist. This is the C-c C-k unit. *)
|
not exist. This is the C-c C-k unit. *)
|
||||||
let so3, ir3, _, _ = module_of p3 [ "bump"; "added" ] "v3.so" in
|
let so3, ir3, _, _ = module_of p3 [ "bump"; "added" ] "v3.so" in
|
||||||
let so4, ir4, _, _ = module_of p4 [ "added" ] "v4.so" in
|
let so4, ir4, _, _ = module_of p4 [ "added" ] "v4.so" in
|
||||||
|
(* v5 retypes [extra], which v3 introduced at run time. It is built here
|
||||||
|
and loaded in a process of its own below: what it does is abort. *)
|
||||||
|
let p5 = checked "programs/reload-v5.flan" in
|
||||||
|
let so5, _, _, _ = module_of p5 [ "added" ] "v5.so" in
|
||||||
|
|
||||||
(* A redefinition module must not define what the host already owns:
|
(* A redefinition module must not define what the host already owns:
|
||||||
defining [counter] would give the loaded object a private copy and the
|
defining [counter] would give the loaded object a private copy and the
|
||||||
@ -196,6 +200,32 @@ let () =
|
|||||||
if code <> 0 || text <> want then
|
if code <> 0 || text <> want then
|
||||||
fail "reload\n got: %S (exit %d)\n wanted: %S" text code want;
|
fail "reload\n got: %S (exit %d)\n wanted: %S" text code want;
|
||||||
|
|
||||||
|
(* 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,
|
||||||
|
and every later module asking for that name is handed the same
|
||||||
|
allocation back. v5 asks for it as an i32. Handing back eight bytes for
|
||||||
|
a four-byte type is not an error anything downstream can detect — the
|
||||||
|
new body simply reads fields at offsets the allocation was never laid
|
||||||
|
out for — so the registry compares sizes and dies at the first chance
|
||||||
|
it has.
|
||||||
|
|
||||||
|
Asserted on the message as well as on the exit status: a process that
|
||||||
|
died for some other reason is not this guard firing, and the exit code
|
||||||
|
alone cannot tell the two apart. *)
|
||||||
|
let out5 = tmp "out5" and err5 = tmp "err5" in
|
||||||
|
let code5 =
|
||||||
|
Sys.command
|
||||||
|
(Printf.sprintf "%s %s %s %s > %s 2> %s" (Filename.quote host)
|
||||||
|
(Filename.quote so1) (Filename.quote so3) (Filename.quote so5)
|
||||||
|
(Filename.quote out5) (Filename.quote err5))
|
||||||
|
in
|
||||||
|
let said = In_channel.with_open_bin err5 In_channel.input_all in
|
||||||
|
if code5 = 0 then
|
||||||
|
fail "a global retyped across a reload was accepted (exit 0)";
|
||||||
|
if not (has said "size changed") then
|
||||||
|
fail "a retyped global did not stop on the size guard: %S" said;
|
||||||
|
|
||||||
Printf.printf
|
Printf.printf
|
||||||
"reload: emit %.1fms llc %.1fms ld %.1fms (v2: emit %.1fms llc %.1fms ld %.1fms) host run %.1fms\n"
|
"reload: emit %.1fms llc %.1fms ld %.1fms (v2: emit %.1fms llc %.1fms ld %.1fms) host run %.1fms\n"
|
||||||
emit_ms t1.Build.llc_ms t1.Build.link_ms emit2_ms t2.Build.llc_ms
|
emit_ms t1.Build.llc_ms t1.Build.link_ms emit2_ms t2.Build.llc_ms
|
||||||
@ -203,7 +233,7 @@ let () =
|
|||||||
print_string timings;
|
print_string timings;
|
||||||
|
|
||||||
List.iter (fun p -> try Sys.remove p with Sys_error _ -> ())
|
List.iter (fun p -> try Sys.remove p with Sys_error _ -> ())
|
||||||
[ host; so1; so2; so3; so4; out; tmp "err" ];
|
[ host; so1; so2; so3; so4; so5; out; out5; err5; tmp "err" ];
|
||||||
if !failures = 0 then print_endline "reload: all tests passed"
|
if !failures = 0 then print_endline "reload: all tests passed"
|
||||||
else begin
|
else begin
|
||||||
Printf.printf "\n%d failure(s)\n" !failures;
|
Printf.printf "\n%d failure(s)\n" !failures;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user