Skip to content

Deprecate Obj.set_tag#1725

Merged
gasche merged 6 commits into
ocaml:trunkfrom
stedolan:deprecate-set-tag
Feb 14, 2019
Merged

Deprecate Obj.set_tag#1725
gasche merged 6 commits into
ocaml:trunkfrom
stedolan:deprecate-set-tag

Conversation

@stedolan

Copy link
Copy Markdown
Contributor

Under the multicore GC, object headers (tags and lengths) are immutable. Not in the current sense of "don't change these unless you know what you're doing", but in the stronger sense of "if you ever change these, the GC will randomly segfault as it scans the object you're messing with from another thread". Consequently, Obj.set_tag is unsupported. (Obj.truncate too, although this PR only talks about the former).

Currently, this PR doesn't contain any code. Before doing a bunch of hacking, I'd like to discuss with the maintainers (cc @xavierleroy, @damiendoligez) whether (a) deprecating and later removing set_tag is something they find acceptable in principle and (b) whether the below is a reasonable plan for doing it.

This change will break code that uses Obj.set_tag, although I'd argue that occasional breakage is the price of using Obj. Still, I did some grepping of the stdlib and the OPAM universe to find uses of set_tag, and work out replacements for common uses:

Lazy

CamlinternalLazy uses set_tag to change Lazy_tag to Forward_tag when thunks are forced.

I suggest using an extra word in the Lazy.t structure to distinguish forced and unforced thunks, which will increase the size of unforced thunks from 2 to 3 words. For thunks that are eventually forced the increase is temporary, since the GC short-circuits forced thunks during tracing.

Creating new blocks with unexpected tags

Some code (example) uses set_tag to create a block with a tag other than what OCaml normally chooses. (@alainfrisch has also written about using this trick to get polymorphic comparison to understand hashconsing, although I haven't come across uses in the wild).

For these uses, a new primitive Obj.with_tag : int -> Obj.t -> Obj.t suffices: like dup, but allows the user to specify an alternative tag for the copy. This still allows the construction of objects with unusual tags, but by creating a fresh object with the desired tag, rather than by mutation. If performance is a concern, an optimisation that detects when with_tag is applied directly to an allocation and elides the first allocation is easy enough to implement.

Preventing the GC from scanning int array

At runtime, int array looks just like any other array, so gets needlessly scanned by the GC. Some programs (example) explicitly change the tag of integer arrays to prevent scanning.

Unlike the other uses, this use of set_tag can be silently ignored without affecting correctness. It is a reasonable optimisation, though, and it would be nice to support it properly. Perhaps we could introduce a new intarray type (matching floatarray) which has the same layout as ordinary arrays, but is tagged with No_scan_tag. (Unlike floatarray, the layout is always compatible with standard arrays, so = private int array is a reasonable definition of this type).

Unfixable cases

Some code is not so easily fixable, either by making optimisations based on undocumented properties of the current runtime (example), or by targeting not the OCaml language but a runtime model that explictly includes set_tag (example).

@bluddy

bluddy commented Apr 16, 2018

Copy link
Copy Markdown
Contributor

Perhaps we could introduce a new intarray type (matching floatarray) which has the same layout as ordinary arrays, but is tagged with No_scan_tag.

There should be other cases like this. Perhaps it would be better to dedicate a bit in the 64-bit header to indicate whether the GC should scan the data.

@damiendoligez

Copy link
Copy Markdown
Member

There should be other cases like this. Perhaps it would be better to dedicate a bit in the 64-bit header to indicate whether the GC should scan the data.

I don't think so. Whether data should be scanned by the GC or not, is highly dependent on the data itself, there is no orthogonality here.

@alainfrisch

Copy link
Copy Markdown
Contributor

I suggest using an extra word in the Lazy.t structure to distinguish forced and unforced thunks, which will increase the size of unforced thunks from 2 to 3 words. For thunks that are eventually forced the increase is temporary, since the GC short-circuits forced thunks during tracing.

More generally, can you describe how lazy blocks are supported in the multicore GC? Marking the block as a "forward" block and storing the forward pointer need to be done atomically from the point of view of other threads. How do you achieve that?

The performance impact of the new fields should be evaluated. The size increase could be noticeable for lazy-heavy data structures , and even if lazy thunks are forced quickly the extra allocations can put more pressure on the GC. Also, scanning and other generic operations could become a bit slower.

(Unlike floatarray, the layout is always compatible with standard arrays, so = private int array is a reasonable definition of this type).

I agree that adding intarray is a good idea, but I would refrain from exposing the fact that it's a subtype of "int array". This would allow creating two int array values with the same elements, but which would be different when compared with the generic equality operator.

@kayceesrk

Copy link
Copy Markdown
Contributor

@alainfrisch Here is the issue that tracks Lazy proposal for Multicore OCaml ocaml-multicore/ocaml-multicore#188

dra27
dra27 previously requested changes Jun 30, 2018

@dra27 dra27 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologise hugely for requesting changes on a 1-line GPR!

Comment thread stdlib/obj.mli Outdated
  - Adds Obj.with_tag as a partial replacement.
  - Adds caml_obj_make_forward for use of Camlinternal{Lazy,Mod}
@stedolan

Copy link
Copy Markdown
Contributor Author

New implementation just pushed.

This one keeps the current behaviour for Lazy, which was the most controversial part of the current design. Multicore's invariants have been weakened from "tags never change" to "tags never change unless they're Lazy_tag" (see ocaml-multicore/ocaml-multicore#226).

A new primitive, Obj.with_tag has been added to cover the use-cases of allocating an object with a nonstandard tag. There's also a new optimisation (with tests) in simplif.ml to ensure that patterns like Obj.with_tag (Obj.repr { ... new allocation ... }) do not cause any more allocations than the original set_tag-based code.

@stedolan stedolan dismissed dra27’s stale review February 12, 2019 11:20

(refers to old version)

Comment thread bytecomp/simplif.ml
Comment thread runtime/obj.c Outdated
Comment thread stdlib/camlinternalLazy.ml
Comment thread stdlib/obj.mli
*)
external set_field : t -> int -> t -> unit = "%obj_set_field"
external set_tag : t -> int -> unit = "caml_obj_set_tag"
[@@ocaml.deprecated "Use with_tag instead."]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessarily a great idea to deprecate a feature at the same version that the replacement is added: a maintainer that would follow the deprecation advice must force its users to upgrade to the latest version (or use conditionals). Could we wait for one version between with_tag and set_tag's deprecation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to follow whatever deprecation policy works best here, but I don't really understand the issue. I thought @@ocaml.deprecated was the way to signal that a replacement is available? Is the concern here programs compiled with -warn-error breaking?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, we would deprecate it now and remove it in the next deprecation purge, in about 5 years. We still don't have any mechanism to indicate degrees of deprecation (or how much time before it gets removed). Such a mechanism might be an intermediate step before deprecation.

In the case of Obj.set_tag I think we'll remove it out-of-cycle to avoid delaying multicore, so it's better to deprecate right now anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessarily a great idea to deprecate a feature at the same version that the replacement is added:

Regarding this comment about @gasche I fear a bit nothing is ever going to be deprecated (people will simply forget) if nothing special is done. One simple measure would be to have in the repo text file with a table of the form:

ID | vReplacement | vDeprecated notice | vRemove
--------------------------------------------------

Before a release one could then apply appropriate changes according to what the table says.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbuenzli yes, that sounds like a good idea. I guess some of your merged PRs have some maybe-deprecate-later identifiers in flight, do you have enough to send a PR to populate that file? (Did we merge PrintExc successor yet?)

(The file should also point to the (M/G)PR that contains the deprecation action/discussion.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Did we merge PrintExc successor yet?)

No, it is #2137, but it is not yet ready, more work needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have a look but a bit busy® right now®.

@gasche

gasche commented Feb 12, 2019

Copy link
Copy Markdown
Member

I have an uneasy feeling when compiler code matches on the name of C functions. I suspect that the externals whose semantic is part of the language (enough that the compiler knows about them) should be %foo functions, that later get turned into C primitives by the unsupporting backends (possibly all backends) if desired.
However, I understand why the PR just uses a new C primitive, given that the rest of the Obj module is in this style (but then, it looks like there were no Obj-specific optimizations in the compiler before?).

@stedolan

Copy link
Copy Markdown
Contributor Author

I was also unsure about exposing this as "caml_obj_with_tag" vs. "%obj_with_tag". In doubt, I went for the version that's less code, but I don't have a strong preference. Do you think this would be better as a primitive + later conversion to a C call?

@gasche

gasche commented Feb 12, 2019

Copy link
Copy Markdown
Member

Personally I think that all of Obj's primitives could/should be moved to %obj_* primitives instead of C calls, because we make assumptions on them, but that can be done separately. (And it may break compatibility of some libraries that re-export Obj, sigh...)

I also don't have very strong feelings on the best deprecation policy.

Comment thread runtime/obj.c

@gasche gasche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the patch for correctness and I think the new primitive is a nice thing to have in any case. The only thing I'm unsure about is the idea of deprecating set_tag right away; for a more widely used API I would frown upon deprecated in the same version its replacement appears, but this might not make a big difference for such a low-level function?

I'm approving, but I think this deserves a third pair of eyes (and maybe a decision on deprecation policy) before merging.

@stedolan

Copy link
Copy Markdown
Contributor Author

I don't understand the AppVeyor failure. It's complaining about the new C primitive, but the Travis build is fine. Perhaps a bootstrap would fix it? I don't understand why this differs on windows. @dra27 ?

@nojb

nojb commented Feb 12, 2019

Copy link
Copy Markdown
Contributor

It fails when building flexlink (only used on Windows) using the bootstrap compiler. My understanding is that the only way out is a bootstrap.

@damiendoligez damiendoligez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Third pair of eyes here. Looks good except for a small (and optional) suggestion.
In this case I think it's OK to deprecate right away.

Comment thread stdlib/obj.mli
*)
external set_field : t -> int -> t -> unit = "%obj_set_field"
external set_tag : t -> int -> unit = "caml_obj_set_tag"
[@@ocaml.deprecated "Use with_tag instead."]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, we would deprecate it now and remove it in the next deprecation purge, in about 5 years. We still don't have any mechanism to indicate degrees of deprecation (or how much time before it gets removed). Such a mechanism might be an intermediate step before deprecation.

In the case of Obj.set_tag I think we'll remove it out-of-cycle to avoid delaying multicore, so it's better to deprecate right now anyway.

Comment thread testsuite/tests/lib-obj/with_tag.ml
@gasche

gasche commented Feb 13, 2019

Copy link
Copy Markdown
Member

@stedolan: you should add Damien as reviewer and then it's good to merge.

@gasche gasche merged commit 5a29ea7 into ocaml:trunk Feb 14, 2019
@gasche

gasche commented Feb 14, 2019

Copy link
Copy Markdown
Member

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants