Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JSC] Make StringConstructor call faster
https://bugs.webkit.org/show_bug.cgi?id=252099
rdar://105306684

Reviewed by Justin Michaud.

String constructor is used in a form of call (not construct) to apply ToString-like effect onto the input.
This patch makes String constructor JSFunction so that we can make this call faster than InternalFunction calls.
This is the same optimization done for Number constructor.

                                     ToT                     Patched

        string-on-string        8.6568+-0.0778     ^      7.2580+-0.0923        ^ definitely 1.1927x faster

* JSTests/microbenchmarks/string-on-string.js: Added.
(shouldBe):
(test):
* Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* Source/JavaScriptCore/jit/ThunkGenerators.cpp:
(JSC::stringConstructorCallThunkGenerator):
* Source/JavaScriptCore/jit/ThunkGenerators.h:
* Source/JavaScriptCore/runtime/Intrinsic.h:
* Source/JavaScriptCore/runtime/StringConstructor.cpp:
(JSC::StringConstructor::StringConstructor):
(JSC::StringConstructor::finishCreation):
(JSC::StringConstructor::create):
* Source/JavaScriptCore/runtime/StringConstructor.h:
* Source/JavaScriptCore/runtime/VM.cpp:
(JSC::thunkGeneratorForIntrinsic):

Canonical link: https://commits.webkit.org/260146@main
  • Loading branch information
Constellation committed Feb 11, 2023
1 parent 1464751 commit 57c000d
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 16 deletions.
15 changes: 15 additions & 0 deletions JSTests/microbenchmarks/string-on-string.js
@@ -0,0 +1,15 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function test(string) {
return String(string);
}
noInline(test);

for (var i = 0; i < 1e5; ++i) {
shouldBe(test("-0"), "-0");
shouldBe(test("240"), "240");
shouldBe(test("-1"), "-1");
}
9 changes: 9 additions & 0 deletions Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Expand Up @@ -3825,6 +3825,15 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, Operand result, CallVaria
return true;
}

case StringConstructorIntrinsic: {
insertChecks();
if (argumentCountIncludingThis <= 1)
setResult(jsConstant(m_vm->smallStrings.emptyString()));
else
setResult(addToGraph(CallStringConstructor, get(virtualRegisterForArgumentIncludingThis(1, registerOffset))));
return true;
}

#if ENABLE(WEBASSEMBLY)
case WasmFunctionIntrinsic: {
if (callOp != Call)
Expand Down
10 changes: 10 additions & 0 deletions Source/JavaScriptCore/jit/ThunkGenerators.cpp
Expand Up @@ -1163,6 +1163,16 @@ MacroAssemblerCodeRef<JITThunkPtrTag> numberConstructorCallThunkGenerator(VM& vm
return jit.finalize(vm.jitStubs->ctiNativeTailCall(vm), "Number");
}
MacroAssemblerCodeRef<JITThunkPtrTag> stringConstructorCallThunkGenerator(VM& vm)
{
SpecializedThunkJIT jit(vm, 1);
jit.loadJSArgument(0, JSRInfo::jsRegT10);
jit.appendFailure(jit.branchIfNotCell(JSRInfo::jsRegT10));
jit.appendFailure(jit.branchIfNotString(JSRInfo::jsRegT10.payloadGPR()));
jit.returnJSValue(JSRInfo::jsRegT10);
return jit.finalize(vm.jitStubs->ctiNativeTailCall(vm), "String");
}
MacroAssemblerCodeRef<JITThunkPtrTag> roundThunkGenerator(VM& vm)
{
SpecializedThunkJIT jit(vm, 1);
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/jit/ThunkGenerators.h
Expand Up @@ -77,6 +77,7 @@ MacroAssemblerCodeRef<JITThunkPtrTag> imulThunkGenerator(VM&);
MacroAssemblerCodeRef<JITThunkPtrTag> randomThunkGenerator(VM&);
MacroAssemblerCodeRef<JITThunkPtrTag> truncThunkGenerator(VM&);
MacroAssemblerCodeRef<JITThunkPtrTag> numberConstructorCallThunkGenerator(VM&);
MacroAssemblerCodeRef<JITThunkPtrTag> stringConstructorCallThunkGenerator(VM&);

MacroAssemblerCodeRef<JITThunkPtrTag> boundFunctionCallGenerator(VM&);

Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/Intrinsic.h
Expand Up @@ -103,6 +103,7 @@ namespace JSC {
macro(ObjectKeysIntrinsic) \
macro(ObjectToStringIntrinsic) \
macro(ReflectGetPrototypeOfIntrinsic) \
macro(StringConstructorIntrinsic) \
macro(StringPrototypeCodePointAtIntrinsic) \
macro(StringPrototypeLocaleCompareIntrinsic) \
macro(StringPrototypeValueOfIntrinsic) \
Expand Down
19 changes: 15 additions & 4 deletions Source/JavaScriptCore/runtime/StringConstructor.cpp
Expand Up @@ -36,7 +36,7 @@ static JSC_DECLARE_HOST_FUNCTION(stringFromCodePoint);

namespace JSC {

const ClassInfo StringConstructor::s_info = { "Function"_s, &InternalFunction::s_info, &stringConstructorTable, nullptr, CREATE_METHOD_TABLE(StringConstructor) };
const ClassInfo StringConstructor::s_info = { "Function"_s, &Base::s_info, &stringConstructorTable, nullptr, CREATE_METHOD_TABLE(StringConstructor) };

/* Source for StringConstructor.lut.h
@begin stringConstructorTable
Expand All @@ -52,15 +52,26 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(StringConstructor);
static JSC_DECLARE_HOST_FUNCTION(callStringConstructor);
static JSC_DECLARE_HOST_FUNCTION(constructWithStringConstructor);

StringConstructor::StringConstructor(VM& vm, Structure* structure)
: InternalFunction(vm, structure, callStringConstructor, constructWithStringConstructor)
StringConstructor::StringConstructor(VM& vm, NativeExecutable* executable, JSGlobalObject* globalObject, Structure* structure)
: Base(vm, executable, globalObject, structure)
{
}

void StringConstructor::finishCreation(VM& vm, StringPrototype* stringPrototype)
{
Base::finishCreation(vm, 1, vm.propertyNames->String.string(), PropertyAdditionMode::WithoutStructureTransition);
Base::finishCreation(vm);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, stringPrototype, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum | PropertyAttribute::DontDelete);
putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(1), PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly);
putDirectWithoutTransition(vm, vm.propertyNames->name, jsString(vm, vm.propertyNames->String.string()), PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum);
}

StringConstructor* StringConstructor::create(VM& vm, Structure* structure, StringPrototype* stringPrototype, GetterSetter*)
{
JSGlobalObject* globalObject = structure->globalObject();
NativeExecutable* executable = vm.getHostFunction(callStringConstructor, ImplementationVisibility::Public, StringConstructorIntrinsic, constructWithStringConstructor, nullptr, vm.propertyNames->String.string());
StringConstructor* constructor = new (NotNull, allocateCell<StringConstructor>(vm)) StringConstructor(vm, executable, globalObject, structure);
constructor->finishCreation(vm, stringPrototype);
return constructor;
}

// ------------------------------ Functions --------------------------------
Expand Down
19 changes: 7 additions & 12 deletions Source/JavaScriptCore/runtime/StringConstructor.h
Expand Up @@ -20,37 +20,32 @@

#pragma once

#include "InternalFunction.h"
#include "JSFunction.h"

namespace JSC {

class StringPrototype;
class GetterSetter;

class StringConstructor final : public InternalFunction {
class StringConstructor final : public JSFunction {
public:
typedef InternalFunction Base;
using Base = JSFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;

static StringConstructor* create(VM& vm, Structure* structure, StringPrototype* stringPrototype, GetterSetter*)
{
StringConstructor* constructor = new (NotNull, allocateCell<StringConstructor>(vm)) StringConstructor(vm, structure);
constructor->finishCreation(vm, stringPrototype);
return constructor;
}
static StringConstructor* create(VM&, Structure*, StringPrototype*, GetterSetter*);

DECLARE_INFO;

static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info());
return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info());
}

private:
StringConstructor(VM&, Structure*);
StringConstructor(VM&, NativeExecutable*, JSGlobalObject*, Structure*);
void finishCreation(VM&, StringPrototype*);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(StringConstructor, InternalFunction);
static_assert(sizeof(StringConstructor) == sizeof(JSFunction), "Allocate StringConstructor in JSFunction IsoSubspace");

JSString* stringFromCharCode(JSGlobalObject*, int32_t);
JSString* stringConstructor(JSGlobalObject*, JSValue);
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/runtime/VM.cpp
Expand Up @@ -612,6 +612,8 @@ static ThunkGenerator thunkGeneratorForIntrinsic(Intrinsic intrinsic)
return remoteFunctionCallGenerator;
case NumberConstructorIntrinsic:
return numberConstructorCallThunkGenerator;
case StringConstructorIntrinsic:
return stringConstructorCallThunkGenerator;
default:
return nullptr;
}
Expand Down

0 comments on commit 57c000d

Please sign in to comment.