-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bytecode miscompilation of recursive definition of empty first class module #12153
Comments
Blocks of size 0 are atoms in bytecode, i.e. they're all preallocated in read-only memory. The code in One possible fix is to consider zero-sized blocks as |
I can reproduce the issue with other block-0 constructs like let rec (k : int array) = let _ = k in [||];; Personally I think that the logic in Rec_check is morally correct. If the runtime wants to allocate 0-arg blocks as immutable, it should ensure that this is a correct optimization, and it is not the case here. A simple fix would be to ensure that I am not sure that the fix you propose of using let glob = ref 0
let rec (x : int array) = glob.contents <- y; [||]
and y = 1 The following behavior would be plausible: first Finally: I'm not opposed to changing Rec_check to reject more programs due to this corner case. A fix would be to change the size-computation to return Dynamic when we statically see that the argument count will be 0. It is a bit ugly though because it makes the code more complex to reflect a concern that comes much later in the pipeline, but we already do this in some parts of Rec_check. |
If I remember correctly, the issue is that the GC cannot deal with blocks of size less than 1, so all atoms must live outside the heap. The native compiler ensures that zero-sized blocks are always statically allocated, but the bytecode compiler has to rely on the runtime's atom table.
I think it's correct, and my reasoning for that is that it's already used for all immediate values (integers, constant constructors) and structured constants.
You don't really have to use empty arrays: for the issue you're looking for,
I don't think removing unboxed float records would change anything. Storing a value in a mutable field escapes it, so even if the store itself doesn't read the value you must consider it dereferenced.
We could actually add a |
Good points.
I would be in favor of restricting the set of definitions we allow to err on the side of soundness, instead of trying to be clever to accept more programs of doubtful usefulness. If the value is Constant, then it does not need to be inside the recursive nest, and we can force it to not depend on other values. |
I broadly agree with you. I'm less sure about what to do in the following case: let rec x =
let y = if false then x else 0 in
Constr y According to the current rules, this is accepted and classified as Static. But after simplification, this could turn into: let rec x = Constr 0 Do you think we should stick to the original decision to pre-allocate and patch, or should we keep the current behaviour of switching to the dynamic strategy, as it is (probably) safe for constants ? One way to solve all this would be to compile the recursive definitions right after we check them, but this is currently not possible because we cannot compute the size of functions in the typedtree. Which is a problem solved by #8956, so I guess that's one more reason to try to make progress there... |
Recursive value definitions... the gift that keeps on giving... My first reaction when reading @lthls analysis is that there's nothing to patch in a zero-sized block! Actually, there is no data field to patch, but the current code still wants to patch the tag. From day 1 of OCaml, the only atom (zero-sized block) used to represent OCaml values is the one with tag 0, which is used for empty arrays and for structs containing no values. Atoms with tag != 0 are supported by the run-time system but not used to represent OCaml values. Moreover, dummy blocks are created with tag 0. So there is really nothing to patch in a zero-sized block, as the tag is already the correct one. This could be implemented by adding a case to Alternatively, we can tweak the analysis and compilation of recursive values once more to handle this case specially. Your choice. |
Ah, I missed the fact that atomic blocks always use tag 0. Indeed in that case skipping the update is a dead simple fix. |
fixes ocaml#12153 Suggested-by: Xavier Leroy <xavier.leroy@college-de-france.fr> Suggested-by: Vincent Laviron <vincent.laviron@gmail.com> Reported-by: Nick Roberts <nroberts@janestreet.com>
I proposed the fix that @xavierleroy suggested in #12156 . |
fixes ocaml#12153 Suggested-by: Xavier Leroy <xavier.leroy@college-de-france.fr> Suggested-by: Vincent Laviron <vincent.laviron@gmail.com> Reported-by: Nick Roberts <nroberts@janestreet.com>
fixes ocaml#12153 Suggested-by: Xavier Leroy <xavier.leroy@college-de-france.fr> Suggested-by: Vincent Laviron <vincent.laviron@gmail.com> Reported-by: Nick Roberts <nroberts@janestreet.com>
This produces segfaulting bytecode with
trunk
's ocamlc, but not 4.14.1. I haven't bisected the cause yet.Notably, these variations do not segfault:
ocamlopt does not produce segfaulting code.
The text was updated successfully, but these errors were encountered: