mirror of
https://github.com/servo/servo
synced 2026-05-08 16:12:15 +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()])
|
||||
|
||||
Reference in New Issue
Block a user