Commit d89d185f authored by snek's avatar snek Committed by V8 LUCI CQ

[fastcall] expose wasm memory to cfunction

Load current Memory start/size off of the wasm instance when entering
fast calls, so they can use that info for whatever they need to do.
Fast calls from JS set the memory to null, and the memory does not
need to be piped from wasm to slow callbacks as wasm always calls
the fast function.

Change-Id: Ibfa33cdd7dba85300f95cbdacc9a56b3f7181663
Bug: chromium:1052746
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3719005Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: snek <snek@chromium.org>
Reviewed-by: 's avatarManos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81538}
parent 6151ae01
......@@ -544,7 +544,7 @@ struct FastApiCallbackOptions {
* returned instance may be filled with mock data.
*/
static FastApiCallbackOptions CreateForTesting(Isolate* isolate) {
return {false, {0}};
return {false, {0}, nullptr};
}
/**
......@@ -568,6 +568,11 @@ struct FastApiCallbackOptions {
uintptr_t data_ptr;
v8::Value data;
};
/**
* When called from WebAssembly, a view of the calling module's memory.
*/
FastApiTypedArray<uint8_t>* const wasm_memory;
};
namespace internal {
......
......@@ -5205,6 +5205,15 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
CheckForMinusZeroMode::kCheckForMinusZero);
}
},
// Initialize js-specific callback options.
[this](Node* options_stack_slot) {
__ Store(
StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
options_stack_slot,
static_cast<int>(offsetof(v8::FastApiCallbackOptions, wasm_memory)),
__ IntPtrConstant(0));
},
// Generate slow fallback if fast call fails
[this, node]() -> Node* { return GenerateSlowApiCall(node); });
}
......
......@@ -125,12 +125,14 @@ class FastApiCallBuilder {
GraphAssembler* graph_assembler,
const GetParameter& get_parameter,
const ConvertReturnValue& convert_return_value,
const InitializeOptions& initialize_options,
const GenerateSlowApiCall& generate_slow_api_call)
: isolate_(isolate),
graph_(graph),
graph_assembler_(graph_assembler),
get_parameter_(get_parameter),
convert_return_value_(convert_return_value),
initialize_options_(initialize_options),
generate_slow_api_call_(generate_slow_api_call) {}
Node* Build(const FastApiCallFunctionVector& c_functions,
......@@ -150,6 +152,7 @@ class FastApiCallBuilder {
GraphAssembler* graph_assembler_;
const GetParameter& get_parameter_;
const ConvertReturnValue& convert_return_value_;
const InitializeOptions& initialize_options_;
const GenerateSlowApiCall& generate_slow_api_call_;
};
......@@ -291,12 +294,12 @@ Node* FastApiCallBuilder::Build(const FastApiCallFunctionVector& c_functions,
Node* stack_slot = nullptr;
if (c_signature->HasOptions()) {
int kAlign = alignof(v8::FastApiCallbackOptions);
int kSize = sizeof(v8::FastApiCallbackOptions);
const int kAlign = alignof(v8::FastApiCallbackOptions);
const int kSize = sizeof(v8::FastApiCallbackOptions);
// If this check fails, you've probably added new fields to
// v8::FastApiCallbackOptions, which means you'll need to write code
// that initializes and reads from them too.
CHECK_EQ(kSize, sizeof(uintptr_t) * 2);
static_assert(kSize == sizeof(uintptr_t) * 3);
stack_slot = __ StackSlot(kSize, kAlign);
__ Store(
......@@ -310,6 +313,8 @@ Node* FastApiCallBuilder::Build(const FastApiCallFunctionVector& c_functions,
static_cast<int>(offsetof(v8::FastApiCallbackOptions, data)),
data_argument);
initialize_options_(stack_slot);
builder.AddParam(MachineType::Pointer()); // stack_slot
}
......@@ -366,9 +371,11 @@ Node* BuildFastApiCall(Isolate* isolate, Graph* graph,
const CFunctionInfo* c_signature, Node* data_argument,
const GetParameter& get_parameter,
const ConvertReturnValue& convert_return_value,
const InitializeOptions& initialize_options,
const GenerateSlowApiCall& generate_slow_api_call) {
FastApiCallBuilder builder(isolate, graph, graph_assembler, get_parameter,
convert_return_value, generate_slow_api_call);
convert_return_value, initialize_options,
generate_slow_api_call);
return builder.Build(c_functions, c_signature, data_argument);
}
......
......@@ -49,6 +49,7 @@ bool CanOptimizeFastSignature(const CFunctionInfo* c_signature);
using GetParameter = std::function<Node*(int, OverloadsResolutionResult&,
GraphAssemblerLabel<0>*)>;
using ConvertReturnValue = std::function<Node*(const CFunctionInfo*, Node*)>;
using InitializeOptions = std::function<void(Node*)>;
using GenerateSlowApiCall = std::function<Node*()>;
Node* BuildFastApiCall(Isolate* isolate, Graph* graph,
......@@ -57,6 +58,7 @@ Node* BuildFastApiCall(Isolate* isolate, Graph* graph,
const CFunctionInfo* c_signature, Node* data_argument,
const GetParameter& get_parameter,
const ConvertReturnValue& convert_return_value,
const InitializeOptions& initialize_options,
const GenerateSlowApiCall& generate_slow_api_call);
} // namespace fast_api_call
......
......@@ -181,9 +181,9 @@ void WasmGraphBuilder::Start(unsigned params) {
Param(Linkage::kJSCallClosureParamIndex, "%closure")));
break;
case kWasmApiFunctionRefMode:
// We need an instance node anyway, because FromJS() needs to pass it to
// the WasmIsValidRefValue runtime function.
instance_node_ = UndefinedValue();
instance_node_ = gasm_->Load(
MachineType::TaggedPointer(), Param(0),
wasm::ObjectAccess::ToTagged(WasmApiFunctionRef::kInstanceOffset));
break;
}
graph()->SetEnd(graph()->NewNode(mcgraph()->common()->End(0)));
......@@ -7269,6 +7269,38 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
[](const CFunctionInfo* signature, Node* c_return_value) {
return c_return_value;
},
// Initialize wasm-specific callback options fields
[this](Node* options_stack_slot) {
#ifdef V8_SANDBOXED_POINTERS
Node* mem_start = LOAD_INSTANCE_FIELD_NO_ELIMINATION(
MemoryStart, MachineType::SandboxedPointer());
#else
Node* mem_start = LOAD_INSTANCE_FIELD_NO_ELIMINATION(
MemoryStart, MachineType::UintPtr());
#endif
Node* mem_size = LOAD_INSTANCE_FIELD_NO_ELIMINATION(
MemorySize, MachineType::UintPtr());
constexpr int kSize = sizeof(FastApiTypedArray<uint8_t>);
constexpr int kAlign = alignof(FastApiTypedArray<uint8_t>);
Node* stack_slot = gasm_->StackSlot(kSize, kAlign);
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
stack_slot, 0, mem_size);
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
stack_slot, sizeof(size_t), mem_start);
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
options_stack_slot,
static_cast<int>(
offsetof(v8::FastApiCallbackOptions, wasm_memory)),
stack_slot);
},
// Generate fallback slow call if fast call fails
[this, callable_node, native_context, receiver_node]() -> Node* {
int wasm_count = static_cast<int>(sig_->parameter_count());
......
......@@ -187,6 +187,8 @@ class FastCApiObject {
CHECK_SELF_OR_FALLBACK(0);
self->fast_call_count_++;
CHECK_NULL(options.wasm_memory);
if (should_fallback) {
options.fallback = true;
return 0;
......@@ -753,6 +755,30 @@ class FastCApiObject {
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
static bool TestWasmMemoryFastCallback(Local<Object> receiver,
uint32_t address,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(false);
self->fast_call_count_++;
CHECK_NOT_NULL(options.wasm_memory);
uint8_t* memory = nullptr;
CHECK(options.wasm_memory->getStorageIfAligned(&memory));
memory[address] = 42;
return true;
}
static void TestWasmMemorySlowCallback(
const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
args.GetIsolate()->ThrowError("should be unreachable from wasm");
}
static void FastCallCount(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
......@@ -1083,6 +1109,15 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
Local<Value>(), signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &is_valid_api_object_c_func));
CFunction test_wasm_memory_c_func =
CFunction::Make(FastCApiObject::TestWasmMemoryFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "test_wasm_memory",
FunctionTemplate::New(
isolate, FastCApiObject::TestWasmMemorySlowCallback, Local<Value>(),
Local<Signature>(), 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &test_wasm_memory_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "fast_call_count",
FunctionTemplate::New(
......
......@@ -1591,7 +1591,8 @@ Handle<WasmTypeInfo> Factory::NewWasmTypeInfo(
}
Handle<WasmApiFunctionRef> Factory::NewWasmApiFunctionRef(
Handle<JSReceiver> callable, Handle<HeapObject> suspender) {
Handle<JSReceiver> callable, Handle<HeapObject> suspender,
Handle<WasmInstanceObject> instance) {
Map map = *wasm_api_function_ref_map();
auto result = WasmApiFunctionRef::cast(AllocateRawWithImmortalMap(
map.instance_size(), AllocationType::kOld, map));
......@@ -1608,6 +1609,11 @@ Handle<WasmApiFunctionRef> Factory::NewWasmApiFunctionRef(
} else {
result.set_suspender(*undefined_value());
}
if (!instance.is_null()) {
result.set_instance(*instance);
} else {
result.set_instance(*undefined_value());
}
return handle(result, isolate());
}
......@@ -1630,7 +1636,8 @@ Handle<WasmJSFunctionData> Factory::NewWasmJSFunctionData(
Address opt_call_target, Handle<JSReceiver> callable, int return_count,
int parameter_count, Handle<PodArray<wasm::ValueType>> serialized_sig,
Handle<CodeT> wrapper_code, Handle<Map> rtt, Handle<HeapObject> suspender) {
Handle<WasmApiFunctionRef> ref = NewWasmApiFunctionRef(callable, suspender);
Handle<WasmApiFunctionRef> ref =
NewWasmApiFunctionRef(callable, suspender, Handle<WasmInstanceObject>());
Handle<WasmInternalFunction> internal =
NewWasmInternalFunction(opt_call_target, ref, rtt);
Map map = *wasm_js_function_data_map();
......@@ -1691,8 +1698,8 @@ Handle<WasmCapiFunctionData> Factory::NewWasmCapiFunctionData(
Address call_target, Handle<Foreign> embedder_data,
Handle<CodeT> wrapper_code, Handle<Map> rtt,
Handle<PodArray<wasm::ValueType>> serialized_sig) {
Handle<WasmApiFunctionRef> ref =
NewWasmApiFunctionRef(Handle<JSReceiver>(), Handle<HeapObject>());
Handle<WasmApiFunctionRef> ref = NewWasmApiFunctionRef(
Handle<JSReceiver>(), Handle<HeapObject>(), Handle<WasmInstanceObject>());
Handle<WasmInternalFunction> internal =
NewWasmInternalFunction(call_target, ref, rtt);
Map map = *wasm_capi_function_data_map();
......
......@@ -636,7 +636,8 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Address call_target, Handle<Object> ref, int func_index,
Address sig_address, int wrapper_budget, Handle<Map> rtt);
Handle<WasmApiFunctionRef> NewWasmApiFunctionRef(
Handle<JSReceiver> callable, Handle<HeapObject> suspender);
Handle<JSReceiver> callable, Handle<HeapObject> suspender,
Handle<WasmInstanceObject> instance);
// {opt_call_target} is kNullAddress for JavaScript functions, and
// non-null for exported Wasm functions.
Handle<WasmJSFunctionData> NewWasmJSFunctionData(
......
......@@ -1101,7 +1101,7 @@ void ImportedFunctionEntry::SetWasmToJs(
DCHECK(wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToJsWrapper ||
wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToCapiWrapper);
Handle<WasmApiFunctionRef> ref =
isolate->factory()->NewWasmApiFunctionRef(callable, suspender);
isolate->factory()->NewWasmApiFunctionRef(callable, suspender, instance_);
instance_->imported_function_refs().set(index_, *ref);
instance_->imported_function_targets()[index_] =
wasm_to_js_wrapper->instruction_start();
......@@ -1491,7 +1491,7 @@ void WasmInstanceObject::ImportWasmJSFunctionIntoTable(
// Update the dispatch table.
Handle<HeapObject> suspender = handle(js_function->GetSuspender(), isolate);
Handle<WasmApiFunctionRef> ref =
isolate->factory()->NewWasmApiFunctionRef(callable, suspender);
isolate->factory()->NewWasmApiFunctionRef(callable, suspender, instance);
WasmIndirectFunctionTable::cast(
instance->indirect_function_tables().get(table_index))
.Set(entry_index, sig_id, call_target, *ref);
......
......@@ -22,6 +22,9 @@ extern class WasmApiFunctionRef extends HeapObject {
native_context: NativeContext;
callable: JSReceiver|Undefined;
suspender: WasmSuspenderObject|Undefined;
// Present when compiling JSFastApiCall wrappers, needed
// to load memory start/size fields.
instance: WasmInstanceObject|Undefined;
}
// This is the representation that is used internally by wasm to represent
......
......@@ -27732,7 +27732,8 @@ struct BasicApiChecker {
}
static Ret FastCallbackNoFallback(v8::Local<v8::Object> receiver,
Value argument) {
v8::FastApiCallbackOptions options = {false, {0}};
v8::FastApiCallbackOptions options =
v8::FastApiCallbackOptions::CreateForTesting(v8::Isolate::GetCurrent());
return Impl::FastCallback(receiver, argument, options);
}
......@@ -27767,7 +27768,8 @@ template <typename Value, typename Impl, typename Ret,
typename = std::enable_if_t<!std::is_void<Ret>::value>>
static v8::AnyCType FastCallbackNoFallbackWrapper(v8::AnyCType receiver,
v8::AnyCType argument) {
v8::FastApiCallbackOptions options = {false, {0}};
v8::FastApiCallbackOptions options =
v8::FastApiCallbackOptions::CreateForTesting(v8::Isolate::GetCurrent());
v8::AnyCType ret = PrimitiveToMixedType<Ret>(Impl::FastCallback(
receiver.object_value, PrimitiveFromMixedType<Value>(argument), options));
return ret;
......@@ -27784,7 +27786,8 @@ template <typename Value, typename Impl, typename Ret,
typename = std::enable_if_t<std::is_void<Ret>::value>>
static void FastCallbackNoFallbackWrapper(v8::AnyCType receiver,
v8::AnyCType argument) {
v8::FastApiCallbackOptions options = {false, {0}};
v8::FastApiCallbackOptions options =
v8::FastApiCallbackOptions::CreateForTesting(v8::Isolate::GetCurrent());
return Impl::FastCallback(receiver.object_value,
PrimitiveFromMixedType<Value>(argument), options);
}
......@@ -43,13 +43,20 @@ function buildWasm(name, sig, body) {
[kWasmI32],
),
);
const test_wasm_memory = builder.addImport(
'fast_c_api',
'test_wasm_memory',
makeSig([kWasmI32], [kWasmI32]),
);
builder
.addMemory(1, 1)
.addFunction(name, sig)
.addBody(body({
add_all_no_options,
add_all_no_options_mismatch,
add_all_nested_bound,
overloaded_add_all_32bit_int,
test_wasm_memory,
}))
.exportFunc();
const x = {};
......@@ -61,6 +68,7 @@ function buildWasm(name, sig, body) {
.bind(fast_c_api)
.bind(x),
overloaded_add_all_32bit_int: fast_c_api.overloaded_add_all_32bit_int_no_sig.bind(fast_c_api),
test_wasm_memory: fast_c_api.test_wasm_memory.bind(fast_c_api),
},
});
return module.exports[name];
......@@ -179,3 +187,21 @@ fast_c_api.reset_counts();
assertEquals(overload_result, overloaded_add_all_32bit_int_wasm(true));
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
// ------------- Test test_wasm_memory ---------------
const test_wasm_memory_wasm = buildWasm(
'test_wasm_memory_wasm', makeSig([], [kWasmI32]),
({ test_wasm_memory }) => [
...wasmI32Const(12),
kExprCallFunction, test_wasm_memory,
kExprDrop,
...wasmI32Const(12),
kExprI32LoadMem8U, 0, 0,
],
);
// Test hits fast path.
fast_c_api.reset_counts();
assertEquals(42, test_wasm_memory_wasm())
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment