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
FFI documentation: naked pointers are obsolete #9610
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK apart from one suggestion.
@xavierleroy There are a couple of extraneous commits in the history here. |
Another approach (taken in Ctypes) is to use |
9d76ae5
to
39559e2
Compare
I cleaned up the commit history. The nativeint encoding is a good trick, I'll add a mention. |
A naive question: for pointers that are aligned on more that one byte (so in practice most of them, I think, except for strings?), what about turning them into (ocaml) integers by setting their lowest bit to 1? Is that a safe thing to do? |
Very safe as far as I know, and a "well-known" folklore trick to exchange unboxed pointers with C.
No, the |
Ah! And thanks for the clarification wrt. Then I guess I would find it useful to explicitly mention this trick in the documentation as well. |
I think this is premature especially as a last-minute change to 4.11. The discussion at https://discuss.ocaml.org/t/ann-a-dynamic-checker-for-detecting-naked-pointers/5805 shows that the consequences of this deprecation might have been not fully evaluated, and the tool that might have a chance to help assessing the impact has just been released. Moreover the discussion shows some confusion (in my opinion) about the actual usage and interest of naked pointers, and also about the virtual addressing techniques that could provide alternatives that do not result in breakage. There is a draft proposal for an alternative to be proposed for discussion soon, and an ML workshop paper in preparation. |
I think that the backwards-compatibility motivation in your draft has been thoroughly discussed already, and is not a good reason to allow naked pointers. Their use has been strongly discouraged for years, and as a consequence the remaining uses are rare and mostly already unsafe. The potential future uses you describe are more interesting, but you are proposing that existing programs have worse performance and that current maintainers do difficult and time-consuming work to support use cases that are not currently possible. This seems a bad trade-off to me. I think it would be better for those implementing these hypothetical future extensions to add the support their extensions need and argue then for the cost/benefit of their extension against slowing down existing programs. |
I think you misspelt "overdue" :-) We've been thinking how to get rid of naked pointer support since 2013 at least (see the comment about "will change in 4.02.0"). It's time to proceed.
On the contrary, now that we have an instrumented version of OCaml 4.10 to detect naked pointer uses, 4.11 is a good time to announce that they are deprecated. If all goes according to plan, 4.12 will have a strict "no naked pointer" mode (where naked pointers can crash the runtime), although it will not be the default. And naked pointers must go away before Multicore OCaml is merged. So, it is time to do something about it. |
Yes, as long as the pointers are 2-aligned, this is as good as the nativeint encapsulation. The latter also supports unaligned pointers. A word of caution, though: with these pointer-as-integer encodings, marshaling and other ad-hoc polymorphic primitives will do something, but not necessarily something that makes sense. With the Abstract encapsulation, you get the guarantee that ad-hoc polymorphic primitives will fail. And with Custom encapsulation, you can define those that make sense for the C type in question, and you can control finalization too. So, in the end, Custom encapsulation is still the safest approach. |
As discussed, I documented the two alternate encodings of pointers (as nativeints and as tagged ints). I am adamant that this should go into 4.11. Paging Dr. @Octachron , release manager and documentation guru. |
I believe that a bare minimum that users are in right to expect regarding backwards-compatibility is that if people still depend on a feature, then viable alternatives to deprecation are investigated. One can argue that nobody depends on it, or that the alternative is hard to implement or incurs a too large performance penalty. But this is not what my paper suggests (now a submission to the ML workshop) and I will not change my mind until I read a proper and convincing response. @xavierleroy wrote:
For people reading out of context, the "will change in 4.02.0" does not appear in the documentation since it is prefixed with
But this does not respond to my points.
I do not remember reading this in the paper claiming that multicore is backwards-compatible, so I do not believe this is a core aspect of the multicore design, even if this has been claimed from the start by proponents of no-naked-pointers. In addition, I understood the following comment as an invitation: https://discuss.ocaml.org/t/ann-a-dynamic-checker-for-detecting-naked-pointers/5805/10. I believe that my design makes it possible to support naked pointers in multicore OCaml very simply and with comparable performance while avoiding breaking real code. Per my first paragraph, I believe it should be investigated. That does not prevent telling people to test the naked pointer detector, so that you might even get additional empirical data before making a decision. I may be wrong. Maybe a fatal flaw will be found in a few days or weeks, who knows? But this cannot be decided in a hurry like this. @lpw25 wrote:
I do not know what discussion you are referring to @lpw25. My draft is meant as an answer to all that was available to the scientific community that I could gather (including old discussions from the previous issue tracker and the caml-list):
I find your reply very disconnected from what I present in the paper. I understand it was written in a hurry but if you want more time to write a proper response you can just ask.
I disagree, as per the bare minimum set above, a new solution that was not available before should be investigated if it prevents future breakage, although I understand the current direction given the assumptions that have been made at the time.
The word of caution from the documentation was introduced in 2000 and only subject to 2 modifications: the replacement of "Caml" with "OCaml" in 2012, and the replacement of "confuse the garbage collector" by "crash the garbage collector" in 2013 which is semantically equivalent. (Strangely, this word of caution states that Custom or Abstract blocks do something to prevent use after free, and I do not understand what this alludes to.) So, when current examples of naked pointers came to be, the documentation was already essentially in its current form. I do not see what "strong discouragement" you are referring to...
...and even if that was the case, this is non-sequitur to me: code does not magically rewrites itself, especially in case the assumption that transition to wrapped pointers is straightforward was incorrect.
This misrepresents again my paper. Comparable performance with no-naked-pointers is among the success criteria. This is plausible per se, and even more if you take into account that "for some applications, the overhead of having an associated heap block will be too high" in the own words of @mshinwell (he meant it for a much more restrictive form of out-of-heap pointers that cannot be deallocated, but if it was important enough in such a pretty minority usecase, then it should be even more important for dynamically-deallocable out-of-heap pointers).
The design I propose is very simple. The whole removal of the page table seems daunting to me in comparison (not counting the work wasted by the community adapting or throwing away existing code). Also, this is meant to avoid breaking current programs, as my paper makes very clear. In addition to be very far from my paper's claims without providing arguments, there is another inconsistency with what you wrote about the supposed difficulty that I would like to point out: you have co-authored a paper (https://arxiv.org/abs/2004.11663v2) claiming that OCaml multicore is backwards-compatible including in terms of performance, but then it directly benefits from deprecating naked pointers when you can reuse for free an off-the-shelf high-performance multithreaded allocator for allocations larger than 128 words (a shortcut which I found was actually still possible in my design). So, either this simplification is a secondary aspect not part of the design requirements of multicore and is still open for discussion, or I would have expected to see a clear description of the breakage (it is unclear to me that it is even alluded to) together with an investigation on its scale (for instance by trying to find evidence supporting the sort of empirical claim you make, using something like the detector that was just provided to the community). The latter is not done, so I go with the first option. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. One suggestion, given it's come up multiple times in the discussion: add a sentence that says something about the ad-hoc polymorphic primitives. For example, that with three of the representations here (Abstract_tag
, boxed integers, tagged integers) these primitives will work on the raw pointer value, whereas with Custom_tag
it is possible to define the primitives' behaviour.
@gadmm If you want to argue from backwards compatibility you are going to need to produce some actual examples of code that is still actively used and developed that actually relies on the feature. If you want to argue that there is no runtime cost to your proposal you are going to need to at least produce a basic prototype to benchmark. If you want to argue that it is simple and not time consuming to implement, then implement it. |
I should clarify this point. There are two proposed paths:
In path 2 programs will have worse performance between points (i) and (ii). This is what I meant. We don't know how long that period would be, but it might be a while. This is one reason why it would be better to remove the page table now. We can always add "some other thing" later anyway. |
To play the Guillaume advocate (I'm not so comfortable with the very confrontational style he brings with the discussion, but I think it is a discussion worth having):
It's easy for me to understand that there is a difference of perception between language maintainers (including the Multicore developers) and other contributors on this issue. For some it was a done deal, for others a surprise. Guillaume's discussion style can be a pain in the ass, but I wish we could take his questions and his own suggestions as a reminder that a serious discussion or explanation is overdue. |
Just a quick note to point out that secondary-heaps work fine with no-naked-pointers -- or as fine as they do now. You just need to mark the objects black. |
@lpw25 : I understood @xavierleroy's comment in #9564 (comment) as a discouragement from using the black-header trick. You seem to have a different mental model where it is a valid way to have naked pointers to OCaml values outside the heap. I wish we could converge to a clearer story about the accepted possibilities, their advantages and downsides. (On a related front, we have discussed 1-tagging naked pointers, but @gadmm points out (in his document I think?) that this can break cross-language or binary-level tools that wish to trace pointers. From this perspective @yallop's nativeint-boxing trick is more appropriate, but it has a higher runtime cost.) |
I think this discussion is making mountains out of molehills. There is one technical point that I'd like to clarify concerning the Ancient library and similar approaches to out-of-heap, well-formed OCaml data: removing the page table makes these approaches easier, not harder nor impossible. Today, Ancient needs to record its pages in OCaml's page table as "static data" (well formed but not managed by the GC) in order for ad-hoc polymorphic primitives (equality, marshaling, hashing) to treat Ancient data like normal OCaml values. In turn, this requires page-aligned storage, etc. Once we all adopt the "no naked pointers" convention, ad-hoc polymorphic primitives will just work over Ancient data. The only bookkeeping that Ancient should do is put black headers on all its blocks, so that the GC does not traverse and mark them. And even this is an optimization: if the blocks are not black, the GC will traverse them once, mark them black, and this color will stay forever after. So, Ancient is not an argument in favor of keeping naked pointers and the page table. Ancient is rather a fine example of low-level hackery that will become simpler once we get rid of both. |
While following this discussion but having no particularly strong opinion about it, this point resonated with me. I'd like to point to the Rust core team, who post their dev meetings online, which allows the community to keep up-to-date with the discussions. |
In ordinary FFI code, yes, because you need to understand how the OCaml GCs work. In low-level system code like Ancient, no, because those systems already need to understand that. |
To put some historical context on this, I implemented a flat address space without a pagetable lookup as a prototype on OCaml/Xen a decade ago (Usenix HotCloud 2010, Fig 2 for layout, Fig 4 for simple perf). Back then in the OCaml 3.11 days, the interactions between the write barrier and the page table lookups was an absolutely horrendous performance hog for some 64-bit workloads (but not 32-bit, where the page table is a lot simpler). @xavierleroy then fixed this bottleneck with a big improvement in OCaml 4.01.0 in around 2013:
Implementing a flat VA brings with it a host of considerations (interactions with ASLR, how to embed well as a library, portability across operating systems with respect to memory overcommit, and so on). Overcoming all this is obviously possible, but the tradeoff we chose in the multicore prototype was towards a more self-describing memory hygiene. With this option, you can always bring the page table back as an optimisation, but it's difficult to go back in the other direction if assumptions about VAs permeate your design. As part of my work on MirageOS, I habitually compile OCaml to many weird and wonderful environments where controlling assumptions about the environment are useful (from baremetal chips over to embedded libraries). I believe the current design permits this very useful and long-standing property of OCaml to continue into the world of multicore. |
Beyond the discussion on the fate of naked pointers and the essence of backward compatibility, the documentation change seems like a clear improvement to me. It seems much better to focus in the documentation on a working, if not optimal, encoding for out-of-heap pointer rather than a solution that might be correctly used by an expert. (As far as can see, even @gadmm propositions would require a much more thorough documentation than the existing one to be fully usable without knowing a lot about the GC and memory handling.) It would be clearly worthwhile to have a more low-level oriented documentation. However, this should probably be another discussion. And the manual might not the better place for such documentation. |
@Octachron Indeed, my comments were more about the normative side of removing naked pointers from the language. I can reply in more details later to the interesting comments, but I feel like there has been some progress, with @lpw25 encouraging some prototype with benchmarks, which is what I proposed to do with @rmdouglas in my draft. If I look at the definition of deprecation in e.g. the C++ standard, I see the following:
So the current wording (“this usage is obsolete and will stop being supported soon”) is even stronger than deprecation in that sense. Even a wording along the lines of “candidate for removal” instead of the former would allow my current exploration to fit alongside official plans, and not be seen as a rogue project carried out behind your back, while still warning users. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partly taking a point already made elsewhere by @Octachron, "obsolete" could be replaced with "deprecated" and "soon" with "the next major release of OCaml", although I don't think that either is strictly a necessary change (indeed, "the next major release of OCaml" makes it sound like "a loooong way off" and mentioning the specifics of multicore in order to clarify that we expect OCaml 5.00 to be on the horizon seems an odd thing to go into in an FFI chapter...).
It's a documentation change only, so I agree that there's no reason for it not to be announced with 4.11. If the world changes (even more!) between now and 4.12, we can always change the docs back.
The black header trickIt is incorrect to say that Ancient could be implemented with the black header trick, as it currently exist. Ancient offers a I do see how the black header trick allows to implement something that answers the question of a “third-generation” heap fully compatible with existent values (including polymorphic operations, but excluding mutations), that is never traversed but never deallocated (if we exclude trying to reason about reachability from the GC). However, the impossibility to deallocate makes it non-compositional, and I do not think this can be improved with the black header trick or while preserving the constraint that polymorphic operators must keep their meaning on all types. When speaking of Ancient “and similar approaches” (@xavierleroy) or “secondary heaps” (@lpw25), it is better understood from the point of view of manual memory management, where memory is manipulated as a resource (in particular by reasoning about usage, not reachability from the GC). From the point of view of resource management, the behaviour of polymorphic operators is natural: 1) resources are unique, so physical comparison is the right comparison for them; 2) resources are non-copiable so marshalling should not work on them. |
That's your claim, and it leaves me completely indifferent. You're moving the goalposts in an attempt to win the game, but I'm not interested in playing. I'd gladly leave explicit deallocation to other languages. |
OK, if you think it is more appropriate. My French mind tends to confuse these two words. See commit 414ae40.
I agree in principle, because "soon" is too vague. However, to me, "major release" means 4.12 or 4.13 or ..., with "minor release" being e.g. 4.10.1. We don't have a clear word for "release so important we bump the first component of the version number". In the end, let's go with "in OCaml version 5.00" (414ae40). It is precise, so users know what to expect, and it matches our development roadmap well: OCaml 5.00 will result from the full merge of Multicore OCaml, and intermediate 4.xx releases can keep supporting pointers outside the heap as their default configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the current wording. I think that a Change entry is warranted to attract the attention of users. Do you want me to add it?
Definitely!
That would be very nice. Thanks! |
Also: better explain how to encapsulate naked pointers in Abstract blocks.
Following review comments. Also: fix the assertion in the third val_of_typtr function.
Change entry added, I will merge and cherry-pick soon. |
You are right in the sense that this is my own view to explain the behaviour of polymorphic operators. However, to clarify, the goalposts have not moved: you claimed that the black header trick supports Ancient and similar approaches, but in OCaml they all support prompt deallocation. This is not to argue further regarding this PR: as I said, I am fine with a statement that warns users about the current direction while the door is left open to experimentation. Can I please ask you what you have in mind regarding implicit deallocation? GHC compact regions (alluded to by Gabriel) have it. They would indeed support polymorphic operations seamlessly. They work by darkening the region while marking pointers to the region. But doing that in the same way requires the same kind of technology that I propose to support naked pointers. We were interested in looking at it, so it might be worthwhile to share your thoughts, as this will help it becoming reality. For instance can we consider doing something smart in the write barrier to deal with mutations inside the region, assuming the test is efficient enough (i.e. more efficient than the 32-bit page table)? @avsm Thanks for sharing your experience with a flat VA (here and on the forum), it is interesting to hear about the potential issues. I agree it is important to support as many platforms. If I understand correctly, some of the issues you had were due to the fact that you were using a different technique (you mentioned using |
To be clear, the VAS used in the concurrent minor collector is quite different from what is proposed. In the concurrent collector, the VAS is fixed-sized, does not grow or shrink, and is small (4 GB reserved on 64-bit systems) and could be made much smaller if the number of physical cores is fewer (4 GB is configured for 128 cores). There is no page table of any sort in the multicore design. Moreover, the concurrent collector is not being proposed for upstreaming. The proposed parallel minor collector does not need the VAS hacks. I don't think a comparison with the concurrent collector design to justify the proposal is necessarily valid. |
FFI documentation: naked pointers are obsolete (cherry picked from commit ae5eb6b)
Likewise my proposal is parametric in the reserved area size and I give the example of Go where they are as small as 64MB on 64-bit. It is not clear we need to go as small, but it is possible. I'll write to you in private to ask more about what you have in mind. |
Remove the FFI documentation saying that it's almost OK to use naked pointers as OCaml values.
State that this usage is obsolete starting from OCaml 4.11.
Explain better how to encapsulate naked pointers inside Abstract or Custom blocks.