Bug 226418 - Optimize Function.prototype.toString
Summary: Optimize Function.prototype.toString
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Tadeu Zagallo
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-05-29 08:45 PDT by Tadeu Zagallo
Modified: 2021-06-04 08:58 PDT (History)
11 users (show)

See Also:


Attachments
Patch (23.66 KB, patch)
2021-05-29 09:16 PDT, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (28.12 KB, patch)
2021-06-01 08:39 PDT, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (41.27 KB, patch)
2021-06-03 09:07 PDT, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (43.70 KB, patch)
2021-06-03 16:35 PDT, Tadeu Zagallo
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (46.00 KB, patch)
2021-06-03 16:51 PDT, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch for landing (46.06 KB, patch)
2021-06-04 08:10 PDT, Tadeu Zagallo
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Tadeu Zagallo 2021-05-29 08:45:36 PDT
<rdar://77861846>
Comment 1 Tadeu Zagallo 2021-05-29 09:16:07 PDT
Created attachment 430096 [details]
Patch
Comment 2 Filip Pizlo 2021-05-29 09:38:20 PDT
Comment on attachment 430096 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430096&action=review

Looks good, but your OSR exit should really be a slow path.

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:10522
> +    speculationCheck(BadType, JSValueSource::unboxedCell(result.gpr()), node, m_jit.branchIfEmpty(result.gpr()));

This should be a slow path.

You don't want to OSR exit just because you got passed a function that was stringified yet.  OSR exits are far too expensive to be part of the mechanism by which functions get stringified for the first time.

> Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:8233
> +        speculate(
> +            BadType, jsValueValue(asString), m_node,
> +            m_out.isNull(asString));

Slow path please.
Comment 3 Tadeu Zagallo 2021-06-01 08:39:09 PDT
Created attachment 430265 [details]
Patch
Comment 4 Keith Miller 2021-06-01 09:59:58 PDT
Comment on attachment 430265 [details]
Patch

Looks like a test is berked.
Comment 5 Saam Barati 2021-06-01 13:42:42 PDT
Comment on attachment 430265 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430265&action=review

> Source/JavaScriptCore/ChangeLog:10
> +        Make it an intrinsic and add support for it in DFG and FTL. I added a new
> +        microbenchmark that speeds up by >40x;

This explanation can be a lot more descriptive. The important speedup isn't from making it an intrinsic. It's from caching it. This makes no mention of that.

> Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:2859
> +        setForNode(node, m_vm.stringStructure.get());

any chance we can constant fold this in the compiler if the executable is a constant and its m_asString is set?

Maybe to make that safe, we'd need a storeStoreFence before storing to m_asString of Executable.

> Source/JavaScriptCore/dfg/DFGDoesGC.cpp:115
> +    case ExecutableToString:

this is wrong

> Source/JavaScriptCore/dfg/DFGNodeType.h:423
> +    macro(ExecutableToString, NodeResultJS | NodeMustGenerate) \

Why must generate?

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:10522
> +    JITCompiler::Jump slowPath = m_jit.branchIfEmpty(result.gpr());

branchIfEmpty is for JSValues. You should be using a branchTestPtr here. (Logically they're the same on 64-bit, but this aint right on 32-bit)

> Source/JavaScriptCore/runtime/FunctionExecutable.cpp:80
> +    visitor.append(thisObject->m_asString);

why not put this in rare data?

> Source/JavaScriptCore/runtime/FunctionExecutable.h:325
> +    JSValue toStringSlow(JSGlobalObject*);

this should return JSString*, not JSValue
Comment 6 Saam Barati 2021-06-01 13:59:18 PDT
Comment on attachment 430265 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430265&action=review

> Source/JavaScriptCore/runtime/FunctionExecutableInlines.h:42
> +        m_asString.set(vm, this, asString(toStringSlow(globalObject)));

what if this throws? Then what're we setting this to? I feel like we need to only set this if toStringSlow doesn't throw.
Comment 7 Saam Barati 2021-06-01 14:15:36 PDT
Comment on attachment 430265 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430265&action=review

> Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:8245
> +        setJSValue(m_out.phi(Int64, fastResult, slowResult));

nit: I think Int64 => pointerType() is a bit more semantically correct here
Comment 8 Saam Barati 2021-06-01 14:18:07 PDT
Comment on attachment 430265 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430265&action=review

> Source/JavaScriptCore/runtime/FunctionPrototype.cpp:84
>              RELEASE_AND_RETURN(scope, JSValue::encode(jsMakeNontrivialString(globalObject, "function ", function->name(vm), "() {\n    [native code]\n}")));

why aren't we caching for host/builtin?
Comment 9 Tadeu Zagallo 2021-06-03 09:07:12 PDT
Created attachment 430475 [details]
Patch
Comment 10 Saam Barati 2021-06-03 12:04:57 PDT
Comment on attachment 430475 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430475&action=review

Mostly LGTM, but I found a bug.

> Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:2867
> +                    asString = static_cast<NativeExecutable*>(function->executable())->asString();
> +                asString = function->jsExecutable()->asString();

this looks wrong, and like it should crash for host functions in a debug assert, as you call jsExecutable. Can you make sure we have a test? I think it should be trivial-ish by just calling toString on a builtin constructor

To fix, I think you just need to put it in an else.

Also, a small comment on organization, you could add a method to JSFunction called "asStringConcurrently" or similar, that's called from AI. The details will go in there, which are maybe slightly nicer?

> Source/JavaScriptCore/dfg/DFGClobberize.h:1823
> +        read(JSCell_structureID);

I wonder if this is actually necessary. It's a bit deceptive, but we don't actually care if the StructureID actually changes. Since we're reading the class info, that's guaranteed to never change via transitions. So we're ok if someone writes structure in a loop, that shouldn't really prevent us from being hoisted out of said loop

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:10537
> +    slowCases.append(m_jit.branchPtr(CCallHelpers::Equal, result.gpr(), TrustedImmPtr(JSBoundFunction::info())));

nit: maybe it's worth a static assert that JSBoundFunction is final? That guarantees this branch has to be correct.

> Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:8251
> +        m_out.branch(m_out.equal(classInfo, m_out.constIntPtr(JSBoundFunction::info())), rarely(slowCase), usually(notBoundFunctionCase));

nit: I don't think we should prescribe this a-priori as a slow case. Maybe the program really is toStringing a bound function

> Source/JavaScriptCore/runtime/FunctionExecutable.cpp:126
> +    auto throwScope = DECLARE_THROW_SCOPE(vm);
> +    auto catchScope = DECLARE_CATCH_SCOPE(vm);

ditto about just needing throwScope

> Source/JavaScriptCore/runtime/FunctionExecutable.cpp:130
> +            return ::JSC::asString(jsMakeNontrivialString(globalObject, "function ", name().string(), "() {\n    [native code]\n}"));

if this throws, asString is the wrong thing...

> Source/JavaScriptCore/runtime/FunctionExecutable.cpp:183
> +        return ::JSC::asString(jsMakeNontrivialString(globalObject, functionHeader, name, src));

ditto about OOM and asString being wrong

> Source/JavaScriptCore/runtime/FunctionExecutable.cpp:189
> +    if (catchScope.exception()) {
> +        throwScope.throwException(globalObject, catchScope.exception());
> +        return nullptr;
> +    }

I think this can just be 
RETURN_IF_EXCEPTION(throwScope, nullptr)

> Source/JavaScriptCore/runtime/FunctionExecutable.h:289
> +    JSString* asString() const

maybe "asStringConcurrently"? AFAICT, the only use is the compiler. So it's helpful to document that it can be used in that context.

> Source/JavaScriptCore/runtime/JSFunction.cpp:255
> +        return asString(jsMakeNontrivialString(globalObject, "function ", function->nameString(), "() {\n    [native code]\n}"));

this is wrong if OOM, it won't be a string

> Source/JavaScriptCore/runtime/NativeExecutable.cpp:101
> +    auto throwScope = DECLARE_THROW_SCOPE(vm);
> +    auto catchScope = DECLARE_CATCH_SCOPE(vm);

this is a non idiomatic way of doing things. I don't get why you need the catch scope at all. I think the throw scope itself is sufficient. After the call to asString, I'd just do:
RETURN_IF_EXCEPTION(throwScope, nullptr);

> JSTests/microbenchmarks/function-to-string.js:22
> +function test() {

Maybe also good to have some microbenchmarks for:
- CSE
- LICM
Comment 11 Tadeu Zagallo 2021-06-03 16:35:02 PDT
Created attachment 430513 [details]
Patch
Comment 12 Tadeu Zagallo 2021-06-03 16:51:34 PDT
Created attachment 430514 [details]
Patch
Comment 13 Saam Barati 2021-06-03 17:02:21 PDT
Comment on attachment 430514 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430514&action=review

Nice. r=me if you fix the final OOM bug with jsMakeNontrivialString and asString-ing it.

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:10533
> +    speculateCellType(node->child1(), function.gpr(), SpecFunction, JSFunctionType);

nit: I'd call speculateFunction(node->child1(), function.gpr()) here, so we're not repeating the contents of it.

> Source/JavaScriptCore/runtime/JSFunction.cpp:255
> +        return asString(jsMakeNontrivialString(globalObject, "function ", function->nameString(), "() {\n    [native code]\n}"));

still the same bug as before regarding OOM.
Comment 14 Tadeu Zagallo 2021-06-03 17:22:21 PDT
Comment on attachment 430514 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=430514&action=review

Thanks for the reviews

>> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:10533
>> +    speculateCellType(node->child1(), function.gpr(), SpecFunction, JSFunctionType);
> 
> nit: I'd call speculateFunction(node->child1(), function.gpr()) here, so we're not repeating the contents of it.

oh, that's better, I copied this from compileGetExecutable, I'll update both places.

>> Source/JavaScriptCore/runtime/JSFunction.cpp:255
>> +        return asString(jsMakeNontrivialString(globalObject, "function ", function->nameString(), "() {\n    [native code]\n}"));
> 
> still the same bug as before regarding OOM.

missed this one, will fix before landing.
Comment 15 Tadeu Zagallo 2021-06-04 08:10:16 PDT
Created attachment 430579 [details]
Patch for landing
Comment 16 EWS 2021-06-04 08:58:21 PDT
Committed r278462 (238482@main): <https://commits.webkit.org/238482@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 430579 [details].