blob: fdecae2531d1de6794bb41c690ba4cffc002d3e7 [file] [log] [blame]
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cinttypes>
#include "include/v8-wasm.h"
#include "src/base/memory.h"
#include "src/base/platform/mutex.h"
#include "src/builtins/builtins-inl.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/frames-inl.h"
#include "src/heap/heap-inl.h"
#include "src/objects/smi.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/memory-tracing.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-serialization.h"
namespace v8::internal {
namespace {
struct WasmCompileControls {
uint32_t MaxWasmBufferSize = std::numeric_limits<uint32_t>::max();
bool AllowAnySizeForAsync = true;
};
using WasmCompileControlsMap = std::map<v8::Isolate*, WasmCompileControls>;
// We need per-isolate controls, because we sometimes run tests in multiple
// isolates concurrently. Methods need to hold the accompanying mutex on access.
// To avoid upsetting the static initializer count, we lazy initialize this.
DEFINE_LAZY_LEAKY_OBJECT_GETTER(WasmCompileControlsMap,
GetPerIsolateWasmControls)
base::LazyMutex g_PerIsolateWasmControlsMutex = LAZY_MUTEX_INITIALIZER;
bool IsWasmCompileAllowed(v8::Isolate* isolate, v8::Local<v8::Value> value,
bool is_async) {
base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0);
const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate);
return (is_async && ctrls.AllowAnySizeForAsync) ||
(value->IsArrayBuffer() && value.As<v8::ArrayBuffer>()->ByteLength() <=
ctrls.MaxWasmBufferSize) ||
(value->IsArrayBufferView() &&
value.As<v8::ArrayBufferView>()->ByteLength() <=
ctrls.MaxWasmBufferSize);
}
// Use the compile controls for instantiation, too
bool IsWasmInstantiateAllowed(v8::Isolate* isolate,
v8::Local<v8::Value> module_or_bytes,
bool is_async) {
base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0);
const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate);
if (is_async && ctrls.AllowAnySizeForAsync) return true;
if (!module_or_bytes->IsWasmModuleObject()) {
return IsWasmCompileAllowed(isolate, module_or_bytes, is_async);
}
v8::Local<v8::WasmModuleObject> module =
v8::Local<v8::WasmModuleObject>::Cast(module_or_bytes);
return static_cast<uint32_t>(
module->GetCompiledModule().GetWireBytesRef().size()) <=
ctrls.MaxWasmBufferSize;
}
v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate,
const char* message) {
return v8::Exception::RangeError(
v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(message))
.ToLocalChecked());
}
void ThrowRangeException(v8::Isolate* isolate, const char* message) {
isolate->ThrowException(NewRangeException(isolate, message));
}
bool WasmModuleOverride(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
if (IsWasmCompileAllowed(info.GetIsolate(), info[0], false)) return false;
ThrowRangeException(info.GetIsolate(), "Sync compile not allowed");
return true;
}
bool WasmInstanceOverride(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
if (IsWasmInstantiateAllowed(info.GetIsolate(), info[0], false)) return false;
ThrowRangeException(info.GetIsolate(), "Sync instantiate not allowed");
return true;
}
} // namespace
// Returns a callable object. The object returns the difference of its two
// parameters when it is called.
RUNTIME_FUNCTION(Runtime_SetWasmCompileControls) {
HandleScope scope(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
CHECK_EQ(args.length(), 2);
int block_size = args.smi_value_at(0);
bool allow_async = Boolean::cast(args[1])->ToBool(isolate);
base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
WasmCompileControls& ctrl = (*GetPerIsolateWasmControls())[v8_isolate];
ctrl.AllowAnySizeForAsync = allow_async;
ctrl.MaxWasmBufferSize = static_cast<uint32_t>(block_size);
v8_isolate->SetWasmModuleCallback(WasmModuleOverride);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_SetWasmInstantiateControls) {
HandleScope scope(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
CHECK_EQ(args.length(), 0);
v8_isolate->SetWasmInstanceCallback(WasmInstanceOverride);
return ReadOnlyRoots(isolate).undefined_value();
}
namespace {
void PrintIndentation(int stack_size) {
const int max_display = 80;
if (stack_size <= max_display) {
PrintF("%4d:%*s", stack_size, stack_size, "");
} else {
PrintF("%4d:%*s", stack_size, max_display, "...");
}
}
int WasmStackSize(Isolate* isolate) {
// TODO(wasm): Fix this for mixed JS/Wasm stacks with both --trace and
// --trace-wasm.
int n = 0;
for (DebuggableStackFrameIterator it(isolate); !it.done(); it.Advance()) {
if (it.is_wasm()) n++;
}
return n;
}
} // namespace
RUNTIME_FUNCTION(Runtime_CountUnoptimizedWasmToJSWrapper) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
Tagged<WasmInstanceObject> instance_object =
Tagged<WasmInstanceObject>::cast(args[0]);
Tagged<WasmTrustedInstanceData> trusted_data =
instance_object->trusted_data(isolate);
Address wrapper_start = isolate->builtins()
->code(Builtin::kWasmToJsWrapperAsm)
->instruction_start();
int result = 0;
Tagged<WasmDispatchTable> dispatch_table =
trusted_data->dispatch_table_for_imports();
int import_count = dispatch_table->length();
for (int i = 0; i < import_count; ++i) {
if (dispatch_table->target(i) == wrapper_start) {
++result;
}
}
Tagged<ProtectedFixedArray> dispatch_tables = trusted_data->dispatch_tables();
int table_count = dispatch_tables->length();
for (int table_index = 0; table_index < table_count; ++table_index) {
if (dispatch_tables->get(table_index) == Smi::zero()) continue;
Tagged<WasmDispatchTable> table =
Tagged<WasmDispatchTable>::cast(dispatch_tables->get(table_index));
int table_size = table->length();
for (int entry_index = 0; entry_index < table_size; ++entry_index) {
if (table->target(entry_index) == wrapper_start) ++result;
}
}
return Smi::FromInt(result);
}
RUNTIME_FUNCTION(Runtime_HasUnoptimizedWasmToJSWrapper) {
SealHandleScope shs{isolate};
DCHECK_EQ(1, args.length());
Tagged<JSFunction> function = JSFunction::cast(args[0]);
Tagged<SharedFunctionInfo> sfi = function->shared();
Tagged<Object> func_data = sfi->GetData(isolate);
if (!IsWasmFunctionData(func_data)) return isolate->heap()->ToBoolean(false);
Address call_target =
WasmFunctionData::cast(func_data)->internal()->call_target();
Address wrapper = Builtins::EntryOf(Builtin::kWasmToJsWrapperAsm, isolate);
return isolate->heap()->ToBoolean(call_target == wrapper);
}
RUNTIME_FUNCTION(Runtime_HasUnoptimizedJSToJSWrapper) {
HandleScope shs(isolate);
DCHECK_EQ(1, args.length());
Handle<Object> param = args.at<Object>(0);
if (!WasmJSFunction::IsWasmJSFunction(*param)) {
return isolate->heap()->ToBoolean(false);
}
Handle<WasmJSFunction> wasm_js_function = Handle<WasmJSFunction>::cast(param);
Handle<WasmJSFunctionData> function_data =
handle(wasm_js_function->shared()->wasm_js_function_data(), isolate);
Handle<JSFunction> external_function =
WasmInternalFunction::GetOrCreateExternal(
handle(function_data->internal(), isolate));
Handle<Code> external_function_code =
handle(external_function->code(isolate), isolate);
Handle<Code> function_data_code =
handle(function_data->wrapper_code(isolate), isolate);
Tagged<Code> wrapper = isolate->builtins()->code(Builtin::kJSToJSWrapper);
// TODO(saelo): we have to use full pointer comparison here until all Code
// objects are located in trusted space. Currently, builtin Code objects are
// still inside the main pointer compression cage.
static_assert(!kAllCodeObjectsLiveInTrustedSpace);
if (!wrapper.SafeEquals(*external_function_code)) {
return isolate->heap()->ToBoolean(false);
}
if (wrapper != *function_data_code) {
return isolate->heap()->ToBoolean(false);
}
return isolate->heap()->ToBoolean(true);
}
RUNTIME_FUNCTION(Runtime_WasmTraceEnter) {
HandleScope shs(isolate);
DCHECK_EQ(0, args.length());
PrintIndentation(WasmStackSize(isolate));
// Find the caller wasm frame.
wasm::WasmCodeRefScope wasm_code_ref_scope;
DebuggableStackFrameIterator it(isolate);
DCHECK(!it.done());
DCHECK(it.is_wasm());
WasmFrame* frame = WasmFrame::cast(it.frame());
// Find the function name.
int func_index = frame->function_index();
const wasm::WasmModule* module = frame->trusted_instance_data()->module();
wasm::ModuleWireBytes wire_bytes =
wasm::ModuleWireBytes(frame->native_module()->wire_bytes());
wasm::WireBytesRef name_ref =
module->lazily_generated_names.LookupFunctionName(wire_bytes, func_index);
wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref);
wasm::WasmCode* code = frame->wasm_code();
PrintF(code->is_liftoff() ? "~" : "*");
if (name.empty()) {
PrintF("wasm-function[%d] {\n", func_index);
} else {
PrintF("wasm-function[%d] \"%.*s\" {\n", func_index, name.length(),
name.begin());
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTraceExit) {
HandleScope shs(isolate);
DCHECK_EQ(1, args.length());
Tagged<Smi> return_addr_smi = Smi::cast(args[0]);
PrintIndentation(WasmStackSize(isolate));
PrintF("}");
// Find the caller wasm frame.
wasm::WasmCodeRefScope wasm_code_ref_scope;
DebuggableStackFrameIterator it(isolate);
DCHECK(!it.done());
DCHECK(it.is_wasm());
WasmFrame* frame = WasmFrame::cast(it.frame());
int func_index = frame->function_index();
const wasm::WasmModule* module = frame->trusted_instance_data()->module();
const wasm::FunctionSig* sig = module->functions[func_index].sig;
size_t num_returns = sig->return_count();
// If we have no returns, we should have passed {Smi::zero()}.
DCHECK_IMPLIES(num_returns == 0, IsZero(return_addr_smi));
if (num_returns == 1) {
wasm::ValueType return_type = sig->GetReturn(0);
switch (return_type.kind()) {
case wasm::kI32: {
int32_t value =
base::ReadUnalignedValue<int32_t>(return_addr_smi.ptr());
PrintF(" -> %d\n", value);
break;
}
case wasm::kI64: {
int64_t value =
base::ReadUnalignedValue<int64_t>(return_addr_smi.ptr());
PrintF(" -> %" PRId64 "\n", value);
break;
}
case wasm::kF32: {
float value = base::ReadUnalignedValue<float>(return_addr_smi.ptr());
PrintF(" -> %f\n", value);
break;
}
case wasm::kF64: {
double value = base::ReadUnalignedValue<double>(return_addr_smi.ptr());
PrintF(" -> %f\n", value);
break;
}
default:
PrintF(" -> Unsupported type\n");
break;
}
} else {
// TODO(wasm) Handle multiple return values.
PrintF("\n");
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_IsAsmWasmCode) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
auto function = JSFunction::cast(args[0]);
if (!function->shared()->HasAsmWasmData()) {
return ReadOnlyRoots(isolate).false_value();
}
if (function->shared()->HasBuiltinId() &&
function->shared()->builtin_id() == Builtin::kInstantiateAsmJs) {
// Hasn't been compiled yet.
return ReadOnlyRoots(isolate).false_value();
}
return ReadOnlyRoots(isolate).true_value();
}
namespace {
bool DisallowWasmCodegenFromStringsCallback(v8::Local<v8::Context> context,
v8::Local<v8::String> source) {
return false;
}
} // namespace
RUNTIME_FUNCTION(Runtime_DisallowWasmCodegen) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
bool flag = Boolean::cast(args[0])->ToBool(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8_isolate->SetAllowWasmCodeGenerationCallback(
flag ? DisallowWasmCodegenFromStringsCallback : nullptr);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_IsWasmCode) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
auto function = JSFunction::cast(args[0]);
Tagged<Code> code = function->code(isolate);
bool is_js_to_wasm = code->kind() == CodeKind::JS_TO_WASM_FUNCTION ||
(code->builtin_id() == Builtin::kJSToWasmWrapper);
return isolate->heap()->ToBoolean(is_js_to_wasm);
}
RUNTIME_FUNCTION(Runtime_IsWasmTrapHandlerEnabled) {
DisallowGarbageCollection no_gc;
DCHECK_EQ(0, args.length());
return isolate->heap()->ToBoolean(trap_handler::IsTrapHandlerEnabled());
}
RUNTIME_FUNCTION(Runtime_IsWasmPartialOOBWriteNoop) {
DisallowGarbageCollection no_gc;
DCHECK_EQ(0, args.length());
return isolate->heap()->ToBoolean(wasm::kPartialOOBWritesAreNoops);
}
RUNTIME_FUNCTION(Runtime_IsThreadInWasm) {
DisallowGarbageCollection no_gc;
DCHECK_EQ(0, args.length());
return isolate->heap()->ToBoolean(trap_handler::IsThreadInWasm());
}
RUNTIME_FUNCTION(Runtime_GetWasmRecoveredTrapCount) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
size_t trap_count = trap_handler::GetRecoveredTrapCount();
return *isolate->factory()->NewNumberFromSize(trap_count);
}
RUNTIME_FUNCTION(Runtime_GetWasmExceptionTagId) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<WasmExceptionPackage> exception = args.at<WasmExceptionPackage>(0);
Handle<WasmInstanceObject> instance_object = args.at<WasmInstanceObject>(1);
Handle<WasmTrustedInstanceData> trusted_data =
handle(instance_object->trusted_data(isolate), isolate);
Handle<Object> tag =
WasmExceptionPackage::GetExceptionTag(isolate, exception);
CHECK(IsWasmExceptionTag(*tag));
Handle<FixedArray> tags_table(trusted_data->tags_table(), isolate);
for (int index = 0; index < tags_table->length(); ++index) {
if (tags_table->get(index) == *tag) return Smi::FromInt(index);
}
UNREACHABLE();
}
RUNTIME_FUNCTION(Runtime_GetWasmExceptionValues) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<WasmExceptionPackage> exception = args.at<WasmExceptionPackage>(0);
Handle<Object> values_obj =
WasmExceptionPackage::GetExceptionValues(isolate, exception);
CHECK(IsFixedArray(*values_obj)); // Only called with correct input.
Handle<FixedArray> values = Handle<FixedArray>::cast(values_obj);
Handle<FixedArray> externalized_values =
isolate->factory()->NewFixedArray(values->length());
for (int i = 0; i < values->length(); i++) {
Handle<Object> value = handle(values->get(i), isolate);
if (!IsSmi(*value)) {
// Note: This will leak string views to JS. This should be fine for a
// debugging function.
value = wasm::WasmToJSObject(isolate, value);
}
externalized_values->set(i, *value);
}
return *isolate->factory()->NewJSArrayWithElements(externalized_values);
}
RUNTIME_FUNCTION(Runtime_SerializeWasmModule) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<WasmModuleObject> module_obj = args.at<WasmModuleObject>(0);
wasm::NativeModule* native_module = module_obj->native_module();
DCHECK(!native_module->compilation_state()->failed());
wasm::WasmSerializer wasm_serializer(native_module);
size_t byte_length = wasm_serializer.GetSerializedNativeModuleSize();
Handle<JSArrayBuffer> array_buffer =
isolate->factory()
->NewJSArrayBufferAndBackingStore(byte_length,
InitializedFlag::kUninitialized)
.ToHandleChecked();
CHECK(wasm_serializer.SerializeNativeModule(
{static_cast<uint8_t*>(array_buffer->backing_store()), byte_length}));
return *array_buffer;
}
// Take an array buffer and attempt to reconstruct a compiled wasm module.
// Return undefined if unsuccessful.
RUNTIME_FUNCTION(Runtime_DeserializeWasmModule) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<JSArrayBuffer> buffer = args.at<JSArrayBuffer>(0);
Handle<JSTypedArray> wire_bytes = args.at<JSTypedArray>(1);
CHECK(!buffer->was_detached());
CHECK(!wire_bytes->WasDetached());
Handle<JSArrayBuffer> wire_bytes_buffer = wire_bytes->GetBuffer();
base::Vector<const uint8_t> wire_bytes_vec{
reinterpret_cast<const uint8_t*>(wire_bytes_buffer->backing_store()) +
wire_bytes->byte_offset(),
wire_bytes->byte_length()};
base::Vector<uint8_t> buffer_vec{
reinterpret_cast<uint8_t*>(buffer->backing_store()),
buffer->byte_length()};
// Note that {wasm::DeserializeNativeModule} will allocate. We assume the
// JSArrayBuffer backing store doesn't get relocated.
MaybeHandle<WasmModuleObject> maybe_module_object =
wasm::DeserializeNativeModule(isolate, buffer_vec, wire_bytes_vec,
wasm::CompileTimeImports{}, {});
Handle<WasmModuleObject> module_object;
if (!maybe_module_object.ToHandle(&module_object)) {
return ReadOnlyRoots(isolate).undefined_value();
}
return *module_object;
}
RUNTIME_FUNCTION(Runtime_WasmGetNumberOfInstances) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
Handle<WasmModuleObject> module_obj = args.at<WasmModuleObject>(0);
int instance_count = 0;
Tagged<WeakArrayList> weak_instance_list =
module_obj->script()->wasm_weak_instance_list();
for (int i = 0; i < weak_instance_list->length(); ++i) {
if (weak_instance_list->Get(i).IsWeak()) instance_count++;
}
return Smi::FromInt(instance_count);
}
RUNTIME_FUNCTION(Runtime_WasmNumCodeSpaces) {
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<JSObject> argument = args.at<JSObject>(0);
wasm::NativeModule* native_module;
if (IsWasmInstanceObject(*argument)) {
native_module = WasmInstanceObject::cast(*argument)
->trusted_data(isolate)
->native_module();
} else if (IsWasmModuleObject(*argument)) {
native_module = WasmModuleObject::cast(*argument)->native_module();
} else {
UNREACHABLE();
}
size_t num_spaces = native_module->GetNumberOfCodeSpacesForTesting();
return *isolate->factory()->NewNumberFromSize(num_spaces);
}
RUNTIME_FUNCTION(Runtime_WasmTraceMemory) {
SealHandleScope scope(isolate);
DisallowGarbageCollection no_gc;
DCHECK_EQ(1, args.length());
auto info_addr = Smi::cast(args[0]);
wasm::MemoryTracingInfo* info =
reinterpret_cast<wasm::MemoryTracingInfo*>(info_addr.ptr());
// Find the caller wasm frame.
wasm::WasmCodeRefScope wasm_code_ref_scope;
DebuggableStackFrameIterator it(isolate);
DCHECK(!it.done());
DCHECK(it.is_wasm());
WasmFrame* frame = WasmFrame::cast(it.frame());
// TODO(14259): Fix for multi-memory.
auto memory_object = frame->trusted_instance_data()->memory_object(0);
uint8_t* mem_start = reinterpret_cast<uint8_t*>(
memory_object->array_buffer()->backing_store());
int func_index = frame->function_index();
int pos = frame->position();
wasm::ExecutionTier tier = frame->wasm_code()->is_liftoff()
? wasm::ExecutionTier::kLiftoff
: wasm::ExecutionTier::kTurbofan;
wasm::TraceMemoryOperation(tier, info, func_index, pos, mem_start);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTierUpFunction) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSFunction> function = args.at<JSFunction>(0);
CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
Handle<WasmExportedFunction> exp_fun =
Handle<WasmExportedFunction>::cast(function);
Tagged<WasmTrustedInstanceData> trusted_data = exp_fun->instance_data();
int func_index = exp_fun->function_index();
wasm::TierUpNowForTesting(isolate, trusted_data, func_index);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmNull) {
HandleScope scope(isolate);
return ReadOnlyRoots(isolate).wasm_null();
}
RUNTIME_FUNCTION(Runtime_WasmEnterDebugging) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
wasm::GetWasmEngine()->EnterDebuggingForIsolate(isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmLeaveDebugging) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
wasm::GetWasmEngine()->LeaveDebuggingForIsolate(isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_IsWasmDebugFunction) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSFunction> function = args.at<JSFunction>(0);
CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
Handle<WasmExportedFunction> exp_fun =
Handle<WasmExportedFunction>::cast(function);
wasm::NativeModule* native_module = exp_fun->instance_data()->native_module();
uint32_t func_index = exp_fun->function_index();
wasm::WasmCodeRefScope code_ref_scope;
wasm::WasmCode* code = native_module->GetCode(func_index);
return isolate->heap()->ToBoolean(code && code->is_liftoff() &&
code->for_debugging());
}
RUNTIME_FUNCTION(Runtime_IsLiftoffFunction) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSFunction> function = args.at<JSFunction>(0);
CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
Handle<WasmExportedFunction> exp_fun =
Handle<WasmExportedFunction>::cast(function);
wasm::NativeModule* native_module = exp_fun->instance_data()->native_module();
uint32_t func_index = exp_fun->function_index();
wasm::WasmCodeRefScope code_ref_scope;
wasm::WasmCode* code = native_module->GetCode(func_index);
return isolate->heap()->ToBoolean(code && code->is_liftoff());
}
RUNTIME_FUNCTION(Runtime_IsTurboFanFunction) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSFunction> function = args.at<JSFunction>(0);
CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
Handle<WasmExportedFunction> exp_fun =
Handle<WasmExportedFunction>::cast(function);
wasm::NativeModule* native_module = exp_fun->instance_data()->native_module();
uint32_t func_index = exp_fun->function_index();
wasm::WasmCodeRefScope code_ref_scope;
wasm::WasmCode* code = native_module->GetCode(func_index);
return isolate->heap()->ToBoolean(code && code->is_turbofan());
}
RUNTIME_FUNCTION(Runtime_IsUncompiledWasmFunction) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSFunction> function = args.at<JSFunction>(0);
CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
Handle<WasmExportedFunction> exp_fun =
Handle<WasmExportedFunction>::cast(function);
wasm::NativeModule* native_module = exp_fun->instance_data()->native_module();
uint32_t func_index = exp_fun->function_index();
return isolate->heap()->ToBoolean(!native_module->HasCode(func_index));
}
RUNTIME_FUNCTION(Runtime_FreezeWasmLazyCompilation) {
DCHECK_EQ(1, args.length());
DisallowGarbageCollection no_gc;
auto instance_object = WasmInstanceObject::cast(args[0]);
instance_object->module_object()->native_module()->set_lazy_compile_frozen(
true);
return ReadOnlyRoots(isolate).undefined_value();
}
// This runtime function enables WebAssembly imported strings through an
// embedder callback and thereby bypasses the value in v8_flags.
RUNTIME_FUNCTION(Runtime_SetWasmImportedStringsEnabled) {
DCHECK_EQ(1, args.length());
bool enable = Object::BooleanValue(*args.at(0), isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
WasmImportedStringsEnabledCallback enabled = [](v8::Local<v8::Context>) {
return true;
};
WasmImportedStringsEnabledCallback disabled = [](v8::Local<v8::Context>) {
return false;
};
v8_isolate->SetWasmImportedStringsEnabledCallback(enable ? enabled
: disabled);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_FlushWasmCode) {
wasm::GetWasmEngine()->FlushCode();
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmCompiledExportWrappersCount) {
int count = isolate->counters()
->wasm_compiled_export_wrapper()
->GetInternalPointer()
->load();
return Smi::FromInt(count);
}
RUNTIME_FUNCTION(Runtime_WasmSwitchToTheCentralStackCount) {
int count = isolate->wasm_switch_to_the_central_stack_counter();
return Smi::FromInt(count);
}
RUNTIME_FUNCTION(Runtime_CheckIsOnCentralStack) {
// This function verifies that itself, and therefore the JS function that
// called it, is running on the central stack. This is used to check that wasm
// switches to the central stack to run JS imports.
CHECK(isolate->IsOnCentralStack());
return ReadOnlyRoots(isolate).undefined_value();
}
// The GenerateRandomWasmModule function is only implemented in non-official
// builds (to save binary size). Hence also skip the runtime function in
// official builds.
#ifndef OFFICIAL_BUILD
RUNTIME_FUNCTION(Runtime_WasmGenerateRandomModule) {
HandleScope scope{isolate};
Zone temporary_zone{isolate->allocator(), "WasmGenerateRandomModule"};
constexpr size_t kMaxInputBytes = 512;
ZoneVector<uint8_t> input_bytes{&temporary_zone};
auto add_input_bytes = [&input_bytes](void* bytes, size_t max_bytes) {
size_t num_bytes = std::min(kMaxInputBytes - input_bytes.size(), max_bytes);
input_bytes.resize(input_bytes.size() + num_bytes);
memcpy(input_bytes.end() - num_bytes, bytes, num_bytes);
};
if (args.length() == 0) {
// If we are called without any arguments, use the RNG from the isolate to
// generate between 1 and kMaxInputBytes random bytes.
int num_bytes =
1 + isolate->random_number_generator()->NextInt(kMaxInputBytes);
input_bytes.resize(num_bytes);
isolate->random_number_generator()->NextBytes(input_bytes.data(),
num_bytes);
} else {
for (int i = 0; i < args.length(); ++i) {
if (IsJSTypedArray(args[i])) {
Tagged<JSTypedArray> typed_array = JSTypedArray::cast(args[i]);
add_input_bytes(typed_array->DataPtr(), typed_array->GetByteLength());
} else if (IsJSArrayBuffer(args[i])) {
Tagged<JSArrayBuffer> array_buffer = JSArrayBuffer::cast(args[i]);
add_input_bytes(array_buffer->backing_store(),
array_buffer->GetByteLength());
} else if (IsSmi(args[i])) {
int smi_value = Smi::cast(args[i]).value();
add_input_bytes(&smi_value, kIntSize);
} else if (IsHeapNumber(args[i])) {
double value = HeapNumber::cast(args[i])->value();
add_input_bytes(&value, kDoubleSize);
} else {
// TODO(14637): Extract bytes from more types.
}
}
}
// Don't limit any expressions in the generated Wasm module.
constexpr auto options =
wasm::fuzzing::WasmModuleGenerationOptions::kGenerateAll;
base::Vector<const uint8_t> module_bytes =
wasm::fuzzing::GenerateRandomWasmModule<options>(
&temporary_zone, base::VectorOf(input_bytes));
if (module_bytes.empty()) return ReadOnlyRoots(isolate).undefined_value();
wasm::ErrorThrower thrower{isolate, "WasmGenerateRandomModule"};
MaybeHandle<WasmModuleObject> maybe_module_object =
wasm::GetWasmEngine()->SyncCompile(
isolate, wasm::WasmFeatures::FromFlags(), wasm::CompileTimeImports{},
&thrower, wasm::ModuleWireBytes{module_bytes});
if (thrower.error()) {
FATAL(
"wasm::GenerateRandomWasmModule produced a module which did not "
"compile: %s",
thrower.error_msg());
}
return *maybe_module_object.ToHandleChecked();
}
#endif // OFFICIAL_BUILD
} // namespace v8::internal