-
Notifications
You must be signed in to change notification settings - Fork 925
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
Add -gc=custom option #3302
Add -gc=custom option #3302
Conversation
src/runtime/gc_stack_portable.go
Outdated
@@ -61,3 +55,12 @@ func trackPointer(ptr unsafe.Pointer) | |||
func swapStackChain(dst **stackChainObject) { | |||
*dst, stackChainStart = stackChainStart, *dst | |||
} | |||
|
|||
func init() { |
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.
Since custom GC won't call markStack it gets removed during compilation as does the volatility. An init method is never removed so seems to allow the volatility to be correct regardless of markStack.
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 agree at the moment it's not removed, I'm just concerned about smarter optimizers that notice that all the writes to stackChainStart
happen after the load..
Maybe we should keep the volatile load in markStack
and add one in init
as you have here?
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.
(Edit: tested this with the standard conservative gc and it seems to work.)
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.
That's a good point. I went ahead and added it to both locations for now
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.
Don't put it in an init. As I said in the comment above, the following is unsafe:
Since custom GC won't call markStack
Therefore, because markStack
must be used, this change can be reverted.
src/runtime/gc_custom.go
Outdated
} | ||
|
||
func markRoots(start, end uintptr) { | ||
// GC manages roots so ignore to allow gc_stack_portable to compile |
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.
Some shuffling of code into separate files with build tags could remove the need for these stubs but it didn't seem worth it
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.
This is unsafe. The GC must call markStack
which will in turn call markRoots
. Otherwise the stack won't be scanned for objects. The reason this call is ignored in -gc=leaking
and -gc=none
is because they don't deallocate anything anyway.
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.
The custom GC can access the stack pointer and mark the stack without calling this function itself. In the case of the bdwgc implementation, it's C code to read the stack via a pointer to a local variable seems to work fine, and if wasn't our code would crash quickly as for other cases of missing the stack.
More abstractly, when using a library like bdwgc there is no API to mark - just add roots (not needed for the stack as above though). So there isn't really a way to wire this to it.
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.
In the case of the bdwgc implementation, it's C code to read the stack via a pointer to a local variable
I don't think this is correct for WebAssembly. Yes, this works for any other architecture but there is a reason why we need to keep this custom shadow stack in TinyGo for WebAssembly: WebAssembly can put values on the VM evaluation stack which isn't accessible from outside that particular function.
If bdwgc somehow manages to read the stack without this, please let me know how it is able to do this. Because I'm pretty sure this is fundamentally impossible.
seems to work fine, and if wasn't our code would crash quickly as for other cases of missing the stack.
No. I've found GC bugs where some roots weren't marked after months of use, so "it doesn't crash" isn't enough proof it works fine.
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.
The shadow stack is managed by copying to the C stack right? That's why it works, since the C stack is marked.
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.
We see that thanks to recent changes, that's all markStack does in TinyGo too
It is just marking the C stack. That's what bdwgc does too and any custom GC would be doing it. There doesn't seem to be a need for an explicit call to do it if the C library implementing GC does it automatically
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.
As far as I can tell the main concern is still the volatile load - it's not so much that markStack is needed for finding the stack but that it is needed to guarantee the shadow stack is fresh. Implementations can choose to ignore the call to markRoots
if they don't need it, as with bdwgc.
I went ahead and reverted the volatile load change and added markRoots here, which with bdwgc will be no-op. Hopefully that makes sense
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.
Hmm, you are right. It is indeed enough to force the compiler to have an up-to-date shadow stack by doing the volatile load. Then it can scan the globals conservatively and find the current stack that way.
@@ -1,5 +1,5 @@ | |||
//go:build tinygo.wasm && !custommalloc |
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.
custommalloc was added recently and not released yet, if adding gc.custom
it doesn't seem worth having a separate tag for it.
Confirmed the interface works in https://github.com/corazawaf/coraza-proxy-wasm/compare/main...anuraaga:gccustom?expand=1 |
@aykevl Are you happy with this? |
Any movement @aykevl? |
@jcchavezs I'm currently relocating so unfortunately I don't really have the time to look at this right now. Hopefully next week. |
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.
Did a first pass review. It looks quite reasonable, with a few notes here and there on how to do things more safely.
That said, I don't think we will want to promise any kind of API stability here, and I think this should be noted in gc_custom.go. The reason is that promising anything like that will make this feature a much bigger maintenance burden for a feature that shouldn't be used much anyway (if it is used a lot, support for that GC should be implemented upstream instead).
src/runtime/gc_custom.go
Outdated
// - func KeepAlive(x interface{}) | ||
// - func SetFinalizer(obj interface{}, finalizer interface{}) |
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.
See #3336. We don't support these yet so I've simplified things a bit for now.
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.
It's not a high priority but I was going to implement setFinalizer in the custom implementation. Is it ok to keep them to try it? I guess in principle anyways a finalizer is a feature of the gc
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'm interested in implementing finalizer, but for now I've removed the methods from this PR to make sure it compiles with the dev branch where the change was merged
// In addition, if targeting wasi, the following functions should be exported for interoperability | ||
// with wasi libraries that use them. Note, this requires the export directive, not go:linkname. | ||
// | ||
// - func malloc(size uintptr) unsafe.Pointer | ||
// - func free(ptr unsafe.Pointer) | ||
// - func calloc(nmemb, size uintptr) unsafe.Pointer | ||
// - func realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer |
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.
These are normally defined by the runtime, and call runtime.alloc
/runtime.free
as needed. Would it be possible to keep it that way and let the custom GC only provide the functions above?
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.
We added the custommalloc build tag and this intends to replace it with just the concept of a custom GC.
Replacing malloc with an implementation that doesn't delegate to Go GC (in my case mimalloc) had a dramatic improvement in performance and is important for allowing high performance. I'd be fine with keeping the current tag though if it seems better.
src/runtime/gc_custom.go
Outdated
} | ||
|
||
func markRoots(start, end uintptr) { | ||
// GC manages roots so ignore to allow gc_stack_portable to compile |
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.
This is unsafe. The GC must call markStack
which will in turn call markRoots
. Otherwise the stack won't be scanned for objects. The reason this call is ignored in -gc=leaking
and -gc=none
is because they don't deallocate anything anyway.
src/runtime/gc_stack_portable.go
Outdated
@@ -61,3 +55,12 @@ func trackPointer(ptr unsafe.Pointer) | |||
func swapStackChain(dst **stackChainObject) { | |||
*dst, stackChainStart = stackChainStart, *dst | |||
} | |||
|
|||
func init() { |
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.
Don't put it in an init. As I said in the comment above, the following is unsafe:
Since custom GC won't call markStack
Therefore, because markStack
must be used, this change can be reverted.
BTW this is the implementation of the GC with this PR in case missed from the linked issue I'm fairly confident that the stack is getting marked fine without any explicit call to anything because before #3291, it was quickly crashing and after that it's stable |
Co-authored-by: Ayke <[email protected]>
Thanks for the comment @aykevl - is there anything left for this PR? Think I addressed all the comments. Thanks! |
Rebased once more, probably the last time. If this isn't going in, really need a response saying that so we can move on |
Ping @aykevl |
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.
This looks good to me. It doesn't touch many parts of the runtime and therefore should have a low maintenance overhead.
@dgryski any comments before I merge?
Fine to merge from me. It might be nice to have a unit test that checks we don't break this in the future, but that's not a blocking issue for me. |
Yeah a test would be nice, but that means we'd have to at least make a stub custom allocator. |
Ok, so let's merge and add a doc+warning in another PR. |
* Add -gc=custom option
Fixes #3278
A custom GC can be useful for prototyping GC algorithms or writing app-specific GCs that optimize based on knowledge local to an app (e.g., scoped arenas). There is interest in adding bdwgc to TinyGo, but in preparation I think this option can make prototyping that easier, as well as any future improvements.