1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
From 5ddfaf5238a7ecc4f350c55c4b772ccdb96a35e7 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 23 Sep 2026 14:33:27 +0100
Subject: [PATCH] JavaScriptCore: sign-extend i32 C call arguments on RISCV64
The RISC-V psABI requires a 32-bit integer argument to be sign-extended
to its full 64-bit argument register. BBQ loads i32 values from canonical
slots with lwu, which zero-extends, so a C call made from generated wasm
code can receive a wrong value for any i32 argument with bit 31 set.
Add emitSignExtendI32ArgsForCCall(), which emits sext.w over every I32
argument that ends up in a register, and call it from both emitCCall()
overloads. It is a no-op on every other architecture.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
Source/JavaScriptCore/wasm/WasmBBQJIT.cpp | 19 +++++++++++++++++++
Source/JavaScriptCore/wasm/WasmBBQJIT.h | 6 ++++++
Source/JavaScriptCore/wasm/WasmBBQJIT64.h | 2 ++
3 files changed, 27 insertions(+)
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
@@ -4396,6 +4396,25 @@ void BBQJIT::restoreValuesAfterCall(cons
// whenever they are next used.
}
+void BBQJIT::emitSignExtendI32ArgsForCCall(const CallInformation& callInfo, const RTT& signature)
+{
+#if CPU(RISCV64)
+ for (size_t i = 0; i < callInfo.params.size(); ++i) {
+ auto type = signature.argumentType(i);
+ if (type.kind != TypeKind::I32)
+ continue;
+ Location loc = Location::fromArgumentLocation(callInfo.params[i], type.kind);
+ if (!loc.isGPR())
+ continue;
+ // sext.w rd, rs lowers via signExtend32To64 -> rv_addiw rd, rs, 0
+ m_jit.signExtend32To64(loc.asGPR(), loc.asGPR());
+ }
+#else
+ UNUSED_PARAM(callInfo);
+ UNUSED_PARAM(signature);
+#endif
+}
+
template<size_t N>
void BBQJIT::returnValuesFromCall(Vector<Value, N>& results, const RTT& functionType, const CallInformation& callInfo)
{
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT.h
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.h
@@ -2089,6 +2089,12 @@ public:
template<typename Args>
void saveValuesAcrossCallAndPassArguments(const Args& arguments, const CallInformation&, const RTT& signature);
+ // On RISC-V the psABI requires 32-bit integer arguments to be sign-extended
+ // in their 64-bit argument registers; BBQ otherwise zero-extends them when
+ // loading from canonical i32 slots (lwu). Emit sext.w on any I32 arg that
+ // ends up in a register. No-op on other architectures.
+ void emitSignExtendI32ArgsForCCall(const CallInformation& callInfo, const RTT& signature);
+
void slowPathSpillBindings(const RegisterBindings&);
void slowPathRestoreBindings(const RegisterBindings&);
void NODELETE restoreValuesAfterCall(const CallInformation&);
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT64.h
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT64.h
@@ -581,6 +581,7 @@ void BBQJIT::emitCCall(Func function, st
// Preserve caller-saved registers and other info
prepareForExceptions();
saveValuesAcrossCallAndPassArguments(arguments, callInfo, functionRTT.get());
+ emitSignExtendI32ArgsForCCall(callInfo, functionRTT.get());
// Materialize address of native function and call register
void* taggedFunctionPtr = tagCFunctionPtr<void*, OperationPtrTag>(function);
@@ -611,6 +612,7 @@ void BBQJIT::emitCCall(Func function, st
// Preserve caller-saved registers and other info
prepareForExceptions();
saveValuesAcrossCallAndPassArguments(arguments, callInfo, functionRTT.get());
+ emitSignExtendI32ArgsForCCall(callInfo, functionRTT.get());
// Materialize address of native function and call register
void* taggedFunctionPtr = tagCFunctionPtr<void*, OperationPtrTag>(function);
|