use [raw_field] primitives in camlinternalMod#9691
Conversation
638bdb3 to
0905589
Compare
stdlib/camlinternalMod.ml
Outdated
| assert (Obj.size o >= Obj.size n); | ||
| for i = 0 to Obj.size n - 1 do | ||
| Obj.set_raw_field o i (Obj.raw_field n i) | ||
| done |
There was a problem hiding this comment.
I'm not sure how to reason about correctness on fields that are not the code pointer. I have the impression that we are doing a raw write, when we probably should use a proper write barrier. If this is indeed the case, then the fix would be to loop over the precise closure representation, instead of handling all fields as raw data. (I don't think there is OCaml-side code doing this yet, and it might benefit from some auxiliary functions in Obj.)
There was a problem hiding this comment.
I added a type Obj.Closure.info = { arity: int; start_env : int }, which is now used to use set_raw_field on closure metadata fields and set_field on the others.
There was a problem hiding this comment.
You're right that closures need as special case. However this code is not good for the reason you mention.
Here I think it's enough to copy field 0 using raw-field accesses and all other fields using normal accesses. In block o there is only one code pointer at offset 0, the rest is integers. As mentioned elsewhere, caml_modify (i.e. Obj.set_field) of a code pointer over an integer is safe.
There was a problem hiding this comment.
In your new code you still need to justify why you're taking the startenv of the new block and not the one of the old block.
There was a problem hiding this comment.
In block
othere is only one code pointer at offset 0, the rest is integers.
My understanding is that overwrite_closure may be called with n an arbitrary closure and o being the template value of the init_mod case:
Obj.repr (fun _ -> raise (Undefined_recursive_module loc))Here we have a non-integer closure field for loc.
There was a problem hiding this comment.
In your new code you still need to justify why you're taking the startenv of the new block and not the one of the old block.
Can I start by filling the segment [o.start_env; n.start_env] with unit values?
There was a problem hiding this comment.
Well spotted. I stand corrected. But I think this is also a problem with your approach.
There are TWO kinds of assignments between fields that must be avoided:
- assigning via
caml_modify(i.e. Obj.set_field) when the old value is a code pointer - assigning directly (i.e. Obj.set_raw_field) when the old value is a pointer within the major heap, because then it may escape marking even though it is live elsewhere. (This is why
caml_modifydoes acaml_darkenon the old value.)
If you Obj.set_raw over the field of o that contains the loc free variable, you're running into the second forbidden case.
There was a problem hiding this comment.
One solution is to first set fields 1...size - 1 of o to an integer value
Obj.set_field o i (Obj.repr 0)
so that the loc value is properly seen by the GC.
Then you can do your copy with raw assignments up to startenv(n), and normal assignments above.
There was a problem hiding this comment.
let n_start_env = Obj.Closure.((info n).start_env) in
let o_start_env = Obj.Closure.((info o).start_env) in
(* if the environment of n starts before the one of o,
clear the raw fields in between. *)
for i = n_start_env to o_start_env - 1 do
Obj.set_raw_field o i Nativeint.one
done;
(* if the environment of o starts before the one of n,
clear the environment fields in between. *)
for i = o_start_env to n_start_env - 1 do
Obj.set_field o i (Obj.repr ())
done;
for i = 0 to n_start_env - 1 do
(* code pointers, closure info fields, infix headers *)
Obj.set_raw_field o i (Obj.raw_field n i)
done;
for i = n_start_env to Obj.size n - 1 do
(* environment fields *)
Obj.set_field o i (Obj.field n i)
done;
()what do you think?
|
The |
xavierleroy
left a comment
There was a problem hiding this comment.
overwrite_closure is getting pretty robust, that's nice. The last set of assignments could be avoided (see below), but that's a performance optimization, not a correctness issue.
I'm afraid there is still an issue with the construction of the initial, dummy closure.
Yet! |
9b0bfae to
367e7e4
Compare
xavierleroy
left a comment
There was a problem hiding this comment.
Looks good, except a signed/unsigned error in the extraction of the "arity" field.
stdlib/obj.ml
Outdated
| (* the nativeints below are unsigned, but we know they can | ||
| always fit an OCaml integer so we use [to_int] | ||
| instead of [unsigned_to_int]. *) | ||
| let arity = | ||
| if Sys.word_size = 64 then | ||
| to_int (shift_right_logical info 56) | ||
| else | ||
| to_int (shift_right_logical info 24) |
There was a problem hiding this comment.
Arity is signed (negative arity = tupled function), so please use shift_right and update the comment.
There was a problem hiding this comment.
Thanks! This should now be fixed.
runtime/obj.c
Outdated
| } | ||
|
|
||
| if (tg == Closure_tag) { | ||
| /* Closinfo_val is the seconnd field, so we need size at least 2 */ |
There was a problem hiding this comment.
Typo: "seconnd".
Cosmetic: switch (tg) { case Closure_tag: ... case String_tag: ... } could look good. Maybe.
There was a problem hiding this comment.
I did the switch (haha) in a separate commit, let me know if you like it.
367e7e4 to
65270a9
Compare
xavierleroy
left a comment
There was a problem hiding this comment.
The new "switch" is good, but carried you too far! See explanation below.
| case Custom_tag: { | ||
| /* It is difficult to correctly use custom objects allocated | ||
| through [Obj.new_block], so we disallow it here. The first | ||
| field of a custom object must contain a valid pointer to | ||
| a block of custom operations. Without initialisation, hashing, | ||
| finalising or serialising this custom object will lead to | ||
| crashes. See #9513 for more details. */ | ||
| caml_invalid_argument ("Obj.new_block"); | ||
| } |
There was a problem hiding this comment.
I'm sorry to be difficult, but the Custom_tag case must be rejected BEFORE doing caml_alloc. The reason is as follows: if the block is big enough, it is allocated in the major heap, with tag Custom_tag but an invalid "operations" field. Then, we raise an exception, so the block is unreachable. So, at the next major GC sweep, it will be collected, and the sweeper will look into the "operations" field to see if there is a finalization function. This will segfault.
There was a problem hiding this comment.
I see. I noticed that the allocation would happen, but I figured that this would be fine given that this exception corresponds to a programming error, that is not supposed to be handled by the program. Your point about the segfault is very reasonable :-)
For the other failure cases in the function, there is no such issue: there are failure cases that can lead to ill-formed values on the heap, but they are not ill-formed enough that it would be a problem for the GC, which is the only case we have to consider for this allocation that is dead on arrival.
There was a problem hiding this comment.
@xavierleroy I moved back the Custom_tag check, but then I pushed a second commit that moves the allocation within the switch, so that it is always after the failure test. Let me know what you prefer.
7a2ca1d to
df5658d
Compare
xavierleroy
left a comment
There was a problem hiding this comment.
I'm approving because the code looks correct to me and to stop you from torturing it some more :-)
This change is careful to avoid writing a value into what was previously a raw field or conversely, clearing fields that change category first. We also clear the end of the block, to make it easier to reason about lifetime of values that could have been referenced there. (We don't expect this to make a different in practice.)
df5658d to
4cde684
Compare
4cde684 to
514d2ff
Compare
|
There was a suspicious Travis failure before, so let's wait until T&A succeed. |
|
The CI was failing with a weird failure (it claimed that it did not know about |
|
Now Travis is green but AppVeyor fails (when trying to build FlexDLL, it seems) with a strange error that does look related to this PR (but may be an issue with the PR that introduced |
|
I'll run CI precheck on this PR. Concerning the Appveyor failure, I don't know the details of the "build FlexDLL while we're building OCaml" procedure, so I'm mentioning @dra27. |
|
(precheck seems happy, the only failure was also present in other builds.) |
|
It looks like the "build FlexDLL while we're building OCaml" procedure assumes a properly-bootstrapped system and will fail if boot/ocamlc doesn't know about newly-added primitives. I'll discuss that on caml-devel. |
|
Note: the flexdll build failure was fixed in #9700. |
This module was introduced in ocaml#9691, for use in CamlinternalMod, but rendered obsolete by ocaml#10205.
Looking at #9690 (changes to caml_{alloc,update}_dummy for the new closure representation of #9619), I have the impression that camlinternalMod.update_mod needs to be updated as well. Here is a naive proposal.
(cc @xavierleroy @jhjourdan)