Diff
Modified: trunk/JSTests/ChangeLog (287737 => 287738)
--- trunk/JSTests/ChangeLog 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/JSTests/ChangeLog 2022-01-07 04:54:03 UTC (rev 287738)
@@ -1,3 +1,20 @@
+2022-01-06 Saam Barati <[email protected]>
+
+ preparePatchpointForExceptions needs to handle tuples
+ https://bugs.webkit.org/show_bug.cgi?id=234909
+
+ Reviewed by Yusuke Suzuki.
+
+ Add support to the builder to have functions return tuples.
+
+ * wasm/Builder.js:
+ (const._normalizeFunctionSignature):
+ (const._maybeRegisterType):
+ * wasm/Builder_WebAssemblyBinary.js:
+ (const.emitters.Type):
+ * wasm/stress/exception-throw-from-function-returning-tuple.js: Added.
+ (import.Builder.from.string_appeared_here.import.as.assert.from.string_appeared_here.testCatchWithExceptionThrownFromFunctionReturningTuple):
+
2022-01-03 Yusuke Suzuki <[email protected]>
Array.prototype.toLocaleString does not respect deletion of Object.prototype.toLocaleString
Modified: trunk/JSTests/wasm/Builder.js (287737 => 287738)
--- trunk/JSTests/wasm/Builder.js 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/JSTests/wasm/Builder.js 2022-01-07 04:54:03 UTC (rev 287738)
@@ -46,10 +46,16 @@
assert.isArray(params);
for (const p of params)
assert.truthy(WASM.isValidValueType(p) || p === "void", `Type parameter ${p} needs a valid value type`);
- if (typeof(ret) === "undefined")
- ret = "void";
- assert.isNotArray(ret, `Multiple return values not supported by WebAssembly yet`);
- assert.truthy(WASM.isValidBlockType(ret), `Type return ${ret} must be valid block type`);
+ if (typeof ret === "undefined")
+ ret = [];
+ else if (typeof ret === "string") {
+ if (ret === "void")
+ ret = [];
+ else
+ ret = [ret];
+ }
+ for (let type of ret)
+ assert.truthy(WASM.isValidBlockType(type), `Type return ${type} must be valid block type`);
return [params, ret];
};
@@ -77,14 +83,19 @@
const [params, ret] = _normalizeFunctionSignature(type.params, type.ret);
assert.isNotUndef(typeSection, `Can not add type if a type section is not present`);
// Try reusing an equivalent type from the type section.
- types:
for (let i = 0; i !== typeSection.data.length; ++i) {
+ let shallowEqual = (a, b) => {
+ if (a.length !== b.length)
+ return false;
+ for (let i = 0; i < a.length; ++i) {
+ if (a[i] !== b[i])
+ return false;
+ }
+ return true;
+ };
+
const t = typeSection.data[i];
- if (t.ret === ret && params.length === t.params.length) {
- for (let j = 0; j !== t.params.length; ++j) {
- if (params[j] !== t.params[j])
- continue types;
- }
+ if (shallowEqual(ret, t.ret) && shallowEqual(params, t.params)) {
type = i;
break;
}
Modified: trunk/JSTests/wasm/Builder_WebAssemblyBinary.js (287737 => 287738)
--- trunk/JSTests/wasm/Builder_WebAssemblyBinary.js 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/JSTests/wasm/Builder_WebAssemblyBinary.js 2022-01-07 04:54:03 UTC (rev 287738)
@@ -108,15 +108,14 @@
put(bin, "varuint32", section.data.length);
for (const entry of section.data) {
put(bin, "varint7", WASM.typeValue["func"]);
+
put(bin, "varuint32", entry.params.length);
for (const param of entry.params)
put(bin, "varint7", WASM.typeValue[param]);
- if (entry.ret === "void")
- put(bin, "varuint1", 0);
- else {
- put(bin, "varuint1", 1);
- put(bin, "varint7", WASM.typeValue[entry.ret]);
- }
+
+ put(bin, "varuint32", entry.ret.length);
+ for (const type of entry.ret)
+ put(bin, "varint7", WASM.typeValue[type]);
}
},
Import: (section, bin) => {
Modified: trunk/JSTests/wasm/self-test/test_BuilderJSON.js (287737 => 287738)
--- trunk/JSTests/wasm/self-test/test_BuilderJSON.js 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/JSTests/wasm/self-test/test_BuilderJSON.js 2022-01-07 04:54:03 UTC (rev 287738)
@@ -113,23 +113,26 @@
.Func([], "f32")
.Func([], "f64")
.Func(["i32", "i64", "f32", "f64"])
+ .Func([], ["i32", "i64"])
.End();
const j = JSON.parse(b.json());
- assert.eq(j.section[0].data.length, 7);
+ assert.eq(j.section[0].data.length, 8);
assert.eq(j.section[0].data[0].params, []);
- assert.eq(j.section[0].data[0].ret, "void");
+ assert.eq(j.section[0].data[0].ret, []);
assert.eq(j.section[0].data[1].params, []);
- assert.eq(j.section[0].data[1].ret, "void");
+ assert.eq(j.section[0].data[1].ret, []);
assert.eq(j.section[0].data[2].params, []);
- assert.eq(j.section[0].data[2].ret, "i32");
+ assert.eq(j.section[0].data[2].ret, ["i32"]);
assert.eq(j.section[0].data[3].params, []);
- assert.eq(j.section[0].data[3].ret, "i64");
+ assert.eq(j.section[0].data[3].ret, ["i64"]);
assert.eq(j.section[0].data[4].params, []);
- assert.eq(j.section[0].data[4].ret, "f32");
+ assert.eq(j.section[0].data[4].ret, ["f32"]);
assert.eq(j.section[0].data[5].params, []);
- assert.eq(j.section[0].data[5].ret, "f64");
+ assert.eq(j.section[0].data[5].ret, ["f64"]);
assert.eq(j.section[0].data[6].params, ["i32", "i64", "f32", "f64"]);
- assert.eq(j.section[0].data[6].ret, "void");
+ assert.eq(j.section[0].data[6].ret, []);
+ assert.eq(j.section[0].data[7].params, []);
+ assert.eq(j.section[0].data[7].ret, ["i32", "i64"]);
})();
(function EmptyImportSection() {
@@ -195,11 +198,11 @@
.End();
const j = JSON.parse(b.json());
assert.eq(j.section[0].data.length, 3);
- assert.eq(j.section[0].data[0].ret, "void");
+ assert.eq(j.section[0].data[0].ret, []);
assert.eq(j.section[0].data[0].params, []);
- assert.eq(j.section[0].data[1].ret, "i32");
+ assert.eq(j.section[0].data[1].ret, ["i32"]);
assert.eq(j.section[0].data[1].params, []);
- assert.eq(j.section[0].data[2].ret, "void");
+ assert.eq(j.section[0].data[2].ret, []);
assert.eq(j.section[0].data[2].params, ["i64", "i64"]);
})();
@@ -313,7 +316,7 @@
assert.eq(j.section[0].name, "Type");
assert.eq(j.section[0].data.length, 1);
assert.eq(j.section[0].data[0].params, []);
- assert.eq(j.section[0].data[0].ret, "void");
+ assert.eq(j.section[0].data[0].ret, []);
assert.eq(j.section[1].name, "Code");
assert.eq(j.section[1].data.length, 1);
assert.eq(j.section[1].data[0].name, undefined);
@@ -332,7 +335,7 @@
assert.eq(j.section.length, 2);
assert.eq(j.section[0].data.length, 1);
assert.eq(j.section[0].data[0].params, ["i32", "i64", "f32", "f64"]);
- assert.eq(j.section[0].data[0].ret, "void");
+ assert.eq(j.section[0].data[0].ret, []);
assert.eq(j.section[1].data.length, 1);
assert.eq(j.section[1].data[0].type, 0);
assert.eq(j.section[1].data[0].parameterCount, 4);
@@ -417,7 +420,7 @@
.End();
const j = JSON.parse(b.json());
assert.eq(j.section[0].data.length, 1);
- assert.eq(j.section[0].data[0].ret, "void");
+ assert.eq(j.section[0].data[0].ret, []);
assert.eq(j.section[0].data[0].params, []);
assert.eq(j.section[1].data.length, 3);
assert.eq(j.section[1].data[0].field, "foo");
Added: trunk/JSTests/wasm/stress/exception-throw-from-function-returning-tuple.js (0 => 287738)
--- trunk/JSTests/wasm/stress/exception-throw-from-function-returning-tuple.js (rev 0)
+++ trunk/JSTests/wasm/stress/exception-throw-from-function-returning-tuple.js 2022-01-07 04:54:03 UTC (rev 287738)
@@ -0,0 +1,83 @@
+import Builder from '../Builder.js'
+import * as assert from '../assert.js'
+
+function testCatchWithExceptionThrownFromFunctionReturningTuple() {
+ const b = new Builder();
+ b.Type().End()
+ .Function().End()
+ .Exception().Signature({ params: []}).End()
+ .Export().Function("call")
+ .Exception("foo", 0)
+ .End()
+ .Code()
+
+ .Function("call", { params: ["i32", "i32"], ret: "i32" })
+ .GetLocal(0)
+ .GetLocal(1)
+ .Try("i32")
+ .Call(1)
+ .Drop()
+ .Drop()
+ .Catch(0)
+ .I32Const(2)
+ .End()
+ .Drop()
+ .I32Add()
+ .End()
+
+ .Function("call2", { params: [], ret: ["i32", "i32", "i32"] })
+ .Throw(0)
+ .End()
+ .End();
+
+ const bin = b.WebAssembly().get();
+ const module = new WebAssembly.Module(bin);
+ const instance = new WebAssembly.Instance(module, { });
+
+ for (let i = 0; i < 1000; ++i)
+ assert.eq(instance.exports.call(42, 5), 47, "catching an exported wasm tag thrown from JS should be possible");
+}
+
+function testCatchWithExceptionThrownFromFunctionReturningTuple2() {
+ const b = new Builder();
+ b.Type().End()
+ .Function().End()
+ .Exception().Signature({ params: []}).End()
+ .Export().Function("call")
+ .Exception("foo", 0)
+ .End()
+ .Code()
+
+ .Function("call", { params: ["i32", "i32"], ret: "i32" })
+ .GetLocal(0)
+ .GetLocal(1)
+ .Try("f32")
+ .I32Const(10)
+ .I32Const(10)
+ .Call(1)
+ .Drop()
+ .Drop()
+ .Drop()
+ .Drop()
+ .Catch(0)
+ .F32Const(2)
+ .End()
+ .Drop()
+ .I32Add()
+ .End()
+
+ .Function("call2", { params: ["i32", "i32"], ret: ["f32", "f32", "f32", "f32", "f32"] })
+ .Throw(0)
+ .End()
+ .End();
+
+ const bin = b.WebAssembly().get();
+ const module = new WebAssembly.Module(bin);
+ const instance = new WebAssembly.Instance(module, { });
+
+ for (let i = 0; i < 1000; ++i)
+ assert.eq(instance.exports.call(42, 5), 47, "catching an exported wasm tag thrown from JS should be possible");
+}
+
+testCatchWithExceptionThrownFromFunctionReturningTuple();
+testCatchWithExceptionThrownFromFunctionReturningTuple2();
Modified: trunk/Source/_javascript_Core/ChangeLog (287737 => 287738)
--- trunk/Source/_javascript_Core/ChangeLog 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/Source/_javascript_Core/ChangeLog 2022-01-07 04:54:03 UTC (rev 287738)
@@ -1,3 +1,29 @@
+2022-01-06 Saam Barati <[email protected]>
+
+ preparePatchpointForExceptions needs to handle tuples
+ https://bugs.webkit.org/show_bug.cgi?id=234909
+
+ Reviewed by Yusuke Suzuki.
+
+ We got the offsets wrong when building a stackmap in B3IRGenerator
+ for exception sites. We need to index into StackmapGenerationParams
+ differently from indexing into the patchpoint's children. StackmapGenerationParams
+ reserves its first N entries for the N return values. The patchpoint's
+ children contains no results though, so we don't need to account for
+ the number of return values when indexing into the children() vector
+ of the PatchpointValue. To make this code simpler, we keep track of the
+ number of live values we need when throwing. These values are both
+ at the end of StackmapGenerationParams and at the end of the children()
+ vector. So we just look at the last "number of live values" in both
+ vectors to get the correct ValueRep and correct type. The code for
+ calls also didn't account for the fact that call arguments will be
+ appended after the live values we're building into a stackmap. This
+ patch fixes that code to always put the live values last.
+
+ * wasm/WasmB3IRGenerator.cpp:
+ (JSC::Wasm::PatchpointExceptionHandle::generate const):
+ (JSC::Wasm::B3IRGenerator::preparePatchpointForExceptions):
+
2022-01-06 Alex Christensen <[email protected]>
Start using C++20
Modified: trunk/Source/_javascript_Core/wasm/WasmB3IRGenerator.cpp (287737 => 287738)
--- trunk/Source/_javascript_Core/wasm/WasmB3IRGenerator.cpp 2022-01-07 04:28:29 UTC (rev 287737)
+++ trunk/Source/_javascript_Core/wasm/WasmB3IRGenerator.cpp 2022-01-07 04:54:03 UTC (rev 287738)
@@ -94,7 +94,7 @@
static constexpr unsigned s_invalidCallSiteIndex = std::numeric_limits<unsigned>::max();
unsigned m_callSiteIndex { s_invalidCallSiteIndex };
- unsigned m_offset;
+ unsigned m_numLiveValues;
};
class B3IRGenerator {
@@ -403,7 +403,7 @@
PartialResult WARN_UNUSED_RETURN addCallRef(const Signature&, Vector<ExpressionType>& args, ResultList& results);
PartialResult WARN_UNUSED_RETURN addUnreachable();
PartialResult WARN_UNUSED_RETURN emitIndirectCall(Value* calleeInstance, Value* calleeCode, const Signature&, Vector<ExpressionType>& args, ResultList&);
- B3::Value* createCallPatchpoint(BasicBlock*, Origin, const Signature&, Vector<ExpressionType>& args, const ScopedLambda<void(PatchpointValue*)>& patchpointFunctor);
+ B3::Value* createCallPatchpoint(BasicBlock*, Origin, const Signature&, Vector<ExpressionType>& args, const ScopedLambda<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>& patchpointFunctor);
void dump(const ControlStack&, const Stack* expressionStack);
void setParser(FunctionParser<B3IRGenerator>* parser) { m_parser = parser; };
@@ -583,12 +583,12 @@
if (m_callSiteIndex == s_invalidCallSiteIndex)
return;
- StackMap values;
- if (params.value()->numChildren() >= m_offset) {
- values = StackMap(params.value()->numChildren() - m_offset);
- for (unsigned i = m_offset; i < params.value()->numChildren(); ++i)
- values[i - m_offset] = OSREntryValue(params[i], params.value()->child(i)->type());
- }
+ StackMap values(m_numLiveValues);
+ unsigned paramsOffset = params.size() - m_numLiveValues;
+ unsigned childrenOffset = params.value()->numChildren() - m_numLiveValues;
+ for (unsigned i = 0; i < m_numLiveValues; ++i)
+ values[i] = OSREntryValue(params[i + paramsOffset], params.value()->child(i + childrenOffset)->type());
+
generator->addStackMap(m_callSiteIndex, WTFMove(values));
jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis));
}
@@ -1213,7 +1213,7 @@
B3::Type returnType = toB3ResultType(&signature);
Value* callResult = createCallPatchpoint(m_currentBlock, origin(), signature, args,
- scopedLambdaRef<void(PatchpointValue*)>([=] (PatchpointValue* patchpoint) -> void {
+ scopedLambdaRef<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>([=] (PatchpointValue* patchpoint, Box<PatchpointExceptionHandle> handle) -> void {
patchpoint->effects.writesPinned = true;
patchpoint->effects.readsPinned = true;
// We need to clobber all potential pinned registers since we might be leaving the instance.
@@ -1224,10 +1224,9 @@
patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
patchpoint->append(calleeCode, ValueRep::SomeRegister);
- PatchpointExceptionHandle handle = preparePatchpointForExceptions(m_currentBlock, patchpoint);
patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
AllowMacroScratchRegisterUsage allowScratch(jit);
- handle.generate(jit, params, this);
+ handle->generate(jit, params, this);
jit.call(params[params.proc().resultCount(returnType)].gpr(), WasmEntryPtrTag);
});
}));
@@ -2609,14 +2608,10 @@
liveValues.append(get(block, data.exception()));
}
- unsigned offset = patch->numChildren();
- if (patch->type() != Void)
- offset++;
-
patch->effects.exitsSideways = true;
patch->appendVectorWithRep(liveValues, ValueRep::LateColdAny);
- return PatchpointExceptionHandle { m_callSiteIndex, offset };
+ return PatchpointExceptionHandle { m_callSiteIndex, static_cast<unsigned>(liveValues.size()) };
}
auto B3IRGenerator::addCatchToUnreachable(unsigned exceptionIndex, const Signature& signature, ControlType& data, ResultList& results) -> PartialResult
@@ -2915,7 +2910,7 @@
}
-B3::Value* B3IRGenerator::createCallPatchpoint(BasicBlock* block, Origin origin, const Signature& signature, Vector<ExpressionType>& args, const ScopedLambda<void(PatchpointValue*)>& patchpointFunctor)
+B3::Value* B3IRGenerator::createCallPatchpoint(BasicBlock* block, Origin origin, const Signature& signature, Vector<ExpressionType>& args, const ScopedLambda<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>& patchpointFunctor)
{
Vector<B3::ConstrainedValue> constrainedArguments;
CallInformation wasmCallInfo = wasmCallingConvention().callInformationFor(signature);
@@ -2924,13 +2919,17 @@
m_proc.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), wasmCallInfo.headerAndArgumentStackSizeInBytes));
+ Box<PatchpointExceptionHandle> exceptionHandle = Box<PatchpointExceptionHandle>::create();
+
B3::Type returnType = toB3ResultType(&signature);
PatchpointValue* patchpoint = m_proc.add<PatchpointValue>(returnType, origin);
patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
- patchpointFunctor(patchpoint);
+ patchpointFunctor(patchpoint, exceptionHandle);
patchpoint->appendVector(constrainedArguments);
+ *exceptionHandle = preparePatchpointForExceptions(block, patchpoint);
+
if (returnType != B3::Void) {
Vector<B3::ValueRep, 1> resultConstraints;
for (auto valueLocation : wasmCallInfo.results)
@@ -2985,7 +2984,7 @@
m_currentBlock->appendNewControlValue(m_proc, B3::Branch, origin(), isWasmCall, FrequentedBlock(isWasmBlock), FrequentedBlock(isEmbedderBlock));
Value* wasmCallResult = createCallPatchpoint(isWasmBlock, origin(), signature, args,
- scopedLambdaRef<void(PatchpointValue*)>([=] (PatchpointValue* patchpoint) -> void {
+ scopedLambdaRef<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>([=] (PatchpointValue* patchpoint, Box<PatchpointExceptionHandle> handle) -> void {
patchpoint->effects.writesPinned = true;
patchpoint->effects.readsPinned = true;
// We need to clobber all potential pinned registers since we might be leaving the instance.
@@ -2992,10 +2991,9 @@
// We pessimistically assume we could be calling to something that is bounds checking.
// FIXME: We shouldn't have to do this: https://bugs.webkit.org/show_bug.cgi?id=172181
patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
- PatchpointExceptionHandle handle = preparePatchpointForExceptions(isWasmBlock, patchpoint);
patchpoint->setGenerator([this, handle, unlinkedWasmToWasmCalls, functionIndex] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
AllowMacroScratchRegisterUsage allowScratch(jit);
- handle.generate(jit, params, this);
+ handle->generate(jit, params, this);
CCallHelpers::Call call = jit.threadSafePatchableNearCall();
jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndex] (LinkBuffer& linkBuffer) {
unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndex });
@@ -3013,7 +3011,7 @@
Load, pointerType(), origin(), instanceValue(), safeCast<int32_t>(Instance::offsetOfWasmToEmbedderStub(functionIndex)));
Value* embedderCallResult = createCallPatchpoint(isEmbedderBlock, origin(), signature, args,
- scopedLambdaRef<void(PatchpointValue*)>([=] (PatchpointValue* patchpoint) -> void {
+ scopedLambdaRef<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>([=] (PatchpointValue* patchpoint, Box<PatchpointExceptionHandle> handle) -> void {
patchpoint->effects.writesPinned = true;
patchpoint->effects.readsPinned = true;
patchpoint->append(jumpDestination, ValueRep::SomeRegister);
@@ -3021,10 +3019,9 @@
// We pessimistically assume we could be calling to something that is bounds checking.
// FIXME: We shouldn't have to do this: https://bugs.webkit.org/show_bug.cgi?id=172181
patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
- PatchpointExceptionHandle handle = preparePatchpointForExceptions(isEmbedderBlock, patchpoint);
patchpoint->setGenerator([this, handle, returnType] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
AllowMacroScratchRegisterUsage allowScratch(jit);
- handle.generate(jit, params, this);
+ handle->generate(jit, params, this);
jit.call(params[params.proc().resultCount(returnType)].gpr(), WasmEntryPtrTag);
});
}));
@@ -3045,7 +3042,7 @@
} else {
Value* patch = createCallPatchpoint(m_currentBlock, origin(), signature, args,
- scopedLambdaRef<void(PatchpointValue*)>([=] (PatchpointValue* patchpoint) -> void {
+ scopedLambdaRef<void(PatchpointValue*, Box<PatchpointExceptionHandle>)>([=] (PatchpointValue* patchpoint, Box<PatchpointExceptionHandle> handle) -> void {
patchpoint->effects.writesPinned = true;
patchpoint->effects.readsPinned = true;
@@ -3052,10 +3049,9 @@
// We need to clobber the size register since the LLInt always bounds checks
if (m_mode == MemoryMode::Signaling || m_info.memory.isShared())
patchpoint->clobberLate(RegisterSet { PinnedRegisterInfo::get().boundsCheckingSizeRegister });
- PatchpointExceptionHandle handle = preparePatchpointForExceptions(m_currentBlock, patchpoint);
patchpoint->setGenerator([this, handle, unlinkedWasmToWasmCalls, functionIndex] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
AllowMacroScratchRegisterUsage allowScratch(jit);
- handle.generate(jit, params, this);
+ handle->generate(jit, params, this);
CCallHelpers::Call call = jit.threadSafePatchableNearCall();
jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndex] (LinkBuffer& linkBuffer) {
unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndex });