Skip to content
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

rework cppgc for prototype chain #1047

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

rework cppgc for prototype chain #1047

wants to merge 11 commits into from

Conversation

littledivy
Copy link
Member

@littledivy littledivy commented Jan 16, 2025

Implement prototype inheritance into cppgc object wraps.

struct Foo {}

impl GarbageCollected for Foo {}

#[op2]
impl Foo {
    fn super_method(&self) {
        println!("super method called!");
    }
}

struct Bar {}

impl GarbageCollected for Bar {}

#[op2(inherit = Foo)]
impl Bar {
    #[constructor]
    #[cppgc]
    fn new() -> (Foo, Bar) {
        let foo = Foo {};
        let bar = Bar {};

        (foo, bar)
    }

    #[fast]
    fn use_proto(&self, #[proto] foo: &Foo) {
        // do something with foo
    }
}
const bar = new Bar();

assert(bar instanceof Foo);
bar.superMethod();
bar.useProto();

@littledivy littledivy marked this pull request as ready for review January 16, 2025 14:41

/// A good enough number for the maximum prototype chain depth based on the
/// usage.
const MAX_PROTO_CHAIN: usize = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we're not gonna hit this limit immediately in Deno?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is only for native object proto chain. The actual JS proto chain isn't restricted.

@@ -58,6 +132,78 @@ pub fn make_cppgc_object<'a, T: GarbageCollected + 'static>(
wrap_object(scope, obj, t)
}

pub fn make_cppgc_object2<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change a name to something more descriptive to suggest that it uses the second argument as a prototype

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's named because its easier for the macro to do: format!("make_cppgc_object{}", chain.len()). These aren't supposed to be publicly used. I've added #[doc(hidden)]

Copy link
Member

@lucacasonato lucacasonato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think cppgc_template_constructor should throw a TypeError.

/// ErasedPtr is a wrapper around a `v8::cppgc::Ptr` that allows downcasting
/// to a specific type. Safety is guaranteed by the `tag` field in the
/// `CppGcObject` struct.
struct ErasedPtr {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this abstraction is much nicer than what we had previously. With this it is impossible to accidentially use T without checking ptr.tag anymore. Great!

fn trace(&self, visitor: &v8::cppgc::Visitor) {
// Trace all the objects top-down the prototype chain.
for ptr in self.0.iter().flatten() {
ptr.ptr.trace(visitor);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this dispatch correctly? We don't want to call EreasedPtr's T here - I assume this all works out, but can you add some comments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Copy link
Member

@lucacasonato lucacasonato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devsnek PTAL also

core/cppgc.rs Show resolved Hide resolved
tag: TypeId::of::<T>(),
member: t,
},
PrototypeChainStore([
Copy link
Member

@devsnek devsnek Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do normal objects need this? its unfortunate to have double the allocations. could we instead have two tags, one for plain objects and one for prototype backed objects? plain objects can continue to use wrap/unwrap, and prototype backed objects can use wrap2/unwrap2.

}
}

struct PrototypeChainStore([Option<ErasedPtr>; MAX_PROTO_CHAIN]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe crazy but could this be an interior linked list of ErasedPtr stored on each object? not sure if saving the memory is worth the slight overhead of traversing when unwrapping, perhaps worth testing?

Copy link
Member Author

@littledivy littledivy Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried it out:

#[repr(C)]
struct CppGcObject<T: GarbageCollected> {
  tag: TypeId,
  next: Option<ErasedPtr>,
  member: T,
}
./dcore_proto_linked_list test.mjs # linked list
🛑 deno_core binary is meant for development and testing purposes.
Run test.mjs

1e8x new DOMPoint(): 10290ms
1e8x point.x: 2985ms
./dcore_proto_array test.mjs # array
🛑 deno_core binary is meant for development and testing purposes.
Run test.mjs

1e8x new DOMPoint(): 10544ms
1e8x point.x: 2785ms

Maybe it makes sense to use linked list when we increase depth further than 3?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants