Wasm should support call_indirect
https://bugs.webkit.org/show_bug.cgi?id=165718
Reviewed by Filip Pizlo.
JSTests:
* wasm/Builder.js:
* wasm/function-tests/call-indirect-params.js: Added.
* wasm/function-tests/call-indirect.js: Added.
* wasm/js-api/call-indirect.js: Added.
(const.wasmModuleWhichImportJS):
(MonomorphicImport):
(Polyphic2Import):
(VirtualImport):
* wasm/wasm.json:
Source/JavaScriptCore:
This patch adds support for call_indirect. The basic framework for
an indirect call is that the module holds a buffer containing a
stub for each function in the index space. Whenever a function
needs to do an indirect call it gets a index into that table. In
order to ensure call_indirect is calling a valid function the
functionIndexSpace also needs a pointer to a canonicalized
signature. When making an indirect call, we first check the index
is in range, then check the signature matches the value we were given.
This patch also differentiates between FunctionIndexSpaces and
ImmutableFunctionIndexSpaces. Since we don't know the size of the
FunctionIndexSpace when we start parsing we need to be able to
resize the IndexSpace. However, once we have finished parsing all
the sections we want to prevent an relocation of the function
index space pointer.
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::B3IRGenerator):
(JSC::Wasm::B3IRGenerator::addCall):
(JSC::Wasm::B3IRGenerator::addCallIndirect):
(JSC::Wasm::createJSToWasmWrapper):
(JSC::Wasm::parseAndCompile):
* wasm/WasmB3IRGenerator.h:
* wasm/WasmCallingConvention.h:
(JSC::Wasm::CallingConvention::setupCall):
* wasm/WasmFormat.h:
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser::setErrorMessage):
(JSC::Wasm::FunctionParser<Context>::FunctionParser):
(JSC::Wasm::FunctionParser<Context>::parseExpression):
* wasm/WasmPlan.cpp:
(JSC::Wasm::Plan::run):
* wasm/WasmPlan.h:
(JSC::Wasm::Plan::takeFunctionIndexSpace):
* wasm/WasmValidate.cpp:
(JSC::Wasm::Validate::addCallIndirect):
(JSC::Wasm::validateFunction):
* wasm/WasmValidate.h:
* wasm/js/JSWebAssemblyModule.cpp:
(JSC::JSWebAssemblyModule::create):
(JSC::JSWebAssemblyModule::JSWebAssemblyModule):
* wasm/js/JSWebAssemblyModule.h:
(JSC::JSWebAssemblyModule::signatureForFunctionIndexSpace):
(JSC::JSWebAssemblyModule::offsetOfFunctionIndexSpace):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@209652 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index 7571685..ef2ddd8 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,20 @@
+2016-12-09 Keith Miller <keith_miller@apple.com>
+
+ Wasm should support call_indirect
+ https://bugs.webkit.org/show_bug.cgi?id=165718
+
+ Reviewed by Filip Pizlo.
+
+ * wasm/Builder.js:
+ * wasm/function-tests/call-indirect-params.js: Added.
+ * wasm/function-tests/call-indirect.js: Added.
+ * wasm/js-api/call-indirect.js: Added.
+ (const.wasmModuleWhichImportJS):
+ (MonomorphicImport):
+ (Polyphic2Import):
+ (VirtualImport):
+ * wasm/wasm.json:
+
2016-12-09 JF Bastien <jfbastien@apple.com>
WebAssembly: implement data section
diff --git a/JSTests/wasm/Builder.js b/JSTests/wasm/Builder.js
index 5951417..0f7ab97 100644
--- a/JSTests/wasm/Builder.js
+++ b/JSTests/wasm/Builder.js
@@ -236,6 +236,7 @@
break;
case "target_count": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
case "target_table": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
+ case "reserved": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
default: throw new Error(`Implementation problem: unhandled immediate "${expect.name}" on "${op}"`);
}
}
diff --git a/JSTests/wasm/function-tests/call-indirect-params.js b/JSTests/wasm/function-tests/call-indirect-params.js
new file mode 100644
index 0000000..4148211a
--- /dev/null
+++ b/JSTests/wasm/function-tests/call-indirect-params.js
@@ -0,0 +1,28 @@
+import Builder from '../Builder.js'
+
+const b = new Builder();
+b.Type().End()
+ .Function().End()
+ .Code()
+
+ .Function({ params: ["i32"], ret: "i32" })
+ .I32Const(1)
+ .End()
+
+ .Function({ params: ["i32"], ret: "i32" })
+ .GetLocal(0)
+ .End()
+
+ .Function({ params: ["i32", "i32"], ret: "i32" })
+ .GetLocal(1)
+ .GetLocal(0)
+ .CallIndirect(0, 0)
+ .End()
+
+
+const bin = b.WebAssembly()
+bin.trim();
+testWasmModuleFunctions(bin.get(), 3, [], [],
+ [[{ type: "i32", value: 1 }, [{ type: "i32", value: 0 }, { type: "i32", value: 4 }]],
+ [{ type: "i32", value: 4 }, [{ type: "i32", value: 1 }, { type: "i32", value: 4 }]],
+ ]);
diff --git a/JSTests/wasm/function-tests/call-indirect.js b/JSTests/wasm/function-tests/call-indirect.js
new file mode 100644
index 0000000..a4b5fbe
--- /dev/null
+++ b/JSTests/wasm/function-tests/call-indirect.js
@@ -0,0 +1,27 @@
+import Builder from '../Builder.js'
+
+const b = new Builder();
+b.Type().End()
+ .Function().End()
+ .Code()
+
+ .Function({ params: [], ret: "i32" })
+ .I32Const(1)
+ .End()
+
+ .Function({ params: [], ret: "i32" })
+ .I32Const(2)
+ .End()
+
+ .Function({ params: ["i32"], ret: "i32" })
+ .GetLocal(0)
+ .CallIndirect(0, 0)
+ .End()
+
+
+const bin = b.WebAssembly()
+bin.trim();
+testWasmModuleFunctions(bin.get(), 3, [], [],
+ [[{ type: "i32", value: 1 }, [{ type: "i32", value: 0 }]],
+ [{ type: "i32", value: 1 }, [{ type: "i32", value: 0 }]],
+ ]);
diff --git a/JSTests/wasm/js-api/call-indirect.js b/JSTests/wasm/js-api/call-indirect.js
new file mode 100644
index 0000000..b827ef9
--- /dev/null
+++ b/JSTests/wasm/js-api/call-indirect.js
@@ -0,0 +1,87 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const wasmModuleWhichImportJS = () => {
+ const builder = (new Builder())
+ .Type().End()
+ .Import()
+ .Function("imp", "func", { params: ["i32"] })
+ .End()
+ .Function().End()
+ .Export()
+ .Function("changeCounter")
+ .End()
+ .Code()
+ .Function("changeCounter", { params: ["i32", "i32"] })
+ .I32Const(42)
+ .GetLocal(0)
+ .I32Add()
+ .GetLocal(1)
+ .CallIndirect(0, 0) // Calls func(param[0] + 42).
+ .End()
+ .End();
+ const bin = builder.WebAssembly().get();
+ const module = new WebAssembly.Module(bin);
+ return module;
+};
+
+
+(function MonomorphicImport() {
+ let counter = 0;
+ const counterSetter = v => counter = v;
+ const module = wasmModuleWhichImportJS();
+ const instance = new WebAssembly.Instance(module, { imp: { func: counterSetter } });
+ for (let i = 0; i < 4096; ++i) {
+ // Invoke this a bunch of times to make sure the IC in the wasm -> JS stub works correctly.
+ instance.exports.changeCounter(i, 0);
+ assert.eq(counter, i + 42);
+ }
+})();
+
+(function Polyphic2Import() {
+ let counterA = 0;
+ let counterB = undefined;
+ const counterASetter = v => counterA = v;
+ const counterBSetter = v => counterB = { valueB: v };
+ const module = wasmModuleWhichImportJS();
+ const instanceA = new WebAssembly.Instance(module, { imp: { func: counterASetter } });
+ const instanceB = new WebAssembly.Instance(module, { imp: { func: counterBSetter } });
+ for (let i = 0; i < 2048; ++i) {
+ instanceA.exports.changeCounter(i, 0);
+ assert.isA(counterA, "number");
+ assert.eq(counterA, i + 42);
+ instanceB.exports.changeCounter(i, 0);
+ assert.isA(counterB, "object");
+ assert.eq(counterB.valueB, i + 42);
+ }
+})();
+
+(function VirtualImport() {
+ const num = 10; // It's definitely going virtual at 10!
+ let counters = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ const counterSetters = [
+ v => counters[0] = v,
+ v => counters[1] = v + 1,
+ v => counters[2] = v + 2,
+ v => counters[3] = v + 3,
+ v => counters[4] = v + 4,
+ v => counters[5] = v + 5,
+ v => counters[6] = v + 6,
+ v => counters[7] = v + 7,
+ v => counters[8] = v + 8,
+ v => counters[9] = v + 9,
+ ];
+ assert.eq(counters.length, num);
+ assert.eq(counterSetters.length, num);
+ const module = wasmModuleWhichImportJS();
+ let instances = [];
+ for (let i = 0; i < num; ++i)
+ instances[i] = new WebAssembly.Instance(module, { imp: { func: counterSetters[i] } });
+ for (let i = 0; i < 2048; ++i) {
+ for (let j = 0; j < num; ++j) {
+ instances[j].exports.changeCounter(i, 0);
+ assert.isA(counters[j], "number");
+ assert.eq(counters[j], i + 42 + j);
+ }
+ }
+})();
diff --git a/JSTests/wasm/wasm.json b/JSTests/wasm/wasm.json
index 4b8fb19..ec0efc5 100644
--- a/JSTests/wasm/wasm.json
+++ b/JSTests/wasm/wasm.json
@@ -64,7 +64,7 @@
"get_global": { "category": "special", "value": 35, "return": ["global"], "parameter": [], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "read a global variable" },
"set_global": { "category": "special", "value": 36, "return": [""], "parameter": ["global"], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "write a global variable" },
"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"}], "description": "call a function indirect with an expected signature" },
+ "call_indirect": { "category": "call", "value": 17, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "reserved", "type": "varuint1"}], "description": "call a function indirect with an expected signature" },
"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" },
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 0190610..8850105 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,55 @@
+2016-12-09 Keith Miller <keith_miller@apple.com>
+
+ Wasm should support call_indirect
+ https://bugs.webkit.org/show_bug.cgi?id=165718
+
+ Reviewed by Filip Pizlo.
+
+ This patch adds support for call_indirect. The basic framework for
+ an indirect call is that the module holds a buffer containing a
+ stub for each function in the index space. Whenever a function
+ needs to do an indirect call it gets a index into that table. In
+ order to ensure call_indirect is calling a valid function the
+ functionIndexSpace also needs a pointer to a canonicalized
+ signature. When making an indirect call, we first check the index
+ is in range, then check the signature matches the value we were given.
+
+ This patch also differentiates between FunctionIndexSpaces and
+ ImmutableFunctionIndexSpaces. Since we don't know the size of the
+ FunctionIndexSpace when we start parsing we need to be able to
+ resize the IndexSpace. However, once we have finished parsing all
+ the sections we want to prevent an relocation of the function
+ index space pointer.
+
+ * wasm/WasmB3IRGenerator.cpp:
+ (JSC::Wasm::B3IRGenerator::B3IRGenerator):
+ (JSC::Wasm::B3IRGenerator::addCall):
+ (JSC::Wasm::B3IRGenerator::addCallIndirect):
+ (JSC::Wasm::createJSToWasmWrapper):
+ (JSC::Wasm::parseAndCompile):
+ * wasm/WasmB3IRGenerator.h:
+ * wasm/WasmCallingConvention.h:
+ (JSC::Wasm::CallingConvention::setupCall):
+ * wasm/WasmFormat.h:
+ * wasm/WasmFunctionParser.h:
+ (JSC::Wasm::FunctionParser::setErrorMessage):
+ (JSC::Wasm::FunctionParser<Context>::FunctionParser):
+ (JSC::Wasm::FunctionParser<Context>::parseExpression):
+ * wasm/WasmPlan.cpp:
+ (JSC::Wasm::Plan::run):
+ * wasm/WasmPlan.h:
+ (JSC::Wasm::Plan::takeFunctionIndexSpace):
+ * wasm/WasmValidate.cpp:
+ (JSC::Wasm::Validate::addCallIndirect):
+ (JSC::Wasm::validateFunction):
+ * wasm/WasmValidate.h:
+ * wasm/js/JSWebAssemblyModule.cpp:
+ (JSC::JSWebAssemblyModule::create):
+ (JSC::JSWebAssemblyModule::JSWebAssemblyModule):
+ * wasm/js/JSWebAssemblyModule.h:
+ (JSC::JSWebAssemblyModule::signatureForFunctionIndexSpace):
+ (JSC::JSWebAssemblyModule::offsetOfFunctionIndexSpace):
+
2016-12-09 JF Bastien <jfbastien@apple.com>
WebAssembly: implement data section
diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
index 820e588..1cfe254 100644
--- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
+++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
@@ -40,6 +40,8 @@
#include "B3VariableValue.h"
#include "B3WasmAddressValue.h"
#include "B3WasmBoundsCheckValue.h"
+#include "JSWebAssemblyInstance.h"
+#include "JSWebAssemblyModule.h"
#include "VirtualRegister.h"
#include "WasmCallingConvention.h"
#include "WasmFunctionParser.h"
@@ -130,7 +132,7 @@
static constexpr ExpressionType emptyExpression = nullptr;
- B3IRGenerator(MemoryInformation&, Procedure&, WasmInternalFunction*, Vector<UnlinkedWasmToWasmCall>&);
+ B3IRGenerator(const MemoryInformation&, Procedure&, WasmInternalFunction*, Vector<UnlinkedWasmToWasmCall>&, const ImmutableFunctionIndexSpace&);
bool WARN_UNUSED_RETURN addArguments(const Vector<Type>&);
bool WARN_UNUSED_RETURN addLocal(Type, uint32_t);
@@ -164,7 +166,9 @@
bool WARN_UNUSED_RETURN endBlock(ControlEntry&, ExpressionList& expressionStack);
bool WARN_UNUSED_RETURN addEndToUnreachable(ControlEntry&);
- bool WARN_UNUSED_RETURN addCall(unsigned calleeIndex, const Signature*, Vector<ExpressionType>& args, ExpressionType& result);
+ // Calls
+ bool WARN_UNUSED_RETURN addCall(uint32_t calleeIndex, const Signature*, Vector<ExpressionType>& args, ExpressionType& result);
+ bool WARN_UNUSED_RETURN addCallIndirect(const Signature*, Vector<ExpressionType>& args, ExpressionType& result);
void dump(const Vector<ControlEntry>& controlStack, const ExpressionList& expressionStack);
@@ -179,6 +183,7 @@
void unifyValuesWithBlock(const ExpressionList& resultStack, ResultList& stack);
Value* zeroForType(Type);
+ const ImmutableFunctionIndexSpace& m_functionIndexSpace;
Procedure& m_proc;
BasicBlock* m_currentBlock;
Vector<Variable*> m_locals;
@@ -186,10 +191,12 @@
GPRReg m_memoryBaseGPR;
GPRReg m_memorySizeGPR;
Value* m_zeroValues[numTypes];
+ Value* m_functionIndexSpaceValue;
};
-B3IRGenerator::B3IRGenerator(MemoryInformation& memory, Procedure& procedure, WasmInternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls)
- : m_proc(procedure)
+B3IRGenerator::B3IRGenerator(const MemoryInformation& memory, Procedure& procedure, WasmInternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ImmutableFunctionIndexSpace& functionIndexSpace)
+ : m_functionIndexSpace(functionIndexSpace)
+ , m_proc(procedure)
, m_unlinkedWasmToWasmCalls(unlinkedWasmToWasmCalls)
{
m_currentBlock = m_proc.addBlock();
@@ -224,6 +231,8 @@
}
wasmCallingConvention().setupFrameInPrologue(compilation, m_proc, Origin(), m_currentBlock);
+
+ m_functionIndexSpaceValue = m_currentBlock->appendNew<ConstPtrValue>(m_proc, Origin(), functionIndexSpace.buffer.get());
}
Value* B3IRGenerator::zeroForType(Type type)
@@ -592,7 +601,7 @@
return true;
}
-bool B3IRGenerator::addCall(unsigned functionIndex, const Signature* signature, Vector<ExpressionType>& args, ExpressionType& result)
+bool B3IRGenerator::addCall(uint32_t functionIndex, const Signature* signature, Vector<ExpressionType>& args, ExpressionType& result)
{
ASSERT(signature->arguments.size() == args.size());
@@ -618,6 +627,57 @@
return true;
}
+bool B3IRGenerator::addCallIndirect(const Signature* signature, Vector<ExpressionType>& args, ExpressionType& result)
+{
+ ExpressionType calleeIndex = args.takeLast();
+ ASSERT(signature->arguments.size() == args.size());
+
+ // Check the index we are looking for is valid.
+ {
+ ExpressionType maxValidIndex = m_currentBlock->appendIntConstant(m_proc, Origin(), Int32, m_functionIndexSpace.size);
+ CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, Origin(),
+ m_currentBlock->appendNew<Value>(m_proc, Equal, Origin(), m_zeroValues[linearizeType(I32)],
+ m_currentBlock->appendNew<Value>(m_proc, LessThan, Origin(), calleeIndex, maxValidIndex)));
+
+ check->setGenerator([] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+ jit.breakpoint();
+ });
+ }
+
+ // Compute the offset in the function index space we are looking for.
+ ExpressionType offset = m_currentBlock->appendNew<Value>(m_proc, Mul, Origin(),
+ m_currentBlock->appendNew<Value>(m_proc, ZExt32, Origin(), calleeIndex),
+ m_currentBlock->appendIntConstant(m_proc, Origin(), pointerType(), sizeof(CallableFunction)));
+ ExpressionType callableFunction = m_currentBlock->appendNew<Value>(m_proc, Add, Origin(), m_functionIndexSpaceValue, offset);
+
+ // Check the signature matches the value we expect.
+ {
+ ExpressionType calleeSignature = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(), callableFunction, OBJECT_OFFSETOF(CallableFunction, signature));
+ ExpressionType expectedSignature = m_currentBlock->appendNew<ConstPtrValue>(m_proc, Origin(), signature);
+ CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, Origin(),
+ m_currentBlock->appendNew<Value>(m_proc, NotEqual, Origin(), calleeSignature, expectedSignature));
+
+ check->setGenerator([] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+ jit.breakpoint();
+ });
+ }
+
+ ExpressionType calleeCode = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(), callableFunction, OBJECT_OFFSETOF(CallableFunction, code));
+
+ result = wasmCallingConvention().setupCall(m_proc, m_currentBlock, Origin(), args, toB3Type(signature->returnType),
+ [&] (PatchpointValue* patchpoint) {
+ patchpoint->effects.writesPinned = true;
+ patchpoint->effects.readsPinned = true;
+
+ patchpoint->append(calleeCode, ValueRep::SomeRegister);
+
+ patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
+ jit.call(params[0].gpr());
+ });
+ });
+ return true;
+}
+
void B3IRGenerator::unify(Variable* variable, ExpressionType source)
{
m_currentBlock->appendNew<VariableValue>(m_proc, Set, Origin(), variable, source);
@@ -657,7 +717,7 @@
dataLogLn("\n");
}
-static std::unique_ptr<Compilation> createJSToWasmWrapper(VM& vm, const Signature* signature, MacroAssemblerCodePtr mainFunction, MemoryInformation& memory)
+static std::unique_ptr<Compilation> createJSToWasmWrapper(VM& vm, const Signature* signature, MacroAssemblerCodePtr mainFunction, const MemoryInformation& memory)
{
Procedure proc;
BasicBlock* block = proc.addBlock();
@@ -737,13 +797,13 @@
return std::make_unique<Compilation>(vm, proc);
}
-std::unique_ptr<WasmInternalFunction> parseAndCompile(VM& vm, const uint8_t* functionStart, size_t functionLength, MemoryInformation& memory, const Signature* signature, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const FunctionIndexSpace& functionIndexSpace, unsigned optLevel)
+std::unique_ptr<WasmInternalFunction> parseAndCompile(VM& vm, const uint8_t* functionStart, size_t functionLength, const Signature* signature, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& info, unsigned optLevel)
{
auto result = std::make_unique<WasmInternalFunction>();
Procedure procedure;
- B3IRGenerator context(memory, procedure, result.get(), unlinkedWasmToWasmCalls);
- FunctionParser<B3IRGenerator> parser(context, functionStart, functionLength, signature, functionIndexSpace);
+ B3IRGenerator context(info.memory, procedure, result.get(), unlinkedWasmToWasmCalls, functionIndexSpace);
+ FunctionParser<B3IRGenerator> parser(context, functionStart, functionLength, signature, functionIndexSpace, info);
if (!parser.parse())
RELEASE_ASSERT_NOT_REACHED();
@@ -757,7 +817,7 @@
dataLog("Post SSA: ", procedure);
result->code = std::make_unique<Compilation>(vm, procedure, optLevel);
- result->jsToWasmEntryPoint = createJSToWasmWrapper(vm, signature, result->code->code(), memory);
+ result->jsToWasmEntryPoint = createJSToWasmWrapper(vm, signature, result->code->code(), info.memory);
return result;
}
diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
index 042007e..ba10aa3 100644
--- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
+++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
@@ -37,7 +37,7 @@
class MemoryInformation;
-std::unique_ptr<WasmInternalFunction> parseAndCompile(VM&, const uint8_t*, size_t, MemoryInformation&, const Signature*, Vector<UnlinkedWasmToWasmCall>&, const FunctionIndexSpace&, unsigned optLevel = 1);
+std::unique_ptr<WasmInternalFunction> parseAndCompile(VM&, const uint8_t*, size_t, const Signature*, Vector<UnlinkedWasmToWasmCall>&, const ImmutableFunctionIndexSpace&, const ModuleInformation&, unsigned optLevel = 1);
} } // namespace JSC::Wasm
diff --git a/Source/JavaScriptCore/wasm/WasmCallingConvention.h b/Source/JavaScriptCore/wasm/WasmCallingConvention.h
index d7ebd80..4c06e5a 100644
--- a/Source/JavaScriptCore/wasm/WasmCallingConvention.h
+++ b/Source/JavaScriptCore/wasm/WasmCallingConvention.h
@@ -163,8 +163,8 @@
B3::PatchpointValue* patchpoint = block->appendNew<B3::PatchpointValue>(proc, returnType, origin);
patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
- patchpoint->appendVector(constrainedArguments);
patchpointFunctor(patchpoint);
+ patchpoint->appendVector(constrainedArguments);
switch (returnType) {
case B3::Void:
diff --git a/Source/JavaScriptCore/wasm/WasmFormat.h b/Source/JavaScriptCore/wasm/WasmFormat.h
index 4f4cfb1..ab86b39 100644
--- a/Source/JavaScriptCore/wasm/WasmFormat.h
+++ b/Source/JavaScriptCore/wasm/WasmFormat.h
@@ -187,6 +187,12 @@
};
typedef Vector<CallableFunction> FunctionIndexSpace;
+
+struct ImmutableFunctionIndexSpace {
+ MallocPtr<CallableFunction> buffer;
+ size_t size;
+};
+
} } // namespace JSC::Wasm
#endif // ENABLE(WEBASSEMBLY)
diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h
index ec19325..207018b 100644
--- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h
+++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h
@@ -45,7 +45,7 @@
typedef typename Context::ControlType ControlType;
typedef typename Context::ExpressionList ExpressionList;
- FunctionParser(Context&, const uint8_t* functionStart, size_t functionLength, const Signature*, const FunctionIndexSpace&);
+ FunctionParser(Context&, const uint8_t* functionStart, size_t functionLength, const Signature*, const ImmutableFunctionIndexSpace&, const ModuleInformation&);
bool WARN_UNUSED_RETURN parse();
@@ -71,22 +71,28 @@
template<OpType>
bool WARN_UNUSED_RETURN binaryCase();
- void setErrorMessage(String&& message) { m_context.setErrorMessage(WTFMove(message)); }
+ bool setErrorMessage(String&& message)
+ {
+ m_context.setErrorMessage(WTFMove(message));
+ return false;
+ }
Context& m_context;
ExpressionList m_expressionStack;
Vector<ControlEntry> m_controlStack;
const Signature* m_signature;
- const FunctionIndexSpace& m_functionIndexSpace;
+ const ImmutableFunctionIndexSpace& m_functionIndexSpace;
+ const ModuleInformation& m_info;
unsigned m_unreachableBlocks { 0 };
};
template<typename Context>
-FunctionParser<Context>::FunctionParser(Context& context, const uint8_t* functionStart, size_t functionLength, const Signature* signature, const FunctionIndexSpace& functionIndexSpace)
+FunctionParser<Context>::FunctionParser(Context& context, const uint8_t* functionStart, size_t functionLength, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& info)
: Parser(functionStart, functionLength)
, m_context(context)
, m_signature(signature)
, m_functionIndexSpace(functionIndexSpace)
+ , m_info(info)
{
if (verbose)
dataLogLn("Parsing function starting at: ", (uintptr_t)functionStart, " of length: ", functionLength);
@@ -350,10 +356,10 @@
if (!parseVarUInt32(functionIndex))
return false;
- if (functionIndex >= m_functionIndexSpace.size())
+ if (functionIndex >= m_functionIndexSpace.size)
return false;
- const Signature* calleeSignature = m_functionIndexSpace[functionIndex].signature;
+ const Signature* calleeSignature = m_functionIndexSpace.buffer.get()[functionIndex].signature;
if (calleeSignature->arguments.size() > m_expressionStack.size())
return false;
@@ -375,6 +381,42 @@
return true;
}
+ case OpType::CallIndirect: {
+ uint32_t signatureIndex;
+ if (!parseVarUInt32(signatureIndex))
+ return false;
+
+ uint8_t reserved;
+ if (!parseVarUInt1(reserved))
+ return false;
+
+ if (m_info.signatures.size() <= signatureIndex)
+ return setErrorMessage("Tried to use a signature outside the range of valid signatures");
+
+ const Signature* calleeSignature = &m_info.signatures[signatureIndex];
+ size_t argumentCount = calleeSignature->arguments.size() + 1; // Add the callee's index.
+ if (argumentCount > m_expressionStack.size())
+ return setErrorMessage("Not enough values on the stack for call_indirect");
+
+ Vector<ExpressionType> args;
+ if (!args.tryReserveCapacity(argumentCount))
+ return setErrorMessage("Out of memory");
+
+ size_t firstArgumentIndex = m_expressionStack.size() - argumentCount;
+ for (unsigned i = firstArgumentIndex; i < m_expressionStack.size(); ++i)
+ args.uncheckedAppend(m_expressionStack[i]);
+ m_expressionStack.shrink(firstArgumentIndex);
+
+ ExpressionType result = Context::emptyExpression;
+ if (!m_context.addCallIndirect(calleeSignature, args, result))
+ return false;
+
+ if (result != Context::emptyExpression)
+ m_expressionStack.append(result);
+
+ return true;
+ }
+
case OpType::Block: {
Type inlineSignature;
if (!parseResultType(inlineSignature))
@@ -517,8 +559,7 @@
case OpType::GrowMemory:
case OpType::CurrentMemory:
case OpType::GetGlobal:
- case OpType::SetGlobal:
- case OpType::CallIndirect: {
+ case OpType::SetGlobal: {
// FIXME: Not yet implemented.
return false;
}
diff --git a/Source/JavaScriptCore/wasm/WasmPlan.cpp b/Source/JavaScriptCore/wasm/WasmPlan.cpp
index d89a1b3..d6c6083 100644
--- a/Source/JavaScriptCore/wasm/WasmPlan.cpp
+++ b/Source/JavaScriptCore/wasm/WasmPlan.cpp
@@ -72,7 +72,8 @@
}
m_moduleInformation = WTFMove(moduleParser.moduleInformation());
m_functionLocationInBinary = WTFMove(moduleParser.functionLocationInBinary());
- m_functionIndexSpace = WTFMove(moduleParser.functionIndexSpace());
+ m_functionIndexSpace.size = moduleParser.functionIndexSpace().size();
+ m_functionIndexSpace.buffer = moduleParser.functionIndexSpace().releaseBuffer();
}
if (verbose)
dataLogLn("Parsed module.");
@@ -103,7 +104,7 @@
dataLogLn("Processing import function number ", importFunctionIndex, ": ", import->module, ": ", import->field);
Signature* signature = m_moduleInformation->importFunctions.at(import->kindIndex);
m_wasmToJSStubs.uncheckedAppend(importStubGenerator(m_vm, m_callLinkInfos, signature, importFunctionIndex));
- m_functionIndexSpace[importFunctionIndex].code = m_wasmToJSStubs[importFunctionIndex].code().executableAddress();
+ m_functionIndexSpace.buffer.get()[importFunctionIndex].code = m_wasmToJSStubs[importFunctionIndex].code().executableAddress();
}
for (unsigned functionIndex = 0; functionIndex < m_functionLocationInBinary.size(); ++functionIndex) {
@@ -114,9 +115,9 @@
ASSERT(functionLength <= m_sourceLength);
Signature* signature = m_moduleInformation->internalFunctionSignatures[functionIndex];
unsigned functionIndexSpace = m_wasmToJSStubs.size() + functionIndex;
- ASSERT(m_functionIndexSpace[functionIndexSpace].signature == signature);
+ ASSERT(m_functionIndexSpace.buffer.get()[functionIndexSpace].signature == signature);
- String error = validateFunction(functionStart, functionLength, signature, m_functionIndexSpace, m_moduleInformation->memory);
+ String error = validateFunction(functionStart, functionLength, signature, m_functionIndexSpace, *m_moduleInformation);
if (!error.isNull()) {
if (verbose) {
for (unsigned i = 0; i < functionLength; ++i)
@@ -128,14 +129,14 @@
}
unlinkedWasmToWasmCalls.uncheckedAppend(Vector<UnlinkedWasmToWasmCall>());
- m_wasmInternalFunctions.uncheckedAppend(parseAndCompile(*m_vm, functionStart, functionLength, m_moduleInformation->memory, signature, unlinkedWasmToWasmCalls.at(functionIndex), m_functionIndexSpace));
- m_functionIndexSpace[functionIndexSpace].code = m_wasmInternalFunctions[functionIndex]->code->code().executableAddress();
+ m_wasmInternalFunctions.uncheckedAppend(parseAndCompile(*m_vm, functionStart, functionLength, signature, unlinkedWasmToWasmCalls.at(functionIndex), m_functionIndexSpace, *m_moduleInformation));
+ m_functionIndexSpace.buffer.get()[functionIndexSpace].code = m_wasmInternalFunctions[functionIndex]->code->code().executableAddress();
}
// Patch the call sites for each WebAssembly function.
for (auto& unlinked : unlinkedWasmToWasmCalls) {
for (auto& call : unlinked)
- MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(m_functionIndexSpace[call.functionIndex].code));
+ MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(m_functionIndexSpace.buffer.get()[call.functionIndex].code));
}
m_failed = false;
diff --git a/Source/JavaScriptCore/wasm/WasmPlan.h b/Source/JavaScriptCore/wasm/WasmPlan.h
index 9b0c1b2..3cdbb65 100644
--- a/Source/JavaScriptCore/wasm/WasmPlan.h
+++ b/Source/JavaScriptCore/wasm/WasmPlan.h
@@ -95,7 +95,7 @@
return WTFMove(m_wasmToJSStubs);
}
- FunctionIndexSpace&& takeFunctionIndexSpace()
+ ImmutableFunctionIndexSpace&& takeFunctionIndexSpace()
{
RELEASE_ASSERT(!failed());
return WTFMove(m_functionIndexSpace);
@@ -107,7 +107,7 @@
Bag<CallLinkInfo> m_callLinkInfos;
Vector<WasmToJSStub> m_wasmToJSStubs;
Vector<std::unique_ptr<WasmInternalFunction>> m_wasmInternalFunctions;
- FunctionIndexSpace m_functionIndexSpace;
+ ImmutableFunctionIndexSpace m_functionIndexSpace;
VM* m_vm;
const uint8_t* m_source;
diff --git a/Source/JavaScriptCore/wasm/WasmValidate.cpp b/Source/JavaScriptCore/wasm/WasmValidate.cpp
index a024885..22f95bb 100644
--- a/Source/JavaScriptCore/wasm/WasmValidate.cpp
+++ b/Source/JavaScriptCore/wasm/WasmValidate.cpp
@@ -110,8 +110,9 @@
bool WARN_UNUSED_RETURN endBlock(ControlEntry&, ExpressionList& expressionStack);
bool WARN_UNUSED_RETURN addEndToUnreachable(ControlEntry&);
-
+ // Calls
bool WARN_UNUSED_RETURN addCall(unsigned calleeIndex, const Signature*, const Vector<ExpressionType>& args, ExpressionType& result);
+ bool WARN_UNUSED_RETURN addCallIndirect(const Signature*, const Vector<ExpressionType>& args, ExpressionType& result);
void dump(const Vector<ControlEntry>& controlStack, const ExpressionList& expressionStack);
@@ -347,6 +348,35 @@
return true;
}
+bool Validate::addCallIndirect(const Signature* signature, const Vector<ExpressionType>& args, ExpressionType& result)
+{
+ const auto argumentCount = signature->arguments.size();
+ if (argumentCount != args.size() - 1) {
+ StringBuilder builder;
+ builder.append("Arity mismatch in call_indirect, expected: ");
+ builder.appendNumber(signature->arguments.size());
+ builder.append(" but got: ");
+ builder.appendNumber(args.size());
+ m_errorMessage = builder.toString();
+ return false;
+ }
+
+ for (unsigned i = 0; i < argumentCount; ++i) {
+ if (args[i] != signature->arguments[i]) {
+ m_errorMessage = makeString("Expected argument type: ", toString(signature->arguments[i]), " does not match passed argument type: ", toString(args[i]));
+ return false;
+ }
+ }
+
+ if (args.last() != I32) {
+ m_errorMessage = makeString("Expected call_indirect target index to have type: i32 but got type: ", toString(args.last()));
+ return false;
+ }
+
+ result = signature->returnType;
+ return true;
+}
+
bool Validate::unify(const ExpressionList& values, const ControlType& block)
{
ASSERT(values.size() <= 1);
@@ -371,10 +401,11 @@
// Think of this as penance for the sin of bad error messages.
}
-String validateFunction(const uint8_t* source, size_t length, const Signature* signature, const FunctionIndexSpace& functionIndexSpace, const MemoryInformation& memory)
+String validateFunction(const uint8_t* source, size_t length, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& info)
{
- Validate context(signature->returnType, memory);
- FunctionParser<Validate> validator(context, source, length, signature, functionIndexSpace);
+ Validate context(signature->returnType, info.memory);
+ FunctionParser<Validate> validator(context, source, length, signature, functionIndexSpace, info);
+
if (!validator.parse()) {
// FIXME: add better location information here. see: https://bugs.webkit.org/show_bug.cgi?id=164288
// FIXME: We should never not have an error message if we return false.
diff --git a/Source/JavaScriptCore/wasm/WasmValidate.h b/Source/JavaScriptCore/wasm/WasmValidate.h
index 454f9f5..861fd72 100644
--- a/Source/JavaScriptCore/wasm/WasmValidate.h
+++ b/Source/JavaScriptCore/wasm/WasmValidate.h
@@ -31,7 +31,7 @@
namespace JSC { namespace Wasm {
-String validateFunction(const uint8_t*, size_t, const Signature*, const FunctionIndexSpace&, const MemoryInformation&);
+String validateFunction(const uint8_t*, size_t, const Signature*, const ImmutableFunctionIndexSpace&, const ModuleInformation&);
} } // namespace JSC::Wasm
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp
index de773b8..83a3973 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp
@@ -38,9 +38,9 @@
const ClassInfo JSWebAssemblyModule::s_info = { "WebAssembly.Module", &Base::s_info, nullptr, CREATE_METHOD_TABLE(JSWebAssemblyModule) };
-JSWebAssemblyModule* JSWebAssemblyModule::create(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>&& moduleInformation, Bag<CallLinkInfo>&& callLinkInfos, Vector<Wasm::WasmToJSStub>&& wasmToJSStubs, Wasm::FunctionIndexSpace&& functionIndexSpace, SymbolTable* exportSymbolTable, unsigned calleeCount)
+JSWebAssemblyModule* JSWebAssemblyModule::create(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>&& moduleInformation, Bag<CallLinkInfo>&& callLinkInfos, Vector<Wasm::WasmToJSStub>&& wasmToJSStubs, Wasm::ImmutableFunctionIndexSpace&& functionIndexSpace, SymbolTable* exportSymbolTable, unsigned calleeCount)
{
- auto* instance = new (NotNull, allocateCell<JSWebAssemblyModule>(vm.heap, allocationSize(calleeCount))) JSWebAssemblyModule(vm, structure, std::forward<std::unique_ptr<Wasm::ModuleInformation>>(moduleInformation), std::forward<Bag<CallLinkInfo>>(callLinkInfos), std::forward<Vector<Wasm::WasmToJSStub>>(wasmToJSStubs), std::forward<Wasm::FunctionIndexSpace>(functionIndexSpace), calleeCount);
+ auto* instance = new (NotNull, allocateCell<JSWebAssemblyModule>(vm.heap, allocationSize(calleeCount))) JSWebAssemblyModule(vm, structure, std::forward<std::unique_ptr<Wasm::ModuleInformation>>(moduleInformation), std::forward<Bag<CallLinkInfo>>(callLinkInfos), std::forward<Vector<Wasm::WasmToJSStub>>(wasmToJSStubs), std::forward<Wasm::ImmutableFunctionIndexSpace>(functionIndexSpace), calleeCount);
instance->finishCreation(vm, exportSymbolTable);
return instance;
}
@@ -50,7 +50,7 @@
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
}
-JSWebAssemblyModule::JSWebAssemblyModule(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>&& moduleInformation, Bag<CallLinkInfo>&& callLinkInfos, Vector<Wasm::WasmToJSStub>&& wasmToJSStubs, Wasm::FunctionIndexSpace&& functionIndexSpace, unsigned calleeCount)
+JSWebAssemblyModule::JSWebAssemblyModule(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>&& moduleInformation, Bag<CallLinkInfo>&& callLinkInfos, Vector<Wasm::WasmToJSStub>&& wasmToJSStubs, Wasm::ImmutableFunctionIndexSpace&& functionIndexSpace, unsigned calleeCount)
: Base(vm, structure)
, m_moduleInformation(WTFMove(moduleInformation))
, m_callLinkInfos(WTFMove(callLinkInfos))
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.h
index 05d2cf0..7be607a 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.h
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.h
@@ -42,14 +42,14 @@
public:
typedef JSDestructibleObject Base;
- static JSWebAssemblyModule* create(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&&, Bag<CallLinkInfo>&&, Vector<Wasm::WasmToJSStub>&&, Wasm::FunctionIndexSpace&&, SymbolTable*, unsigned);
+ static JSWebAssemblyModule* create(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&&, Bag<CallLinkInfo>&&, Vector<Wasm::WasmToJSStub>&&, Wasm::ImmutableFunctionIndexSpace&&, SymbolTable*, unsigned);
static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
DECLARE_INFO;
const Wasm::ModuleInformation& moduleInformation() const { return *m_moduleInformation.get(); }
SymbolTable* exportSymbolTable() const { return m_exportSymbolTable.get(); }
- Wasm::Signature* signatureForFunctionIndexSpace(unsigned functionIndexSpace) const { return m_functionIndexSpace.at(functionIndexSpace).signature; }
+ Wasm::Signature* signatureForFunctionIndexSpace(unsigned functionIndexSpace) const { ASSERT(functionIndexSpace < m_functionIndexSpace.size); return m_functionIndexSpace.buffer.get()[functionIndexSpace].signature; }
unsigned importCount() const { return m_wasmToJSStubs.size(); }
JSWebAssemblyCallee* calleeFromFunctionIndexSpace(unsigned functionIndexSpace)
@@ -65,8 +65,10 @@
return bitwise_cast<WriteBarrier<JSWebAssemblyCallee>*>(bitwise_cast<char*>(this) + offsetOfCallees());
}
+ static ptrdiff_t offsetOfFunctionIndexSpace() { return OBJECT_OFFSETOF(JSWebAssemblyModule, m_functionIndexSpace); }
+
protected:
- JSWebAssemblyModule(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&&, Bag<CallLinkInfo>&&, Vector<Wasm::WasmToJSStub>&&, Wasm::FunctionIndexSpace&&, unsigned calleeCount);
+ JSWebAssemblyModule(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&&, Bag<CallLinkInfo>&&, Vector<Wasm::WasmToJSStub>&&, Wasm::ImmutableFunctionIndexSpace&&, unsigned calleeCount);
void finishCreation(VM&, SymbolTable*);
static void destroy(JSCell*);
static void visitChildren(JSCell*, SlotVisitor&);
@@ -86,7 +88,7 @@
Bag<CallLinkInfo> m_callLinkInfos;
WriteBarrier<SymbolTable> m_exportSymbolTable;
Vector<Wasm::WasmToJSStub> m_wasmToJSStubs;
- Wasm::FunctionIndexSpace m_functionIndexSpace;
+ const Wasm::ImmutableFunctionIndexSpace m_functionIndexSpace;
unsigned m_calleeCount;
};
diff --git a/Source/JavaScriptCore/wasm/wasm.json b/Source/JavaScriptCore/wasm/wasm.json
index 4b8fb19..ec0efc5 100644
--- a/Source/JavaScriptCore/wasm/wasm.json
+++ b/Source/JavaScriptCore/wasm/wasm.json
@@ -64,7 +64,7 @@
"get_global": { "category": "special", "value": 35, "return": ["global"], "parameter": [], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "read a global variable" },
"set_global": { "category": "special", "value": 36, "return": [""], "parameter": ["global"], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "write a global variable" },
"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"}], "description": "call a function indirect with an expected signature" },
+ "call_indirect": { "category": "call", "value": 17, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "reserved", "type": "varuint1"}], "description": "call a function indirect with an expected signature" },
"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" },