blob: 5be34d63fce4c5b3b68b23948d73d959dad3f0cf [file] [log] [blame]
// Copyright 2016 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 "src/builtins/builtins-inl.h"
#include "src/builtins/data-view-ops.h"
#include "src/common/assert-scope.h"
#include "src/common/message-template.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/debug.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/frames.h"
#include "src/heap/factory.h"
#include "src/numbers/conversions.h"
#include "src/objects/objects-inl.h"
#include "src/strings/unicode-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/serialized-signature-inl.h"
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/wasm/wasm-value.h"
namespace v8 {
namespace internal {
// TODO(13036): See if we can find a way to have the stack walker visit
// tagged values being passed from Wasm to runtime functions. In the meantime,
// disallow access to safe-looking-but-actually-unsafe stack-backed handles
// and thereby force manual creation of safe handles (backed by HandleScope).
class RuntimeArgumentsWithoutHandles : public RuntimeArguments {
public:
RuntimeArgumentsWithoutHandles(int length, Address* arguments)
: RuntimeArguments(length, arguments) {}
private:
// Disallowing the superclass method.
template <class S = Object>
V8_INLINE Handle<S> at(int index) const;
};
#define RuntimeArguments RuntimeArgumentsWithoutHandles
// (End of TODO(13036)-related hackery.)
namespace {
template <typename FrameType>
class FrameFinder {
public:
explicit FrameFinder(Isolate* isolate,
std::initializer_list<StackFrame::Type>
skipped_frame_types = {StackFrame::EXIT})
: frame_iterator_(isolate, isolate->thread_local_top()) {
// We skip at least one frame.
DCHECK_LT(0, skipped_frame_types.size());
for (auto type : skipped_frame_types) {
DCHECK_EQ(type, frame_iterator_.frame()->type());
USE(type);
frame_iterator_.Advance();
}
// Type check the frame where the iterator stopped now.
DCHECK_NOT_NULL(frame());
}
FrameType* frame() { return FrameType::cast(frame_iterator_.frame()); }
private:
StackFrameIterator frame_iterator_;
};
Tagged<WasmTrustedInstanceData> GetWasmInstanceDataOnStackTop(
Isolate* isolate) {
Address fp = Isolate::c_entry_fp(isolate->thread_local_top());
fp = Memory<Address>(fp + ExitFrameConstants::kCallerFPOffset);
#ifdef DEBUG
intptr_t marker =
Memory<intptr_t>(fp + CommonFrameConstants::kContextOrFrameTypeOffset);
DCHECK(StackFrame::MarkerToType(marker) == StackFrame::WASM);
#endif
const int offset = WasmFrameConstants::kWasmInstanceOffset;
Tagged<Object> trusted_instance_data(Memory<Address>(fp + offset));
return WasmTrustedInstanceData::cast(trusted_instance_data);
}
Tagged<Context> GetNativeContextFromWasmInstanceOnStackTop(Isolate* isolate) {
return GetWasmInstanceDataOnStackTop(isolate)->native_context();
}
class V8_NODISCARD ClearThreadInWasmScope {
public:
explicit ClearThreadInWasmScope(Isolate* isolate)
: isolate_(isolate), is_thread_in_wasm_(trap_handler::IsThreadInWasm()) {
// In some cases we call this from Wasm code inlined into JavaScript
// so the flag might not be set.
if (is_thread_in_wasm_) {
trap_handler::ClearThreadInWasm();
}
}
~ClearThreadInWasmScope() {
DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
!trap_handler::IsThreadInWasm());
if (!isolate_->has_exception() && is_thread_in_wasm_) {
trap_handler::SetThreadInWasm();
}
// Otherwise we only want to set the flag if the exception is caught in
// wasm. This is handled by the unwinder.
}
private:
Isolate* isolate_;
const bool is_thread_in_wasm_;
};
Tagged<Object> ThrowWasmError(
Isolate* isolate, MessageTemplate message,
std::initializer_list<DirectHandle<Object>> args = {}) {
Handle<JSObject> error_obj =
isolate->factory()->NewWasmRuntimeError(message, base::VectorOf(args));
JSObject::AddProperty(isolate, error_obj,
isolate->factory()->wasm_uncatchable_symbol(),
isolate->factory()->true_value(), NONE);
return isolate->Throw(*error_obj);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmGenericWasmToJSObject) {
SealHandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Tagged<Object> value = args[0];
if (IsWasmFuncRef(value)) {
Tagged<WasmInternalFunction> internal =
WasmFuncRef::cast(value)->internal(isolate);
Tagged<JSFunction> external;
if (internal->try_get_external(&external)) return external;
// Slow path:
HandleScope scope(isolate);
return *WasmInternalFunction::GetOrCreateExternal(
handle(internal, isolate));
}
if (IsWasmNull(value)) return ReadOnlyRoots(isolate).null_value();
return value;
}
// Takes a JS object and a wasm type as Smi. Type checks the object against the
// type; if the check succeeds, returns the object in its wasm representation;
// otherwise throws a type error.
RUNTIME_FUNCTION(Runtime_WasmGenericJSToWasmObject) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<Object> value(args[1], isolate);
// Make sure ValueType fits properly in a Smi.
static_assert(wasm::ValueType::kLastUsedBit + 1 <= kSmiValueSize);
int raw_type = args.smi_value_at(2);
wasm::ValueType type = wasm::ValueType::FromRawBitField(raw_type);
if (type.has_index()) {
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
const wasm::WasmModule* module = trusted_instance_data->module();
DCHECK_NOT_NULL(module);
uint32_t canonical_index =
module->isorecursive_canonical_type_ids[type.ref_index()];
type = wasm::ValueType::RefMaybeNull(canonical_index, type.nullability());
}
const char* error_message;
Handle<Object> result;
if (!JSToWasmObject(isolate, value, type, &error_message).ToHandle(&result)) {
return isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kWasmTrapJSTypeError));
}
return *result;
}
// Takes a JS object and a wasm type as Smi. Type checks the object against the
// type; if the check succeeds, returns the object in its wasm representation;
// otherwise throws a type error.
RUNTIME_FUNCTION(Runtime_WasmJSToWasmObject) {
// TODO(manoskouk): Use {SaveAndClearThreadInWasmFlag} in runtime-internal.cc
// and runtime-strings.cc.
bool thread_in_wasm = trap_handler::IsThreadInWasm();
if (thread_in_wasm) trap_handler::ClearThreadInWasm();
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
// 'raw_instance' can be either a WasmInstanceObject or undefined.
Handle<Object> value(args[0], isolate);
// Make sure ValueType fits properly in a Smi.
static_assert(wasm::ValueType::kLastUsedBit + 1 <= kSmiValueSize);
int raw_type = args.smi_value_at(1);
wasm::ValueType expected_canonical =
wasm::ValueType::FromRawBitField(raw_type);
const char* error_message;
Handle<Object> result;
bool success =
JSToWasmObject(isolate, value, expected_canonical, &error_message)
.ToHandle(&result);
Tagged<Object> ret = success
? *result
: isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kWasmTrapJSTypeError));
if (thread_in_wasm && !isolate->has_exception()) {
trap_handler::SetThreadInWasm();
}
return ret;
}
RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
// {memory_index} and {delta_pages} are checked to be positive Smis in the
// WasmMemoryGrow builtin which calls this runtime function.
uint32_t memory_index = args.positive_smi_value_at(1);
uint32_t delta_pages = args.positive_smi_value_at(2);
Handle<WasmMemoryObject> memory_object{
trusted_instance_data->memory_object(memory_index), isolate};
int ret = WasmMemoryObject::Grow(isolate, memory_object, delta_pages);
// The WasmMemoryGrow builtin which calls this runtime function expects us to
// always return a Smi.
DCHECK(!isolate->has_exception());
return Smi::FromInt(ret);
}
RUNTIME_FUNCTION(Runtime_TrapHandlerThrowWasmError) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
std::vector<FrameSummary> summary;
FrameFinder<WasmFrame> frame_finder(isolate, {StackFrame::EXIT});
WasmFrame* frame = frame_finder.frame();
// TODO(ahaas): We cannot use frame->position() here because for inlined
// function it does not return the correct source position. We should remove
// frame->position() to avoid problems in the future.
frame->Summarize(&summary);
DCHECK(summary.back().IsWasm());
int pos = summary.back().AsWasm().SourcePosition();
wasm::WasmCodeRefScope code_ref_scope;
auto wire_bytes = frame->wasm_code()->native_module()->wire_bytes();
wasm::WasmOpcode op = static_cast<wasm::WasmOpcode>(wire_bytes.at(pos));
MessageTemplate message = MessageTemplate::kWasmTrapMemOutOfBounds;
if (op == wasm::kGCPrefix || op == wasm::kExprRefAsNonNull ||
op == wasm::kExprCallRef || op == wasm::kExprReturnCallRef ||
// Calling imported string function with null can trigger a signal.
op == wasm::kExprCallFunction || op == wasm::kExprReturnCall) {
message = MessageTemplate::kWasmTrapNullDereference;
#if DEBUG
} else {
if (wasm::WasmOpcodes::IsPrefixOpcode(op)) {
op = wasm::Decoder{wire_bytes}
.read_prefixed_opcode<wasm::Decoder::NoValidationTag>(
&wire_bytes.begin()[pos])
.first;
}
DCHECK(wasm::WasmOpcodes::IsMemoryAccessOpcode(op));
#endif // DEBUG
}
return ThrowWasmError(isolate, message);
}
RUNTIME_FUNCTION(Runtime_ThrowWasmError) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
int message_id = args.smi_value_at(0);
return ThrowWasmError(isolate, MessageTemplateFromInt(message_id));
}
RUNTIME_FUNCTION(Runtime_ThrowWasmStackOverflow) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
SealHandleScope shs(isolate);
DCHECK_LE(0, args.length());
return isolate->StackOverflow();
}
RUNTIME_FUNCTION(Runtime_WasmThrowJSTypeError) {
// The caller may be wasm or JS. Only clear the thread_in_wasm flag if the
// caller is wasm, and let the unwinder set it back depending on the handler.
if (trap_handler::IsTrapHandlerEnabled() && trap_handler::IsThreadInWasm()) {
trap_handler::ClearThreadInWasm();
}
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kWasmTrapJSTypeError));
}
// This error is thrown from a wasm-to-JS wrapper, so unlike
// Runtime_ThrowWasmError, this function does not check or unset the
// thread-in-wasm flag.
RUNTIME_FUNCTION(Runtime_ThrowBadSuspenderError) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapBadSuspender);
}
RUNTIME_FUNCTION(Runtime_WasmThrowRangeError) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
MessageTemplate message_id = MessageTemplateFromInt(args.smi_value_at(0));
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewRangeError(message_id));
}
RUNTIME_FUNCTION(Runtime_WasmThrowDataViewTypeError) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
MessageTemplate message_id = MessageTemplateFromInt(args.smi_value_at(0));
DataViewOp op = static_cast<DataViewOp>(isolate->error_message_param());
Handle<String> op_name =
isolate->factory()->NewStringFromAsciiChecked(ToString(op));
Handle<Object> value(args[1], isolate);
THROW_NEW_ERROR_RETURN_FAILURE(isolate,
NewTypeError(message_id, op_name, value));
}
RUNTIME_FUNCTION(Runtime_WasmThrowDataViewDetachedError) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
MessageTemplate message_id = MessageTemplateFromInt(args.smi_value_at(0));
DataViewOp op = static_cast<DataViewOp>(isolate->error_message_param());
Handle<String> op_name =
isolate->factory()->NewStringFromAsciiChecked(ToString(op));
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(message_id, op_name));
}
RUNTIME_FUNCTION(Runtime_WasmThrowTypeError) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
MessageTemplate message_id = MessageTemplateFromInt(args.smi_value_at(0));
Handle<Object> arg(args[1], isolate);
if (IsSmi(*arg)) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(message_id));
} else {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(message_id, arg));
}
}
RUNTIME_FUNCTION(Runtime_WasmThrow) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Tagged<Context> context = GetNativeContextFromWasmInstanceOnStackTop(isolate);
isolate->set_context(context);
Handle<WasmExceptionTag> tag(WasmExceptionTag::cast(args[0]), isolate);
Handle<FixedArray> values(FixedArray::cast(args[1]), isolate);
auto js_tag = WasmTagObject::cast(context->wasm_js_tag());
if (*tag == js_tag->tag()) {
return isolate->Throw(values->get(0));
} else {
Handle<WasmExceptionPackage> exception =
WasmExceptionPackage::New(isolate, tag, values);
return isolate->Throw(*exception);
}
}
RUNTIME_FUNCTION(Runtime_WasmReThrow) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
return isolate->ReThrow(args[0]);
}
RUNTIME_FUNCTION(Runtime_WasmStackGuard) {
ClearThreadInWasmScope wasm_flag(isolate);
SealHandleScope shs(isolate);
DCHECK_EQ(0, args.length());
// Check if this is a real stack overflow.
StackLimitCheck check(isolate);
if (check.WasmHasOverflowed()) return isolate->StackOverflow();
return isolate->stack_guard()->HandleInterrupts(
StackGuard::InterruptLevel::kAnyEffect);
}
RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
ClearThreadInWasmScope wasm_flag(isolate);
DCHECK_EQ(2, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
int func_index = args.smi_value_at(1);
TRACE_EVENT1("v8.wasm", "wasm.CompileLazy", "func_index", func_index);
DisallowHeapAllocation no_gc;
SealHandleScope scope(isolate);
DCHECK(isolate->context().is_null());
isolate->set_context(trusted_instance_data->native_context());
bool success = wasm::CompileLazy(isolate, trusted_instance_data, func_index);
if (!success) {
DCHECK(v8_flags.wasm_lazy_validation);
AllowHeapAllocation throwing_unwinds_the_stack;
wasm::ThrowLazyCompilationError(
isolate, trusted_instance_data->native_module(), func_index);
DCHECK(isolate->has_exception());
return ReadOnlyRoots{isolate}.exception();
}
return Smi::FromInt(
wasm::JumpTableOffset(trusted_instance_data->module(), func_index));
}
RUNTIME_FUNCTION(Runtime_WasmAllocateFeedbackVector) {
ClearThreadInWasmScope wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
int declared_func_index = args.smi_value_at(1);
wasm::NativeModule** native_module_stack_slot =
reinterpret_cast<wasm::NativeModule**>(args.address_of_arg_at(2));
wasm::NativeModule* native_module = trusted_instance_data->native_module();
const wasm::WasmModule* module = native_module->module();
DCHECK(native_module->enabled_features().has_inlining() ||
module->is_wasm_gc);
// We have to save the native_module on the stack, in case the allocation
// triggers a GC and we need the module to scan LiftoffSetupFrame stack frame.
*native_module_stack_slot = native_module;
DCHECK(isolate->context().is_null());
isolate->set_context(trusted_instance_data->native_context());
int func_index = declared_func_index + module->num_imported_functions;
int num_slots = NumFeedbackSlots(module, func_index);
Handle<FixedArray> vector =
isolate->factory()->NewFixedArrayWithZeroes(num_slots);
DCHECK_EQ(trusted_instance_data->feedback_vectors()->get(declared_func_index),
Smi::zero());
trusted_instance_data->feedback_vectors()->set(declared_func_index, *vector);
return *vector;
}
namespace {
void ReplaceWrapper(Isolate* isolate,
Handle<WasmTrustedInstanceData> trusted_instance_data,
int function_index, Handle<Code> wrapper_code) {
Tagged<WasmFuncRef> func_ref;
CHECK(trusted_instance_data->try_get_func_ref(function_index, &func_ref));
Tagged<JSFunction> external_function;
CHECK(func_ref->internal(isolate)->try_get_external(&external_function));
external_function->set_code(*wrapper_code);
Tagged<WasmExportedFunctionData> function_data =
external_function->shared()->wasm_exported_function_data();
function_data->set_wrapper_code(*wrapper_code);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmCompileWrapper) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<WasmExportedFunctionData> function_data(
WasmExportedFunctionData::cast(args[0]), isolate);
Handle<WasmTrustedInstanceData> trusted_data{function_data->instance_data(),
isolate};
DCHECK(isolate->context().is_null());
isolate->set_context(trusted_data->native_context());
const wasm::WasmModule* module = trusted_data->module();
const int function_index = function_data->function_index();
const wasm::WasmFunction& function = module->functions[function_index];
const wasm::FunctionSig* sig = function.sig;
const uint32_t canonical_sig_index =
module->isorecursive_canonical_type_ids[function.sig_index];
// The start function is not guaranteed to be registered as
// an exported function (although it is called as one).
// If there is no entry for the start function, the tier-up is abandoned.
Tagged<WasmFuncRef> func_ref;
if (!trusted_data->try_get_func_ref(function_index, &func_ref)) {
DCHECK_EQ(function_index, module->start_function_index);
return ReadOnlyRoots(isolate).undefined_value();
}
// This runtime function is called for wrapper tier up, but wrapper tierup
// never happens for imports.
bool imported = function.imported;
V8_ASSUME(!imported);
Handle<Code> wrapper_code =
wasm::JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
isolate, sig, canonical_sig_index, module, imported);
// Replace the wrapper for the function that triggered the tier-up.
// This is to ensure that the wrapper is replaced, even if the function
// is implicitly exported and is not part of the export_table.
ReplaceWrapper(isolate, trusted_data, function_index, wrapper_code);
// Iterate over all exports to replace eagerly the wrapper for all functions
// that share the signature of the function that tiered up.
for (wasm::WasmExport exp : module->export_table) {
if (exp.kind != wasm::kExternalFunction) {
continue;
}
int index = static_cast<int>(exp.index);
const wasm::WasmFunction& exp_function = module->functions[index];
if (exp_function.sig == sig && index != function_index) {
ReplaceWrapper(isolate, trusted_data, index, wrapper_code);
}
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_IsWasmExternalFunction) {
DCHECK_EQ(1, args.length());
return isolate->heap()->ToBoolean(
WasmExternalFunction::IsWasmExternalFunction(args[0]));
}
RUNTIME_FUNCTION(Runtime_TierUpWasmToJSWrapper) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<WasmApiFunctionRef> ref(WasmApiFunctionRef::cast(args[0]), isolate);
DCHECK(isolate->context().is_null());
isolate->set_context(ref->native_context());
std::unique_ptr<wasm::ValueType[]> reps;
wasm::FunctionSig sig =
wasm::SerializedSignatureHelper::DeserializeSignature(ref->sig(), &reps);
Handle<Object> origin = handle(ref->call_origin(), isolate);
if (IsWasmFuncRef(*origin)) {
// The tierup for `WasmInternalFunction is special, as there is no instance.
int suspender_count = ref->suspend() == wasm::kSuspendWithSuspender;
size_t expected_arity = sig.parameter_count() - suspender_count;
wasm::ImportCallKind kind = wasm::kDefaultImportCallKind;
if (IsJSFunction(ref->callable())) {
Tagged<SharedFunctionInfo> shared =
JSFunction::cast(ref->callable())->shared();
expected_arity =
shared->internal_formal_parameter_count_without_receiver();
if (expected_arity != sig.parameter_count() - suspender_count) {
kind = wasm::ImportCallKind::kJSFunctionArityMismatch;
}
}
Handle<Code> wasm_to_js_wrapper_code =
compiler::CompileWasmToJSWrapper(
isolate, &sig, kind, static_cast<int>(expected_arity),
static_cast<wasm::Suspend>(ref->suspend()))
.ToHandleChecked();
// We have to install the optimized wrapper as `code`, as the generated
// code may move. `call_target` would become stale then.
Handle<WasmInternalFunction> internal_function{
WasmFuncRef::cast(*origin)->internal(isolate), isolate};
ref->set_code(*wasm_to_js_wrapper_code);
internal_function->set_call_target(
Builtins::EntryOf(Builtin::kWasmToOnHeapWasmToJsTrampoline, isolate));
return ReadOnlyRoots(isolate).undefined_value();
}
Handle<WasmInstanceObject> instance_object(
WasmInstanceObject::cast(ref->instance()), isolate);
if (IsTuple2(*origin)) {
Handle<Tuple2> tuple = Handle<Tuple2>::cast(origin);
instance_object =
handle(WasmInstanceObject::cast(tuple->value1()), isolate);
origin = handle(tuple->value2(), isolate);
}
Handle<WasmTrustedInstanceData> trusted_data(
instance_object->trusted_data(isolate), isolate);
// Get the function's canonical signature index.
uint32_t canonical_sig_index = std::numeric_limits<uint32_t>::max();
const wasm::WasmModule* module = trusted_data->module();
if (WasmApiFunctionRef::CallOriginIsImportIndex(origin)) {
int func_index = WasmApiFunctionRef::CallOriginAsIndex(origin);
canonical_sig_index =
module->isorecursive_canonical_type_ids[module->functions[func_index]
.sig_index];
} else {
// Indirect function table index.
int entry_index = WasmApiFunctionRef::CallOriginAsIndex(origin);
int table_count = trusted_data->dispatch_tables()->length();
// We have to find the table which contains the correct entry.
for (int table_index = 0; table_index < table_count; ++table_index) {
if (!trusted_data->has_dispatch_table(table_index)) continue;
Tagged<WasmDispatchTable> table =
trusted_data->dispatch_table(table_index);
if (entry_index < table->length() && table->ref(entry_index) == *ref) {
canonical_sig_index = table->sig(entry_index);
break;
}
}
}
DCHECK_NE(canonical_sig_index, std::numeric_limits<uint32_t>::max());
// Compile a wrapper for the target callable.
Handle<JSReceiver> callable(JSReceiver::cast(ref->callable()), isolate);
wasm::Suspend suspend = static_cast<wasm::Suspend>(ref->suspend());
wasm::WasmCodeRefScope code_ref_scope;
wasm::NativeModule* native_module = trusted_data->native_module();
wasm::WasmImportData resolved({}, -1, callable, &sig, canonical_sig_index,
wasm::WellKnownImport::kUninstantiated);
wasm::ImportCallKind kind = resolved.kind();
callable = resolved.callable(); // Update to ultimate target.
DCHECK_NE(wasm::ImportCallKind::kLinkError, kind);
wasm::CompilationEnv env = wasm::CompilationEnv::ForModule(native_module);
// {expected_arity} should only be used if kind != kJSFunctionArityMismatch.
int suspender_count = resolved.suspend() == wasm::kSuspendWithSuspender;
int expected_arity =
static_cast<int>(sig.parameter_count()) - suspender_count;
if (kind == wasm::ImportCallKind ::kJSFunctionArityMismatch) {
expected_arity = Handle<JSFunction>::cast(callable)
->shared()
->internal_formal_parameter_count_without_receiver();
}
wasm::WasmImportWrapperCache* cache = native_module->import_wrapper_cache();
wasm::WasmCode* wasm_code =
cache->MaybeGet(kind, canonical_sig_index, expected_arity, suspend);
if (!wasm_code) {
wasm::WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
&env, kind, &sig, false, expected_arity, suspend);
std::unique_ptr<wasm::WasmCode> compiled_code = native_module->AddCode(
result.func_index, result.code_desc, result.frame_slot_count,
result.ool_spill_count, result.tagged_parameter_slots,
result.protected_instructions_data.as_vector(),
result.source_positions.as_vector(),
result.inlining_positions.as_vector(), result.deopt_data.as_vector(),
GetCodeKind(result), wasm::ExecutionTier::kNone,
wasm::kNotForDebugging);
wasm_code = native_module->PublishCode(std::move(compiled_code));
isolate->counters()->wasm_generated_code_size()->Increment(
wasm_code->instructions().length());
isolate->counters()->wasm_reloc_size()->Increment(
wasm_code->reloc_info().length());
if (V8_UNLIKELY(native_module->log_code())) {
wasm::GetWasmEngine()->LogCode(base::VectorOf(&wasm_code, 1));
// Log the code immediately in the current isolate.
wasm::GetWasmEngine()->LogOutstandingCodesForIsolate(isolate);
}
wasm::WasmImportWrapperCache::ModificationScope cache_scope(cache);
wasm::WasmImportWrapperCache::CacheKey key(kind, canonical_sig_index,
expected_arity, suspend);
cache_scope[key] = wasm_code;
}
if (WasmApiFunctionRef::CallOriginIsImportIndex(origin)) {
int func_index = WasmApiFunctionRef::CallOriginAsIndex(origin);
ImportedFunctionEntry entry(trusted_data, func_index);
entry.set_target(wasm_code->instruction_start());
} else {
// Indirect function table index.
int entry_index = WasmApiFunctionRef::CallOriginAsIndex(origin);
int table_count = trusted_data->dispatch_tables()->length();
// We have to find the table which contains the correct entry.
for (int table_index = 0; table_index < table_count; ++table_index) {
if (!trusted_data->has_dispatch_table(table_index)) continue;
Tagged<WasmDispatchTable> table =
trusted_data->dispatch_table(table_index);
if (entry_index < table->length() && table->ref(entry_index) == *ref) {
table->SetTarget(entry_index, wasm_code->instruction_start());
// {ref} is used in at most one table.
break;
}
}
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTriggerTierUp) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
SealHandleScope shs(isolate);
{
DisallowGarbageCollection no_gc;
DCHECK_EQ(1, args.length());
Tagged<WasmTrustedInstanceData> trusted_data =
WasmTrustedInstanceData::cast(args[0]);
FrameFinder<WasmFrame> frame_finder(isolate);
int func_index = frame_finder.frame()->function_index();
DCHECK_EQ(trusted_data, frame_finder.frame()->trusted_instance_data());
if (V8_UNLIKELY(v8_flags.wasm_sync_tier_up)) {
if (!trusted_data->native_module()->HasCodeWithTier(
func_index, wasm::ExecutionTier::kTurbofan)) {
wasm::TierUpNowForTesting(isolate, trusted_data, func_index);
}
// We call this function when the tiering budget runs out, so reset that
// budget to appropriately delay the next call.
int array_index =
wasm::declared_function_index(trusted_data->module(), func_index);
trusted_data->tiering_budget_array()[array_index] =
v8_flags.wasm_tiering_budget;
} else {
wasm::TriggerTierUp(isolate, trusted_data, func_index);
}
}
// We're reusing this interrupt mechanism to interrupt long-running loops.
StackLimitCheck check(isolate);
// We don't need to handle stack overflows here, because the function that
// performed this runtime call did its own stack check at its beginning.
// However, we can't DCHECK(!check.JsHasOverflowed()) here, because the
// additional stack space used by the CEntryStub and this runtime function
// itself might have pushed us above the limit where a stack check would
// fail.
if (check.InterruptRequested()) {
// Note: This might trigger a GC, which invalidates the {args} object (see
// https://crbug.com/v8/13036#2).
Tagged<Object> result = isolate->stack_guard()->HandleInterrupts();
if (IsException(result)) return result;
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
int memory_index = args.smi_value_at(1);
double offset_double = args.number_value_at(2);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
int32_t expected_value = NumberToInt32(args[3]);
Tagged<BigInt> timeout_ns = BigInt::cast(args[4]);
Handle<JSArrayBuffer> array_buffer{
trusted_instance_data->memory_object(memory_index)->array_buffer(),
isolate};
// Should have trapped if address was OOB.
DCHECK_LT(offset, array_buffer->byte_length());
// Trap if memory is not shared, or wait is not allowed on the isolate
if (!array_buffer->is_shared() || !isolate->allow_atomics_wait()) {
return ThrowWasmError(
isolate, MessageTemplate::kAtomicsOperationNotAllowed,
{isolate->factory()->NewStringFromAsciiChecked("Atomics.wait")});
}
return FutexEmulation::WaitWasm32(isolate, array_buffer, offset,
expected_value, timeout_ns->AsInt64());
}
RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
int memory_index = args.smi_value_at(1);
double offset_double = args.number_value_at(2);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
Tagged<BigInt> expected_value = BigInt::cast(args[3]);
Tagged<BigInt> timeout_ns = BigInt::cast(args[4]);
Handle<JSArrayBuffer> array_buffer{
trusted_instance_data->memory_object(memory_index)->array_buffer(),
isolate};
// Should have trapped if address was OOB.
DCHECK_LT(offset, array_buffer->byte_length());
// Trap if memory is not shared, or if wait is not allowed on the isolate
if (!array_buffer->is_shared() || !isolate->allow_atomics_wait()) {
return ThrowWasmError(
isolate, MessageTemplate::kAtomicsOperationNotAllowed,
{isolate->factory()->NewStringFromAsciiChecked("Atomics.wait")});
}
return FutexEmulation::WaitWasm64(isolate, array_buffer, offset,
expected_value->AsInt64(),
timeout_ns->AsInt64());
}
namespace {
Tagged<Object> ThrowTableOutOfBounds(
Isolate* isolate, Handle<WasmTrustedInstanceData> trusted_instance_data) {
// Handle out-of-bounds access here in the runtime call, rather
// than having the lower-level layers deal with JS exceptions.
if (isolate->context().is_null()) {
isolate->set_context(trusted_instance_data->native_context());
}
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmRefFunc) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t function_index = args.positive_smi_value_at(1);
return *WasmTrustedInstanceData::GetOrCreateFuncRef(
isolate, trusted_instance_data, function_index);
}
RUNTIME_FUNCTION(Runtime_WasmInternalFunctionCreateExternal) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
// TODO(14564): Pass WasmFuncRef here instead of WasmInternalFunction.
Handle<WasmInternalFunction> internal(WasmInternalFunction::cast(args[0]),
isolate);
return *WasmInternalFunction::GetOrCreateExternal(internal);
}
RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t table_index = args.positive_smi_value_at(1);
uint32_t entry_index = args.positive_smi_value_at(2);
DCHECK_LT(table_index, trusted_instance_data->tables()->length());
auto table = handle(
WasmTableObject::cast(trusted_instance_data->tables()->get(table_index)),
isolate);
// We only use the runtime call for lazily initialized function references.
DCHECK(IsUndefined(table->instance())
? table->type() == wasm::kWasmFuncRef
: (IsSubtypeOf(
table->type(), wasm::kWasmFuncRef,
WasmInstanceObject::cast(table->instance())->module()) ||
IsSubtypeOf(
table->type(),
wasm::ValueType::RefNull(wasm::HeapType::kFuncShared),
WasmInstanceObject::cast(table->instance())->module())));
if (!table->is_in_bounds(entry_index)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
}
return *WasmTableObject::Get(isolate, table, entry_index);
}
RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t table_index = args.positive_smi_value_at(1);
uint32_t entry_index = args.positive_smi_value_at(2);
Handle<Object> element(args[3], isolate);
DCHECK_LT(table_index, trusted_instance_data->tables()->length());
auto table = handle(
WasmTableObject::cast(trusted_instance_data->tables()->get(table_index)),
isolate);
// We only use the runtime call for lazily initialized function references.
DCHECK(IsUndefined(table->instance())
? table->type() == wasm::kWasmFuncRef
: (IsSubtypeOf(
table->type(), wasm::kWasmFuncRef,
WasmInstanceObject::cast(table->instance())->module()) ||
IsSubtypeOf(
table->type(),
wasm::ValueType::RefNull(wasm::HeapType::kFuncShared),
WasmInstanceObject::cast(table->instance())->module())));
if (!table->is_in_bounds(entry_index)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
}
WasmTableObject::Set(isolate, table, entry_index, element);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTableInit) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(6, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t table_index = args.positive_smi_value_at(1);
uint32_t elem_segment_index = args.positive_smi_value_at(2);
static_assert(
wasm::kV8MaxWasmTableSize < kSmiMaxValue,
"Make sure clamping to Smi range doesn't make an invalid call valid");
uint32_t dst = args.positive_smi_value_at(3);
uint32_t src = args.positive_smi_value_at(4);
uint32_t count = args.positive_smi_value_at(5);
DCHECK(!isolate->context().is_null());
// TODO(14616): Pass the correct instance data.
base::Optional<MessageTemplate> opt_error =
WasmTrustedInstanceData::InitTableEntries(
isolate, trusted_instance_data, trusted_instance_data, table_index,
elem_segment_index, dst, src, count);
if (opt_error.has_value()) {
return ThrowWasmError(isolate, opt_error.value());
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(6, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t table_dst_index = args.positive_smi_value_at(1);
uint32_t table_src_index = args.positive_smi_value_at(2);
static_assert(
wasm::kV8MaxWasmTableSize < kSmiMaxValue,
"Make sure clamping to Smi range doesn't make an invalid call valid");
uint32_t dst = args.positive_smi_value_at(3);
uint32_t src = args.positive_smi_value_at(4);
uint32_t count = args.positive_smi_value_at(5);
DCHECK(!isolate->context().is_null());
bool oob = !WasmTrustedInstanceData::CopyTableEntries(
isolate, trusted_instance_data, table_dst_index, table_src_index, dst,
src, count);
if (oob) return ThrowTableOutOfBounds(isolate, trusted_instance_data);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTableGrow) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK(isolate->IsOnCentralStack());
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t table_index = args.positive_smi_value_at(1);
Handle<Object> value(args[2], isolate);
uint32_t delta = args.positive_smi_value_at(3);
Handle<WasmTableObject> table(
WasmTableObject::cast(trusted_instance_data->tables()->get(table_index)),
isolate);
int result = WasmTableObject::Grow(isolate, table, delta, value);
return Smi::FromInt(result);
}
RUNTIME_FUNCTION(Runtime_WasmTableFill) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t table_index = args.positive_smi_value_at(1);
uint32_t start = args.positive_smi_value_at(2);
Handle<Object> value(args[3], isolate);
uint32_t count = args.positive_smi_value_at(4);
Handle<WasmTableObject> table(
WasmTableObject::cast(trusted_instance_data->tables()->get(table_index)),
isolate);
uint32_t table_size = table->current_length();
if (start > table_size) {
return ThrowTableOutOfBounds(isolate, trusted_instance_data);
}
// Even when table.fill goes out-of-bounds, as many entries as possible are
// put into the table. Only afterwards we trap.
uint32_t fill_count = std::min(count, table_size - start);
if (fill_count < count) {
return ThrowTableOutOfBounds(isolate, trusted_instance_data);
}
WasmTableObject::Fill(isolate, table, start, value, fill_count);
return ReadOnlyRoots(isolate).undefined_value();
}
namespace {
// Returns true if any breakpoint was hit, false otherwise.
bool ExecuteWasmDebugBreaks(
Isolate* isolate, Handle<WasmTrustedInstanceData> trusted_instance_data,
WasmFrame* frame) {
Handle<Script> script{trusted_instance_data->module_object()->script(),
isolate};
auto* debug_info = trusted_instance_data->native_module()->GetDebugInfo();
// Enter the debugger.
DebugScope debug_scope(isolate->debug());
// Check for instrumentation breakpoints first, but still execute regular
// breakpoints afterwards.
bool paused_on_instrumentation = false;
DCHECK_EQ(script->break_on_entry(),
!!trusted_instance_data->break_on_entry());
if (script->break_on_entry()) {
MaybeHandle<FixedArray> maybe_on_entry_breakpoints =
WasmScript::CheckBreakPoints(isolate, script,
WasmScript::kOnEntryBreakpointPosition,
frame->id());
script->set_break_on_entry(false);
// Update the "break_on_entry" flag on all live instances.
i::Tagged<i::WeakArrayList> weak_instance_list =
script->wasm_weak_instance_list();
for (int i = 0; i < weak_instance_list->length(); ++i) {
if (weak_instance_list->Get(i).IsCleared()) continue;
i::WasmInstanceObject::cast(weak_instance_list->Get(i).GetHeapObject())
->trusted_data(isolate)
->set_break_on_entry(false);
}
DCHECK(!trusted_instance_data->break_on_entry());
if (!maybe_on_entry_breakpoints.is_null()) {
isolate->debug()->OnInstrumentationBreak();
paused_on_instrumentation = true;
}
}
if (debug_info->IsStepping(frame)) {
debug_info->ClearStepping(isolate);
StepAction step_action = isolate->debug()->last_step_action();
isolate->debug()->ClearStepping();
isolate->debug()->OnDebugBreak(isolate->factory()->empty_fixed_array(),
step_action);
return true;
}
// Check whether we hit a breakpoint.
Handle<FixedArray> breakpoints;
if (WasmScript::CheckBreakPoints(isolate, script, frame->position(),
frame->id())
.ToHandle(&breakpoints)) {
debug_info->ClearStepping(isolate);
StepAction step_action = isolate->debug()->last_step_action();
isolate->debug()->ClearStepping();
if (isolate->debug()->break_points_active()) {
// We hit one or several breakpoints. Notify the debug listeners.
isolate->debug()->OnDebugBreak(breakpoints, step_action);
}
return true;
}
return paused_on_instrumentation;
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmDebugBreak) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
FrameFinder<WasmFrame> frame_finder(
isolate, {StackFrame::EXIT, StackFrame::WASM_DEBUG_BREAK});
WasmFrame* frame = frame_finder.frame();
Handle<WasmTrustedInstanceData> trusted_data{frame->trusted_instance_data(),
isolate};
isolate->set_context(trusted_data->native_context());
if (!ExecuteWasmDebugBreaks(isolate, trusted_data, frame)) {
// We did not hit a breakpoint. If we are in stepping code, but the user did
// not request stepping, clear this (to save further calls into this runtime
// function).
auto* debug_info = trusted_data->native_module()->GetDebugInfo();
debug_info->ClearStepping(frame);
}
// Execute a stack check before leaving this function. This is to handle any
// interrupts set by the debugger (e.g. termination), but also to execute Wasm
// code GC to get rid of temporarily created Wasm code.
StackLimitCheck check(isolate);
if (check.InterruptRequested()) {
Tagged<Object> interrupt_object =
isolate->stack_guard()->HandleInterrupts();
// Interrupt handling can create an exception, including the
// termination exception.
if (IsException(interrupt_object, isolate)) return interrupt_object;
DCHECK(IsUndefined(interrupt_object, isolate));
}
return ReadOnlyRoots(isolate).undefined_value();
}
// Assumes copy ranges are in-bounds and copy length > 0.
// TODO(manoskouk): Unify part of this with the implementation in
// wasm-extern-refs.cc
RUNTIME_FUNCTION(Runtime_WasmArrayCopy) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DisallowGarbageCollection no_gc;
DCHECK_EQ(5, args.length());
Tagged<WasmArray> dst_array = WasmArray::cast(args[0]);
uint32_t dst_index = args.positive_smi_value_at(1);
Tagged<WasmArray> src_array = WasmArray::cast(args[2]);
uint32_t src_index = args.positive_smi_value_at(3);
uint32_t length = args.positive_smi_value_at(4);
DCHECK_GT(length, 0);
bool overlapping_ranges =
dst_array.ptr() == src_array.ptr() &&
(dst_index < src_index ? dst_index + length > src_index
: src_index + length > dst_index);
wasm::ValueType element_type = src_array->type()->element_type();
if (element_type.is_reference()) {
ObjectSlot dst_slot = dst_array->ElementSlot(dst_index);
ObjectSlot src_slot = src_array->ElementSlot(src_index);
if (overlapping_ranges) {
isolate->heap()->MoveRange(dst_array, dst_slot, src_slot, length,
UPDATE_WRITE_BARRIER);
} else {
isolate->heap()->CopyRange(dst_array, dst_slot, src_slot, length,
UPDATE_WRITE_BARRIER);
}
} else {
void* dst = reinterpret_cast<void*>(dst_array->ElementAddress(dst_index));
void* src = reinterpret_cast<void*>(src_array->ElementAddress(src_index));
size_t copy_size = length * element_type.value_kind_size();
if (overlapping_ranges) {
MemMove(dst, src, copy_size);
} else {
MemCopy(dst, src, copy_size);
}
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmArrayNewSegment) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t segment_index = args.positive_smi_value_at(1);
uint32_t offset = args.positive_smi_value_at(2);
uint32_t length = args.positive_smi_value_at(3);
Handle<Map> rtt(Map::cast(args[4]), isolate);
wasm::ArrayType* type =
reinterpret_cast<wasm::ArrayType*>(rtt->wasm_type_info()->native_type());
uint32_t element_size = type->element_type().value_kind_size();
// This check also implies no overflow.
if (length > static_cast<uint32_t>(WasmArray::MaxLength(element_size))) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapArrayTooLarge);
}
if (type->element_type().is_numeric()) {
// No chance of overflow due to the check above.
uint32_t length_in_bytes = length * element_size;
if (!base::IsInBounds<uint32_t>(
offset, length_in_bytes,
trusted_instance_data->data_segment_sizes()->get(segment_index))) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapDataSegmentOutOfBounds);
}
Address source =
trusted_instance_data->data_segment_starts()->get(segment_index) +
offset;
return *isolate->factory()->NewWasmArrayFromMemory(length, rtt, source);
} else {
Handle<Object> elem_segment_raw = handle(
trusted_instance_data->element_segments()->get(segment_index), isolate);
const wasm::WasmElemSegment* module_elem_segment =
&trusted_instance_data->module()->elem_segments[segment_index];
// If the segment is initialized in the instance, we have to get its length
// from there, as it might have been dropped. If the segment is
// uninitialized, we need to fetch its length from the module.
int segment_length =
IsFixedArray(*elem_segment_raw)
? Handle<FixedArray>::cast(elem_segment_raw)->length()
: module_elem_segment->element_count;
if (!base::IsInBounds<size_t>(offset, length, segment_length)) {
return ThrowWasmError(
isolate, MessageTemplate::kWasmTrapElementSegmentOutOfBounds);
}
// TODO(14616): Pass the correct instance data.
Handle<Object> result = isolate->factory()->NewWasmArrayFromElementSegment(
trusted_instance_data, trusted_instance_data, segment_index, offset,
length, rtt);
if (IsSmi(*result)) {
return ThrowWasmError(
isolate, static_cast<MessageTemplate>(Smi::cast(*result).value()));
} else {
return *result;
}
}
}
RUNTIME_FUNCTION(Runtime_WasmArrayInitSegment) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(6, args.length());
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t segment_index = args.positive_smi_value_at(1);
Handle<WasmArray> array(WasmArray::cast(args[2]), isolate);
uint32_t array_index = args.positive_smi_value_at(3);
uint32_t segment_offset = args.positive_smi_value_at(4);
uint32_t length = args.positive_smi_value_at(5);
wasm::ArrayType* type = reinterpret_cast<wasm::ArrayType*>(
array->map()->wasm_type_info()->native_type());
uint32_t element_size = type->element_type().value_kind_size();
if (type->element_type().is_numeric()) {
if (!base::IsInBounds<uint32_t>(array_index, length, array->length())) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapArrayOutOfBounds);
}
// No chance of overflow, due to the check above and the limit in array
// length.
uint32_t length_in_bytes = length * element_size;
if (!base::IsInBounds<uint32_t>(
segment_offset, length_in_bytes,
trusted_instance_data->data_segment_sizes()->get(segment_index))) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapDataSegmentOutOfBounds);
}
Address source =
trusted_instance_data->data_segment_starts()->get(segment_index) +
segment_offset;
Address dest = array->ElementAddress(array_index);
#if V8_TARGET_BIG_ENDIAN
MemCopyAndSwitchEndianness(reinterpret_cast<void*>(dest),
reinterpret_cast<void*>(source), length,
element_size);
#else
MemCopy(reinterpret_cast<void*>(dest), reinterpret_cast<void*>(source),
length_in_bytes);
#endif
return *isolate->factory()->undefined_value();
} else {
Handle<Object> elem_segment_raw = handle(
trusted_instance_data->element_segments()->get(segment_index), isolate);
const wasm::WasmElemSegment* module_elem_segment =
&trusted_instance_data->module()->elem_segments[segment_index];
// If the segment is initialized in the instance, we have to get its length
// from there, as it might have been dropped. If the segment is
// uninitialized, we need to fetch its length from the module.
int segment_length =
IsFixedArray(*elem_segment_raw)
? Handle<FixedArray>::cast(elem_segment_raw)->length()
: module_elem_segment->element_count;
if (!base::IsInBounds<size_t>(segment_offset, length, segment_length)) {
return ThrowWasmError(
isolate, MessageTemplate::kWasmTrapElementSegmentOutOfBounds);
}
if (!base::IsInBounds(array_index, length, array->length())) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapArrayOutOfBounds);
}
// If the element segment has not been initialized yet, lazily initialize it
// now.
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// TODO(14616): Fix the instance data.
base::Optional<MessageTemplate> opt_error =
wasm::InitializeElementSegment(&zone, isolate, trusted_instance_data,
trusted_instance_data, segment_index);
if (opt_error.has_value()) {
return ThrowWasmError(isolate, opt_error.value());
}
auto elements = handle(
FixedArray::cast(
trusted_instance_data->element_segments()->get(segment_index)),
isolate);
if (length > 0) {
isolate->heap()->CopyRange(*array, array->ElementSlot(array_index),
elements->RawFieldOfElementAt(segment_offset),
length, UPDATE_WRITE_BARRIER);
}
return *isolate->factory()->undefined_value();
}
}
// Allocate a new suspender, and prepare for stack switching by updating the
// active continuation, active suspender and stack limit.
RUNTIME_FUNCTION(Runtime_WasmAllocateSuspender) {
HandleScope scope(isolate);
Handle<WasmSuspenderObject> suspender = WasmSuspenderObject::New(isolate);
// Update the continuation state.
auto parent = handle(WasmContinuationObject::cast(
isolate->root(RootIndex::kActiveContinuation)),
isolate);
Handle<WasmContinuationObject> target =
WasmContinuationObject::New(isolate, wasm::JumpBuffer::Inactive, parent);
auto target_stack =
Managed<wasm::StackMemory>::cast(target->stack())->get().get();
isolate->wasm_stacks()->Add(target_stack);
isolate->roots_table().slot(RootIndex::kActiveContinuation).store(*target);
// Update the suspender state.
FullObjectSlot active_suspender_slot =
isolate->roots_table().slot(RootIndex::kActiveSuspender);
suspender->set_parent(HeapObject::cast(*active_suspender_slot));
suspender->set_state(WasmSuspenderObject::kActive);
suspender->set_continuation(*target);
active_suspender_slot.store(*suspender);
// Stack limit will be updated in WasmReturnPromiseOnSuspendAsm builtin.
wasm::JumpBuffer* jmpbuf = reinterpret_cast<wasm::JumpBuffer*>(
parent->ReadExternalPointerField<kWasmContinuationJmpbufTag>(
WasmContinuationObject::kJmpbufOffset, isolate));
DCHECK_EQ(jmpbuf->state, wasm::JumpBuffer::Active);
jmpbuf->state = wasm::JumpBuffer::Inactive;
return *suspender;
}
#define RETURN_RESULT_OR_TRAP(call) \
do { \
Handle<Object> result; \
if (!(call).ToHandle(&result)) { \
DCHECK(isolate->has_exception()); \
/* Mark any exception as uncatchable by Wasm. */ \
Handle<JSObject> exception(JSObject::cast(isolate->exception()), \
isolate); \
Handle<Name> uncatchable = \
isolate->factory()->wasm_uncatchable_symbol(); \
LookupIterator it(isolate, exception, uncatchable, LookupIterator::OWN); \
if (!JSReceiver::HasProperty(&it).FromJust()) { \
JSObject::AddProperty(isolate, exception, uncatchable, \
isolate->factory()->true_value(), NONE); \
} \
return ReadOnlyRoots(isolate).exception(); \
} \
DCHECK(!isolate->has_exception()); \
return *result; \
} while (false)
// "Special" because the type must be in a recgroup of its own.
// Used by "JS String Builtins".
RUNTIME_FUNCTION(Runtime_WasmCastToSpecialPrimitiveArray) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
int bits = args.smi_value_at(1);
DCHECK(bits == 8 || bits == 16);
if (args[0] == ReadOnlyRoots(isolate).null_value()) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapNullDereference);
}
MessageTemplate illegal_cast = MessageTemplate::kWasmTrapIllegalCast;
if (!IsWasmArray(args[0])) return ThrowWasmError(isolate, illegal_cast);
Tagged<WasmArray> obj = WasmArray::cast(args[0]);
Tagged<WasmTypeInfo> wti = obj->map()->wasm_type_info();
const wasm::WasmModule* module =
WasmInstanceObject::cast(wti->instance())->module();
DCHECK(module->has_array(wti->type_index()));
uint32_t expected = bits == 8
? wasm::TypeCanonicalizer::kPredefinedArrayI8Index
: wasm::TypeCanonicalizer::kPredefinedArrayI16Index;
if (module->isorecursive_canonical_type_ids[wti->type_index()] != expected) {
return ThrowWasmError(isolate, illegal_cast);
}
return obj;
}
// Returns the new string if the operation succeeds. Otherwise throws an
// exception and returns an empty result.
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(5, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
uint32_t utf8_variant_value = args.positive_smi_value_at(2);
double offset_double = args.number_value_at(3);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
uint32_t size = NumberToUint32(args[4]);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
uint64_t mem_size = trusted_instance_data->memory_size(memory);
if (!base::IsInBounds<uint64_t>(offset, size, mem_size)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
const base::Vector<const uint8_t> bytes{
trusted_instance_data->memory_base(memory) + offset, size};
MaybeHandle<v8::internal::String> result_string =
isolate->factory()->NewStringFromUtf8(bytes, utf8_variant);
if (utf8_variant == unibrow::Utf8Variant::kUtf8NoTrap) {
DCHECK(!isolate->has_exception());
if (result_string.is_null()) {
return *isolate->factory()->wasm_null();
}
return *result_string.ToHandleChecked();
}
RETURN_RESULT_OR_TRAP(result_string);
}
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8Array) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(4, args.length());
HandleScope scope(isolate);
uint32_t utf8_variant_value = args.positive_smi_value_at(0);
Handle<WasmArray> array(WasmArray::cast(args[1]), isolate);
uint32_t start = NumberToUint32(args[2]);
uint32_t end = NumberToUint32(args[3]);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
MaybeHandle<v8::internal::String> result_string =
isolate->factory()->NewStringFromUtf8(array, start, end, utf8_variant);
if (utf8_variant == unibrow::Utf8Variant::kUtf8NoTrap) {
DCHECK(!isolate->has_exception());
if (result_string.is_null()) {
return *isolate->factory()->wasm_null();
}
return *result_string.ToHandleChecked();
}
RETURN_RESULT_OR_TRAP(result_string);
}
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(4, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
double offset_double = args.number_value_at(2);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
uint32_t size_in_codeunits = NumberToUint32(args[3]);
uint64_t mem_size = trusted_instance_data->memory_size(memory);
if (size_in_codeunits > kMaxUInt32 / 2 ||
!base::IsInBounds<uint64_t>(offset, size_in_codeunits * 2, mem_size)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
if (offset & 1) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapUnalignedAccess);
}
const uint8_t* bytes = trusted_instance_data->memory_base(memory) + offset;
const base::uc16* codeunits = reinterpret_cast<const base::uc16*>(bytes);
RETURN_RESULT_OR_TRAP(isolate->factory()->NewStringFromTwoByteLittleEndian(
{codeunits, size_in_codeunits}));
}
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16Array) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(3, args.length());
HandleScope scope(isolate);
Handle<WasmArray> array(WasmArray::cast(args[0]), isolate);
uint32_t start = NumberToUint32(args[1]);
uint32_t end = NumberToUint32(args[2]);
RETURN_RESULT_OR_TRAP(
isolate->factory()->NewStringFromUtf16(array, start, end));
}
RUNTIME_FUNCTION(Runtime_WasmSubstring) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(3, args.length());
HandleScope scope(isolate);
Handle<String> string(String::cast(args[0]), isolate);
int start = args.positive_smi_value_at(1);
int length = args.positive_smi_value_at(2);
string = String::Flatten(isolate, string);
return *isolate->factory()->NewCopiedSubstring(string, start, length);
}
// Returns the new string if the operation succeeds. Otherwise traps.
RUNTIME_FUNCTION(Runtime_WasmStringConst) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(2, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
static_assert(
base::IsInRange(wasm::kV8MaxWasmStringLiterals, 0, Smi::kMaxValue));
uint32_t index = args.positive_smi_value_at(1);
DCHECK_LT(index, trusted_instance_data->module()->stringref_literals.size());
const wasm::WasmStringRefLiteral& literal =
trusted_instance_data->module()->stringref_literals[index];
const base::Vector<const uint8_t> module_bytes =
trusted_instance_data->native_module()->wire_bytes();
const base::Vector<const uint8_t> string_bytes = module_bytes.SubVector(
literal.source.offset(), literal.source.end_offset());
// TODO(12868): No need to re-validate WTF-8. Also, result should be cached.
return *isolate->factory()
->NewStringFromUtf8(string_bytes, unibrow::Utf8Variant::kWtf8)
.ToHandleChecked();
}
RUNTIME_FUNCTION(Runtime_WasmStringNewSegmentWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(5, args.length());
HandleScope scope(isolate);
Handle<WasmTrustedInstanceData> trusted_instance_data(
WasmTrustedInstanceData::cast(args[0]), isolate);
uint32_t segment_index = args.positive_smi_value_at(1);
uint32_t offset = args.positive_smi_value_at(2);
uint32_t length = args.positive_smi_value_at(3);
unibrow::Utf8Variant variant =
static_cast<unibrow::Utf8Variant>(args.positive_smi_value_at(4));
if (!base::IsInBounds<uint32_t>(
offset, length,
trusted_instance_data->data_segment_sizes()->get(segment_index))) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapDataSegmentOutOfBounds);
}
Address source =
trusted_instance_data->data_segment_starts()->get(segment_index) + offset;
MaybeHandle<String> result = isolate->factory()->NewStringFromUtf8(
{reinterpret_cast<const uint8_t*>(source), length}, variant);
if (variant == unibrow::Utf8Variant::kUtf8NoTrap) {
DCHECK(!isolate->has_exception());
// Only instructions from the stringref proposal can set variant
// kUtf8NoTrap, so WasmNull is appropriate here.
if (result.is_null()) return *isolate->factory()->wasm_null();
return *result.ToHandleChecked();
}
RETURN_RESULT_OR_FAILURE(isolate, result);
}
namespace {
// TODO(12868): Consider unifying with api.cc:String::Utf8Length.
template <typename T>
int MeasureWtf8(base::Vector<const T> wtf16) {
int previous = unibrow::Utf16::kNoPreviousCharacter;
int length = 0;
DCHECK(wtf16.size() <= String::kMaxLength);
static_assert(String::kMaxLength <=
(kMaxInt / unibrow::Utf8::kMaxEncodedSize));
for (size_t i = 0; i < wtf16.size(); i++) {
int current = wtf16[i];
length += unibrow::Utf8::Length(current, previous);
previous = current;
}
return length;
}
int MeasureWtf8(Isolate* isolate, Handle<String> string) {
string = String::Flatten(isolate, string);
DisallowGarbageCollection no_gc;
String::FlatContent content = string->GetFlatContent(no_gc);
DCHECK(content.IsFlat());
return content.IsOneByte() ? MeasureWtf8(content.ToOneByteVector())
: MeasureWtf8(content.ToUC16Vector());
}
size_t MaxEncodedSize(base::Vector<const uint8_t> wtf16) {
DCHECK(wtf16.size() < std::numeric_limits<size_t>::max() /
unibrow::Utf8::kMax8BitCodeUnitSize);
return wtf16.size() * unibrow::Utf8::kMax8BitCodeUnitSize;
}
size_t MaxEncodedSize(base::Vector<const base::uc16> wtf16) {
DCHECK(wtf16.size() < std::numeric_limits<size_t>::max() /
unibrow::Utf8::kMax16BitCodeUnitSize);
return wtf16.size() * unibrow::Utf8::kMax16BitCodeUnitSize;
}
bool HasUnpairedSurrogate(base::Vector<const uint8_t> wtf16) { return false; }
bool HasUnpairedSurrogate(base::Vector<const base::uc16> wtf16) {
return unibrow::Utf16::HasUnpairedSurrogate(wtf16.begin(), wtf16.size());
}
// TODO(12868): Consider unifying with api.cc:String::WriteUtf8.
template <typename T>
int EncodeWtf8(base::Vector<char> bytes, size_t offset,
base::Vector<const T> wtf16, unibrow::Utf8Variant variant,
MessageTemplate* message, MessageTemplate out_of_bounds) {
// The first check is a quick estimate to decide whether the second check
// is worth the computation.
if (!base::IsInBounds<size_t>(offset, MaxEncodedSize(wtf16), bytes.size()) &&
!base::IsInBounds<size_t>(offset, MeasureWtf8(wtf16), bytes.size())) {
*message = out_of_bounds;
return -1;
}
bool replace_invalid = false;
switch (variant) {
case unibrow::Utf8Variant::kWtf8:
break;
case unibrow::Utf8Variant::kUtf8:
if (HasUnpairedSurrogate(wtf16)) {
*message = MessageTemplate::kWasmTrapStringIsolatedSurrogate;
return -1;
}
break;
case unibrow::Utf8Variant::kLossyUtf8:
replace_invalid = true;
break;
default:
UNREACHABLE();
}
char* dst_start = bytes.begin() + offset;
char* dst = dst_start;
int previous = unibrow::Utf16::kNoPreviousCharacter;
for (auto code_unit : wtf16) {
dst += unibrow::Utf8::Encode(dst, code_unit, previous, replace_invalid);
previous = code_unit;
}
DCHECK_LE(dst - dst_start, static_cast<ptrdiff_t>(kMaxInt));
return static_cast<int>(dst - dst_start);
}
template <typename GetWritableBytes>
Tagged<Object> EncodeWtf8(Isolate* isolate, unibrow::Utf8Variant variant,
Handle<String> string,
GetWritableBytes get_writable_bytes, size_t offset,
MessageTemplate out_of_bounds_message) {
string = String::Flatten(isolate, string);
MessageTemplate message;
int written;
{
DisallowGarbageCollection no_gc;
String::FlatContent content = string->GetFlatContent(no_gc);
base::Vector<char> dst = get_writable_bytes(no_gc);
written = content.IsOneByte()
? EncodeWtf8(dst, offset, content.ToOneByteVector(), variant,
&message, out_of_bounds_message)
: EncodeWtf8(dst, offset, content.ToUC16Vector(), variant,
&message, out_of_bounds_message);
}
if (written < 0) {
DCHECK_NE(message, MessageTemplate::kNone);
return ThrowWasmError(isolate, message);
}
return *isolate->factory()->NewNumberFromInt(written);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmStringMeasureUtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string(String::cast(args[0]), isolate);
string = String::Flatten(isolate, string);
int length;
{
DisallowGarbageCollection no_gc;
String::FlatContent content = string->GetFlatContent(no_gc);
DCHECK(content.IsFlat());
if (content.IsOneByte()) {
length = MeasureWtf8(content.ToOneByteVector());
} else {
base::Vector<const base::uc16> code_units = content.ToUC16Vector();
if (unibrow::Utf16::HasUnpairedSurrogate(code_units.begin(),
code_units.size())) {
length = -1;
} else {
length = MeasureWtf8(code_units);
}
}
}
return *isolate->factory()->NewNumberFromInt(length);
}
RUNTIME_FUNCTION(Runtime_WasmStringMeasureWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string(String::cast(args[0]), isolate);
int length = MeasureWtf8(isolate, string);
return *isolate->factory()->NewNumberFromInt(length);
}
RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(5, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
uint32_t utf8_variant_value = args.positive_smi_value_at(2);
Handle<String> string(String::cast(args[3]), isolate);
double offset_double = args.number_value_at(4);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
char* memory_start =
reinterpret_cast<char*>(trusted_instance_data->memory_base(memory));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
auto get_writable_bytes =
[&](const DisallowGarbageCollection&) -> base::Vector<char> {
return {memory_start, trusted_instance_data->memory_size(memory)};
};
return EncodeWtf8(isolate, utf8_variant, string, get_writable_bytes, offset,
MessageTemplate::kWasmTrapMemOutOfBounds);
}
RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf8Array) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(4, args.length());
HandleScope scope(isolate);
uint32_t utf8_variant_value = args.positive_smi_value_at(0);
Handle<String> string(String::cast(args[1]), isolate);
Handle<WasmArray> array(WasmArray::cast(args[2]), isolate);
uint32_t start = NumberToUint32(args[3]);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
auto get_writable_bytes =
[&](const DisallowGarbageCollection&) -> base::Vector<char> {
return {reinterpret_cast<char*>(array->ElementAddress(0)), array->length()};
};
return EncodeWtf8(isolate, utf8_variant, string, get_writable_bytes, start,
MessageTemplate::kWasmTrapArrayOutOfBounds);
}
RUNTIME_FUNCTION(Runtime_WasmStringToUtf8Array) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string(String::cast(args[0]), isolate);
uint32_t length = MeasureWtf8(isolate, string);
wasm::WasmValue initial_value(int8_t{0});
Tagged<WeakArrayList> rtts = isolate->heap()->wasm_canonical_rtts();
// This function can only get called from Wasm code, so we can safely assume
// that the canonical RTT is still around.
Handle<Map> map = handle(
Map::cast(rtts->Get(wasm::TypeCanonicalizer::kPredefinedArrayI8Index)
.GetHeapObject()),
isolate);
Handle<WasmArray> array = isolate->factory()->NewWasmArray(
wasm::kWasmI8, length, initial_value, map);
auto get_writable_bytes =
[&](const DisallowGarbageCollection&) -> base::Vector<char> {
return {reinterpret_cast<char*>(array->ElementAddress(0)), length};
};
Tagged<Object> write_result =
EncodeWtf8(isolate, unibrow::Utf8Variant::kLossyUtf8, string,
get_writable_bytes, 0, MessageTemplate::kNone);
DCHECK(IsNumber(write_result) && Object::Number(write_result) == length);
USE(write_result);
return *array;
}
RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf16) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(6, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
Tagged<String> string = String::cast(args[2]);
double offset_double = args.number_value_at(3);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
uint32_t start = args.positive_smi_value_at(4);
uint32_t length = args.positive_smi_value_at(5);
DCHECK(base::IsInBounds<uint32_t>(start, length, string->length()));
size_t mem_size = trusted_instance_data->memory_size(memory);
static_assert(String::kMaxLength <=
(std::numeric_limits<size_t>::max() / sizeof(base::uc16)));
if (!base::IsInBounds<size_t>(offset, length * sizeof(base::uc16),
mem_size)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
if (offset & 1) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapUnalignedAccess);
}
#if defined(V8_TARGET_LITTLE_ENDIAN)
uint16_t* dst = reinterpret_cast<uint16_t*>(
trusted_instance_data->memory_base(memory) + offset);
String::WriteToFlat(string, dst, start, length);
#elif defined(V8_TARGET_BIG_ENDIAN)
// TODO(12868): The host is big-endian but we need to write the string
// contents as little-endian.
USE(string);
USE(start);
UNIMPLEMENTED();
#else
#error Unknown endianness
#endif
return Smi::zero(); // Unused.
}
RUNTIME_FUNCTION(Runtime_WasmStringAsWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string(String::cast(args[0]), isolate);
int wtf8_length = MeasureWtf8(isolate, string);
Handle<ByteArray> array = isolate->factory()->NewByteArray(wtf8_length);
auto utf8_variant = unibrow::Utf8Variant::kWtf8;
auto get_writable_bytes =
[&](const DisallowGarbageCollection&) -> base::Vector<char> {
return {reinterpret_cast<char*>(array->begin()),
static_cast<size_t>(wtf8_length)};
};
EncodeWtf8(isolate, utf8_variant, string, get_writable_bytes, 0,
MessageTemplate::kWasmTrapArrayOutOfBounds);
return *array;
}
RUNTIME_FUNCTION(Runtime_WasmStringViewWtf8Encode) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(7, args.length());
HandleScope scope(isolate);
Tagged<WasmTrustedInstanceData> trusted_instance_data =
WasmTrustedInstanceData::cast(args[0]);
uint32_t utf8_variant_value = args.positive_smi_value_at(1);
Handle<ByteArray> array(ByteArray::cast(args[2]), isolate);
double addr_double = args.number_value_at(3);
uintptr_t addr = static_cast<uintptr_t>(addr_double);
uint32_t start = NumberToUint32(args[4]);
uint32_t end = NumberToUint32(args[5]);
uint32_t memory = args.positive_smi_value_at(6);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
DCHECK_LE(start, end);
DCHECK(base::IsInBounds<size_t>(start, end - start, array->length()));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
size_t length = end - start;
if (!base::IsInBounds<size_t>(addr, length,
trusted_instance_data->memory_size(memory))) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
uint8_t* memory_start = trusted_instance_data->memory_base(memory);
const uint8_t* src = reinterpret_cast<const uint8_t*>(array->begin() + start);
uint8_t* dst = memory_start + addr;
std::vector<size_t> surrogates;
if (utf8_variant != unibrow::Utf8Variant::kWtf8) {
unibrow::Wtf8::ScanForSurrogates({src, length}, &surrogates);
if (utf8_variant == unibrow::Utf8Variant::kUtf8 && !surrogates.empty()) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapStringIsolatedSurrogate);
}
}
MemCopy(dst, src, length);
for (size_t surrogate : surrogates) {
DCHECK_LT(surrogate, length);
DCHECK_EQ(utf8_variant, unibrow::Utf8Variant::kLossyUtf8);
unibrow::Utf8::Encode(reinterpret_cast<char*>(dst + surrogate),
unibrow::Utf8::kBadChar, 0, false);
}
// Unused.
return Tagged<Smi>(0);
}
RUNTIME_FUNCTION(Runtime_WasmStringViewWtf8Slice) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(3, args.length());
HandleScope scope(isolate);
Handle<ByteArray> array(ByteArray::cast(args[0]), isolate);
uint32_t start = NumberToUint32(args[1]);
uint32_t end = NumberToUint32(args[2]);
DCHECK_LT(start, end);
DCHECK(base::IsInBounds<size_t>(start, end - start, array->length()));
// This can't throw because the result can't be too long if the input wasn't,
// and encoding failures are ruled out too because {start}/{end} are aligned.
return *isolate->factory()
->NewStringFromUtf8(array, start, end,
unibrow::Utf8Variant::kWtf8)
.ToHandleChecked();
}
RUNTIME_FUNCTION(Runtime_WasmStringFromCodePoint) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
uint32_t code_point = NumberToUint32(args[0]);
if (code_point <= unibrow::Utf16::kMaxNonSurrogateCharCode) {
return *isolate->factory()->LookupSingleCharacterStringFromCode(code_point);
}
if (code_point > 0x10FFFF) {
// Allocate a new number to preserve the to-uint conversion (e.g. if
// args[0] == -1, we want the error message to report 4294967295).
return ThrowWasmError(isolate, MessageTemplate::kInvalidCodePoint,
{isolate->factory()->NewNumberFromUint(code_point)});
}
base::uc16 char_buffer[] = {
unibrow::Utf16::LeadSurrogate(code_point),
unibrow::Utf16::TrailSurrogate(code_point),
};
Handle<SeqTwoByteString> result =
isolate->factory()
->NewRawTwoByteString(arraysize(char_buffer))
.ToHandleChecked();
DisallowGarbageCollection no_gc;
CopyChars(result->GetChars(no_gc), char_buffer, arraysize(char_buffer));
return *result;
}
RUNTIME_FUNCTION(Runtime_WasmStringHash) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
Tagged<String> string(String::cast(args[0]));
uint32_t hash = string->EnsureHash();
return Smi::FromInt(static_cast<int>(hash));
}
} // namespace internal
} // namespace v8