Reimplement the static analysis that supports poll point insertion#6
Reimplement the static analysis that supports poll point insertion#6xavierleroy wants to merge 6 commits intodamiendoligez:safepointsfrom
Conversation
Following the dataflow approach discussed with @damiendoligez.
damiendoligez
left a comment
There was a problem hiding this comment.
Nice code, except that there's a bug, and then it inserts too many polls: one for every handler that's in a nonallocating loop, even if polling in one of them would be enough. For example, in
let f x =
while true do
while true do () done
doneyou will insert poll calls in both loops, even though one poll in the inner loop is enough.
It would probably be better to mix the analysis with the poll insertions (or the decisions to insert, at least) so that, when you insert a poll in a loop, you can continue the analysis with the knowledge that this loop will poll. Of course correctness will be harder to establish...
| | Iop (Icall_ind | Icall_imm _) -> (* TODO: make sure we can ignore exn *) | ||
| before i.next end_ exn |
There was a problem hiding this comment.
Isn't it obvious that this should be treated like an operation that can raise, like the very next case?
There was a problem hiding this comment.
Or it could be treated like an Ialloc, on the assumption that the called function polls "enough". I'm confused.
| ignore (before funbody true true); | ||
| get_handler |
There was a problem hiding this comment.
| ignore (before funbody true true); | |
| get_handler | |
| ignore (before funbody true true); | |
| get_handler |
| if handler_ok n then | ||
| i | ||
| else | ||
| Mach.instr_cons (Iop (Ipoll { return_label = None })) [||] [||] i |
There was a problem hiding this comment.
As soon as a handler is "not-OK", you're going to insert a poll before every jump to this handler (even from the body of the catch, which cannot be part of the loop). It would be better to simply insert the poll once at the top of the handler itself.
| List.iter | ||
| (fun (n, h) -> set_handler n (before h end_ exn)) | ||
| handlers; | ||
| before body end_ exn |
There was a problem hiding this comment.
There's a bug here: you can't just ignore i.next, it might contain a non-allocating loop.
| List.iter | |
| (fun (n, h) -> set_handler n (before h end_ exn)) | |
| handlers; | |
| before body end_ exn | |
| let join = before i.next end_ exn in | |
| List.iter | |
| (fun (n, h) -> set_handler n (before h join exn)) | |
| handlers; | |
| before body join exn |
| let update changed (n, h) = | ||
| let b0 = get_handler n in | ||
| let b1 = before h end_ exn in | ||
| if b1 = b0 then changed else (set_handler n b1; true) in | ||
| while List.fold_left update false handlers do () done; | ||
| before body end_ exn |
There was a problem hiding this comment.
| let update changed (n, h) = | |
| let b0 = get_handler n in | |
| let b1 = before h end_ exn in | |
| if b1 = b0 then changed else (set_handler n b1; true) in | |
| while List.fold_left update false handlers do () done; | |
| before body end_ exn | |
| let join = before i.next end_ exn in | |
| let update changed (n, h) = | |
| let b0 = get_handler n in | |
| let b1 = before h join exn in | |
| if b1 = b0 then changed else (set_handler n b1; true) in | |
| while List.fold_left update false handlers do () done; | |
| before body join exn |
| | Iexit n -> | ||
| get_handler n | ||
| | Itrywith(body, handler) -> | ||
| before body end_ (before handler end_ exn) |
There was a problem hiding this comment.
| before body end_ (before handler end_ exn) | |
| let join = before i.next end_ exn in | |
| before body join (before handler join exn) |
| let join = before i.next end_ exn in | ||
| Array.exists (fun i -> before i join exn) branches | ||
| | Icatch (Cmm.Nonrecursive, handlers, body) -> | ||
| List.iter |
There was a problem hiding this comment.
Same bug as in polled_loops_analysis.
| next = instr i.next; | ||
| } | ||
| | Icatch (Nonrecursive, hdl, body) -> | ||
| | Icatch (rc, hdl, body) -> |
There was a problem hiding this comment.
Because you ignore the rec_flag, you will insert too many polls. For example, in nonrecursive catch handlers that are followed by a nonallocating loop:
let f x y z t =
begin match x with
| 0 when y -> z
| _ -> t
end;
while true do ()
doneHere the catch handler of the match gets an unnecessary poll (after fixing the bug).
There was a problem hiding this comment.
You're right, the rec_flag should be taken into account, and polls should only go on "true" back-edges, not all jumps to handlers.
|
@xavierleroy Don't fix your code just yet, I'm implementing my own suggestions now. |
|
Why don't you merge the parts you like in your previous proposal? It will be easier to work on a consolidated version. |
…l#13294) The toplevel printer detects cycles by keeping a hashtable of values that it has already traversed. However, some OCaml runtime types (at least bigarrays) may be partially uninitialized, and hashing them at arbitrary program points may read uninitialized memory. In particular, the OCaml testsuite fails when running with a memory-sanitizer enabled, as bigarray printing results in reads to uninitialized memory: ``` ==133712==WARNING: MemorySanitizer: use-of-uninitialized-value #0 0x4e6d11 in caml_ba_hash /var/home/edwin/git/ocaml/runtime/bigarray.c:486:45 #1 0x52474a in caml_hash /var/home/edwin/git/ocaml/runtime/hash.c:251:35 #2 0x599ebf in caml_interprete /var/home/edwin/git/ocaml/runtime/interp.c:1065:14 #3 0x5a909a in caml_main /var/home/edwin/git/ocaml/runtime/startup_byt.c:575:9 #4 0x540ccb in main /var/home/edwin/git/ocaml/runtime/main.c:37:3 #5 0x7f0910abb087 in __libc_start_call_main (/lib64/libc.so.6+0x2a087) (BuildId: 8f53abaad945a669f2bdcd25f471d80e077568ef) #6 0x7f0910abb14a in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x2a14a) (BuildId: 8f53abaad945a669f2bdcd25f471d80e077568ef) #7 0x441804 in _start (/var/home/edwin/git/ocaml/runtime/ocamlrun+0x441804) (BuildId: 7a60eef57e1c2baf770bc38d10d6c227e60ead37) Uninitialized value was created by a heap allocation #0 0x47d306 in malloc (/var/home/edwin/git/ocaml/runtime/ocamlrun+0x47d306) (BuildId: 7a60eef57e1c2baf770bc38d10d6c227e60ead37) #1 0x4e7960 in caml_ba_alloc /var/home/edwin/git/ocaml/runtime/bigarray.c:246:12 #2 0x4e801f in caml_ba_create /var/home/edwin/git/ocaml/runtime/bigarray.c:673:10 #3 0x59b8fc in caml_interprete /var/home/edwin/git/ocaml/runtime/interp.c:1058:14 #4 0x5a909a in caml_main /var/home/edwin/git/ocaml/runtime/startup_byt.c:575:9 #5 0x540ccb in main /var/home/edwin/git/ocaml/runtime/main.c:37:3 #6 0x7f0910abb087 in __libc_start_call_main (/lib64/libc.so.6+0x2a087) (BuildId: 8f53abaad945a669f2bdcd25f471d80e077568ef) #7 0x7f0910abb14a in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x2a14a) (BuildId: 8f53abaad945a669f2bdcd25f471d80e077568ef) ocaml#8 0x441804 in _start (/var/home/edwin/git/ocaml/runtime/ocamlrun+0x441804) (BuildId: 7a60eef57e1c2baf770bc38d10d6c227e60ead37) SUMMARY: MemorySanitizer: use-of-uninitialized-value /var/home/edwin/git/ocaml/runtime/bigarray.c:486:45 in caml_ba_hash ``` The only use of hashing in genprintval is to avoid cycles, that is, it is only useful for OCaml values that contain other OCaml values (including possibly themselves). Bigarrays cannot introduce cycles, and they are always printed as "<abstr>" anyway. The present commit proposes to be more conservative in which values are hashed by the cycle detector to avoid this issue: we skip hashing any value with tag above No_scan_tag -- which may not contain any OCaml values. Suggested-by: Gabriel Scherer <gabriel.scherer@gmail.com> Signed-off-by: Edwin Török <edwin.torok@cloud.com> Co-authored-by: Edwin Török <edwin.torok@cloud.com>
Following the dataflow approach discussed with @damiendoligez.
I'm not using a generic dataflow engine yet, because it might be less efficient, so for the moment I have two specialized analyses.