blob: bfdb932667c8cba3c7d1ab1f7820424c708995b0 [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/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/module-compiler.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-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_;
};
WasmInstanceObject GetWasmInstanceOnStackTop(
Isolate* isolate,
std::initializer_list<StackFrame::Type> skipped_frame_types = {
StackFrame::EXIT}) {
return FrameFinder<WasmFrame>(isolate, skipped_frame_types)
.frame()
->wasm_instance();
}
Context GetNativeContextFromWasmInstanceOnStackTop(Isolate* isolate) {
return GetWasmInstanceOnStackTop(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_pending_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_;
};
Object ThrowWasmError(Isolate* isolate, MessageTemplate message,
Handle<Object> arg0 = Handle<Object>()) {
Handle<JSObject> error_obj =
isolate->factory()->NewWasmRuntimeError(message, arg0);
JSObject::AddProperty(isolate, error_obj,
isolate->factory()->wasm_uncatchable_symbol(),
isolate->factory()->true_value(), NONE);
return isolate->Throw(*error_obj);
}
} // namespace
// 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 = internal::wasm::JSToWasmObject(
isolate, value, expected_canonical, &error_message)
.ToHandle(&result);
Object ret = success ? *result
: isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kWasmTrapJSTypeError));
if (thread_in_wasm && !isolate->has_pending_exception()) {
trap_handler::SetThreadInWasm();
}
return ret;
}
RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
// {delta_pages} is checked to be a positive smi in the WasmMemoryGrow builtin
// which calls this runtime function.
uint32_t delta_pages = args.positive_smi_value_at(1);
int ret = WasmMemoryObject::Grow(
isolate, handle(instance.memory_object(), isolate), delta_pages);
// The WasmMemoryGrow builtin which calls this runtime function expects us to
// always return a Smi.
DCHECK(!isolate->has_pending_exception());
return Smi::FromInt(ret);
}
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_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);
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());
isolate->set_context(GetNativeContextFromWasmInstanceOnStackTop(isolate));
Handle<WasmExceptionTag> tag(WasmExceptionTag::cast(args[0]), isolate);
Handle<FixedArray> values(FixedArray::cast(args[1]), isolate);
Handle<WasmExceptionPackage> exception =
WasmExceptionPackage::New(isolate, tag, values);
wasm::GetWasmEngine()->SampleThrowEvent(isolate);
return isolate->Throw(*exception);
}
RUNTIME_FUNCTION(Runtime_WasmReThrow) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
wasm::GetWasmEngine()->SampleRethrowEvent(isolate);
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.JsHasOverflowed()) return isolate->StackOverflow();
return isolate->stack_guard()->HandleInterrupts();
}
RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
ClearThreadInWasmScope wasm_flag(isolate);
DisallowHeapAllocation no_gc;
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
int func_index = args.smi_value_at(1);
DCHECK(isolate->context().is_null());
isolate->set_context(instance.native_context());
bool success = wasm::CompileLazy(isolate, instance, func_index);
if (!success) {
DCHECK(v8_flags.wasm_lazy_validation);
AllowHeapAllocation throwing_unwinds_the_stack;
wasm::ThrowLazyCompilationError(
isolate, instance.module_object().native_module(), func_index);
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots{isolate}.exception();
}
return Smi::FromInt(wasm::JumpTableOffset(instance.module(), func_index));
}
RUNTIME_FUNCTION(Runtime_WasmAllocateFeedbackVector) {
ClearThreadInWasmScope wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<WasmInstanceObject> instance(WasmInstanceObject::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 = instance->module_object().native_module();
DCHECK(native_module->enabled_features().has_inlining());
// 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(instance->native_context());
const wasm::WasmModule* module = native_module->module();
int func_index = declared_func_index + module->num_imported_functions;
int num_slots = native_module->enabled_features().has_inlining()
? NumFeedbackSlots(module, func_index)
: 0;
Handle<FixedArray> vector =
isolate->factory()->NewFixedArrayWithZeroes(num_slots);
DCHECK_EQ(instance->feedback_vectors().get(declared_func_index), Smi::zero());
instance->feedback_vectors().set(declared_func_index, *vector);
return *vector;
}
namespace {
void ReplaceWrapper(Isolate* isolate, Handle<WasmInstanceObject> instance,
int function_index, Handle<Code> wrapper_code) {
Handle<WasmInternalFunction> internal =
WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
function_index)
.ToHandleChecked();
Handle<JSFunction> exported_function =
WasmInternalFunction::GetOrCreateExternal(internal);
exported_function->set_code(*wrapper_code);
WasmExportedFunctionData function_data =
exported_function->shared().wasm_exported_function_data();
function_data.set_wrapper_code(*wrapper_code);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmCompileWrapper) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<WasmInstanceObject> instance(WasmInstanceObject::cast(args[0]),
isolate);
Handle<WasmExportedFunctionData> function_data(
WasmExportedFunctionData::cast(args[1]), isolate);
DCHECK(isolate->context().is_null());
isolate->set_context(instance->native_context());
const wasm::WasmModule* module = instance->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.
if (WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
function_index)
.is_null()) {
DCHECK_EQ(function_index, module->start_function_index);
return ReadOnlyRoots(isolate).undefined_value();
}
Handle<Code> wrapper_code =
wasm::JSToWasmWrapperCompilationUnit::CompileSpecificJSToWasmWrapper(
isolate, sig, canonical_sig_index, module);
// Replace the wrapper for the function that triggered the tier-up.
// This is to verify that the wrapper is replaced, even if the function
// is implicitly exported and is not part of the export_table.
ReplaceWrapper(isolate, instance, 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, instance, index, wrapper_code);
}
}
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());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
FrameFinder<WasmFrame> frame_finder(isolate);
int func_index = frame_finder.frame()->function_index();
DCHECK_EQ(instance, frame_finder.frame()->wasm_instance());
wasm::TriggerTierUp(instance, 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).
Object result = isolate->stack_guard()->HandleInterrupts();
if (result.IsException()) return result;
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmAtomicNotify) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
double offset_double = args.number_value_at(1);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
uint32_t count = NumberToUint32(args[2]);
Handle<JSArrayBuffer> array_buffer{instance.memory_object().array_buffer(),
isolate};
// Should have trapped if address was OOB.
DCHECK_LT(offset, array_buffer->byte_length());
if (!array_buffer->is_shared()) return Smi::FromInt(0);
return FutexEmulation::Wake(array_buffer, offset, count);
}
RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait) {
ClearThreadInWasmScope clear_wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
double offset_double = args.number_value_at(1);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
int32_t expected_value = NumberToInt32(args[2]);
BigInt timeout_ns = BigInt::cast(args[3]);
Handle<JSArrayBuffer> array_buffer{instance.memory_object().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(4, args.length());
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
double offset_double = args.number_value_at(1);
uintptr_t offset = static_cast<uintptr_t>(offset_double);
BigInt expected_value = BigInt::cast(args[2]);
BigInt timeout_ns = BigInt::cast(args[3]);
Handle<JSArrayBuffer> array_buffer{instance.memory_object().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 {
Object ThrowTableOutOfBounds(Isolate* isolate,
Handle<WasmInstanceObject> instance) {
// 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(instance->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<WasmInstanceObject> instance(WasmInstanceObject::cast(args[0]),
isolate);
uint32_t function_index = args.positive_smi_value_at(1);
return *WasmInstanceObject::GetOrCreateWasmInternalFunction(isolate, instance,
function_index);
}
RUNTIME_FUNCTION(Runtime_WasmInternalFunctionCreateExternal) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
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());
WasmInstanceObject instance = WasmInstanceObject::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, instance.tables().length());
auto table = handle(WasmTableObject::cast(instance.tables().get(table_index)),
isolate);
// We only use the runtime call for lazily initialized function references.
DCHECK(
table->instance().IsUndefined()
? table->type() == wasm::kWasmFuncRef
: IsSubtypeOf(table->type(), wasm::kWasmFuncRef,
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());
WasmInstanceObject instance = WasmInstanceObject::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, instance.tables().length());
auto table = handle(WasmTableObject::cast(instance.tables().get(table_index)),
isolate);
// We only use the runtime call for lazily initialized function references.
DCHECK(
table->instance().IsUndefined()
? table->type() == wasm::kWasmFuncRef
: IsSubtypeOf(table->type(), wasm::kWasmFuncRef,
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<WasmInstanceObject> instance(WasmInstanceObject::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());
base::Optional<MessageTemplate> opt_error =
WasmInstanceObject::InitTableEntries(isolate, instance, 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<WasmInstanceObject> instance(WasmInstanceObject::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 = !WasmInstanceObject::CopyTableEntries(
isolate, instance, table_dst_index, table_src_index, dst, src, count);
if (oob) return ThrowTableOutOfBounds(isolate, instance);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTableGrow) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
WasmInstanceObject instance = WasmInstanceObject::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(instance.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<WasmInstanceObject> instance(WasmInstanceObject::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(instance->tables().get(table_index)), isolate);
uint32_t table_size = table->current_length();
if (start > table_size) {
return ThrowTableOutOfBounds(isolate, instance);
}
// 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, instance);
}
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<WasmInstanceObject> instance,
WasmFrame* frame) {
Handle<Script> script{instance->module_object().script(), isolate};
auto* debug_info = instance->module_object().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(), !!instance->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::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())
.set_break_on_entry(false);
}
DCHECK(!instance->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();
auto instance = handle(frame->wasm_instance(), isolate);
isolate->set_context(instance->native_context());
if (!ExecuteWasmDebugBreaks(isolate, instance, 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 =
instance->module_object().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()) {
Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
// Interrupt handling can create an exception, including the
// termination exception.
if (interrupt_object.IsException(isolate)) return interrupt_object;
DCHECK(interrupt_object.IsUndefined(isolate));
}
return ReadOnlyRoots(isolate).undefined_value();
}
// Assumes copy ranges are in-bounds and copy length > 0.
RUNTIME_FUNCTION(Runtime_WasmArrayCopy) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DisallowGarbageCollection no_gc;
DCHECK_EQ(5, args.length());
WasmArray dst_array = WasmArray::cast(args[0]);
uint32_t dst_index = args.positive_smi_value_at(1);
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<WasmInstanceObject> instance(WasmInstanceObject::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,
instance->data_segment_sizes().get(segment_index))) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapDataSegmentOutOfBounds);
}
Address source =
instance->data_segment_starts().get(segment_index) + offset;
return *isolate->factory()->NewWasmArrayFromMemory(length, rtt, source);
} else {
Handle<Object> elem_segment_raw =
handle(instance->element_segments().get(segment_index), isolate);
const wasm::WasmElemSegment* module_elem_segment =
&instance->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 =
elem_segment_raw->IsFixedArray()
? 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);
}
Handle<Object> result = isolate->factory()->NewWasmArrayFromElementSegment(
instance, segment_index, offset, length, rtt);
if (result->IsSmi()) {
return ThrowWasmError(
isolate, static_cast<MessageTemplate>(result->ToSmi().value()));
} else {
return *result;
}
}
}
RUNTIME_FUNCTION(Runtime_WasmArrayInitSegment) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(6, args.length());
Handle<WasmInstanceObject> instance(WasmInstanceObject::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,
instance->data_segment_sizes().get(segment_index))) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapDataSegmentOutOfBounds);
}
Address source =
instance->data_segment_starts().get(segment_index) + segment_offset;
Address dest = array->ElementAddress(array_index);
MemCopy(reinterpret_cast<void*>(dest), reinterpret_cast<void*>(source),
length_in_bytes);
return *isolate->factory()->undefined_value();
} else {
Handle<Object> elem_segment_raw =
handle(instance->element_segments().get(segment_index), isolate);
const wasm::WasmElemSegment* module_elem_segment =
&instance->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 =
elem_segment_raw->IsFixedArray()
? 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);
base::Optional<MessageTemplate> opt_error =
wasm::InitializeElementSegment(&zone, isolate, instance, segment_index);
if (opt_error.has_value()) {
return ThrowWasmError(isolate, opt_error.value());
}
auto elements = handle(
FixedArray::cast(instance->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();
}
}
namespace {
// Synchronize the stack limit with the active continuation for stack-switching.
// This can be done before or after changing the stack pointer itself, as long
// as we update both before the next stack check.
// {StackGuard::SetStackLimit} doesn't update the value of the jslimit if it
// contains a sentinel value, and it is also thread-safe. So if an interrupt is
// requested before, during or after this call, it will be preserved and handled
// at the next stack check.
void SyncStackLimit(Isolate* isolate) {
DisallowGarbageCollection no_gc;
auto continuation = WasmContinuationObject::cast(
isolate->root(RootIndex::kActiveContinuation));
wasm::StackMemory* stack =
Managed<wasm::StackMemory>::cast(continuation.stack()).raw();
if (v8_flags.trace_wasm_stack_switching) {
PrintF("Switch to stack #%d\n", stack->id());
}
uintptr_t limit = reinterpret_cast<uintptr_t>(stack->jmpbuf()->stack_limit);
isolate->stack_guard()->SetStackLimit(limit);
isolate->RecordStackSwitchForScanning();
}
} // namespace
// Allocate a new suspender, and prepare for stack switching by updating the
// active continuation, active suspender and stack limit.
RUNTIME_FUNCTION(Runtime_WasmAllocateSuspender) {
CHECK(v8_flags.experimental_wasm_stack_switching);
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);
SyncStackLimit(isolate);
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;
}
// Update the stack limit after a stack switch, and preserve pending interrupts.
RUNTIME_FUNCTION(Runtime_WasmSyncStackLimit) {
CHECK(v8_flags.experimental_wasm_stack_switching);
SyncStackLimit(isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
#define RETURN_RESULT_OR_TRAP(call) \
do { \
Handle<Object> result; \
if (!(call).ToHandle(&result)) { \
DCHECK(isolate->has_pending_exception()); \
/* Mark any exception as uncatchable by Wasm. */ \
Handle<JSObject> exception(JSObject::cast(isolate->pending_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_pending_exception()); \
return *result; \
} while (false)
// 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);
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
uint32_t utf8_variant_value = args.positive_smi_value_at(2);
uint32_t offset = NumberToUint32(args[3]);
uint32_t size = NumberToUint32(args[4]);
DCHECK_EQ(memory, 0);
USE(memory);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
// TODO(13918): Support multiple memories.
uint64_t mem_size = instance.memory0_size();
if (!base::IsInBounds<uint64_t>(offset, size, mem_size)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
const base::Vector<const uint8_t> bytes{instance.memory0_start() + offset,
size};
MaybeHandle<v8::internal::String> result_string =
isolate->factory()->NewStringFromUtf8(bytes, utf8_variant);
if (utf8_variant == unibrow::Utf8Variant::kUtf8NoTrap) {
DCHECK(!isolate->has_pending_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_pending_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);
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
uint32_t offset = NumberToUint32(args[2]);
uint32_t size_in_codeunits = NumberToUint32(args[3]);
DCHECK_EQ(memory, 0);
USE(memory);
// TODO(13918): Support multiple memories.
uint64_t mem_size = instance.memory0_size();
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 = instance.memory0_start() + 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));
}
// 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);
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
static_assert(
base::IsInRange(wasm::kV8MaxWasmStringLiterals, 0, Smi::kMaxValue));
uint32_t index = args.positive_smi_value_at(1);
DCHECK_LT(index, instance.module()->stringref_literals.size());
const wasm::WasmStringRefLiteral& literal =
instance.module()->stringref_literals[index];
const base::Vector<const uint8_t> module_bytes =
instance.module_object().native_module()->wire_bytes();
const base::Vector<const uint8_t> string_bytes =
module_bytes.SubVector(literal.source.offset(),
literal.source.offset() + literal.source.length());
// TODO(12868): No need to re-validate WTF-8. Also, result should be cached.
return *isolate->factory()
->NewStringFromUtf8(string_bytes, unibrow::Utf8Variant::kWtf8)
.ToHandleChecked();
}
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>
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);
WasmInstanceObject instance = WasmInstanceObject::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);
uint32_t offset = NumberToUint32(args[4]);
DCHECK_EQ(memory, 0);
USE(memory);
DCHECK(utf8_variant_value <=
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
// TODO(13918): Support multiple memories.
char* memory_start = reinterpret_cast<char*>(instance.memory0_start());
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
auto get_writable_bytes =
[&](const DisallowGarbageCollection&) -> base::Vector<char> {
return {memory_start, instance.memory0_size()};
};
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_WasmStringEncodeWtf16) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(6, args.length());
HandleScope scope(isolate);
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
uint32_t memory = args.positive_smi_value_at(1);
String string = String::cast(args[2]);
uint32_t offset = NumberToUint32(args[3]);
uint32_t start = args.positive_smi_value_at(4);
uint32_t length = args.positive_smi_value_at(5);
DCHECK_EQ(memory, 0);
USE(memory);
DCHECK(base::IsInBounds<uint32_t>(start, length, string.length()));
// TODO(13918): Support multiple memories.
size_t mem_size = instance.memory0_size();
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*>(instance.memory0_start() + 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->GetDataStartAddress()),
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(6, args.length());
HandleScope scope(isolate);
WasmInstanceObject instance = WasmInstanceObject::cast(args[0]);
uint32_t utf8_variant_value = args.positive_smi_value_at(1);
Handle<ByteArray> array(ByteArray::cast(args[2]), isolate);
uint32_t addr = NumberToUint32(args[3]);
uint32_t start = NumberToUint32(args[4]);
uint32_t end = NumberToUint32(args[5]);
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;
// TODO(13918): Support multiple memories.
if (!base::IsInBounds<size_t>(addr, length, instance.memory0_size())) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapMemOutOfBounds);
}
uint8_t* memory_start = reinterpret_cast<uint8_t*>(instance.memory0_start());
const uint8_t* src =
reinterpret_cast<const uint8_t*>(array->GetDataStartAddress() + 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 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) {
return ThrowWasmError(isolate, MessageTemplate::kInvalidCodePoint,
handle(args[0], isolate));
}
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());
String string(String::cast(args[0]));
uint32_t hash = string.EnsureHash();
return Smi::FromInt(static_cast<int>(hash));
}
} // namespace internal
} // namespace v8