Skip to content

Commit

Permalink
[WASM] V128 should be throwable to match Chrome and the spec
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=275383
rdar://106030051

Reviewed by Yusuke Suzuki.

Adds support for v128 parameters in tags to BBQ and OMG tiers and the
WASM/JS interface.

* JSTests/wasm/stress/exception-containing-v128.js: Added.
(async test):
* JSTests/wasm/stress/import-exception-tag-with-v128.js: Added.
(async test):
* Source/JavaScriptCore/wasm/WasmBBQJIT.cpp:
(JSC::Wasm::BBQJITImpl::BBQJIT::addThrow):
* Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp:
(JSC::Wasm::BBQJITImpl::BBQJIT::emitCatchImpl):
* Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp:
(JSC::Wasm::BBQJITImpl::BBQJIT::emitCatchImpl):
* Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp:
(JSC::Wasm::OMGIRGenerator::addCatchToUnreachable):
(JSC::Wasm::OMGIRGenerator::addThrow):
* Source/JavaScriptCore/wasm/WasmOMGIRGenerator32_64.cpp:
(JSC::Wasm::OMGIRGenerator::addCatchToUnreachable):
(JSC::Wasm::OMGIRGenerator::addThrow):
* Source/JavaScriptCore/wasm/WasmOperations.cpp:
(JSC::Wasm::JSC_DEFINE_NOEXCEPT_JIT_OPERATION):
* Source/JavaScriptCore/wasm/WasmSlowPaths.cpp:
(JSC::LLInt::WASM_SLOW_PATH_DECL):
* Source/JavaScriptCore/wasm/WasmTag.h:
* Source/JavaScriptCore/wasm/js/WebAssemblyTagConstructor.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):

Canonical link: https://commits.webkit.org/279989@main
  • Loading branch information
ddegazio committed Jun 13, 2024
1 parent 11e29eb commit eba05a5
Show file tree
Hide file tree
Showing 17 changed files with 252 additions and 67 deletions.
2 changes: 1 addition & 1 deletion JSTests/wasm/stress/big-try-simd.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function test() {
const { test } = instance.exports

for (let i = 0; i < 10000; ++i) {
assert.throws(() => test(42), TypeError, "an exported wasm function cannot contain a v128 parameter or return value")
assert.eq(test(42), undefined);
}
}

Expand Down
109 changes: 109 additions & 0 deletions JSTests/wasm/stress/exception-containing-v128.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//@ skip unless $isSIMDPlatform
import { instantiate } from "../wabt-wrapper.js";
import * as assert from "../assert.js";

let wat = `
(module
(tag $vtag (export "vtag") (param v128))
(func (export "testCatchOwnV128") (result i32)
try (result i32)
v128.const i32x4 0 0 42 0
throw $vtag
catch $vtag
i32x4.extract_lane 2
end
)
(func $innerFunc
v128.const i32x4 0 42 0 0
throw $vtag
)
(func (export "testCatchCalleeV128") (result i32)
try (result i32)
call $innerFunc
i32.const 0
catch $vtag
i32x4.extract_lane 1
end
)
(tag $interleaved (param v128 i32 v128 f32 v128 i64 v128 f64 v128 f32 i64 v128 f64 i32 v128))
(func $throwsInterleaved
v128.const i32x4 1 2 3 4
i32.const 43
v128.const i32x4 1 2 3 4
f32.const 43
v128.const i32x4 1 2 3 4
i64.const 43
v128.const i32x4 1 2 3 4
f64.const 43
v128.const i32x4 1 2 3 4
f32.const 43
i64.const 43
v128.const i32x4 1 2 3 4
f64.const 43
i32.const 43
v128.const i32x4 1 2 3 4
throw $interleaved
)
(func (export "testLotsOfInterleavedTypes") (result i32) (local v128 v128 v128 v128 v128 v128 v128)
try (result i32)
call $throwsInterleaved
i32.const 0
catch $interleaved
local.set 0
drop
drop
local.set 1
drop
drop
local.set 2
drop
local.set 3
drop
local.set 4
drop
local.set 5
drop
local.set 6
local.get 0
local.get 1
local.get 2
local.get 3
local.get 4
local.get 5
local.get 6
i32x4.add
i32x4.add
i32x4.add
i32x4.add
i32x4.add
i32x4.add
local.tee 0
i32x4.extract_lane 1
local.get 0
i32x4.extract_lane 3
i32.add
end
)
(func (export "testThrowV128ToJS")
v128.const i32x4 0 0 0 0
throw $vtag
)
)
`;

async function test() {
const instance = await instantiate(wat, {}, { exceptions: true, simd: true });
assert.eq(instance.exports.testCatchOwnV128(), 42);
assert.eq(instance.exports.testCatchCalleeV128(), 42);
assert.eq(instance.exports.testLotsOfInterleavedTypes(), 42);
assert.throws(() => instance.exports.testThrowV128ToJS(), WebAssembly.Exception);
}

await assert.asyncTest(test());
57 changes: 57 additions & 0 deletions JSTests/wasm/stress/import-exception-tag-with-v128.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//@ skip unless $isSIMDPlatform
import { instantiate } from "../wabt-wrapper.js";
import * as assert from "../assert.js";

let wat = `
(module
(import "tags" "vtag" (tag $vtag (param v128)))
(import "tags" "vvtag" (tag $vvtag (param v128 v128)))
(import "tags" "vitag" (tag $vitag (param v128 i32)))
(func (export "testThrowV128") (result i32)
try (result i32)
v128.const i32x4 0 0 42 0
throw $vtag
catch $vtag
i32x4.extract_lane 2
end
)
(func (export "testThrowV128Pair") (result i32)
try (result i32)
v128.const i32x4 0 21 42 0
v128.const i32x4 0 21 42 0
throw $vvtag
catch $vvtag
i32x4.add
i32x4.extract_lane 1
end
)
(func (export "testThrowV128AndI32") (result i32)
try (result i32)
v128.const i32x4 1 2 3 4
i32.const 42
throw $vitag
catch $vitag
br 0
end
)
)
`;

async function test() {
const vtag = new WebAssembly.Tag({ parameters: ["v128"] });
const vvtag = new WebAssembly.Tag({ parameters: ["v128", "v128"] });
const vitag = new WebAssembly.Tag({ parameters: ["v128", "i32"] });
const instance = await instantiate(wat, { tags: {
vtag: vtag,
vvtag: vvtag,
vitag: vitag
} }, { exceptions: true, simd: true });
assert.eq(instance.exports.testThrowV128(), 42);
assert.eq(instance.exports.testThrowV128Pair(), 42);
assert.eq(instance.exports.testThrowV128AndI32(), 42);
}

await assert.asyncTest(test());
2 changes: 1 addition & 1 deletion JSTests/wasm/stress/tuple-and-simd.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ var f = wasm_instance.exports.main;

assert.throws(() => {
f();
}, TypeError, `an exported wasm function cannot contain a v128 parameter or return value`);
}, WebAssembly.Exception);
14 changes: 7 additions & 7 deletions JSTests/wasm/v8/exceptions-simd.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ load("exceptions-utils.js");
.exportFunc();
var instance = builder.instantiate();

// NOTE: changed from original test since this part of the spec is still in flux.
for (let i = 0; i < 1000; ++i)
assertThrows(() => instance.exports.throw_simd());
assertThrows(() => instance.exports.throw_simd(), WebAssembly.Exception);
})();

(function TestThrowCatchS128Default() {
Expand All @@ -49,7 +48,7 @@ load("exceptions-utils.js");
var instance = builder.instantiate();

for (let i = 0; i < 1000; ++i)
assertThrows(() => instance.exports.throw_catch_simd());
assertEquals(1, instance.exports.throw_catch_simd());
})();

(function TestThrowCatchS128WithValue() {
Expand Down Expand Up @@ -78,8 +77,9 @@ load("exceptions-utils.js");
var ref = [0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78,
0x89, 0x9a, 0xab, 0xbc, 0xcd, 0xde, 0xef, 0xf0];
var array = new Uint8Array(memory.buffer);
array.set(ref, in_idx); // Store reference value in memory.
for (let i = 0; i < 1000; ++i)
assertThrows(() => instance.exports.throw_catch_simd());
// assertArrayEquals(ref, array.slice(out_idx, out_idx + 0x10));
for (let i = 0; i < 1000; ++i) {
array.set(ref, in_idx); // Store reference value in memory.
instance.exports.throw_catch_simd();
assertArrayEquals(ref, array.slice(out_idx, out_idx + 0x10));
}
})();
7 changes: 3 additions & 4 deletions Source/JavaScriptCore/b3/air/AirLowerStackArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ void lowerStackArgs(Code& code)
for (Inst& inst : *block) {
for (Arg& arg : inst.args) {
if (arg.isCallArg()) {
// For now, we assume that we use 8 bytes of the call arg. But that's not
// such an awesome assumption.
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=150454
ASSERT(arg.offset() >= 0);
code.requestCallArgAreaSizeInBytes(arg.offset() + (code.usesSIMD() ? conservativeRegisterBytes(arg.bank()) : conservativeRegisterBytesWithoutVectors(arg.bank())));
// We always check the conservative register bytes for Bank::FP because
// CallArgs do not store which bank they are.
code.requestCallArgAreaSizeInBytes(arg.offset() + (code.usesSIMD() ? conservativeRegisterBytes(Bank::FP) : conservativeRegisterBytesWithoutVectors(Bank::FP)));
}
}
}
Expand Down
18 changes: 8 additions & 10 deletions Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3520,20 +3520,18 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addDelegateToUnreachable(ControlType& t

PartialResult WARN_UNUSED_RETURN BBQJIT::addThrow(unsigned exceptionIndex, Vector<ExpressionType>& arguments, Stack&)
{
Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), arguments.size() * sizeof(uint64_t));
m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize);

LOG_INSTRUCTION("Throw", arguments);

for (unsigned i = 0; i < arguments.size(); i++) {
Location stackLocation = Location::fromStackArgument(i * sizeof(uint64_t));
Value argument = arguments[i];
if (argument.type() == TypeKind::V128)
emitMove(Value::fromF64(0), stackLocation);
else
emitMove(argument, stackLocation);
consume(argument);
unsigned offset = 0;
for (auto arg : arguments) {
Location stackLocation = Location::fromStackArgument(offset * sizeof(uint64_t));
emitMove(arg, stackLocation);
consume(arg);
offset += arg.type() == TypeKind::V128 ? 2 : 1;
}
Checked<int32_t> calleeStackSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), offset * sizeof(uint64_t));
m_maxCalleeStackSize = std::max<int>(calleeStackSize, m_maxCalleeStackSize);

++m_callSiteIndex;
bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value();
Expand Down
12 changes: 7 additions & 5 deletions Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2898,13 +2898,14 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
ScratchScope<1, 0> scratches(*this);
GPRReg bufferGPR = scratches.gpr(0);
m_jit.loadPtr(Address(GPRInfo::returnValueGPR, JSWebAssemblyException::offsetOfPayload() + JSWebAssemblyException::Payload::offsetOfStorage()), bufferGPR);
unsigned offset = 0;
for (unsigned i = 0; i < exceptionSignature.as<FunctionSignature>()->argumentCount(); ++i) {
Type type = exceptionSignature.as<FunctionSignature>()->argumentType(i);
Value result = Value::fromTemp(type.kind, dataCatch.enclosedHeight() + dataCatch.implicitSlots() + i);
Location slot = canonicalSlot(result);
switch (type.kind) {
case TypeKind::I32:
m_jit.load32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR);
m_jit.load32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchGPR);
m_jit.store32(wasmScratchGPR, slot.asAddress());
break;
case TypeKind::I31ref:
Expand All @@ -2926,20 +2927,20 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
case TypeKind::Array:
case TypeKind::Struct:
case TypeKind::Func: {
m_jit.loadPair32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR, wasmScratchGPR2);
m_jit.loadPair32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchGPR, wasmScratchGPR2);
m_jit.storePair32(wasmScratchGPR, wasmScratchGPR2, slot.asAddress());
break;
}
case TypeKind::F32:
m_jit.loadFloat(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR);
m_jit.loadFloat(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeFloat(wasmScratchFPR, slot.asAddress());
break;
case TypeKind::F64:
m_jit.loadDouble(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR);
m_jit.loadDouble(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeDouble(wasmScratchFPR, slot.asAddress());
break;
case TypeKind::V128:
materializeVectorConstant(v128_t { }, Location::fromFPR(wasmScratchFPR));
m_jit.loadVector(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeVector(wasmScratchFPR, slot.asAddress());
break;
case TypeKind::Void:
Expand All @@ -2948,6 +2949,7 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
}
bind(result, slot);
results.append(result);
offset += type.kind == TypeKind::V128 ? 2 : 1;
}
}
}
Expand Down
16 changes: 7 additions & 9 deletions Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2973,14 +2973,14 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
ScratchScope<1, 0> scratches(*this);
GPRReg bufferGPR = scratches.gpr(0);
m_jit.loadPtr(Address(GPRInfo::returnValueGPR, JSWebAssemblyException::offsetOfPayload() + JSWebAssemblyException::Payload::offsetOfStorage()), bufferGPR);
unsigned offset = 0;
for (unsigned i = 0; i < exceptionSignature.as<FunctionSignature>()->argumentCount(); ++i) {
Type type = exceptionSignature.as<FunctionSignature>()->argumentType(i);
Value result = Value::fromTemp(type.kind, dataCatch.enclosedHeight() + dataCatch.implicitSlots() + i);
Location slot = canonicalSlot(result);
switch (type.kind) {
case TypeKind::I32:
m_jit.load32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR);
m_jit.store32(wasmScratchGPR, slot.asAddress());
m_jit.transfer32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), slot.asAddress());
break;
case TypeKind::I31ref:
case TypeKind::I64:
Expand All @@ -3001,20 +3001,17 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
case TypeKind::Array:
case TypeKind::Struct:
case TypeKind::Func: {
m_jit.load64(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchGPR);
m_jit.store64(wasmScratchGPR, slot.asAddress());
m_jit.transfer64(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), slot.asAddress());
break;
}
case TypeKind::F32:
m_jit.loadFloat(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeFloat(wasmScratchFPR, slot.asAddress());
m_jit.transfer32(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), slot.asAddress());
break;
case TypeKind::F64:
m_jit.loadDouble(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + i * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeDouble(wasmScratchFPR, slot.asAddress());
m_jit.transfer64(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), slot.asAddress());
break;
case TypeKind::V128:
materializeVectorConstant(v128_t { }, Location::fromFPR(wasmScratchFPR));
m_jit.loadVector(Address(bufferGPR, JSWebAssemblyException::Payload::Storage::offsetOfData() + offset * sizeof(uint64_t)), wasmScratchFPR);
m_jit.storeVector(wasmScratchFPR, slot.asAddress());
break;
case TypeKind::Void:
Expand All @@ -3023,6 +3020,7 @@ void BBQJIT::emitCatchImpl(ControlData& dataCatch, const TypeDefinition& excepti
}
bind(result, slot);
results.append(result);
offset += type.kind == TypeKind::V128 ? 2 : 1;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/wasm/WasmExceptionType.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ namespace Wasm {
macro(NullStructGet, "struct.get to a null reference"_s) \
macro(NullStructSet, "struct.set to a null reference"_s) \
macro(TypeErrorInvalidV128Use, "an exported wasm function cannot contain a v128 parameter or return value"_s) \
macro(TypeErrorV128TagAccessInJS, "a v128 parameter of a tag may not be accessed from JS"_s) \
macro(NullRefAsNonNull, "ref.as_non_null to a null reference"_s) \
macro(CastFailure, "ref.cast failed to cast reference to target heap type"_s) \
macro(OutOfBoundsDataSegmentAccess, "Offset + array length would exceed the size of a data segment"_s) \
Expand Down Expand Up @@ -138,6 +139,7 @@ ALWAYS_INLINE bool isTypeErrorExceptionType(ExceptionType type)
case ExceptionType::FuncrefNotWasm:
case ExceptionType::InvalidGCTypeUse:
case ExceptionType::TypeErrorInvalidV128Use:
case ExceptionType::TypeErrorV128TagAccessInJS:
return true;
}
return false;
Expand Down
Loading

0 comments on commit eba05a5

Please sign in to comment.