mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
script: Use a u16 for prototype id to speed up Castable::is<T>() (#44364)
`Castable::is<T>()` calls `get_dom_class()` in a way that incurs a
roundtrip to the JS engine and
multiple pointer indirections:
self.reflector().get_jsobject().get() -> JSObject
Object.shape -> BaseShape.clasp -> JSClass
DOMJSClass.dom_class -> DOMClass
interface_chain[depth] == ID -> bool
Since the ID doesn't change after creation of reflectors, we can instead
store the ID on the reflector
and avoid most of that cost. The trick is to use a Depth First Search to
generate IDs that allow a
range check for `Castable::is<T>`. An example of IDs in the DFS looks
like:
EventTarget = 0
Node = 1
CharacterData = 2
Text = 3
CDATASection = 4
Comment = 5
ProcessingInstruction = 6
Document = 7
HTMLDocument = 8
XMLDocument = 9
Testing: Green try run at
https://github.com/webbeef/servo/actions/runs/24640508040 and manual
testing with general browsing.
Signed-off-by: webbeef <me@webbeef.org>
This commit is contained in:
@@ -72,6 +72,11 @@ from configuration import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
|
AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
|
||||||
|
|
||||||
|
# DFS-ordered prototype ID ranges, populated by GlobalGenRoots.PrototypeList()
|
||||||
|
# and consumed by CGIDLInterface to generate PROTO_FIRST/PROTO_LAST constants.
|
||||||
|
_proto_ranges: dict[str, tuple[int, int]] = {}
|
||||||
|
|
||||||
ALLOWED_WARNING_LIST = [
|
ALLOWED_WARNING_LIST = [
|
||||||
'non_camel_case_types',
|
'non_camel_case_types',
|
||||||
'non_upper_case_globals',
|
'non_upper_case_globals',
|
||||||
@@ -3395,6 +3400,7 @@ let val = PrivateValue(ptr::null());
|
|||||||
JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val);
|
JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
name = self.descriptor.name
|
||||||
return CGGeneric(f"""
|
return CGGeneric(f"""
|
||||||
let raw = Root::new(MaybeUnreflectedDom::from_box(object));
|
let raw = Root::new(MaybeUnreflectedDom::from_box(object));
|
||||||
|
|
||||||
@@ -3409,6 +3415,7 @@ assert!(!canonical_proto.is_null());
|
|||||||
|
|
||||||
{create}
|
{create}
|
||||||
let root = raw.reflect_with(obj.get());
|
let root = raw.reflect_with(obj.get());
|
||||||
|
root.reflector().set_proto_id(PrototypeList::ID::{name} as u16);
|
||||||
|
|
||||||
{unforgeable}
|
{unforgeable}
|
||||||
|
|
||||||
@@ -3441,6 +3448,7 @@ class CGWrapGlobalMethod(CGAbstractMethod):
|
|||||||
members = [f"{function}::<D>(cx.into(), obj.handle(), {array.variableName()}.get(), obj.handle());"
|
members = [f"{function}::<D>(cx.into(), obj.handle(), {array.variableName()}.get(), obj.handle());"
|
||||||
for (function, array) in pairs if array.length() > 0]
|
for (function, array) in pairs if array.length() > 0]
|
||||||
membersStr = "\n".join(members)
|
membersStr = "\n".join(members)
|
||||||
|
name = self.descriptor.name
|
||||||
|
|
||||||
return CGGeneric(f"""
|
return CGGeneric(f"""
|
||||||
unsafe {{
|
unsafe {{
|
||||||
@@ -3459,6 +3467,7 @@ unsafe {{
|
|||||||
assert!(!obj.is_null());
|
assert!(!obj.is_null());
|
||||||
|
|
||||||
let root = raw.reflect_with(obj.get());
|
let root = raw.reflect_with(obj.get());
|
||||||
|
root.reflector().set_proto_id(PrototypeList::ID::{name} as u16);
|
||||||
|
|
||||||
let _ac = JSAutoRealm::new(cx.raw_cx(), obj.get());
|
let _ac = JSAutoRealm::new(cx.raw_cx(), obj.get());
|
||||||
rooted!(&in(cx) let mut canonical_proto = ptr::null_mut::<JSObject>());
|
rooted!(&in(cx) let mut canonical_proto = ptr::null_mut::<JSObject>());
|
||||||
@@ -3503,12 +3512,16 @@ class CGIDLInterface(CGThing):
|
|||||||
check = f"ptr::eq(class, unsafe {{ {bindingModule}::Class.get() }})"
|
check = f"ptr::eq(class, unsafe {{ {bindingModule}::Class.get() }})"
|
||||||
else:
|
else:
|
||||||
check = f"ptr::eq(class, unsafe {{ &{bindingModule}::Class.get().dom_class }})"
|
check = f"ptr::eq(class, unsafe {{ &{bindingModule}::Class.get().dom_class }})"
|
||||||
|
# Get DFS-ordered ID range for this interface (set by PrototypeList generation).
|
||||||
|
proto_first, proto_last = _proto_ranges.get(name, (0, 65535))
|
||||||
return f"""
|
return f"""
|
||||||
impl IDLInterface for {name} {{
|
impl IDLInterface for {name} {{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn derives(class: &'static DOMClass) -> bool {{
|
fn derives(class: &'static DOMClass) -> bool {{
|
||||||
{check}
|
{check}
|
||||||
}}
|
}}
|
||||||
|
const PROTO_FIRST: u16 = {proto_first};
|
||||||
|
const PROTO_LAST: u16 = {proto_last};
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -9142,8 +9155,51 @@ class GlobalGenRoots():
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def PrototypeList(config: Configuration) -> CGThing:
|
def PrototypeList(config: Configuration) -> CGThing:
|
||||||
# Prototype ID enum.
|
# Prototype ID enum.
|
||||||
|
# Assign IDs in DFS preorder of the inheritance tree so that
|
||||||
|
# all descendants of a type occupy a contiguous ID range.
|
||||||
|
# This enables O(1) is::<T>() checks via range comparison.
|
||||||
interfaces = config.getDescriptors(isCallback=False, isNamespace=False)
|
interfaces = config.getDescriptors(isCallback=False, isNamespace=False)
|
||||||
protos = [d.name for d in interfaces]
|
|
||||||
|
# Build a tree: parent_name -> sorted list of child names
|
||||||
|
children: dict[str | None, list[str]] = {}
|
||||||
|
name_set = set()
|
||||||
|
for d in interfaces:
|
||||||
|
name = d.name
|
||||||
|
name_set.add(name)
|
||||||
|
parent_name = d.prototypeChain[-2] if len(d.prototypeChain) >= 2 else None
|
||||||
|
children.setdefault(parent_name, []).append(name)
|
||||||
|
|
||||||
|
# Sort children alphabetically for determinism within each level
|
||||||
|
for key in children:
|
||||||
|
children[key].sort()
|
||||||
|
|
||||||
|
# DFS preorder traversal
|
||||||
|
protos: list[str] = []
|
||||||
|
# Track range for each prototype: (first_id, last_id)
|
||||||
|
proto_ranges: dict[str, tuple[int, int]] = {}
|
||||||
|
|
||||||
|
def dfs(name: str) -> None:
|
||||||
|
first_id = len(protos)
|
||||||
|
protos.append(name)
|
||||||
|
for child in children.get(name, []):
|
||||||
|
dfs(child)
|
||||||
|
proto_ranges[name] = (first_id, len(protos) - 1)
|
||||||
|
|
||||||
|
# Start from roots (interfaces with no parent in our set)
|
||||||
|
roots = sorted(children.get(None, []))
|
||||||
|
for root in roots:
|
||||||
|
dfs(root)
|
||||||
|
|
||||||
|
# Any interfaces not in the tree (shouldn't happen, but be safe)
|
||||||
|
for d in interfaces:
|
||||||
|
if d.name not in proto_ranges:
|
||||||
|
first_id = len(protos)
|
||||||
|
protos.append(d.name)
|
||||||
|
proto_ranges[d.name] = (first_id, first_id)
|
||||||
|
|
||||||
|
# Store ranges at module level for use by CGIDLInterface.
|
||||||
|
global _proto_ranges
|
||||||
|
_proto_ranges = proto_ranges
|
||||||
constructors = sorted([MakeNativeName(d.name)
|
constructors = sorted([MakeNativeName(d.name)
|
||||||
for d in config.getDescriptors(hasInterfaceObject=True)
|
for d in config.getDescriptors(hasInterfaceObject=True)
|
||||||
if d.shouldHaveGetConstructorObjectMethod()])
|
if d.shouldHaveGetConstructorObjectMethod()])
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
|
|||||||
pub trait IDLInterface {
|
pub trait IDLInterface {
|
||||||
/// Returns whether the given DOM class derives that interface.
|
/// Returns whether the given DOM class derives that interface.
|
||||||
fn derives(_: &'static DOMClass) -> bool;
|
fn derives(_: &'static DOMClass) -> bool;
|
||||||
|
|
||||||
|
/// First prototype ID in the DFS-ordered range for this interface and its descendants.
|
||||||
|
const PROTO_FIRST: u16 = 0;
|
||||||
|
/// Last prototype ID in the DFS-ordered range for this interface and its descendants.
|
||||||
|
const PROTO_LAST: u16 = u16::MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait to mark an IDL interface as deriving from another one.
|
/// A trait to mark an IDL interface as deriving from another one.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use crate::conversions::{DerivedFrom, IDLInterface, get_dom_class};
|
use crate::conversions::{DerivedFrom, IDLInterface};
|
||||||
use crate::reflector::DomObject;
|
use crate::reflector::DomObject;
|
||||||
use crate::script_runtime::runtime_is_alive;
|
use crate::script_runtime::runtime_is_alive;
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@ pub trait Castable: IDLInterface + DomObject + Sized {
|
|||||||
"Attempting to interact with DOM objects after JS runtime has shut down."
|
"Attempting to interact with DOM objects after JS runtime has shut down."
|
||||||
);
|
);
|
||||||
|
|
||||||
let class = unsafe { get_dom_class(self.reflector().get_jsobject().get()).unwrap() };
|
let id = self.reflector().proto_id();
|
||||||
T::derives(class)
|
id >= T::PROTO_FIRST && id <= T::PROTO_LAST
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast a DOM object upwards to one of the interfaces it derives from.
|
/// Cast a DOM object upwards to one of the interfaces it derives from.
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ pub struct Reflector<T = ()> {
|
|||||||
object: Heap<*mut JSObject>,
|
object: Heap<*mut JSObject>,
|
||||||
/// Associated memory size (of rust side). Used for memory reporting to SM.
|
/// Associated memory size (of rust side). Used for memory reporting to SM.
|
||||||
size: T,
|
size: T,
|
||||||
|
/// Cached prototype ID for fast type checks.
|
||||||
|
proto_id: Cell<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T> js::gc::Traceable for Reflector<T> {
|
unsafe impl<T> js::gc::Traceable for Reflector<T> {
|
||||||
@@ -62,6 +64,18 @@ impl<T> Reflector<T> {
|
|||||||
unsafe { HandleObject::from_raw(self.object.handle()) }
|
unsafe { HandleObject::from_raw(self.object.handle()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the cached prototype ID.
|
||||||
|
#[inline]
|
||||||
|
pub fn proto_id(&self) -> u16 {
|
||||||
|
self.proto_id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the cached prototype ID.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_proto_id(&self, id: u16) {
|
||||||
|
self.proto_id.set(id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the reflector. (May be called only once.)
|
/// Initialize the reflector. (May be called only once.)
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@@ -88,6 +102,7 @@ impl<T: AssociatedMemorySize> Reflector<T> {
|
|||||||
pub fn new() -> Reflector<T> {
|
pub fn new() -> Reflector<T> {
|
||||||
Reflector {
|
Reflector {
|
||||||
object: Heap::default(),
|
object: Heap::default(),
|
||||||
|
proto_id: Cell::new(u16::MAX),
|
||||||
size: T::default(),
|
size: T::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ macro_rules! sizeof_checker (
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the sizes here
|
// Update the sizes here
|
||||||
sizeof_checker!(size_event_target, EventTarget, 48);
|
sizeof_checker!(size_event_target, EventTarget, 56);
|
||||||
sizeof_checker!(size_node, Node, 152);
|
sizeof_checker!(size_node, Node, 160);
|
||||||
sizeof_checker!(size_element, Element, 344);
|
sizeof_checker!(size_element, Element, 352);
|
||||||
sizeof_checker!(size_htmlelement, HTMLElement, 360);
|
sizeof_checker!(size_htmlelement, HTMLElement, 368);
|
||||||
sizeof_checker!(size_div, HTMLDivElement, 360);
|
sizeof_checker!(size_div, HTMLDivElement, 368);
|
||||||
sizeof_checker!(size_span, HTMLSpanElement, 360);
|
sizeof_checker!(size_span, HTMLSpanElement, 368);
|
||||||
sizeof_checker!(size_text, Text, 184);
|
sizeof_checker!(size_text, Text, 192);
|
||||||
sizeof_checker!(size_characterdata, CharacterData, 184);
|
sizeof_checker!(size_characterdata, CharacterData, 192);
|
||||||
|
|||||||
Reference in New Issue
Block a user