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"
|
||||
|
||||
# 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 = [
|
||||
'non_camel_case_types',
|
||||
'non_upper_case_globals',
|
||||
@@ -3395,6 +3400,7 @@ let val = PrivateValue(ptr::null());
|
||||
JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val);
|
||||
"""
|
||||
|
||||
name = self.descriptor.name
|
||||
return CGGeneric(f"""
|
||||
let raw = Root::new(MaybeUnreflectedDom::from_box(object));
|
||||
|
||||
@@ -3409,6 +3415,7 @@ assert!(!canonical_proto.is_null());
|
||||
|
||||
{create}
|
||||
let root = raw.reflect_with(obj.get());
|
||||
root.reflector().set_proto_id(PrototypeList::ID::{name} as u16);
|
||||
|
||||
{unforgeable}
|
||||
|
||||
@@ -3441,6 +3448,7 @@ class CGWrapGlobalMethod(CGAbstractMethod):
|
||||
members = [f"{function}::<D>(cx.into(), obj.handle(), {array.variableName()}.get(), obj.handle());"
|
||||
for (function, array) in pairs if array.length() > 0]
|
||||
membersStr = "\n".join(members)
|
||||
name = self.descriptor.name
|
||||
|
||||
return CGGeneric(f"""
|
||||
unsafe {{
|
||||
@@ -3459,6 +3467,7 @@ unsafe {{
|
||||
assert!(!obj.is_null());
|
||||
|
||||
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());
|
||||
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() }})"
|
||||
else:
|
||||
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"""
|
||||
impl IDLInterface for {name} {{
|
||||
#[inline]
|
||||
fn derives(class: &'static DOMClass) -> bool {{
|
||||
{check}
|
||||
}}
|
||||
const PROTO_FIRST: u16 = {proto_first};
|
||||
const PROTO_LAST: u16 = {proto_last};
|
||||
}}
|
||||
"""
|
||||
|
||||
@@ -9142,8 +9155,51 @@ class GlobalGenRoots():
|
||||
@staticmethod
|
||||
def PrototypeList(config: Configuration) -> CGThing:
|
||||
# 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)
|
||||
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)
|
||||
for d in config.getDescriptors(hasInterfaceObject=True)
|
||||
if d.shouldHaveGetConstructorObjectMethod()])
|
||||
|
||||
@@ -51,6 +51,11 @@ impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
|
||||
pub trait IDLInterface {
|
||||
/// Returns whether the given DOM class derives that interface.
|
||||
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.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::mem;
|
||||
|
||||
use crate::conversions::{DerivedFrom, IDLInterface, get_dom_class};
|
||||
use crate::conversions::{DerivedFrom, IDLInterface};
|
||||
use crate::reflector::DomObject;
|
||||
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."
|
||||
);
|
||||
|
||||
let class = unsafe { get_dom_class(self.reflector().get_jsobject().get()).unwrap() };
|
||||
T::derives(class)
|
||||
let id = self.reflector().proto_id();
|
||||
id >= T::PROTO_FIRST && id <= T::PROTO_LAST
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
/// Associated memory size (of rust side). Used for memory reporting to SM.
|
||||
size: T,
|
||||
/// Cached prototype ID for fast type checks.
|
||||
proto_id: Cell<u16>,
|
||||
}
|
||||
|
||||
unsafe impl<T> js::gc::Traceable for Reflector<T> {
|
||||
@@ -62,6 +64,18 @@ impl<T> Reflector<T> {
|
||||
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.)
|
||||
///
|
||||
/// # Safety
|
||||
@@ -88,6 +102,7 @@ impl<T: AssociatedMemorySize> Reflector<T> {
|
||||
pub fn new() -> Reflector<T> {
|
||||
Reflector {
|
||||
object: Heap::default(),
|
||||
proto_id: Cell::new(u16::MAX),
|
||||
size: T::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ macro_rules! sizeof_checker (
|
||||
);
|
||||
|
||||
// Update the sizes here
|
||||
sizeof_checker!(size_event_target, EventTarget, 48);
|
||||
sizeof_checker!(size_node, Node, 152);
|
||||
sizeof_checker!(size_element, Element, 344);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 360);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 360);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 360);
|
||||
sizeof_checker!(size_text, Text, 184);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 184);
|
||||
sizeof_checker!(size_event_target, EventTarget, 56);
|
||||
sizeof_checker!(size_node, Node, 160);
|
||||
sizeof_checker!(size_element, Element, 352);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 368);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 368);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 368);
|
||||
sizeof_checker!(size_text, Text, 192);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 192);
|
||||
|
||||
Reference in New Issue
Block a user