Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions crates/qjs-cli/hot-functions.order
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
# budget 0x300 __RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute16boxed_truthiness
# budget 0x300 __RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute14boxed_equality
# budget 0xe60 __RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute9get_named
# budget 0x1800 __RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10compact_fn12numeric_plan3run
# pinned: typed-loop executor at 0x200, interpreter's at 0xc40 mod 4 KiB (python3 -m tools.benchmark.layout_pin), head 54
# budget 0x1ee0 __RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10compact_fn12numeric_plan3run
# budget 0x1700 __RNvMNtNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop12helper_graph7numericNtB2_10NumProgram3run
# pinned: typed-loop executor at 0x200, interpreter's at 0xc40 mod 4 KiB (python3 -m tools.benchmark.layout_pin), head 56
__RNvMsz_NtNtNtCs1OjIl8oxbrv_5alloc11collections5btree3mapINtB5_8IntoIteryNtNtNtCscUtGwbhD4WH_5gimli4read6abbrev12AbbreviationE10dying_nextCsg55jX0GwzBC_3std
__RNvXs7_NtNtCsg55jX0GwzBC_3std2io5errorNtB5_5ErrorNtNtCsl8K0bEFm1U0_4core3fmt7Display3fmt
__RINvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute3runNtNtB6_2vm2VmEB8_
Expand All @@ -28,8 +29,7 @@ __RINvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute18try_run_ty
__RNvYINtNvNtCsg55jX0GwzBC_3std2io17default_write_fmt7AdapterINtNtCs1OjIl8oxbrv_5alloc3vec3VechEENtNtCsl8K0bEFm1U0_4core3fmt5Write10write_charB9_
__RNvYNtNtNtNtCsg55jX0GwzBC_3std3sys5stdio4unix6StderrNtNtBa_2io5Write9write_allBa_
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute16get_named_object
__RNvXso_NtNtCsg55jX0GwzBC_3std2io5stdioRNtB5_6StderrNtB7_5Write9write_fmt
__RNvXse_NtNtCsg55jX0GwzBC_3std2io5stdioRNtB5_6StdoutNtB7_5Write9write_fmt
__RNvNvNtCsg55jX0GwzBC_3std2fs4read5inner
__RNvMs4_NtNtCs9nYd1Hk1rek_11qjs_runtime5value5arrayNtB5_8ArrayRef24direct_dense_index_value
__RNvMsn_NtCs1OjIl8oxbrv_5alloc4syncINtB5_3ArcINtNtNtCscUtGwbhD4WH_5gimli4read5dwarf5DwarfINtNtBL_12endian_slice11EndianSliceNtNtBN_9endianity12LittleEndianEEE9drop_slowCsg55jX0GwzBC_3std
__RNvXs7_NtNtCsg55jX0GwzBC_3std2io5stdioNtB5_9StdinLockNtB7_7BufRead9read_line
Expand All @@ -38,35 +38,38 @@ __RNvYINtNtNtCscUtGwbhD4WH_5gimli4read12endian_slice11EndianSliceNtNtB9_9endiani
__RNvNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode15vm_numeric_leaf21try_eval_numeric_leaf
__RNvXNvNtCsg55jX0GwzBC_3std2io17default_write_fmtINtB2_7AdapterNtNtNtNtB6_3sys5stdio4unix6StderrENtNtCsl8K0bEFm1U0_4core3fmt5Write9write_strB6_
__RNvMs3_NtNtCs9nYd1Hk1rek_11qjs_runtime5value5arrayNtB5_9ArrayData21has_property_at_index
__RNvXs_NvNtCsg55jX0GwzBC_3std9panicking13panic_handlerNtB4_19FormatStringPayloadNtNtCsl8K0bEFm1U0_4core5panic12PanicPayload3get
__RNvXNvNtCsg55jX0GwzBC_3std2io17default_write_fmtINtB2_7AdapterINtNtB4_6cursor6CursorQShEENtNtCsl8K0bEFm1U0_4core3fmt5Write9write_strB6_
__RNvMs_NtNtNtCsg55jX0GwzBC_3std2io8buffered9bufwriterINtB4_9BufWriterNtNtB8_5stdio9StdoutRawE14write_all_coldBa_
__RNvXso_NtNtCsg55jX0GwzBC_3std2io5stdioRNtB5_6StderrNtB7_5Write9write_fmt
__RNvNtNtCsg55jX0GwzBC_3std2io5stdio7__eprint
__RNvNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode15vm_numeric_leaf20direct_number_binary
__RNvMsn_NtCs1OjIl8oxbrv_5alloc4syncINtB5_3ArcINtNtNtNtCsg55jX0GwzBC_3std4sync6poison5mutex5MutexINtNtB7_3vec3VechEEE9drop_slowBP_
__RNvXs1g_NtCsl8K0bEFm1U0_4core3fmtRINtNtCs1OjIl8oxbrv_5alloc3vec3VechENtB6_5Debug3fmtCsg55jX0GwzBC_3std
__RNvXse_NtNtCsg55jX0GwzBC_3std2io5stdioRNtB5_6StdoutNtB7_5Write9write_fmt
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute22ordinary_data_property
__RNvNtNtCsg55jX0GwzBC_3std2io5stdio7__eprint
__RNvNtNtCsg55jX0GwzBC_3std3sys9backtrace4lock
__RNvXs_NvNtCsg55jX0GwzBC_3std9panicking13panic_handlerNtB4_19FormatStringPayloadNtNtCsl8K0bEFm1U0_4core5panic12PanicPayload3get
__RNvMs_NtNtNtCsg55jX0GwzBC_3std2io8buffered9bufwriterINtB4_9BufWriterNtNtB8_5stdio9StdoutRawE14write_all_coldBa_
__RNvMNtNtNtCs9nYd1Hk1rek_11qjs_runtime5value6object10slot_readsNtB4_9ObjectRef22own_data_property_read
__RNvMs1_NtNtNtNtCsg55jX0GwzBC_3std3sys4sync6rwlock5queueNtB5_6RwLock21read_unlock_contended
__RNvMs1_NtNtNtNtCsg55jX0GwzBC_3std3sys4sync6rwlock5queueNtB5_6RwLock16unlock_contended
__RNvMs6_NtNtCs9nYd1Hk1rek_11qjs_runtime5value6objectNtB5_9ObjectRef32write_existing_own_data_property
__RNvNtNtNtCsg55jX0GwzBC_3std12backtrace_rs9symbolize5gimli4mmap
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute16boxed_truthiness
__RNvNtNtCsg55jX0GwzBC_3std3sys2fs8read_dir
__RNvMsn_NtCs1OjIl8oxbrv_5alloc4syncINtB5_3ArcNtNtNtCsg55jX0GwzBC_3std6thread6thread5InnerNtNtBM_5alloc6SystemE9drop_slowBM_
__RNvNtNtNtNtCsg55jX0GwzBC_3std3sys2io5error4unix17decode_error_kind
__RNvXNvNtCsg55jX0GwzBC_3std2io17default_write_fmtINtB2_7AdapterINtNtB4_6cursor6CursorQShEENtNtCsl8K0bEFm1U0_4core3fmt5Write9write_strB6_
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute14boxed_equality
__RNvMs8_NtCsg55jX0GwzBC_3std4pathNtB5_10Components25parse_next_component_back
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop7execute9get_named
__RNvNvNtCsg55jX0GwzBC_3std2fs14read_to_string5inner
__RNvMNtCs2IzQ63mrjeP_9addr2line5frameINtB2_9FrameIterINtNtNtCscUtGwbhD4WH_5gimli4read12endian_slice11EndianSliceNtNtBV_9endianity12LittleEndianEE4nextCsg55jX0GwzBC_3std
__RNvNtNtCsg55jX0GwzBC_3std2io5stdio31print_to_buffer_if_capture_used
__RNvMsz_NtNtNtCs1OjIl8oxbrv_5alloc11collections5btree3mapINtB5_8IntoIteryINtNtCsl8K0bEFm1U0_4core6result6ResultINtNtBb_4sync3ArcNtNtNtCscUtGwbhD4WH_5gimli4read6abbrev13AbbreviationsENtB25_5ErrorEE10dying_nextCsg55jX0GwzBC_3std
__RNvNtNtNtNtCsg55jX0GwzBC_3std3sys2io5error4unix17decode_error_kind
__RNvMs_NtNtNtNtCsg55jX0GwzBC_3std12backtrace_rs9symbolize5gimli5machoNtB4_6Object7section
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10compact_fn12numeric_plan3run
__RNvMNtCs2IzQ63mrjeP_9addr2line4unitINtB2_7ResUnitINtNtNtCscUtGwbhD4WH_5gimli4read12endian_slice11EndianSliceNtNtBS_9endianity12LittleEndianEE25find_function_or_locationCsg55jX0GwzBC_3std
__RNvNtCsg55jX0GwzBC_3std5panic19get_backtrace_style
__RNvMsn_NtCs1OjIl8oxbrv_5alloc4syncINtB5_3ArcNtNtNtNtCsg55jX0GwzBC_3std3sys2fs4unix12InnerReadDirE9drop_slowBO_
__RNvYINtNvNtCsg55jX0GwzBC_3std2io17default_write_fmt7AdapterNtNtNtNtB9_3sys5stdio4unix6StderrENtNtCsl8K0bEFm1U0_4core3fmt5Write10write_charB9_
__RNvMs4_NtCs1OjIl8oxbrv_5alloc7raw_vecINtB5_11RawVecInnerNtNtCsg55jX0GwzBC_3std5alloc6SystemE14grow_amortizedBW_
__RNvXs1i_NtCsl8K0bEFm1U0_4core3fmtRNtNtNtB8_5panic8location8LocationNtB6_7Display3fmtCsg55jX0GwzBC_3std
__RNvNtNtCsg55jX0GwzBC_3std3sys2fs12canonicalize
__RNvMNtNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop12helper_graph7numericNtB2_10NumProgram3run
__RNvMsn_NtCs1OjIl8oxbrv_5alloc4syncINtB5_3ArcINtNtNtNtCsg55jX0GwzBC_3std4sync6poison5mutex5MutexINtNtB7_3vec3VechEEE9drop_slowBP_
__RNvXs1g_NtCsl8K0bEFm1U0_4core3fmtRINtNtCs1OjIl8oxbrv_5alloc3vec3VechENtB6_5Debug3fmtCsg55jX0GwzBC_3std
__RNvXs1_NtCs9nYd1Hk1rek_11qjs_runtime5valueNtB5_5ValueNtNtCsl8K0bEFm1U0_4core5clone5Clone5clone
__RINvNtCsl8K0bEFm1U0_4core3ptr13drop_in_placeNtNtCs9nYd1Hk1rek_11qjs_runtime5value5ValueEBK_
__RNvNtNtCs9nYd1Hk1rek_11qjs_runtime8function4call25call_direct_leaf_function
Expand Down Expand Up @@ -221,7 +224,6 @@ __RNvMNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode2vm11general_opsNtB4_2Vm11op_set
__RNvMs_NtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode8vm_propsNtNtB6_2vm2Vm21try_direct_get_string
__RINvNtCsl8K0bEFm1U0_4core3ptr13drop_in_placeTINtNtCs1OjIl8oxbrv_5alloc2rc2RceENtNtNtCs9nYd1Hk1rek_11qjs_runtime5value8property8PropertyEEB1k_
__RNvNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10compact_fn8property16get_prop_element
__RNvMNtNtNtNtCs9nYd1Hk1rek_11qjs_runtime8bytecode10typed_loop12helper_graph7numericNtB2_10NumProgram3run
__RNvMs1_NtNtCs9nYd1Hk1rek_11qjs_runtime5value5arrayNtB5_8ArrayRef12release_into
__RNvXs4_NtCs1OjIl8oxbrv_5alloc6stringNtB5_6StringNtNtCsl8K0bEFm1U0_4core5clone5Clone5clone
__RINvNvMs2_NtCs1OjIl8oxbrv_5alloc7raw_vecINtB8_11RawVecInnerpE7reserve21do_reserve_and_handleNtNtBa_5alloc6GlobalECs9nYd1Hk1rek_11qjs_runtime
Expand Down
8 changes: 8 additions & 0 deletions crates/qjs-runtime/src/bytecode/compact_fn/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,14 @@ pub(super) fn get_prop_computed(
message,
});
}
// An array's `length` is its own non-configurable data property, so a
// computed read of it -- a `for-in` loop's `keys["length"]` -- needs no
// key conversion.
if let (Value::Array(elements), Value::String(key)) = (&object, &key_value)
&& key.as_str() == "length"
{
return Ok(Value::Number(elements.len() as f64));
}
if let Value::Number(number) = &key_value
&& let Some(index) = crate::bytecode::vm_props::array_index_from_number(*number)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ pub(super) fn exit_to_interpreter(
Some(crate::bytecode::ir::Op::EnumerateKeys { cache }) => {
if let Some(pc) = program.resume_pc(ip as usize + 1, usize::from(depth)) {
let top = usize::from(program.local_registers) + usize::from(depth);
if let Some(keys) =
crate::bytecode::vm_ops::enumerate_keys_from_cache(&window[top - 1], cache)
{
execute::store(&mut window[top - 1], Value::Array(keys));
return ExitOutcome::Continue { pc };
}
let target = std::mem::replace(&mut window[top - 1], Value::Undefined);
let mut call_env = env.empty_frame();
return match crate::bytecode::vm_ops::enumerate_keys_cached(
Expand All @@ -108,6 +114,16 @@ pub(super) fn exit_to_interpreter(
if let Some(pc) = program.resume_pc(ip as usize + 1, usize::from(depth) - 1)
&& let Value::String(key) = &window[top - 1]
{
if let Some(enumerable) =
crate::bytecode::vm_ops::for_in_ordinary_property_is_enumerable(
&window[top - 2],
key,
)
{
execute::store(&mut window[top - 1], Value::Undefined);
execute::store(&mut window[top - 2], Value::Boolean(enumerable));
return ExitOutcome::Continue { pc };
}
let key = key.clone();
let target = std::mem::replace(&mut window[top - 2], Value::Undefined);
execute::store(&mut window[top - 1], Value::Undefined);
Expand Down
23 changes: 23 additions & 0 deletions crates/qjs-runtime/src/bytecode/compact_fn/wide/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,29 @@ fn a_for_in_loop_stays_on_the_tier_with_the_interpreter_s_semantics() {
);
}

/// A `for-in` site that enumerated one object whose prototypes had no
/// enumerable key reads only the own keys of the next object with those
/// prototypes -- until a prototype gains an enumerable key.
#[test]
fn a_for_in_site_sees_prototype_keys_appear_and_disappear() {
assert_eq!(
value_of(
"function keys(o) { var r = []; for (var k in o) { if (o[k] !== 0) r.push(k); } return r.join(','); } \
function P() {} var out = []; \
for (var i = 0; i < 3; i++) out.push(keys({ a: 1, b: 2 }), keys(new P())); \
Object.prototype.late = 9; out.push(keys({ a: 1 })); delete Object.prototype.late; \
out.push(keys({ a: 1 })); \
P.prototype.shared = 5; out.push(keys(new P())); \
Object.defineProperty(P.prototype, 'shared', { enumerable: false }); out.push(keys(new P())); \
var q = { x: 1 }; Object.setPrototypeOf(q, { y: 2 }); out.push(keys(q)); \
var shadow = Object.create({ a: 1 }); Object.defineProperty(shadow, 'a', { value: 2, enumerable: false }); \
out.push(keys(shadow), keys({ b: 0, c: 3 })); \
out.join('|');"
),
Value::String("a,b||a,b||a,b||a,late|a|shared||x,y||c".to_owned().into())
);
}

#[test]
fn methods_with_a_home_object_run_inline_and_super_or_private_bodies_keep_their_path() {
// Class and object-literal methods carry a home object, which only
Expand Down
118 changes: 114 additions & 4 deletions crates/qjs-runtime/src/bytecode/enumerate_keys_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,28 @@ use crate::{ArrayRef, ObjectRef, Prototype, Value, value::ObjectWeakRef};
const MAX_ORDINARY_CHAIN_DEPTH: usize = 16;

#[derive(Clone, Default)]
pub(super) struct EnumerateKeysCache(Rc<RefCell<Option<EnumerateKeysCacheEntry>>>);
pub(super) struct EnumerateKeysCache(Rc<RefCell<EnumerateKeysCacheState>>);

#[derive(Default)]
struct EnumerateKeysCacheState {
entry: Option<EnumerateKeysCacheEntry>,
/// The prototype chain -- the links after a target, to the end -- of the
/// last ordinary target, at these layout revisions, with the keys that
/// chain contributes to a `for-in` (its own enumeration, shadowing among
/// its layers already applied). A target inheriting exactly this chain
/// enumerates its own enumerable keys, then those of these it does not
/// have as own properties. `walk(k, v)` recursing over many objects of
/// one shape misses the per-target entry every time.
/// Boxed so the cache stays one small allocation: growing it moved
/// every later heap allocation of a script, and recursive_call_tree's
/// helper program with them (+10% cycles, 2026-09-26).
prototypes: Option<Box<PrototypeKeys>>,
}

struct PrototypeKeys {
chain: Vec<OrdinaryChainLink>,
inherited: Vec<Rc<str>>,
}

impl fmt::Debug for EnumerateKeysCache {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand All @@ -37,18 +58,107 @@ impl EnumerateKeysCache {
/// depended on still has the same identity, own-key layout, and ordinary
/// object prototype link.
pub(super) fn get(&self, target: &Value) -> Option<ArrayRef> {
let entry = self.0.borrow();
let entry = entry.as_ref()?;
let state = self.0.borrow();
let entry = state.entry.as_ref()?;
ordinary_chain_matches(target, &entry.chain).then(|| entry.keys.clone())
}

/// The keys of an ordinary `target` whose prototype chain is the
/// remembered one: its own enumerable string keys in order, then the
/// chain's keys that it does not shadow with an own property.
pub(super) fn get_by_prototypes(&self, target: &Value) -> Option<ArrayRef> {
let Value::Object(object) = target else {
return None;
};
if !is_cacheable_ordinary_object(object) {
return None;
}
let state = self.0.borrow();
let prototypes = state.prototypes.as_ref()?;
match object.prototype_slot() {
Some(Prototype::Object(prototype)) => {
if !ordinary_chain_matches(&Value::Object(prototype), &prototypes.chain) {
return None;
}
}
None if prototypes.chain.is_empty() => {}
_ => return None,
}
let own = object.own_string_keys_with_enumerability();
let key_value = |key: &Rc<str>| Value::String(crate::JsString::from(&**key));
let mut keys: Vec<Value> = own
.iter()
.filter(|(_, enumerable)| *enumerable)
.map(|(key, _)| key_value(key))
.collect();
for key in &prototypes.inherited {
if !own.iter().any(|(own_key, _)| own_key == key) {
keys.push(key_value(key));
}
}
Some(ArrayRef::new(keys))
}

/// Records a just-enumerated key array when `target` has an entirely
/// ordinary object chain. Unsupported values intentionally clear a stale
/// entry: a future ordinary target must rebuild before it can be cached.
/// Also remembers the chain after `target` and the keys it contributes
/// (see `get_by_prototypes`), unless that chain is already remembered.
pub(super) fn record(&self, target: &Value, keys: ArrayRef) {
let chain = capture_ordinary_chain(target);
*self.0.borrow_mut() = chain.map(|chain| EnumerateKeysCacheEntry { chain, keys });
let mut state = self.0.borrow_mut();
if let Some(chain) = &chain {
let prototype_chain = &chain[1..];
let current = state.prototypes.as_ref().is_some_and(|known| {
known.chain.len() == prototype_chain.len()
&& known
.chain
.iter()
.zip(prototype_chain)
.all(|(known, link)| {
known.layout_revision == link.layout_revision
&& link
.object
.upgrade()
.is_some_and(|object| known.object.ptr_eq(&object))
})
});
if !current {
state.prototypes = prototype_keys(prototype_chain).map(Box::new);
}
}
state.entry = chain.map(|chain| EnumerateKeysCacheEntry { chain, keys });
}
}

/// The keys a `for-in` over an object with `chain` as its prototypes visits
/// from that chain: each layer's enumerable own string keys, less any name an
/// earlier layer has (enumerable or not).
fn prototype_keys(chain: &[OrdinaryChainLink]) -> Option<PrototypeKeys> {
let mut seen: Vec<Rc<str>> = Vec::new();
let mut inherited = Vec::new();
for link in chain {
let object = link.object.upgrade()?;
for (key, enumerable) in object.own_string_keys_with_enumerability() {
if seen.contains(&key) {
continue;
}
if enumerable {
inherited.push(key.clone());
}
seen.push(key);
}
}
Some(PrototypeKeys {
chain: chain
.iter()
.map(|link| OrdinaryChainLink {
object: link.object.clone(),
layout_revision: link.layout_revision,
})
.collect(),
inherited,
})
}

fn capture_ordinary_chain(target: &Value) -> Option<Vec<OrdinaryChainLink>> {
Expand Down
12 changes: 12 additions & 0 deletions crates/qjs-runtime/src/bytecode/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,8 @@ pub struct Bytecode {
cached_authoritative_mask_clean: u128,
/// The top level of a direct eval's code (not a function inside it).
direct_eval_code: bool,
/// Whether the body can suspend mid-way: a `yield`, `yield*` or `await`.
cached_suspends: bool,
}

impl Bytecode {
Expand Down Expand Up @@ -943,6 +945,7 @@ impl Bytecode {
cached_hoisted_slots: Vec::new(),
cached_authoritative_mask_clean: 0,
direct_eval_code: false,
cached_suspends: false,
};
// Order matters: closure/arguments metadata reads the simpler caches
// (written-binding names, creates-closures) computed just above. Nested
Expand Down Expand Up @@ -990,6 +993,10 @@ impl Bytecode {
)
});
bytecode.cached_uses_lexical_this = bytecode.compute_uses_lexical_this();
bytecode.cached_suspends = bytecode
.code
.iter()
.any(|op| matches!(op, Op::Yield | Op::Await | Op::YieldDelegate { .. }));
bytecode.readonly_received_upvalue_slots =
bytecode.compute_readonly_received_upvalue_slots();
bytecode.cell_received_upvalue_slots = bytecode.compute_cell_received_upvalue_slots();
Expand Down Expand Up @@ -1428,6 +1435,11 @@ impl Bytecode {
self.direct_eval_code = true;
}

/// Whether the body can suspend mid-way (`yield`, `yield*`, `await`).
pub(super) fn suspends(&self) -> bool {
self.cached_suspends
}

/// Whether this is the top level of a direct eval's code.
pub(super) fn is_direct_eval_code(&self) -> bool {
self.direct_eval_code
Expand Down
Loading
Loading