5 ms·
There’s no way to seal the GC’s unsafety behind a safe API unless the safe code pins every GC object it touches. That’s not what happens in a browser engine - t
by pizlonator 2y ago
There’s no way to seal the GC’s unsafety behind a safe API unless the safe code pins every GC object it touches. That’s not what happens in a browser engine - the GC has to ask the rest of the engine where its pointers are, and if any part of the browser fails to account for a pointer correctly, then you get use after free as soon as the GC runs. It’s a harder problem than you make it seem.
- Dylan16807 2y agoDoesn't Rc already demonstrate that kind of accounting? You're going to have to add GC awareness to objects so it can trace through them, but the code that makes and manipulates those objects can have all the necessary rules enforced by types.
- pizlonator 2y agoIt’s possible with RC but not GC. GC != RC. A type system can let you say things like “make sure someone finds out when I assign this pointer”. But that’s not what GC wants, unless (as I said before) you’re happy with your non-GC heap pinning objects (the browser isn’t, but some GC clients are, sometimes). GC wants a guarantee like: you cannot have a pointer in any data structure unless that data structure can be found by GC, and when it is found, make sure to respond to the GC’s request to scan it. That ends up being hard to do with any type system, unless that type system only allows objects to be allocated by that GC and the abstraction sits in the compiler/runtime below the type system (like in Java, Go, and Fil-C).
- Dylan16807 2y ago> GC != RC. I am aware. > But that’s not what GC wants, unless (as I said before) you’re happy with your non-GC heap pinning objects (the browser isn’t, but some GC clients are, sometimes). I can't figure out exactly what you mean here. Temporary pins on the stack should be fine, and I don't see why anything else would be necessary. > GC wants a guarantee like: you cannot have a pointer in any data structure unless that data structure can be found by GC, and when it is found, make sure to respond to the GC’s request to scan it. That ends up being hard to do with any type system, unless that type system only allows objects to be allocated by that GC and the abstraction sits in the compiler/runtime below the type system (like in Java, Go, and Fil-C). Doing heap allocations through the GC doesn't sound very hard. And why does it need to be below the type system? Maybe instead I should ask you what the already existing GC libraries for Rust get wrong? Do they require users to be unsafe or do something to break safety? Are they unusable to implement javascript?
- pizlonator 2y ago> I can't figure out exactly what you mean here. Temporary pins on the stack should be fine, and I don't see why anything else would be necessary. The browser has a whole native heap (i.e. C++ objects, in current impls) that participates in the JS GC heap as follows: - If the C++ object is referenced from C++ or from JS, it must be kept alive. - If the C++ object references a JS object, then the JS object must be kept alive, so long as the C++ object would have been alive per the previous rule. - It's possible for an object reference chain like JSobject->C++object->C++object->JSobject, and let's assume there are no other pointers to the C++ objects, in which case the last JS object should kept alive by GC only if the first JS object is alive. - It's possible for dead reference cycles to exist like JSobject<->C++object, in which case both should die. This requires that C++ has the ability to place references to JS objects in C++ fields. This is where pinning comes in. It would be quite simple (and memory safe) but also totally incorrect (i.e. massive memory leak) to say that if a C++ field points to a JS object, then the GC just sees that field as a root. This is what I mean by pinning. (Note that "pinning" has many meanings in GC jargon; I'm using the Hermes version of the jargon. I guess I could have said "strong root" or something, but that's weirder to say.) This would be wrong, since it would not allow us to collect the dead cycle at all. Dead cycles are a common case in the browser. It would also cause other subtle breakage. So, what the browser does instead is to have the C++ heap participate in GC: every C++ object that could possibly store a reference to JS objects anywhere can respond to GC callbacks asking it to account for all of those references. And, every C++ object needs to have a story for being referenced exclusively from JS, exclusively from C++, or a combo of the two. And the C++ code needs to be able to participate in whatever barrier discipline is necessary to get generations or incrementality or concurrency that the JS heap wants. There are different ways to do this. Blink's Oilpan is probably the most principled, and that's basically a whole GC-for-C++ framework - very complex stuff. So, tons of inherently not-memory-safe code on the browser side just so it can do business with the JS heap.
- Dylan16807 2y agoI'm trying to figure out where our understandings differ, since that generally sounds familiar and reasonable to me. I guess you're assuming the callback/accounting code needs to be unsafe or able to violate the GC's preconditions? But I don't see why you assume that. As part of the GC, build some data structures that can handle that accounting and present a safe API, then use those data structures anywhere you don't want to create a root. When the browser code accesses the contents, barriers can be applied automatically. The hard part of using a data structure like that is giving it ownership of the data and control over destroying it, but Rc does that too, doesn't it? That kind of thing is why I mentioned Rc. The difference between Rc and GC is much more in the behind-the-scenes tracking than in the API it gives.