-
Notifications
You must be signed in to change notification settings - Fork 114
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
base: main
Are you sure you want to change the base?
Conversation
f319d33
to
2d67870
Compare
core/cppgc.rs
Outdated
@@ -58,6 +132,78 @@ pub fn make_cppgc_object<'a, T: GarbageCollected + 'static>( | |||
wrap_object(scope, obj, t) | |||
} | |||
|
|||
pub fn make_cppgc_object2< |
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.
Maybe change a name to something more descriptive to suggest that it uses the second argument as a prototype
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 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)]
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 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 { |
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.
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); |
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.
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?
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.
Added
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.
@devsnek PTAL also
tag: TypeId::of::<T>(), | ||
member: t, | ||
}, | ||
PrototypeChainStore([ |
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.
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.
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.
@littledivy ^^
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 could work with a custom DST. A safe enum will require heap indirection or just have the same size.
I'll give the DSTs a shot, that might also improve the 3
protochain limitation we have since we could just size it at runtime.
size: u8
data: [CppGcObject]
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.
if you can solve it with one system that's cool, but i just meant have two different wrap mechanisms. the default one would be the existing one, and you can opt into a prototype mode if you're using that.
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.
Ok, I made the prototype chain opt-in with #[op2(base)]
on the base class otherwise it is treated as a basic object with a single CppGcObject allocation.
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.
oh but that would not work with try_unwrap_cppgc_object
. we'll basically have to have 2 functions and the macro would somehow need to know which one to use to "unwrap" self
.
#[op2]
impl A {
fn method(&self) {
// we don't know statically if `self` is with proto or without proto.
//
// the macro produces `try_unwrap_cppgc_object(args.this())`
// that uses the `PrototypeChain` trait to figure that out.
}
}
#[op2]
fn op_a(#[cppgc] a: &A) {
// same here
}
It is doable with extra attributes if you feel strongly about it. I think the PrototypeChain
trait is not that big of a change and most of it is auto generated by the macro anyways.
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.
self would always be with proto. plain fn would be without proto unless you do like #[cppgc(proto)] or something
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.
self would always be with proto.
don't we want it to without proto for single objects tho?
// want this to be without proto:
#[op2]
impl A {}
// want this to be with proto:
#[op2(base)]
impl Base {}
#[op2(inherit = Base)]
impl B {}
So maybe we do this: by default self
and #[cppgc]
are no-proto. And we do proto only when base
and inherit
attribute or#[cppgc(proto)]
is used.
Downside is that devs will have to keep track of what has proto and what doesn't - which was already done for then with the default PrototypeChain
trait.
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.
ultimately I'm just aiming to keep the complexity down for cppgc backed resources. prototype stuff has a lot of its own codegen already so it should be free to use new apis I think.
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.
PTAL 4dc27fb
didn't implement #[cppgc(proto)]
cuz we don't need it right now.
} | ||
} | ||
|
||
struct PrototypeChainStore([Option<ErasedPtr>; MAX_PROTO_CHAIN]); |
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.
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?
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.
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?
Implement prototype inheritance into cppgc object wraps.