I mean, you should hear the screams from Rust/Go people about how bad C is, and now you're just going to do it?
I mean, you should hear the screams from Rust/Go people about how bad C is, and now you're just going to do it?
Adding more unsafe tools to Go or Rust do not diminish their safety guarantees. In fact, when Go and Rust add new unsafe features, they generally do so in a fashion that is much more constrained and has better defined edges than traditional methods, so that it's easier to use them correctly and easier to detect misuses. For example, the borrow checker still runs in Rust unsafe blocks; you still have to circumvent the borrow checker even once you're in unsafe. As for this arena package, it seems to have a few safety mechanisms, and hell, I don't even think it's off the table that it could be made entirely memory safe. This is especially true since memory safe does not mean "free of runtime errors", so all that is needed is to be able to detect the error conditions without allowing potentially undefined behavior to occur first.
Programming language experiments like Go's new arena package are good; they allow exploring what you can do to improve old concepts.
of course, this is completely wrong, for anyone reading this. that is simply not how systems work
The unsafe package in Go provides unsafe.Pointer, which allows you to convert between a pointer and integral type, essentially allowing you to make pointers of any type to any address, giving up type safety. Without it, Go pointers are safe, because the GC will never free memory that has live pointers, and you can't do math on Go pointers.
"you can’t import it by default", "you probably shouldn’t be using it at all", "experimental arena package", and 'you must opt-in to even be able to use it via an GOEXPERIMENT environment variable'
As for screams from Rust/Go people about how bad C is, it said:
> This is highly efficient, but also highly dangerous. What if the programmer makes a mistake [...] > > To mitigate the risk of these kinds of bugs, the arena package will deliberately cause a panic if can detect someone reusing memory after it has been freed.
and goes on to explain that each arena has its own unique/distinct address space, so that if a pointer to an object still exists and is dereference it will get a memory access fault, causing the Go program to terminate, with an error message specifying that arena as the cause of the problem.
Sounds like a pretty reasonable/cautious experiment to me.
As a rust student myself, even the unsafe{} block is stupid
But the great thing with unsafe blocks is that you get to create a safe API abstraction on top. You manually verify closely the unsafe parts, and if you got that short part right you can use the safe wrapper wherever you want safely.
That does not seem to be how it is implemented now. Try the example use-after-free on https://uptrace.dev/blog/posts/go-memory-arena.html under 'Address Sanitizer'. There is no error at all when you dereference, it silently keeps working as freed pointers often do (until they don't). Maybe they will add the virtual address space thing later.
Edit: the issue (https://github.com/golang/go/issues/51317) in which that comment was made also has some people referring to the arena being freed "when the runtime gets around to it" rather than immediately. I can't imagine why the virtual address space wouldn't be made poisonous inside the arena.Free method, but it's possible they do do it later. So your pointers maybe do stop working at an indeterminate time. Sounds pretty much like C. I even tried adding a few `runtime.GC()` calls before dereferencing but to no avail, couldn't get it to crash.
The Java 20 implementation has no such issues (https://gist.github.com/cormacrelf/8ddd3cc1b086e4ade93c029a9...), partly because there is no API for allocating a generic T in the arena, so all dereferences are through the managed MemorySegment API. It only seems to offer raw memory, which you can use to implement arrays of unboxed C-style structs, which is great for FFI and network buffers etc. This would be a good tradeoff for Go as well, surely.
Unboxed C types in Go can be done with cgo (it's not necessarily pleasant, but it works).
Such as? Java buffers use byte arrays by default as underlying storage.
Look for "Direct vs. non-direct buffers". Some buffers are backed by arrays, some are not.
You're basically deciding whether you are okay with a higher cost for I/O or a higher-cost for interacting with the data in the buffer.
Also, I fail to get your conclusion — direct buffers only have a higher cost for initialization/dealloc otherwise reading/writing should be as cheap as it gets. Byte buffers on the other hand do get write barriers but that is also unlikely to be hit that often (and I believe larger byte arrays get allocated in a separate region that doesn’t get moved).
Moreover, when the go team accomplishes the same goals by extending the language instead of using a library, this is well received.
I'm sorry, I'm not really sure what difference between Buffer and byte[] in Java I'm supposed to care about, except for the details of how these objects are allocated in memory. There is not really any semantic difference between them, as far as I can tell.
Maybe I don't understand what you're saying--I'd love for you to clarify the reasons why you think Go developers are, I guess, a little bit dense, or foolish, or whatever negative adjectives you like to apply to people who use Go (you know, rather than talking about the language itself--it's always fun to make fun of people).
Go GC does not move objects in memory (mark and sweep) while other GCs (mark and compact) do, to avoid heap fragmentation and better throughput (see generational GCs).
One issue with mark and compact GCs is that you can not move the array of bytes during a system call. A ByteBuffer/Memory object, is an object that allocates its byte arrays using malloc, outside of the heap, so the bytes are not moved by the GC.
What you're encountering is one of two exceptions in the implementation where you might not get an immediate failure:
1. If you use only a very small amount of an arena chunk and free it, it goes back on a reuse list as an optimization. Accessing that chunk's memory, despite the fact that the arena was freed, is entirely memory safe: nothing else will use that memory. That address space will be properly poisoned once the arena chunk is full, or close to it. 2. If the GC is actively marking, arena chunk poisoning is delayed to avoid races with the GC that might cause it to dereference a pointer into poisoned memory. The arena chunk is poisoned as soon as the GC is done.
The API explicitly does not guarantee a crash on use-after-free[1] because the Go team wanted a valid implementation of arenas to be simply "new" for New and noop for Free. The point is to just stay memory safe and have a high probability of catching an issue _in production_ where presumably arenas are filled up (otherwise why are you using arenas?).
Edit: arguably that optimization should just be turned off for MSAN/ASAN mode for greater user-friendliness, which seems reasonable. I think that was just an oversight.
[1]: https://cs.opensource.google/go/go/+/master:src/arena/arena....
I still have a pointer into the chunk. If you reuse the chunk in a different arena, I still have a pointer into the chunk. At no point is it invalidated, and the new arena will now start allocating new stuff from the start of the chunk. Right? And my old pointer still works, and the data inside it is at some point overwritten. How is that memory safe?
Are you eventually unmapping the virtual address space and remapping the underlying allocation somewhere else? So the poisoning happens when the chunk is picked up for reuse by a new arena?
It doesn't allocate at the start of the chunk, it just picks up wherever the last one left off. That allocated memory in the chunk from previous arena allocations is not reused until the chunk as a whole is unmapped and the GC can confirm that no more pointers point into that chunk's address space (if you leave a dangling pointer, you only waste address space). This points-into property is cheap to check, it's equivalent to whether the chunk has been marked by the GC.
Again, I think that MSAN/ASAN should probably just be more strict with these kinds of use-after-frees. You won't crash, but it's still technically incorrect. (Not much can be done about the "GC is in the mark phase" case, unfortunately. Otherwise MSAN/ASAN will complain when the GC inevitably tries to access a pointer into a delayed chunk.)
Wait until you learn how Rust now has a crate that implements seamless concurrent tracing GC, just like Go... https://redvice.org/2023/samsara-garbage-collector/
But if you have a compacting GC, you shouldn't need an arena. You should be able to get away with fixing the GC.
Practically speaking, "fixing the GC" is just a really, super hard problem. You're making a tradeoff between memory usage, CPU usage, pause times, and the performance of various features like pinned objects.
It makes sense that you may want different tradeoffs in different parts of your program. "Fixing the GC" is right up there with "just make a smart enough compiler" type sentiment that gets you in trouble--it's easy to say that you desire a better GC or better compiler, but when you actually try to make a better GC or better compiler, you find out that it doesn't solve the problems you were hoping it would solve.
Go's GC is tuned aggressively for short pause times. A lot of people actually want short pause times in their GCs, enough so that it's a selling point for Go. In Go, those short pause times were achieved partly by sacrificing CPU efficiency. You can't just go in and "fix" the CPU efficiency problems, but you can make new APIs that give you an escape hatch.
It would be nice for some debug/testing mechanism (akin to -race, or zig's detectLeaks) to proactively verify nothing has outlived the arena.
Yes, it's because Go's GC is primarily tuned for latency at the cost of (memory allocation) throughput. Furthermore, there are only a few knobs for Go's GC.
If Go's GC is implemented a bit differently and has a few more knobs so that it can be optimized for memory allocation throughput, maybe Go doesn't need an arena library. I don't even know other GC-ed languages that have/need an arena library because their GC is customizable enough.