Bug 236367 - [JSC] Accommodate the RISCV64 C calling convention
Summary: [JSC] Accommodate the RISCV64 C calling convention
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Zan Dobersek
URL:
Keywords: InRadar
Depends on:
Blocks: 237775
  Show dependency treegraph
 
Reported: 2022-02-09 08:09 PST by Zan Dobersek
Modified: 2022-04-20 06:37 PDT (History)
14 users (show)

See Also:


Attachments
RFC patch (257.78 KB, patch)
2022-02-09 08:21 PST, Zan Dobersek
no flags Details | Formatted Diff | Diff
RFC patch (259.34 KB, patch)
2022-02-14 05:17 PST, Zan Dobersek
no flags Details | Formatted Diff | Diff
RFC patch (265.90 KB, patch)
2022-02-14 06:38 PST, Zan Dobersek
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
RFC patch (265.94 KB, patch)
2022-02-14 07:07 PST, Zan Dobersek
no flags Details | Formatted Diff | Diff
RFC patch (267.74 KB, patch)
2022-02-14 08:38 PST, Zan Dobersek
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
RFC patch - alternative (128.17 KB, patch)
2022-03-07 07:32 PST, Zan Dobersek
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
RFC patch - alternative (131.98 KB, patch)
2022-03-08 00:20 PST, Zan Dobersek
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
WIP patch (2.07 KB, patch)
2022-04-19 00:38 PDT, Zan Dobersek
no flags Details | Formatted Diff | Diff
EWS run. (5.08 KB, patch)
2022-04-19 11:13 PDT, Zan Dobersek
no flags Details | Formatted Diff | Diff
Patch (7.41 KB, patch)
2022-04-20 01:19 PDT, Zan Dobersek
no flags Details | Formatted Diff | Diff
Patch for landing (7.76 KB, patch)
2022-04-20 03:50 PDT, Zan Dobersek
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Zan Dobersek 2022-02-09 08:09:39 PST
The RISCV64 C calling convention specifies that 32-bit types have to be sign-extended into the upper 32 bits of the 64-bit integer registers. This goes for both signed and unsigned values. Behavior of 8-bit or 16-bit types depends on signedness but it still extends to the whole size of the 64-bit integer register -- unsigned values are zero-extended, signed values are sign-extended.

This is causing problems with how the current JIT-generated code is passing argument values to native JIT functions and how those are then used. JIT generation right now assumes that a 32-bit value can be packed either directly into the lower 32 bits of the argument register or through the narrower 32-bit aliases (e.g. w0-w7 registers on ARM64, edi/esi on x86_64). In the native function, these 32-bits-wide registers are then used directly in different operations on these values.

RV64 has it more complicated. Any native operation on registers containing values of bit-width 32 or smaller expects those values to be properly extended into the upper bits. Right now, this isn't guaranteed and causes problems when the 32-bit value has 1 in bit#31 that isn't extended into the upper 32 bits of the 64-bit integer register.
Comment 1 Zan Dobersek 2022-02-09 08:15:30 PST
Calling convention specification:
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
Comment 2 Zan Dobersek 2022-02-09 08:21:10 PST
Created attachment 451382 [details]
RFC patch
Comment 3 Zan Dobersek 2022-02-09 08:47:47 PST
(In reply to Zan Dobersek from comment #2)
> Created attachment 451382 [details]
> RFC patch

This patch introduces the CCallInteger template through which values of the desired integer type would be accessed in the native JIT operations. The default implementation provides a simple implicit conversion operator that retrieves the value, with an additional conversion operator for converting to enumerator values. The implicit conversion operator minimizes the amount of needed changes in existing code, except for a few now-necessary explicit casts.

For RISCV64, a separate implementation of CCallInteger is provided, with same interface but an implicit conversion operator that on each invocation returns the sign-extended underlying value. This approach avoids any changes in the JIT generation but instead imposes alignment with the calling convention requirements at the point of use.

Main downside is the sign extension that now occurs on each value access, meaning one or two additional instructions for each such occasion. There's also missing sign extension for 8-bit or 16-bit values but those are trivial to add.

The CCallInteger template is then aliased to different descriptive sub-types (like CCallInt32, CCallUIntptr) and the declarations and definitions of all native JIT operations are adjusted to use these types for parameters and return values. CCallHelpers::setupArgumentsImpl() overloads are changed so that immediate values only fit into CCallInteger<> parameters in the target operations.

This is just the initial idea, so any feedback is welcome. It avoids changing JIT details, but it imposes a lot of changes in signatures for the native JIT operations, but even then it shouldn't change behavior for non-RISCV64 platforms.
Comment 4 Yusuke Suzuki 2022-02-09 10:27:47 PST
Comment on attachment 451382 [details]
RFC patch

r- for now since EWS are red.
Comment 5 Zan Dobersek 2022-02-14 05:17:49 PST
Created attachment 451893 [details]
RFC patch

Fixes Clang build errors.
Comment 6 Zan Dobersek 2022-02-14 06:38:29 PST
Created attachment 451898 [details]
RFC patch

Fixes Xcode errors.
Comment 7 Zan Dobersek 2022-02-14 07:07:12 PST
Created attachment 451899 [details]
RFC patch

Marks the header as private in Xcode, maybe that helps.
Comment 8 Zan Dobersek 2022-02-14 08:38:50 PST
Created attachment 451906 [details]
RFC patch

With even more fixes.
Comment 9 Zan Dobersek 2022-02-15 00:08:01 PST
Comment on attachment 451906 [details]
RFC patch

This now builds everywhere, but it's not yet reviewable. The desire is to gather feedback on the approach, hence the RFC call.
Comment 10 Radar WebKit Bug Importer 2022-02-16 08:10:26 PST
<rdar://problem/89026012>
Comment 11 Zan Dobersek 2022-03-07 07:32:07 PST
Created attachment 453981 [details]
RFC patch - alternative

This is an alternative approach that reduces the amount of changes but is more lenient and puts more burden to the person writing out the definitions of JIT operations.

There's no change to argument marshalling in CCallHelpers.h, or the integer types for JIT operations. Only in the definitions would the integer arguments have to use macros to specify the type and the name of the argument. In the definition body, another macro would be used to finally "define" the integer argument, assigning the argument value to the desired name.

For non-RISCV64 platforms this assignment would be pass-through. For RISCV64, the necessary sign extension would be applied to the value.
Comment 12 Zan Dobersek 2022-03-08 00:20:21 PST
Created attachment 454079 [details]
RFC patch - alternative

Should now build via xcode.
Comment 13 Saam Barati 2022-03-18 18:33:24 PDT
Comment on attachment 451906 [details]
RFC patch

Out of the two approaches, this one probably makes more sense. But I like that the second approach is smaller, but don't love that you need to change the implementation of the various operations. 

I wonder if it's possible to do some kind of crazy macro/template magic and change JSC_DEFINE_JIT_OPERATION to automatically do what we want? That way no macros applied to types of arguments or to implementation of the functions
Comment 14 Zan Dobersek 2022-03-20 06:01:48 PDT
(In reply to Saam Barati from comment #13)
> I wonder if it's possible to do some kind of crazy macro/template magic and
> change JSC_DEFINE_JIT_OPERATION to automatically do what we want? That way
> no macros applied to types of arguments or to implementation of the functions

DEFINE_JIT_OPERATION reproduces the function signature, along with different attributes, and expects the function body afterwards. It would probably be possible to include the body in the macro invocation, and then for RISCV64 construct a function body that first sign-extends whatever is necessary before laying in the actual operation implementation.

This would also require tidy parameter lists that always consist of a type-and-name so that these are passed through separate macros extracting either the type or the name of the parameter.

So more faith put into the preprocessor. I don't think C++ templates would be usable directly in DEFINE_JIT_OPERATION since C linkage is required, and that's rejected for templates.
Comment 15 Zan Dobersek 2022-03-20 10:09:38 PDT
(In reply to Zan Dobersek from comment #14)
> This would also require tidy parameter lists that always consist of a
> type-and-name so that these are passed through separate macros extracting
> either the type or the name of the parameter.
> 

Tried this, it's just problems and borderline preprocessor acrobatics of getting things in proper place. I'm skeptical a good solution is possible.
Comment 16 Zan Dobersek 2022-03-20 10:12:08 PDT
(In reply to Saam Barati from comment #13)
> Comment on attachment 451906 [details]
> RFC patch
> 
> Out of the two approaches, this one probably makes more sense. But I like
> that the second approach is smaller, but don't love that you need to change
> the implementation of the various operations. 
> 

I like this approach more cause it gives the opportunity of also cleaning up and clamping down the exact sizes for each integer parameter. It's not the ideal solution cause for RISC-V it will end up doing sign extension on every conversion to the native integer type, but it's the most complete one, it scales into the future and there's no drawbacks for other ISAs.
Comment 17 Zan Dobersek 2022-03-29 02:37:02 PDT
(In reply to Zan Dobersek from comment #16)
> (In reply to Saam Barati from comment #13)
> > Comment on attachment 451906 [details]
> > RFC patch
> > 
> > Out of the two approaches, this one probably makes more sense. But I like
> > that the second approach is smaller, but don't love that you need to change
> > the implementation of the various operations. 
> > 
> 
> I like this approach more cause it gives the opportunity of also cleaning up
> and clamping down the exact sizes for each integer parameter. It's not the
> ideal solution cause for RISC-V it will end up doing sign extension on every
> conversion to the native integer type, but it's the most complete one, it
> scales into the future and there's no drawbacks for other ISAs.

Not going to work out. The templated struct embedding the integer value has to be kept a POD type so that the function can continue using the necessary C linkage. It's doable but it requires a wide usage of aggregate initializers for these objects.

But the blocker is the Apple ARM64 calling convention which enables function arguments of small integer types to be placed on the stack using an alignment smaller than 8 bytes. It doesn't allow this for compact types, which is what the integer-containing POD struct is treated as. So this won't work as intended, and if it did, it might cause unwanted regressions or at least changes of behavior that might not be entirely desired.
Comment 18 Zan Dobersek 2022-04-19 00:38:38 PDT
Created attachment 457865 [details]
WIP patch

This makes it much simpler, it leaves it to the CCallHelpers::setupArguments() call to, at the end of setup, run through the operation arguments, detecting where the sign extension is necessary.

This one should work only for the Baseline JIT, and only for entering CCalls. Higher levels will need separate attention, and e.g. test cases in testmasm and other test programs will require manual sign-extension of the returned 32-bit integer value.

This is still a prototyping patch, the behavior has to be limited to RISC-V, and maybe some additional validation should be added to ensure that only doubles are used as floating-point arguments for any given operation, meaning any other type uses GPRs.
Comment 19 Zan Dobersek 2022-04-19 11:13:38 PDT
Created attachment 457918 [details]
EWS run.
Comment 20 Zan Dobersek 2022-04-20 01:19:57 PDT
Created attachment 457967 [details]
Patch
Comment 21 Yusuke Suzuki 2022-04-20 02:13:43 PDT
Comment on attachment 457967 [details]
Patch

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

I think this patch looks good. r=me with comments.

> Source/JavaScriptCore/jit/CCallHelpers.h:691
> +        if (!std::is_same<ArgumentType, double>::value) {

Use `if constexpr`. And use is_same_v
Also, is it correct? what happens if we are using `float`?
Can you align it to the condition where CCallHelpers use FPRReg?

> Source/JavaScriptCore/jit/CCallHelpers.h:696
> +            if (isRISCV64() && gprIndex < GPRInfo::numberOfArgumentRegisters
> +                && std::is_integral<ArgumentType>::value && sizeof(ArgumentType) == 4) {

Use `if constexpr` and use is_integral_v
Comment 22 Yusuke Suzuki 2022-04-20 02:14:33 PDT
Comment on attachment 457967 [details]
Patch

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

>> Source/JavaScriptCore/jit/CCallHelpers.h:691
>> +        if (!std::is_same<ArgumentType, double>::value) {
> 
> Use `if constexpr`. And use is_same_v
> Also, is it correct? what happens if we are using `float`?
> Can you align it to the condition where CCallHelpers use FPRReg?

Note that wasm has float.
Comment 23 Zan Dobersek 2022-04-20 03:15:41 PDT
Comment on attachment 457967 [details]
Patch

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

>>> Source/JavaScriptCore/jit/CCallHelpers.h:691
>>> +        if (!std::is_same<ArgumentType, double>::value) {
>> 
>> Use `if constexpr`. And use is_same_v
>> Also, is it correct? what happens if we are using `float`?
>> Can you align it to the condition where CCallHelpers use FPRReg?
> 
> Note that wasm has float.

Everything going through CCallHelpers, if floating-point, is of the double type. There's ceilFloat and floorFloat that are used in B3 as fallbacks, but that doesn't affect CCallHelpers.

Before being marshalled, every incoming FPRReg is static-asserted to be going against a double-typed argument in any given operation:
https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/jit/CCallHelpers.h#L419

I'll add a comment explaining the situation.

>> Source/JavaScriptCore/jit/CCallHelpers.h:696
>> +                && std::is_integral<ArgumentType>::value && sizeof(ArgumentType) == 4) {
> 
> Use `if constexpr` and use is_integral_v

The whole function is constexpr already. I'lll remove ALWAYS_INLINE though cause it's meaningless.
Comment 24 Zan Dobersek 2022-04-20 03:50:59 PDT
Created attachment 457973 [details]
Patch for landing
Comment 25 Zan Dobersek 2022-04-20 06:37:12 PDT
Comment on attachment 457973 [details]
Patch for landing

Clearing flags on attachment: 457973

Committed r293092 (249803@trunk): <https://commits.webkit.org/249803@trunk>
Comment 26 Zan Dobersek 2022-04-20 06:37:18 PDT
All reviewed patches have been landed.  Closing bug.