Conversation
This comment was marked as outdated.
This comment was marked as outdated.
4938422 to
170b02a
Compare
|
/bigbonk roast this pr |
This comment was marked as outdated.
This comment was marked as outdated.
ba7b471 to
8a6f84a
Compare
|
/bigbonk roast this pr |
This comment has been minimized.
This comment has been minimized.
57bfc1c to
1d356e2
Compare
This comment has been minimized.
This comment has been minimized.
1d356e2 to
1525e8e
Compare
|
/bigbonk roast this pr |
This comment was marked as outdated.
This comment was marked as outdated.
d64dc80 to
2a09295
Compare
2a09295 to
ec26bc5
Compare
|
/bigbonk roast this pr |
|
@anonrig Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
ec26bc5 to
9dbfffd
Compare
aa52d4d to
d1675b2
Compare
|
Wow, it appears this really does resolve the AI generated concern... nice work! One last comment here - instead of manually doing macro generations why aren't we just using a trait here for all types? Here's the full generated suggestion: Suggestion: trait-based GC tracing instead of macro field-type pattern matching /// # Safety
/// The implementation must report ALL GC-reachable references to the visitor.
/// A missing reference allows the GC to collect a live object, causing
/// use-after-free via cppgc.
pub unsafe trait Trace {
fn trace(&self, visitor: &mut GcVisitor);
}The macro then generates a uniform field walk with no type inspection: fn trace(&self, visitor: &mut GcVisitor) {
Trace::trace(&self.field_a, visitor);
Trace::trace(&self.field_b, visitor);
}If a field type doesn't implement unsafe impl<T> Trace for NotTraced<T> {
fn trace(&self, _visitor: &mut GcVisitor) {} // explicitly opted out
}Provided impls in the jsg crate cover all the common cases: unsafe impl<R: Resource> Trace for jsg::Rc<R> {
fn trace(&self, visitor: &mut GcVisitor) { visitor.visit_ref(self); }
}
unsafe impl<R: Resource> Trace for jsg::Weak<R> {
fn trace(&self, _visitor: &mut GcVisitor) {} // weak refs not traced
}
unsafe impl<T> Trace for jsg::v8::Global<T> {
fn trace(&self, visitor: &mut GcVisitor) { visitor.visit_global(self); }
}
unsafe impl<T: Trace> Trace for Option<T> {
fn trace(&self, visitor: &mut GcVisitor) {
if let Some(inner) = self { inner.trace(visitor); }
}
}
unsafe impl<T: Trace> Trace for Cell<Option<T>> {
fn trace(&self, visitor: &mut GcVisitor) {
// SAFETY: GC tracing is single-threaded within a V8 isolate and
// never re-entrant on the same object. Read-only access through
// as_ptr() is sound under this contract.
if let Some(inner) = unsafe { &*self.as_ptr() } {
inner.trace(visitor);
}
}
}Plain types with no GC edges ( unsafe impl Trace for String {
fn trace(&self, _visitor: &mut GcVisitor) {}
}
// ... and so on for all primitives/stdlib typesThese are all inlined and eliminated by LLVM — there is no runtime cost for non-GC fields. The only overhead is the actual |
5535e38 to
6cc15fc
Compare
|
The generated output of |
6cc15fc to
3007c0c
Compare
There was a problem hiding this comment.
Discussed this with @anonrig. Would be great to see a trait design as mentioned if possible, but that can be a follow-up. Regarding the unwind safety it would be nice to still hear the full story on that as well in terms of how panics propagate and terminate the isolate / get reported.
3007c0c to
7aa7a48
Compare
c42d78b to
677011e
Compare
|
Something that can be addressed later but is worth mentioning. A common pattern with the GC-tracing mechanism currently in the C++ side of things is: class Foo: public jsg::Object {
public:
Foo() = default;
private:
struct PrivateData {
void visitForGc(jsg::GcVisitor& visitor) {
// Visit any member variables that are jsg::Object or jsg::Value here.
}
};
PrivateData privateData_;
void visitForGc(jsg::GcVisitor& visitor) override {
privateData_.visitForGc(visitor);
}
};Specifically, the items to be traced are held by some other child structure. Given the way the macros are currently set up, this isn't possible in the rust design without a fair amount of manual pain... I asked opencode to write up the details... The
A field of type struct PrivateData {
child: jsg::Rc<Child>,
callback: Cell<Option<jsg::v8::Global<jsg::v8::Value>>>,
}
#[jsg_resource]
pub struct Foo {
private_data: PrivateData, // ← NOT traced! Classified as TraceableType::None
}The generated The manual escape hatch The
But there's a catch:
What would need to change to support this pattern Two potential approaches, not mutually exclusive: Option A: Add a struct PrivateData { /* ... */ }
impl jsg::GarbageCollected for PrivateData {
fn trace(&self, visitor: &mut jsg::GcVisitor) {
visitor.visit_rc(&self.child);
visitor.visit_global(&self.callback);
}
fn memory_name(&self) -> &'static CStr { c"PrivateData" }
}
#[jsg_resource]
pub struct Foo {
#[trace] // ← tells macro to call self.private_data.trace(visitor)
private_data: PrivateData,
}This is a small change to Option B: Add a #[jsg_resource(custom_trace)]
pub struct Foo {
private_data: PrivateData,
}
impl jsg::GarbageCollected for Foo {
fn trace(&self, visitor: &mut jsg::GcVisitor) {
self.private_data.trace(visitor);
}
fn memory_name(&self) -> &'static CStr { c"Foo" }
}This is also a small change — check for Recommendation Option A ( Both are missing from the current PR. Given that this nested-struct pattern is common in the C++ codebase, I'd flag this as a gap worth tracking before Rust resources start holding references to other resources through intermediate structs. For the current use case (simple resources like DnsUtil with no GC-visible fields), it doesn't matter. This pattern is pervasive — there are at least 38 instances in Categories include:
Notable examples:
|
|
I disabled auto-merge. I'm currently doing a deep dive review on this PR again. |
|
There's a similar gc tracing gap with collection, e.g. in C++, Problem: C++ equivalent: The C++ GcVisitor provides We see this pattern in Doesn't need to be addressed in this PR per se but will need to be done before we can migrate a large number of APIs to rust. |
|
Also noting for reference... the following gc traceable types have no equivalent rust types yet and cannot be traced: |
|
Another gap that needs to be verified and likely documented: Problem: Proc macros operate on the syntactic AST, not the resolved type system. A field declared as Impact: Any crate-level type alias, use rename, or newtype wrapper around a GC-visible type will be invisible to the macro. This is a fundamental limitation of proc macros — they cannot resolve types. Mitigations:
|
|
I'm documenting these gaps as individual comments so they can be separately referenced.. here's another: Problem: The C++ codebase uses C++ example (streams): kj::OneOf<StreamStates::Closed, StreamStates::Errored, Readable> state;
void visitForGc(jsg::GcVisitor& visitor) {
KJ_SWITCH_ONEOF(state) {
KJ_CASE_ONEOF(closed, StreamStates::Closed) {}
KJ_CASE_ONEOF(errored, StreamStates::Errored) {
visitor.visit(errored.reason);
}
KJ_CASE_ONEOF(readable, Readable) {
readable.visitForGc(visitor);
}
}
}Impact: Stream controllers, event targets, and other stateful resources use this pattern heavily. Porting any of these to Rust would require either manual GarbageCollected implementations or macro support for enum tracing. |
jasnell
left a comment
There was a problem hiding this comment.
LGTM. Addressesing the tracing gaps I commented on will be a critical necessary next step. We won't be able to migrate a large number of APIs until those are fully addressed.
|
I'd still prefer @kentonv to take a look at well as the whole gc/tracing path in jsg carries some fairly sharp edges if we get details wrong, but won't insist on it. |
Sorry, I am not going to be able to help here. It'd take me weeks just to spin up enough context to be able to meaningfully review this -- TBH I don't really even know Rust yet. My main concern is I want to make sure we're following the same patterns in Rust and C++. No inventing a new way of doing things, unless the change of language specifically calls for it. E.g. obviously Rust macros are very different from C++ macros, so macro design may be different. But the way we do GC tracing should basically be transliterated from C++. Hoping we can stay on that track without me personally reviewing all the details. |
677011e to
3e59d17
Compare
Adds gc support to Rust owned objects using the existing GC implementation, rather than using cppgc heap, which the draft implementation used: #5638.