Skip to content
Permalink
Browse files
[WASM-Function-References] Add call_ref instruction
https://bugs.webkit.org/show_bug.cgi?id=222903

Patch by Dmitry Bezhetskov <dbezhetskov@igalia.com> on 2021-05-03
Reviewed by Yusuke Suzuki.

JSTests:

Add basic tests for new call_ref instruction:
https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md.
Add tests for calling same-instance wasm function, foreign-instance
wasm function and for calling imported js function.

* wasm.yaml:
* wasm/function-references/call_ref.js: Added.
(module):
(async basics):
(async indirectCall):
(async importHostCall):
* wasm/wasm.json:

Source/JavaScriptCore:

Add support for call_ref instruction from the typed function
references proposal: https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md.
call_ref calls the given function references from the stack
and it does almost the same stuff as call_indirect but
it doesn't check signatures because wasm types system guaranties
correctness.

* bytecode/BytecodeList.rb:
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* llint/LowLevelInterpreter.asm:
* llint/WebAssembly.asm:
* runtime/Gate.h:
* wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::addCallIndirect):
(JSC::Wasm::AirIRGenerator::addCallRef):
(JSC::Wasm::AirIRGenerator::emitIndirectCall):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::emitIndirectCall):
(JSC::Wasm::B3IRGenerator::addCallIndirect):
(JSC::Wasm::B3IRGenerator::addCallRef):
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* wasm/WasmLLIntGenerator.cpp:
(JSC::Wasm::LLIntGenerator::addCallRef):
* wasm/WasmSlowPaths.cpp:
(JSC::LLInt::doWasmCallRef):
(JSC::LLInt::WASM_SLOW_PATH_DECL):
* wasm/WasmSlowPaths.h:
* wasm/js/JSWebAssemblyTable.cpp:
* wasm/js/WebAssemblyFunction.cpp:
(JSC::WebAssemblyFunction::WebAssemblyFunction):
* wasm/js/WebAssemblyFunction.h:
* wasm/js/WebAssemblyFunctionBase.cpp:
(JSC::WebAssemblyFunctionBase::WebAssemblyFunctionBase):
* wasm/js/WebAssemblyFunctionBase.h:
(JSC::WebAssemblyFunctionBase::offsetOfEntrypointLoadLocation):
* wasm/js/WebAssemblyWrapperFunction.cpp:
(JSC::WebAssemblyWrapperFunction::WebAssemblyWrapperFunction):
* wasm/js/WebAssemblyWrapperFunction.h:
* wasm/wasm.json:

Canonical link: https://commits.webkit.org/237242@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@276896 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
dbezhetskov authored and webkit-commit-queue committed May 3, 2021
1 parent 8ba49be commit e772c5b3f046a8a40a1706dbf40dbfb58617c431
Showing 25 changed files with 453 additions and 136 deletions.
@@ -1,3 +1,23 @@
2021-05-03 Dmitry Bezhetskov <dbezhetskov@igalia.com>

[WASM-Function-References] Add call_ref instruction
https://bugs.webkit.org/show_bug.cgi?id=222903

Reviewed by Yusuke Suzuki.

Add basic tests for new call_ref instruction:
https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md.
Add tests for calling same-instance wasm function, foreign-instance
wasm function and for calling imported js function.

* wasm.yaml:
* wasm/function-references/call_ref.js: Added.
(module):
(async basics):
(async indirectCall):
(async importHostCall):
* wasm/wasm.json:

2021-04-28 Mark Lam <mark.lam@apple.com>

Fix exception assertions in light of the TerminationException.
@@ -33,6 +33,8 @@
cmd: runWebAssemblySuite unless parseRunCommands
- path: wasm/references
cmd: runWebAssemblySuite unless parseRunCommands
- path: wasm/function-references
cmd: runWebAssemblySuite unless parseRunCommands
- path: wasm/fuzz
cmd: runWebAssemblySuite unless parseRunCommands
- path: wasm/lowExecutableMemory
@@ -0,0 +1,75 @@
//@ runWebAssemblySuite("--useWebAssemblyTypedFunctionReferences=true")
import * as assert from '../assert.js';
import { instantiate } from "../wabt-wrapper.js";

function module(bytes, valid = true) {
let buffer = new ArrayBuffer(bytes.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; ++i) {
view[i] = bytes.charCodeAt(i);
}
return new WebAssembly.Module(buffer);
}

async function callFunctionFromTheSameInstance() {
/*
(module
(elem declare funcref (ref.func $foo))
(func $foo (param $x i32) (result i32)
(i32.add (local.get $x)
(i32.const 19)
)
)
(func (export "main") (result i32)
(call_ref (i32.const 10) (ref.func $foo))
)
)
*/
let instance = new WebAssembly.Instance(module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x0a\x02\x60\x01\x7f\x01\x7f\x60\x00\x01\x7f\x03\x03\x02\x00\x01\x07\x08\x01\x04\x6d\x61\x69\x6e\x00\x01\x09\x05\x01\x03\x00\x01\x00\x0a\x11\x02\x07\x00\x20\x00\x41\x13\x6a\x0b\x07\x00\x41\x0a\xd2\x00\x14\x0b"));
assert.eq(instance.exports.main(), 29);
}

async function callFunctionFromTheDifferentInstance() {
let wat = `
(module
(func (export "bar") (param $x i32) (result i32)
(i32.add (local.get $x)
(i32.const 19))
)
)`;
const barProvider = await instantiate(wat, {}, {reference_types: true});

/*
(module
(import "exports" "bar" (func $bar (param i32) (result i32)))
(elem declare funcref (ref.func $bar))
(func (export "main") (result i32)
(call_ref (i32.const 10) (ref.func $bar))
)
)
*/
let instance = new WebAssembly.Instance(
module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x0a\x02\x60\x01\x7f\x01\x7f\x60\x00\x01\x7f\x02\x0f\x01\x07\x65\x78\x70\x6f\x72\x74\x73\x03\x62\x61\x72\x00\x00\x03\x02\x01\x01\x07\x08\x01\x04\x6d\x61\x69\x6e\x00\x01\x09\x05\x01\x03\x00\x01\x00\x0a\x09\x01\x07\x00\x41\x0a\xd2\x00\x14\x0b"),
barProvider);
assert.eq(instance.exports.main(), 29);
}

async function callFunctionFromJS() {
/*
(module
(import "exports" "bar" (func $bar (param i32) (result i32)))
(elem declare funcref (ref.func $bar))
(func (export "main") (result i32)
(call_ref (i32.const 10) (ref.func $bar))
)
)
*/
let instance = new WebAssembly.Instance(
module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x0a\x02\x60\x01\x7f\x01\x7f\x60\x00\x01\x7f\x02\x0f\x01\x07\x65\x78\x70\x6f\x72\x74\x73\x03\x62\x61\x72\x00\x00\x03\x02\x01\x01\x07\x08\x01\x04\x6d\x61\x69\x6e\x00\x01\x09\x05\x01\x03\x00\x01\x00\x0a\x09\x01\x07\x00\x41\x0a\xd2\x00\x14\x0b"),
{exports: {bar : x => x + 19}});
assert.eq(instance.exports.main(), 29);
}

assert.asyncTest(callFunctionFromTheSameInstance());
assert.asyncTest(callFunctionFromTheDifferentInstance());
assert.asyncTest(callFunctionFromJS());
@@ -85,6 +85,7 @@
"data.drop": { "category": "exttable", "value": 252, "return": [], "parameter": [], "immediate": [{"name": "segment_index", "type": "varuint32"}], "description": "shrinks the size of the segment to zero", "extendedOp": 9 },
"call": { "category": "call", "value": 16, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "function_index", "type": "varuint32"}], "description": "call a function by its index" },
"call_indirect": { "category": "call", "value": 17, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "call a function indirect with an expected signature" },
"call_ref": { "category": "call", "value": 20, "return": ["call"], "parameter": ["call"], "immediate": [], "description": "call a function reference" },
"i32.load8_s": { "category": "memory", "value": 44, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
"i32.load8_u": { "category": "memory", "value": 45, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
"i32.load16_s": { "category": "memory", "value": 46, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
@@ -1,3 +1,53 @@
2021-05-03 Dmitry Bezhetskov <dbezhetskov@igalia.com>

[WASM-Function-References] Add call_ref instruction
https://bugs.webkit.org/show_bug.cgi?id=222903

Reviewed by Yusuke Suzuki.

Add support for call_ref instruction from the typed function
references proposal: https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md.
call_ref calls the given function references from the stack
and it does almost the same stuff as call_indirect but
it doesn't check signatures because wasm types system guaranties
correctness.

* bytecode/BytecodeList.rb:
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* llint/LowLevelInterpreter.asm:
* llint/WebAssembly.asm:
* runtime/Gate.h:
* wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::addCallIndirect):
(JSC::Wasm::AirIRGenerator::addCallRef):
(JSC::Wasm::AirIRGenerator::emitIndirectCall):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::emitIndirectCall):
(JSC::Wasm::B3IRGenerator::addCallIndirect):
(JSC::Wasm::B3IRGenerator::addCallRef):
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* wasm/WasmLLIntGenerator.cpp:
(JSC::Wasm::LLIntGenerator::addCallRef):
* wasm/WasmSlowPaths.cpp:
(JSC::LLInt::doWasmCallRef):
(JSC::LLInt::WASM_SLOW_PATH_DECL):
* wasm/WasmSlowPaths.h:
* wasm/js/JSWebAssemblyTable.cpp:
* wasm/js/WebAssemblyFunction.cpp:
(JSC::WebAssemblyFunction::WebAssemblyFunction):
* wasm/js/WebAssemblyFunction.h:
* wasm/js/WebAssemblyFunctionBase.cpp:
(JSC::WebAssemblyFunctionBase::WebAssemblyFunctionBase):
* wasm/js/WebAssemblyFunctionBase.h:
(JSC::WebAssemblyFunctionBase::offsetOfEntrypointLoadLocation):
* wasm/js/WebAssemblyWrapperFunction.cpp:
(JSC::WebAssemblyWrapperFunction::WebAssemblyWrapperFunction):
* wasm/js/WebAssemblyWrapperFunction.h:
* wasm/wasm.json:

2021-05-01 Chris Dumez <cdumez@apple.com>

Start leveraging std::filesystem in WTF::FileSystem
@@ -1474,6 +1474,8 @@
op :wasm_trampoline_wasm_call_no_tls
op :wasm_trampoline_wasm_call_indirect
op :wasm_trampoline_wasm_call_indirect_no_tls
op :wasm_trampoline_wasm_call_ref
op :wasm_trampoline_wasm_call_ref_no_tls

end_section :NativeHelpers

@@ -1495,6 +1497,8 @@
op :call_no_tls_return_location
op :call_indirect_return_location
op :call_indirect_no_tls_return_location
op :call_ref_return_location
op :call_ref_no_tls_return_location

# FIXME: Wasm and JS LLInt should share common opcodes
# https://bugs.webkit.org/show_bug.cgi?id=203656
@@ -1686,6 +1690,22 @@
tableIndex: unsigned,
}

op :call_ref,
args: {
functionReference: VirtualRegister,
signatureIndex: unsigned,
stackOffset: unsigned,
numberOfStackArgs: unsigned,
}

op :call_ref_no_tls,
args: {
functionReference: VirtualRegister,
signatureIndex: unsigned,
stackOffset: unsigned,
numberOfStackArgs: unsigned,
}

op :current_memory,
args: {
dst: VirtualRegister,
@@ -368,6 +368,8 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, const I
case wasm_trampoline_wasm_call_no_tls:
case wasm_trampoline_wasm_call_indirect:
case wasm_trampoline_wasm_call_indirect_no_tls:
case wasm_trampoline_wasm_call_ref:
case wasm_trampoline_wasm_call_ref_no_tls:
return CannotCompile;
}
return CannotCompile;
@@ -2530,14 +2530,20 @@ _wasm_trampoline_wasm_call:
_wasm_trampoline_wasm_call_no_tls:
_wasm_trampoline_wasm_call_indirect:
_wasm_trampoline_wasm_call_indirect_no_tls:
_wasm_trampoline_wasm_call_ref:
_wasm_trampoline_wasm_call_ref_no_tls:
_wasm_trampoline_wasm_call_wide16:
_wasm_trampoline_wasm_call_no_tls_wide16:
_wasm_trampoline_wasm_call_indirect_wide16:
_wasm_trampoline_wasm_call_indirect_no_tls_wide16:
_wasm_trampoline_wasm_call_ref_wide16:
_wasm_trampoline_wasm_call_ref_no_tls_wide16:
_wasm_trampoline_wasm_call_wide32:
_wasm_trampoline_wasm_call_no_tls_wide32:
_wasm_trampoline_wasm_call_indirect_wide32:
_wasm_trampoline_wasm_call_indirect_no_tls_wide32:
_wasm_trampoline_wasm_call_ref_wide32:
_wasm_trampoline_wasm_call_ref_no_tls_wide32:
crash()

end
@@ -877,6 +877,14 @@ wasmOp(call_indirect_no_tls, WasmCallIndirectNoTls, macro(ctx)
slowPathForWasmCall(ctx, _slow_path_wasm_call_indirect_no_tls, macro(targetInstance) move targetInstance, wasmInstance end)
end)

wasmOp(call_ref, WasmCallRef, macro(ctx)
slowPathForWasmCall(ctx, _slow_path_wasm_call_ref, storeWasmInstanceToTLS)
end)

wasmOp(call_ref_no_tls, WasmCallRefNoTls, macro(ctx)
slowPathForWasmCall(ctx, _slow_path_wasm_call_ref_no_tls, macro(targetInstance) move targetInstance, wasmInstance end)
end)

wasmOp(current_memory, WasmCurrentMemory, macro(ctx)
loadp Wasm::Instance::m_memory[wasmInstance], t0
loadp Wasm::Memory::m_handle[t0], t0
@@ -68,6 +68,8 @@ namespace JSC {
v(wasm_call_no_tls, JSEntrySlowPathPtrTag) \
v(wasm_call_indirect, JSEntrySlowPathPtrTag) \
v(wasm_call_indirect_no_tls, JSEntrySlowPathPtrTag) \
v(wasm_call_ref, JSEntrySlowPathPtrTag) \
v(wasm_call_ref_no_tls, JSEntrySlowPathPtrTag) \

#else
#define JSC_WASM_GATE_OPCODES(v)
@@ -325,7 +325,9 @@ class AirIRGenerator {
// Calls
PartialResult WARN_UNUSED_RETURN addCall(uint32_t calleeIndex, const Signature&, Vector<ExpressionType>& args, ResultList& results);
PartialResult WARN_UNUSED_RETURN addCallIndirect(unsigned tableIndex, const Signature&, Vector<ExpressionType>& args, ResultList& results);
PartialResult WARN_UNUSED_RETURN addCallRef(const Signature&, Vector<ExpressionType>& args, ResultList& results);
PartialResult WARN_UNUSED_RETURN addUnreachable();
PartialResult WARN_UNUSED_RETURN emitIndirectCall(TypedTmp calleeInstance, ExpressionType calleeCode, const Signature&, const Vector<ExpressionType>& args, ResultList&);
B3::PatchpointValue* WARN_UNUSED_RETURN emitCallPatchpoint(BasicBlock*, const Signature&, const ResultList& results, const Vector<TypedTmp>& args, Vector<ConstrainedTmp>&& extraArgs = { });

PartialResult addShift(Type, B3::Air::Opcode, ExpressionType value, ExpressionType shift, ExpressionType& result);
@@ -3273,9 +3275,6 @@ auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const Signature& signa
// can be to the embedder for our stack check calculation.
m_maxNumJSCallArguments = std::max(m_maxNumJSCallArguments, static_cast<uint32_t>(args.size()));

auto currentInstance = g64();
append(Move, instanceValue(), currentInstance);

ExpressionType callableFunctionBuffer = g64();
ExpressionType instancesBuffer = g64();
ExpressionType callableFunctionBufferLength = g64();
@@ -3340,15 +3339,42 @@ auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const Signature& signa
});
}

auto calleeInstance = g64();
append(Move, Arg::index(instancesBuffer, calleeIndex, 8, 0), calleeInstance);

return emitIndirectCall(calleeInstance, calleeCode, signature, args, results);
}

auto AirIRGenerator::addCallRef(const Signature& signature, Vector<ExpressionType>& args, ResultList& results) -> PartialResult
{
m_makesCalls = true;
// Note: call ref can call either WebAssemblyFunction or WebAssemblyWrapperFunction. Because
// WebAssemblyWrapperFunction is like calling into the embedder, we conservatively assume all call indirects
// can be to the embedder for our stack check calculation.
ExpressionType calleeFunction = args.takeLast();
m_maxNumJSCallArguments = std::max(m_maxNumJSCallArguments, static_cast<uint32_t>(args.size()));

ExpressionType calleeCode = g64();
append(Move, Arg::addr(calleeFunction, WebAssemblyFunctionBase::offsetOfEntrypointLoadLocation()), calleeCode); // Pointer to callee code.

auto calleeInstance = g64();
append(Move, Arg::addr(calleeFunction, WebAssemblyFunctionBase::offsetOfInstance()), calleeInstance);
append(Move, Arg::addr(calleeInstance, JSWebAssemblyInstance::offsetOfInstance()), calleeInstance);

return emitIndirectCall(calleeInstance, calleeCode, signature, args, results);
}

auto AirIRGenerator::emitIndirectCall(TypedTmp calleeInstance, ExpressionType calleeCode, const Signature& signature, const Vector<ExpressionType>& args, ResultList& results) -> PartialResult
{
auto currentInstance = g64();
append(Move, instanceValue(), currentInstance);

// Do a context switch if needed.
{
auto newContextInstance = g64();
append(Move, Arg::index(instancesBuffer, calleeIndex, 8, 0), newContextInstance);

BasicBlock* doContextSwitch = m_code.addBlock();
BasicBlock* continuation = m_code.addBlock();

append(Branch64, Arg::relCond(MacroAssembler::Equal), newContextInstance, instanceValue());
append(Branch64, Arg::relCond(MacroAssembler::Equal), calleeInstance, currentInstance);
m_currentBlock->setSuccessors(continuation, doContextSwitch);

auto* patchpoint = addPatchpoint(B3::Void);
@@ -3361,26 +3387,26 @@ auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const Signature& signa

patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
AllowMacroScratchRegisterUsage allowScratch(jit);
GPRReg newContextInstance = params[0].gpr();
GPRReg calleeInstance = params[0].gpr();
GPRReg oldContextInstance = params[1].gpr();
const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
ASSERT(newContextInstance != baseMemory);
ASSERT(calleeInstance != baseMemory);
jit.loadPtr(CCallHelpers::Address(oldContextInstance, Instance::offsetOfCachedStackLimit()), baseMemory);
jit.storePtr(baseMemory, CCallHelpers::Address(newContextInstance, Instance::offsetOfCachedStackLimit()));
jit.storeWasmContextInstance(newContextInstance);
jit.storePtr(baseMemory, CCallHelpers::Address(calleeInstance, Instance::offsetOfCachedStackLimit()));
jit.storeWasmContextInstance(calleeInstance);
// FIXME: We should support more than one memory size register
// see: https://bugs.webkit.org/show_bug.cgi?id=162952
ASSERT(pinnedRegs.boundsCheckingSizeRegister != newContextInstance);
ASSERT(pinnedRegs.boundsCheckingSizeRegister != calleeInstance);
GPRReg scratch = params.gpScratch(0);

jit.loadPtr(CCallHelpers::Address(newContextInstance, Instance::offsetOfCachedBoundsCheckingSize()), pinnedRegs.boundsCheckingSizeRegister); // Bound checking size.
jit.loadPtr(CCallHelpers::Address(newContextInstance, Instance::offsetOfCachedMemory()), baseMemory); // Memory::void*.
jit.loadPtr(CCallHelpers::Address(calleeInstance, Instance::offsetOfCachedBoundsCheckingSize()), pinnedRegs.boundsCheckingSizeRegister); // Bound checking size.
jit.loadPtr(CCallHelpers::Address(calleeInstance, Instance::offsetOfCachedMemory()), baseMemory); // Memory::void*.

jit.cageConditionallyAndUntag(Gigacage::Primitive, baseMemory, pinnedRegs.boundsCheckingSizeRegister, scratch);
});

emitPatchpoint(doContextSwitch, patchpoint, Tmp(), newContextInstance, instanceValue());
emitPatchpoint(doContextSwitch, patchpoint, Tmp(), calleeInstance, currentInstance);
append(doContextSwitch, Jump);
doContextSwitch->setSuccessors(continuation);

0 comments on commit e772c5b

Please sign in to comment.