LibJS: Optimize Function.prototype.apply()
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macOS, macos-15, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, Linux, blacksmith-16vcpu-ubuntu-2404, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, Linux, blacksmith-16vcpu-ubuntu-2404, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, Linux, blacksmith-16vcpu-ubuntu-2404, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macOS, macOS-arm64, macos-15) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, Linux, Linux-x86_64, blacksmith-8vcpu-ubuntu-2404) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

...by avoiding `CreateListFromArrayLike` in cases when we could directly
use elements of underlying object's indexed properties storage.

Makes this program go 2.1x faster:
```js
function target(a, b, c) {
    return a + b + c;
}

const args = [1, 2, 3];
let result = 0;

(function() {
    for (let i = 0; i < 10_000_000; i++) {
        result += target.apply(null, args);
    }
})();
```
This commit is contained in:
Aliaksandr Kalenik 2025-06-02 19:22:30 +02:00 committed by Alexander Kalenik
commit 1274f4e2f7
Notes: github-actions[bot] 2025-06-03 15:17:07 +00:00
2 changed files with 26 additions and 0 deletions

View file

@ -70,6 +70,22 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply)
return TRY(JS::call(vm, function, this_arg));
}
// NOTE: Do the check performed by CreateListFromArrayLike here, so we could avoid branching in optimized code path.
if (!arg_array.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, arg_array.to_string_without_side_effects());
// OPTIMIZATION: If argArray has a simple indexed storage without holes and doesn't interfere with indexed property access,
// we can skip CreateListFromArrayLike and directly use the storage elements.
auto& arg_array_object = arg_array.as_object();
auto* storage = arg_array_object.indexed_properties().storage();
if (!arg_array_object.may_interfere_with_indexed_property_access() && storage && storage->is_simple_storage()) {
auto length = TRY(length_of_array_like(vm, arg_array_object));
auto const* simple_storage = static_cast<SimpleIndexedPropertyStorage*>(storage);
auto storage_elements = simple_storage->elements().span();
if (!simple_storage->has_empty_elements() && storage_elements.size() >= length)
return TRY(JS::call(vm, function, this_arg, storage_elements.slice(0, length)));
}
// 4. Let argList be ? CreateListFromArrayLike(argArray).
auto arguments = TRY(create_list_from_array_like(vm, arg_array));

View file

@ -50,6 +50,16 @@ test("basic functionality", () => {
expect((() => this).apply("foo")).toBe(globalThis);
});
test("array with holes", () => {
function target(a, b, c) {
return a + b + c;
}
const args = [1, , 3];
const result = target.apply(null, args);
expect(result).toBe(NaN);
});
describe("errors", () => {
test("does not accept non-function values", () => {
expect(() => {