Towards Better Systems Programming in Ocaml with Out-Of-Heap Allocation
Total Page:16
File Type:pdf, Size:1020Kb
Towards better systems programming in OCaml with out-of-heap allocation Guillaume Munch-Maccagnoni∗ Inria, LS2N CNRS 30th May 2020 Abstract. The current multicore OCaml implementation bans • In Rust, value interoperability with C++ is instrumental in so-called “naked pointers”, pointers to outside the OCaml heap the migration from one language to the other, since programs unless they follow drastic restrictions. A backwards-incompatible that are migrating can remain a long time in a hybrid state. change has been proposed to make way for the new multicore GC • “Swift is a high-performance system programming lan- in OCaml. I argue that out-of-heap pointers are not an anomaly, guage. It has a clean and modern syntax, offers seamless but are part of a better systems programming future. access to existing C and Objective-C code and frameworks, and is memory safe by default.” 1 1. Introduction There is ongoing research to augment OCaml with new kinds of types and values which would allow operating more safely Reserving address space to efficiently recognise in-heap point- with foreign pointers (among others): Radanne, Saffrich, and ers (and do even wilder tricks) is standard in garbage collected Thiemann(2019), my own (2018). languages and allocators, as well as in research about memory Proposed replacements in OCaml’s no-naked-pointers mode management. In addition, allowing foreign pointers for interop- include introducing an indirection via a special block, or dis- erability is a standard feature in common systems programming guising the pointer into an integer (using the lsb as a tag), i.e. languages. as unstructured data. One will be unable, for instance, to create As part of my research to make OCaml a better systems pro- an OCaml-like value on the Rust stack or heap and pass it as an gramming language, I propose to revisit the importance of naked argument to an OCaml function. pointers for 1) interoperability, 2) backwards-compatiblity, and 3) performance, three staples of systems programming. I conclude Thus, among the systems programming languages I looked with a challenge addressed to the ML community. at, none requires such wrapping to access foreign objects. In In the appendix, I show a simple design based on virtual ad- OCaml as well, the discussion at ocaml/ocaml#9534, and the dress space reservation that is proposed to make naked pointers case of LLVM bindings that has been reported, show preliminary compatible with the multicore OCaml GC. empirical evidence of naked foreign pointers in OCaml in the wild. In addition, a third technique is sometimes mentioned regard- 2. Gathered evidence from the practice and ing the no-naked-pointers mode, which is to prefix the out-of- the literature heap values with a “black” header (meaning it is already marked). This technique is unorthodox because it consists in checking 2.1. Interoperability whether one owns a pointer by dereferencing it. One conse- quence is that such values can never be deallocated, because it is In his essay Some were meant for C, Stephen Kell (2017) places never possible to reason about whether they are still reachable the essential value of systems programming languages in “com- from the GC. For this reason, while this can be appropriate for municativity” rather than performance. Among others, he argues runtime-owned values that live for the duration of the program, against “managed” languages in which “user code may deal its interest for users is more marginal. Nevertheless, despite its only with memory that the language implementation can account unsuitability for dynamic allocation, it has been advocated by for”. the designer of no-naked-pointers notably because “for some Concerning foreign pointers specifically, their allowance is applications, the overhead of having an associated heap block indeed standard in common (self-described) systems program- will be too high”2. ming languages beyond C and C++: Rust, Go, and Swift. For Lastly, at ocaml/ocaml#9534 and ocaml/ocaml#9535, without instance: questioning the no-naked-pointers approach, library developers • Rust’s ownership-and-borrowing allows it to interoperate reported the convenience of allowing a special NULL pointer value safely with garbage collected runtimes, and directly manip- for bindings (and other purposes). ulate values from the foreign GCed heap, for instance with SpiderMonkey’s GC for JavaScript (Jeffrey, 2018). 1https://github.com/apple/swift ∗[email protected], Nantes, France 2https://github.com/ocaml/ocaml/pull/9610#discussion_r431007766 1 2.2. Address space reservation In addition to explicitly breaking existing code, as we have seen in the case of foreign pointers, none of the propositions is ideal: The technique we propose relies on reservation of large areas in in theory both have a linear overhead when traversing a structure the virtual address space. compared to the same traversal using foreign pointers for instance, Reserving virtual address space is standard in several common in which case the code adaptation that is required could be non- garbage-collected languages: Java, GHC, and Go. GHC reserves systematic. Yet, no empirical evaluation of the impact of the 3 1 TB by default . The new multi-threaded runtime for Julia proposed changes has been proposed so far—a (non-precise) tool 4 reserves 100 GB for thread stacks . Go used to reserve 512 GB to detect naked pointers has recently appeared7 and will be a up-front at some point, and now reserves more progressively. The good tool to perform such an evaluation. concurrent OCaml multicore GC reserves 4 GB for the minor In addition, the first solution incurs an allocation, whereas the heap. second solution only requires boilerplate, but confuses tools like Go further uses virtual addressing tricks to support interior debuggers, static analysers and such, in ways that are unlikely 5 pointers: pointers to the inside of a block . The bitpattern is used to be fixable over time. Moreover, they do not bring additional to encode the size of the block and thus where it starts. safety in terms of resource-management: what they do is to deal Making use of large areas of reserved address space is a com- in a rather radical manner with the problem of growing the heap mon technique used in research about memory allocators. For while remembering who was outside, which is mostly required by instance, Lvin, Novark, Berger, and Zorn(2008) reads: “On the reliance on the system malloc by the GC to request chunks of modern architectures, especially 64-bit systems, virtual address memory. This issue too can also be solved by virtual addressing space is a plentiful resource.” In Powers, Tench, Berger, and techniques.8 McGregor(2019), a large virtual address space consumption is The latter issue indeed happens in practice; for instance one traded for the ability to merge physical pages, in order to perform industrial user explains that they solve it by forcing a GC to re- compaction without relocation. move out-of-heap pointers after a large out-of-heap structure has been deallocated, when they use the LLVM bindings. However they write: “Short of rewriting the llvm bindings, I am not aware 2.3. Backwards compatibility of a better / more future-proof implementation strategy. Even ignoring any performance concerns about allocating a block For simplicity or conclusion in the lack of an efficient solution, for every one of the very many pointers passed from libllvm to the multicore OCaml GC does not support OCaml’s naked point- OCaml.” ers. Replacing the page table by a technique based on virtual In that case, the technique we propose would avoid a . address space reservation could offer the possibility to avoid rewrite, but also fix the bug code breakage while preserving efficiency, as suggested in an Lastly, another well-known use of out-of-heap pointers, which experiment from 2013 by Jacques-Henri Jourdan6. is known to break with the no-naked-pointers mode, is sym- 9 In the recent years, the perils of breaking backwards compatib- bolised by the Ancient library and the Netmulticore library, lity in a programming language has been demonstrated by Python which propose to allocate OCaml values outside of the garbage- 3, which introduced breaking changes that were expected to be collected heap to offer heaps that can be shared or large (such as minor, including a change to the semantics of strings whose con- larger than available RAM). Neither an indirection nor tagged pointers can be used to replace this use-case, a fact that has been sequences were miscalculated. While little documentation about 10 the role of backwards-compatibility in programming language known from the start . This is the subject of next section. evolution in general is found in the literature, Malloy and Power (2019) investigate the Python 2 vs. Python 3 split after ten years 3. Interest of out-of-heap allocation in and conclude that instead of favouring modernisation, the break- ing changes in Python 3 slowed the language evolution: “[...] current and future OCaml Python software developers are not exploiting the new features and advantages of Python 3, but rather are choosing to retain I have mentioned that beyond foreign pointers, whose support backward compatibility with Python 2. Moreover, Python devel- already benefits backwards-compatibility and interoperability, opers are confining themselves to a language subset, governed there are libraries that allocate OCaml values outside of the heap. by the diminishing intersection of Python 2 [...] and Python 3.” Here I replace this usage in the context of evolving OCaml to We do not know how to predict the impact of breaking changes make it a better systems programming language. in programming languages, even if they are believed to be minor. Thus, a non-breaking solution could in fact quicken the multicore 3.1.