LibJS: Fast-path safe writes into holey array holes

Teach the asm PutByValue path to materialize in-bounds holey array
elements directly when the receiver is a normal extensible Array with
the default prototype chain and no indexed interference. This avoids
bouncing through generic property setting while preserving the lazy
holey length model.

Keep the fast path narrow so inherited setters, inherited non-writable
properties, and non-extensible arrays still fall back to the generic
semantics. Add regression coverage for those cases alongside the large
holey array stress tests.
This commit is contained in:
Andreas Kling
2026-04-09 13:01:21 +02:00
committed by Andreas Kling
parent 036819da22
commit 4c1e2222df
Notes: github-actions[bot] 2026-04-09 18:07:43 +00:00
3 changed files with 126 additions and 5 deletions

View File

@@ -9,6 +9,7 @@
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Op.h>
#include <LibJS/Bytecode/PropertyAccess.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
@@ -256,6 +257,7 @@ i64 asm_slow_path_unary_minus(Interpreter*, u32 pc);
i64 asm_slow_path_postfix_decrement(Interpreter*, u32 pc);
i64 asm_slow_path_to_int32(Interpreter*, u32 pc);
i64 asm_slow_path_put_by_value(Interpreter*, u32 pc);
i64 asm_try_put_by_value_holey_array(Interpreter*, u32 pc);
u64 asm_helper_to_boolean(u64 encoded_value);
u64 asm_helper_math_exp(u64 encoded_value);
i64 asm_try_inline_call(Interpreter*, u32 pc);
@@ -794,6 +796,39 @@ i64 asm_slow_path_put_by_value(Interpreter* interp, u32 pc)
return slow_path_throwing<Op::PutByValue>(*interp, pc);
}
i64 asm_try_put_by_value_holey_array(Interpreter* interp, u32 pc)
{
auto* bytecode = interp->current_executable().bytecode.data();
auto& insn = *reinterpret_cast<Op::PutByValue const*>(&bytecode[pc]);
auto base = interp->get(insn.base());
if (!base.is_object()) [[unlikely]]
return 1;
auto property = interp->get(insn.property());
if (!property.is_non_negative_int32()) [[unlikely]]
return 1;
auto& object = base.as_object();
if (!is<JS::Array>(object)) [[unlikely]]
return 1;
auto& array = static_cast<JS::Array&>(object);
if (array.is_proxy_target()
|| !array.default_prototype_chain_intact()
|| !array.extensible()
|| array.may_interfere_with_indexed_property_access()
|| array.indexed_storage_kind() != IndexedStorageKind::Holey) [[unlikely]]
return 1;
auto index = static_cast<u32>(property.as_i32());
if (index >= array.indexed_array_like_size()) [[unlikely]]
return 1;
array.indexed_put(index, interp->get(insn.src()));
return 0;
}
// Try to inline a JS-to-JS call. Returns 0 on success (callee frame pushed),
// 1 on failure (caller should fall through to slow path).
i64 asm_try_inline_call(Interpreter* interp, u32 pc)

View File

@@ -1380,17 +1380,21 @@ handler PutByValue
load32 t5, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
branch_ge_unsigned t4, t5, .slow
load64 t5, [t3, OBJECT_INDEXED_ELEMENTS]
branch_zero t5, .slow
branch_zero t5, .try_holey_array_slow
mov t0, t5
sub t0, 8
load32 t0, [t0, 0]
branch_ge_unsigned t4, t0, .slow
branch_ge_unsigned t4, t0, .try_holey_array_slow
load64 t1, [t5, t4, 8]
mov t0, EMPTY_TAG_SHIFTED
branch_eq t1, t0, .slow
branch_eq t1, t0, .try_holey_array_slow
load_operand t1, m_src
store64 [t5, t4, 8], t1
dispatch_next
.try_holey_array_slow:
call_interp asm_try_put_by_value_holey_array
branch_nonzero t0, .slow
dispatch_next
.try_typed_array:
# t3 = Object*, t4 = index (u32, non-negative)
# Load cached data pointer (pre-computed: buffer.data() + byte_offset)