| // 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 <optional> |
| |
| #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/deoptimizer/deoptimizer.h" |
| #include "src/execution/arguments-inl.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/execution/frames.h" |
| #include "src/handles/handles.h" |
| #include "src/heap/factory.h" |
| #include "src/heap/read-only-heap.h" |
| #include "src/numbers/conversions.h" |
| #include "src/objects/dictionary-inl.h" |
| #include "src/objects/js-promise-inl.h" |
| #include "src/objects/lookup-inl.h" |
| #include "src/objects/managed-inl.h" |
| #include "src/objects/object-conversions-inl.h" |
| #include "src/objects/object-list-macros.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/property-descriptor.h" |
| #include "src/runtime/runtime-utils.h" |
| #include "src/strings/unicode-inl.h" |
| #include "src/trap-handler/trap-handler.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/signature-hashing.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-globals.h" |
| #include "src/wasm/wasm-export-wrapper-cache.h" |
| #include "src/wasm/wasm-objects-inl.h" |
| #include "src/wasm/wasm-opcodes-inl.h" |
| #include "src/wasm/wasm-stack-wrapper-cache.h" |
| #include "src/wasm/wasm-subtyping.h" |
| #include "src/wasm/wasm-value.h" |
| |
| #if V8_ENABLE_WEBASSEMBLY && V8_ENABLE_DRUMBRAKE |
| #include "src/wasm/interpreter/wasm-interpreter.h" |
| #endif // V8_ENABLE_WEBASSEMBLY && V8_ENABLE_DRUMBRAKE |
| |
| namespace v8::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 DirectHandle<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(), |
| StackFrameIterator::FirstStackOnly{}) { |
| // 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<Object> ThrowWasmError( |
| Isolate* isolate, MessageTemplate message, |
| std::initializer_list<DirectHandle<Object>> args = {}) { |
| #if V8_ENABLE_DRUMBRAKE |
| if (v8_flags.wasm_jitless) { |
| // Store the trap reason to be retrieved later when the interpreter will |
| // trap while detecting the thrown exception. |
| wasm::WasmInterpreterThread::SetRuntimeLastWasmError(isolate, message); |
| } |
| #endif // V8_ENABLE_DRUMBRAKE |
| |
| DirectHandle<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_WasmWasmToJSObject) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<Object> value(args[0], isolate); |
| return *wasm::WasmToJSObject(isolate, value); |
| } |
| |
| // Parameters: |
| // args[0]: the object, any JS value. |
| // args[1]: the expected canonicalized ValueType, Smi-tagged. |
| // 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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<Object> value(args[0], isolate); |
| // Make sure ValueType fits properly in a Smi. |
| static_assert(wasm::CanonicalValueType::kLastUsedBit + 1 <= kSmiValueSize); |
| int raw_type = args.smi_value_at(1); |
| |
| wasm::CanonicalValueType expected = |
| wasm::CanonicalValueType::FromRawBitField(raw_type); |
| const char* error_message; |
| DirectHandle<Object> result; |
| bool success = JSToWasmObject(isolate, value, expected, &error_message) |
| .ToHandle(&result); |
| Tagged<Object> ret = success |
| ? *result |
| : isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kWasmTrapJSTypeError)); |
| return ret; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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); |
| |
| DirectHandle<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() || |
| IsTerminationException(isolate->exception())); |
| return Smi::FromInt(ret); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TrapHandlerThrowWasmError) { |
| CHECK(isolate->IsOnCentralStack()); |
| HandleScope scope(isolate); |
| FrameFinder<WasmFrame> frame_finder(isolate, {StackFrame::EXIT}); |
| WasmFrame* frame = frame_finder.frame(); |
| int pos = frame->position(); |
| |
| 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; |
| } else if (op == wasm::kAtomicPrefix) { |
| op = wasm::Decoder{wire_bytes} |
| .read_prefixed_opcode<wasm::Decoder::NoValidationTag>( |
| &wire_bytes.begin()[pos]) |
| .first; |
| // shared-everything atomic instructions. |
| if (op >= 0xFE4F) { |
| message = MessageTemplate::kWasmTrapNullDereference; |
| } |
| #define CASE(name, ...) || op == wasm::kExpr##name |
| DCHECK_EQ(op >= 0xFE4F, false FOREACH_ATOMIC_GC_OPCODE(CASE)); |
| #undef CASE |
| } |
| return ThrowWasmError(isolate, message); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ThrowWasmError) { |
| DCHECK(isolate->IsOnCentralStack()); |
| 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) { |
| SealHandleScope shs(isolate); |
| DCHECK_LE(0, args.length()); |
| return isolate->StackOverflow(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmThrowJSTypeError) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| THROW_NEW_ERROR_RETURN_FAILURE( |
| isolate, NewTypeError(MessageTemplate::kWasmTrapJSTypeError)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ThrowWasmJSPISuspendError) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| MessageTemplate message = MessageTemplate::kWasmSuspendJSFrames; |
| Tagged<WasmSuspenderObject> suspender = |
| isolate->isolate_data()->active_suspender(); |
| if (IsUndefined(suspender->resume())) { |
| // We are not within a valid "WebAssembly.promising" scope. This is either a |
| // suspender created for a non-promising export call under the stress mode, |
| // or the sentinel suspender representing the initial stack. |
| // Throw with a more precise error message. |
| message = MessageTemplate::kWasmSuspendError; |
| } |
| DirectHandle<JSObject> error_obj = |
| isolate->factory()->NewWasmSuspendError(message); |
| return isolate->Throw(*error_obj); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ThrowWasmFXSuspendError) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| MessageTemplate message = MessageTemplate::kWasmFXSuspendError; |
| DirectHandle<JSObject> error_obj = |
| isolate->factory()->NewWasmSuspendError(message); |
| return isolate->Throw(*error_obj); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmThrowRangeError) { |
| 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) { |
| 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()); |
| DirectHandle<String> op_name = |
| isolate->factory()->NewStringFromAsciiChecked(ToString(op)); |
| DirectHandle<Object> value(args[1], isolate); |
| |
| THROW_NEW_ERROR_RETURN_FAILURE(isolate, |
| NewTypeError(message_id, op_name, value)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmThrowDataViewDetachedError) { |
| 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()); |
| DirectHandle<String> op_name = |
| isolate->factory()->NewStringFromAsciiChecked(ToString(op)); |
| |
| THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(message_id, op_name)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmThrowTypeError) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| MessageTemplate message_id = MessageTemplateFromInt(args.smi_value_at(0)); |
| DirectHandle<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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[2]); |
| Tagged<Context> context = trusted_instance_data->native_context(); |
| isolate->set_context(context); |
| DirectHandle<WasmExceptionTag> tag(Cast<WasmExceptionTag>(args[0]), isolate); |
| DirectHandle<FixedArray> values(Cast<FixedArray>(args[1]), isolate); |
| auto js_tag = Cast<WasmTagObject>(context->wasm_js_tag()); |
| if (*tag == js_tag->tag()) { |
| return isolate->Throw(values->get(0)); |
| } else { |
| DirectHandle<WasmExceptionPackage> exception = |
| WasmExceptionPackage::New(isolate, tag, values); |
| return isolate->Throw(*exception); |
| } |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmReThrow) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| return isolate->ReThrow(args[0]); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStackGuard) { |
| SealHandleScope shs(isolate); |
| DCHECK_EQ(1, args.length()); |
| TRACE_EVENT("v8.execute", "V8.StackGuard"); |
| |
| uint32_t gap = args.positive_smi_value_at(0); |
| |
| // Check if this is a real stack overflow. |
| StackLimitCheck check(isolate); |
| if (check.WasmHasOverflowed(gap)) return isolate->StackOverflow(); |
| |
| return isolate->stack_guard()->HandleInterrupts( |
| StackGuard::InterruptLevel::kAnyEffect); |
| } |
| |
| // For loop back edges in optimized code. Avoids triggering side effects that |
| // could get in the way of optimizations, and doesn't need to check for real |
| // stack overflows because loops don't change the stack height. |
| // Note: API interrupts for debugging purposes can execute arbitrary JS, |
| // and we don't guard against that here. So for very particular (and presumably |
| // very unlikely) circumstances, debugging sessions can cause crashes. |
| // To properly fix that, we should implement lazy-deopt support for Wasm. |
| RUNTIME_FUNCTION(Runtime_WasmStackGuardLoop) { |
| DCHECK_EQ(0, args.length()); |
| SealHandleScope shs(isolate); |
| TRACE_EVENT("v8.execute", "V8.StackGuard"); |
| |
| return isolate->stack_guard()->HandleInterrupts( |
| StackGuard::InterruptLevel::kNoHeapWrites); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmCompileLazy) { |
| SealHandleScope shs(isolate); |
| DCHECK_EQ(2, args.length()); |
| int func_index = args.smi_value_at(1); |
| // Note: This runtime function *must not* cause a GC, because the calling |
| // builtin (also called "WasmCompileLazy") spilled all arguments to the call |
| // but those are never visited by GC. |
| DisallowGarbageCollection no_gc; |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| |
| TRACE_EVENT("v8.wasm", "wasm.CompileLazy", "func_index", func_index); |
| // A raw pointer is fine here, as the native module is kept alive by the |
| // caller implicitly (via the `WasmTrustedInstanceData`). |
| wasm::NativeModule* native_module = trusted_instance_data->native_module(); |
| |
| DCHECK(isolate->context().is_null()); |
| DCHECK(trusted_instance_data->has_native_context()); |
| isolate->set_context(trusted_instance_data->native_context()); |
| wasm::CompileLazy(isolate, native_module, func_index); |
| native_module->counter_updates()->Publish(isolate); |
| return Smi::FromInt( |
| wasm::JumpTableOffset(native_module->module(), func_index)); |
| } |
| |
| namespace { |
| Tagged<FixedArray> AllocateFeedbackVector( |
| Isolate* isolate, |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data, |
| int declared_func_index) { |
| DCHECK(isolate->context().is_null()); |
| isolate->set_context(trusted_instance_data->native_context()); |
| const wasm::WasmModule* module = |
| trusted_instance_data->native_module()->module(); |
| |
| int func_index = declared_func_index + module->num_imported_functions; |
| int num_slots = NumFeedbackSlots(module, func_index); |
| DirectHandle<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); |
| isolate->set_context({}); |
| return *vector; |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_WasmAllocateFeedbackVector) { |
| DCHECK(isolate->IsOnCentralStack()); |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| DCHECK(v8_flags.wasm_inlining); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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(); |
| // 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; |
| return AllocateFeedbackVector(isolate, trusted_instance_data, |
| declared_func_index); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmLiftoffDeoptFinish) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate); |
| // Destroy the Deoptimizer object stored on the isolate. |
| size_t deopt_frame_count = Deoptimizer::DeleteForWasm(isolate); |
| size_t i = 0; |
| |
| // For each liftoff frame, replace the smi in the feedback vector slot holding |
| // the declared function index with the actual feedback vector from the |
| // WasmTrustedInstanceData. If it doesn't exist yet, it needs to be allocated |
| // (which is also the reason why this wasn't done by the deoptimizer already.) |
| for (StackFrameIterator it(isolate); !it.done(); it.Advance()) { |
| StackFrame* frame = it.frame(); |
| if (frame->is_wasm() && WasmFrame::cast(frame)->wasm_code()->is_liftoff()) { |
| Address vector_address = |
| frame->fp() - WasmLiftoffFrameConstants::kFeedbackVectorOffset; |
| Tagged<Object> feedback_slot_value(Memory<intptr_t>(vector_address)); |
| CHECK(feedback_slot_value.IsSmi()); |
| int declared_func_index = Cast<Smi>(feedback_slot_value).value(); |
| Tagged<Object> vector = |
| trusted_instance_data->feedback_vectors()->get(declared_func_index); |
| // If the vector doesn't exist, allocate a new feedback vector. This |
| // happens if the corresponding function wasn't executed in Liftoff yet |
| // for this particular module instantiation. |
| if (vector == Smi::zero()) { |
| if (v8_flags.trace_deopt_verbose) { |
| wasm::WasmCodeRefScope code_ref_scope; |
| const int module_func_index = |
| trusted_instance_data->native_module()->num_imported_functions() + |
| declared_func_index; |
| const wasm::WasmCode* code = |
| trusted_instance_data->native_module()->GetCode( |
| module_func_index); |
| PrintF( |
| "Wasm deoptimization: allocating feedback vector for function %s " |
| "[%d]\n", |
| code ? code->DebugName().c_str() : "<no code object>", |
| module_func_index); |
| } |
| vector = AllocateFeedbackVector(isolate, trusted_instance_data, |
| declared_func_index); |
| } |
| memcpy(reinterpret_cast<void*>(vector_address), &vector, |
| sizeof(intptr_t)); |
| if (++i == deopt_frame_count) { |
| break; // All deopt frames have been visited. |
| } |
| } |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| void ReplaceJSToWasmWrapper( |
| Isolate* isolate, Tagged<WasmTrustedInstanceData> trusted_instance_data, |
| int function_index, Tagged<Code> wrapper_code) { |
| Tagged<WasmFuncRef> func_ref; |
| // Always expect a func_ref. If this fails, we are maybe compiling a wrapper |
| // for the start function. This function is only called once, so this should |
| // not happen. |
| 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)); |
| CHECK(external_function->shared()->HasWasmExportedFunctionData(isolate)); |
| Tagged<WasmExportedFunctionData> function_data = |
| external_function->shared()->wasm_exported_function_data(); |
| external_function->UpdateCode(isolate, wrapper_code); |
| function_data->set_wrapper_code(wrapper_code); |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_TierUpJSToWasmWrapper) { |
| DCHECK_EQ(1, args.length()); |
| |
| // Avoid allocating a HandleScope and handles on the fast path. |
| Tagged<WasmExportedFunctionData> function_data = |
| SbxCast<WasmExportedFunctionData>(TrustedCast<TrustedObject>(args[0])); |
| Tagged<WasmTrustedInstanceData> trusted_data = function_data->instance_data(); |
| |
| 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::CanonicalTypeIndex sig_id = |
| module->canonical_sig_id(function.sig_index); |
| const wasm::CanonicalSig* sig = |
| wasm::GetTypeCanonicalizer()->LookupFunctionSignature(sig_id); |
| |
| Tagged<CodeWrapper> maybe_cached_wrapper = |
| wasm::WasmExportWrapperCache::Get(isolate, sig_id); |
| Tagged<Code> wrapper_code; |
| if (!maybe_cached_wrapper.is_null()) { |
| wrapper_code = maybe_cached_wrapper->code(isolate); |
| } else { |
| // Set the context on the isolate and open a handle scope for allocation of |
| // new objects. Wrap {trusted_data} in a handle so it survives GCs. |
| DCHECK(isolate->context().is_null()); |
| isolate->set_context(trusted_data->native_context()); |
| HandleScope scope(isolate); |
| DirectHandle<WasmTrustedInstanceData> trusted_data_handle{trusted_data, |
| isolate}; |
| DirectHandle<Code> new_wrapper_code = |
| wasm::JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(isolate, |
| sig); |
| |
| // Compilation must have installed the wrapper into the cache. |
| DCHECK_EQ(new_wrapper_code->wrapper(), |
| wasm::WasmExportWrapperCache::Get(isolate, sig_id)); |
| |
| // Reset raw pointers still needed outside the slow path. |
| wrapper_code = *new_wrapper_code; |
| trusted_data = *trusted_data_handle; |
| function_data = {}; |
| } |
| |
| // 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. |
| ReplaceJSToWasmWrapper(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); |
| if (index == function_index) continue; // Already replaced. |
| const wasm::WasmFunction& exp_function = module->functions[index]; |
| if (module->canonical_sig_id(exp_function.sig_index) != sig_id) { |
| continue; // Different signature. |
| } |
| ReplaceJSToWasmWrapper(isolate, trusted_data, index, wrapper_code); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsWasmExternalFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| 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()); |
| DirectHandle<WasmImportData> import_data( |
| SbxCast<WasmImportData>(TrustedCast<TrustedObject>(args[0])), isolate); |
| |
| DCHECK(isolate->context().is_null()); |
| isolate->set_context(import_data->native_context()); |
| |
| const wasm::CanonicalSig* sig = import_data->sig(); |
| DirectHandle<Object> origin(import_data->call_origin(), isolate); |
| wasm::WasmCodeRefScope code_ref_scope; |
| |
| if (IsWasmInternalFunction(*origin)) { |
| // The tierup for `WasmInternalFunction` is special, as there may not be an |
| // instance. |
| int expected_arity = static_cast<int>(sig->parameter_count()); |
| wasm::ImportCallKind kind; |
| if (IsJSFunction(import_data->callable())) { |
| Tagged<SharedFunctionInfo> shared = |
| Cast<JSFunction>(import_data->callable())->shared(); |
| expected_arity = |
| shared->internal_formal_parameter_count_without_receiver(); |
| kind = wasm::ImportCallKind::kJSFunction; |
| } else { |
| kind = wasm::ImportCallKind::kUseCallBuiltin; |
| } |
| wasm::WasmImportWrapperCache* cache = wasm::GetWasmImportWrapperCache(); |
| wasm::Suspend suspend = import_data->suspend(); |
| std::shared_ptr<wasm::WasmWrapperHandle> wrapper_handle = |
| cache->GetCompiled(isolate, {kind, sig, expected_arity, suspend}); |
| DCHECK_EQ(TrustedCast<WasmInternalFunction>(*origin)->call_target(), |
| wrapper_handle->code_pointer()); |
| cache->PublishCounterUpdates(isolate); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| #ifdef DEBUG |
| int table_slot = import_data->table_slot(); |
| DirectHandle<WasmDispatchTable> dispatch_table; |
| DirectHandle<WasmDispatchTableForImports> dispatch_table_for_imports; |
| if (IsWasmDispatchTable(*origin)) { |
| dispatch_table = TrustedCast<WasmDispatchTable>(origin); |
| DCHECK_EQ(sig->index(), dispatch_table->sig(table_slot)); |
| } else { |
| dispatch_table_for_imports = |
| CheckedCast<WasmDispatchTableForImports>(origin); |
| } |
| #endif // DEBUG |
| |
| // Compile a wrapper for the target callable. |
| DirectHandle<JSReceiver> callable(Cast<JSReceiver>(import_data->callable()), |
| isolate); |
| wasm::Suspend suspend = import_data->suspend(); |
| |
| // We don't need to care about exactness of the import here, because that |
| // has already been validated (hence no kLinkError can happen here). |
| wasm::CanonicalValueType expected_type = wasm::CanonicalValueType::Ref( |
| sig->index(), SharedFlag{false}, wasm::RefTypeKind::kFunction); |
| wasm::ResolvedWasmImport resolved({}, -1, callable, expected_type, sig, |
| wasm::WellKnownImport::kUninstantiated); |
| wasm::ImportCallKind kind = resolved.kind(); |
| callable = resolved.callable(); // Update to ultimate target. |
| DCHECK_NE(wasm::ImportCallKind::kLinkError, kind); |
| int expected_arity = static_cast<int>(sig->parameter_count()); |
| if (kind == wasm::ImportCallKind ::kJSFunction) { |
| expected_arity = Cast<JSFunction>(callable) |
| ->shared() |
| ->internal_formal_parameter_count_without_receiver(); |
| } |
| |
| // Lookup or compile a wrapper. |
| wasm::WasmImportWrapperCache* cache = wasm::GetWasmImportWrapperCache(); |
| std::shared_ptr<wasm::WasmWrapperHandle> wrapper_handle = |
| cache->GetCompiled(isolate, {kind, sig, expected_arity, suspend}); |
| |
| #ifdef DEBUG |
| // Check consistency of the dispatch table's target code pointer. The code |
| // pointer is owned by the import wrapper cache and was updated when compiling |
| // the wrapper. |
| if (!dispatch_table.is_null()) { |
| DCHECK_EQ(dispatch_table->target(table_slot), |
| wrapper_handle->code_pointer()); |
| } else { |
| DCHECK_EQ(dispatch_table_for_imports->target(table_slot), |
| wrapper_handle->code_pointer()); |
| } |
| #endif // DEBUG |
| |
| cache->PublishCounterUpdates(isolate); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTriggerTierUp) { |
| SealHandleScope shs(isolate); |
| |
| { |
| // The main code does not allocate, but interrupt handling below could. |
| DisallowGarbageCollection no_gc; |
| DCHECK_EQ(1, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| |
| FrameFinder<WasmFrame> frame_finder(isolate); |
| WasmFrame* frame = frame_finder.frame(); |
| int func_index = frame->GetInnermostFunctionIndex(); |
| DCHECK_EQ(trusted_data, frame->trusted_instance_data()); |
| |
| if (V8_UNLIKELY(v8_flags.wasm_sync_tier_up && |
| !v8_flags.wasm_generate_compilation_hints && |
| !v8_flags.trace_wasm_generate_compilation_hints)) { |
| 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].store( |
| v8_flags.wasm_tiering_budget, std::memory_order_relaxed); |
| } else if (V8_UNLIKELY(v8_flags.wasm_generate_compilation_hints || |
| v8_flags.trace_wasm_generate_compilation_hints)) { |
| // In this case, we do not tierup functions. However we have to remember |
| // that we marked them for tierup. |
| base::MutexGuard marked_for_tierup_mutex_guard( |
| &trusted_data->module()->compilation_hints_mutex); |
| trusted_data->module()->marked_for_tierup.emplace(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].store( |
| v8_flags.wasm_tiering_budget, std::memory_order_relaxed); |
| } 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 (IsExceptionHole(result)) return result; |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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 = Cast<BigInt>(args[4]); |
| |
| CppGCManaged<BackingStore>::Ptr backing_store = |
| trusted_instance_data->memory_object(memory_index)->backing_store(); |
| // Should have trapped if address was OOB. |
| DCHECK_LT(offset, backing_store->byte_length()); |
| |
| // Trap if memory is not shared, or wait is not allowed on the isolate |
| if (!backing_store->is_shared() || !isolate->allow_atomics_wait()) { |
| return ThrowWasmError( |
| isolate, MessageTemplate::kAtomicsOperationNotAllowed, |
| {isolate->factory()->NewStringFromAsciiChecked("Atomics.wait")}); |
| } |
| return FutexEmulation::WaitWasm32(isolate, backing_store.raw(), offset, |
| expected_value, timeout_ns->AsInt64()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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 = Cast<BigInt>(args[3]); |
| Tagged<BigInt> timeout_ns = Cast<BigInt>(args[4]); |
| |
| CppGCManaged<BackingStore>::Ptr backing_store = |
| trusted_instance_data->memory_object(memory_index)->backing_store(); |
| // Should have trapped if address was OOB. |
| DCHECK_LT(offset, backing_store->byte_length()); |
| |
| // Trap if memory is not shared, or wait is not allowed on the isolate |
| if (!backing_store->is_shared() || !isolate->allow_atomics_wait()) { |
| return ThrowWasmError( |
| isolate, MessageTemplate::kAtomicsOperationNotAllowed, |
| {isolate->factory()->NewStringFromAsciiChecked("Atomics.wait")}); |
| } |
| return FutexEmulation::WaitWasm64(isolate, backing_store.raw(), offset, |
| expected_value->AsInt64(), |
| timeout_ns->AsInt64()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmManagedObjectWait) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| Tagged<HeapObject> object = Cast<HeapObject>(args[0]); |
| int field_offset = args.smi_value_at(1); |
| int32_t expected_value = static_cast<int32_t>(args.number_value_at(2)); |
| Tagged<HeapObject> waitqueue = Cast<HeapObject>(args[3]); |
| Tagged<BigInt> timeout_ns = Cast<BigInt>(args[4]); |
| |
| TSAN_ACQUIRE(object.address()); |
| TSAN_ACQUIRE(waitqueue.address()); |
| |
| if (!v8_flags.wasm_skip_null_checks && |
| (object == ReadOnlyRoots(isolate).wasm_null() || |
| waitqueue == ReadOnlyRoots(isolate).wasm_null())) { |
| return ThrowWasmError(isolate, MessageTemplate::kWasmTrapNullDereference); |
| } |
| |
| if (!HeapLayout::InAnySharedSpace(object) || !isolate->allow_atomics_wait()) { |
| return ThrowWasmError( |
| isolate, MessageTemplate::kAtomicsOperationNotAllowed, |
| {isolate->factory()->NewStringFromAsciiChecked("struct.wait")}); |
| } |
| |
| return FutexEmulation::WaitWasmManagedObject( |
| isolate, object, field_offset, |
| Cast<Managed<FutexManagedObjectWaitList>>(waitqueue), expected_value, |
| timeout_ns->AsInt64()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmWaitqueueNew) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| |
| // Memory fence is implemented in Managed<>::From. |
| auto ptr = std::make_shared<FutexManagedObjectWaitList>(); |
| DirectHandle<Managed<FutexManagedObjectWaitList>> managed = |
| Managed<FutexManagedObjectWaitList>::From( |
| isolate, sizeof(FutexManagedObjectWaitList), ptr, |
| AllocationType::kSharedOld); |
| return *managed; |
| } |
| |
| namespace { |
| Tagged<Object> ThrowTableOutOfBounds( |
| Isolate* isolate, |
| DirectHandle<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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| // TODO(14564): Pass WasmFuncRef here instead of WasmInternalFunction. |
| DirectHandle<WasmInternalFunction> internal( |
| TrustedCast<WasmInternalFunction>(args[0]), isolate); |
| return *WasmInternalFunction::GetOrCreateExternal(internal); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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()->ulength().value()); |
| auto table = direct_handle( |
| Cast<WasmTableObject>(trusted_instance_data->tables()->get(table_index)), |
| isolate); |
| // We only use the runtime call for lazily initialized function references. |
| DCHECK(table->unsafe_type().ref_type_kind() == wasm::RefTypeKind::kFunction); |
| |
| if (!table->is_in_bounds(entry_index)) { |
| return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); |
| } |
| |
| return *WasmTableObject::Get(isolate, table, entry_index); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| uint32_t table_index = args.positive_smi_value_at(1); |
| uint32_t entry_index = args.positive_smi_value_at(2); |
| DirectHandle<Object> element(args[3], isolate); |
| DCHECK_LT(table_index, trusted_instance_data->tables()->ulength().value()); |
| auto table = direct_handle( |
| Cast<WasmTableObject>(trusted_instance_data->tables()->get(table_index)), |
| isolate); |
| // We only use the runtime call for lazily initialized function references. |
| DCHECK(table->unsafe_type().ref_type_kind() == wasm::RefTypeKind::kFunction); |
| |
| if (!table->is_in_bounds(entry_index)) { |
| return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); |
| } |
| DirectHandle<WasmDispatchTable> dispatch_table( |
| trusted_instance_data->dispatch_table(table_index), isolate); |
| WasmTableObject::Set(isolate, table, dispatch_table, entry_index, element); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTableInit) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(6, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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()); |
| |
| std::optional<MessageTemplate> opt_error = |
| WasmTrustedInstanceData::InitTableEntries(isolate, 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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(6, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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) { |
| DCHECK(isolate->IsOnCentralStack()); |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| uint32_t table_index = args.positive_smi_value_at(1); |
| DirectHandle<Object> value(args[2], isolate); |
| uint32_t delta = args.positive_smi_value_at(3); |
| |
| DirectHandle<WasmTableObject> table( |
| Cast<WasmTableObject>(trusted_instance_data->tables()->get(table_index)), |
| isolate); |
| DirectHandle<WasmDispatchTable> dispatch_table( |
| trusted_instance_data->dispatch_table(table_index), isolate); |
| int result = |
| WasmTableObject::Grow(isolate, table, dispatch_table, delta, value); |
| |
| return Smi::FromInt(result); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTableFill) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate); |
| uint32_t table_index = args.positive_smi_value_at(1); |
| uint32_t start = args.positive_smi_value_at(2); |
| DirectHandle<Object> value(args[3], isolate); |
| uint32_t count = args.positive_smi_value_at(4); |
| |
| DirectHandle<WasmTableObject> table( |
| Cast<WasmTableObject>(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); |
| } |
| DirectHandle<WasmDispatchTable> dispatch_table( |
| trusted_instance_data->dispatch_table(table_index), isolate); |
| WasmTableObject::Fill(isolate, table, dispatch_table, start, value, |
| fill_count); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| // Returns true if any breakpoint was hit, false otherwise. |
| bool ExecuteWasmDebugBreaks( |
| Isolate* isolate, |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data, |
| WasmFrame* frame) { |
| DirectHandle<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()) { |
| MaybeDirectHandle<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(); |
| const uint32_t weak_instance_len = weak_instance_list->length().value(); |
| for (uint32_t i = 0; i < weak_instance_len; ++i) { |
| if (weak_instance_list->Get(i).IsCleared()) continue; |
| i::Cast<i::WasmInstanceObject>(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->IsFrameBlackboxed(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. |
| DirectHandle<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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| FrameFinder<WasmFrame> frame_finder( |
| isolate, {StackFrame::EXIT, StackFrame::WASM_DEBUG_BREAK}); |
| WasmFrame* frame = frame_finder.frame(); |
| DirectHandle<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 (IsExceptionHole(interrupt_object)) return interrupt_object; |
| DCHECK(IsUndefined(interrupt_object)); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| DISABLE_TSAN Tagged<Object> WasmArrayCopyImpl( |
| Isolate* isolate, Tagged<WasmArray> dst_array, uint32_t dst_index, |
| Tagged<WasmArray> src_array, uint32_t src_index, uint32_t length) { |
| 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::CanonicalValueType element_type = |
| src_array->map()->wasm_type_info()->element_type(); |
| if (element_type.is_ref()) { |
| 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(); |
| } |
| } // namespace |
| |
| // 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) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK_EQ(5, args.length()); |
| Tagged<WasmArray> dst_array = Cast<WasmArray>(args[0]); |
| uint32_t dst_index = args.positive_smi_value_at(1); |
| Tagged<WasmArray> src_array = Cast<WasmArray>(args[2]); |
| uint32_t src_index = args.positive_smi_value_at(3); |
| uint32_t length = args.positive_smi_value_at(4); |
| return WasmArrayCopyImpl(isolate, dst_array, dst_index, src_array, src_index, |
| length); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmAllocateDescriptorStruct) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_data{ |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate}; |
| DirectHandle<Map> map{Cast<Map>(args[1]), isolate}; |
| wasm::ModuleTypeIndex type_index{args.positive_smi_value_at(2)}; |
| DirectHandle<Object> first_field{args[3], isolate}; |
| return *WasmStruct::AllocateDescriptorUninitialized( |
| isolate, trusted_data, type_index, map, first_field); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmArrayNewSegment) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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); |
| DirectHandle<Map> rtt(Cast<Map>(args[4]), isolate); |
| |
| Tagged<WasmTypeInfo> type_info = rtt->wasm_type_info(); |
| wasm::CanonicalValueType element_type = type_info->element_type(); |
| AllocationType allocation = type_info->type().is_shared() |
| ? AllocationType::kSharedOld |
| : AllocationType::kYoung; |
| |
| uint32_t element_size = 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 (element_type.is_numeric()) { |
| // No chance of overflow due to the check above. |
| uint32_t length_in_bytes = length * element_size; |
| |
| wasm::WireBytesRef segment_source = |
| trusted_instance_data->data_segments()->get(segment_index); |
| if (!base::IsInBounds<uint32_t>(offset, length_in_bytes, |
| segment_source.length())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapDataSegmentOutOfBounds); |
| } |
| |
| base::Vector<const uint8_t> source = |
| trusted_instance_data->native_module()->wire_bytes() + |
| segment_source.offset() + offset; |
| return *isolate->factory()->NewWasmArrayFromMemory(length, rtt, allocation, |
| element_type, source); |
| } else { |
| DirectHandle<Object> elem_segment_raw( |
| 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. |
| uint32_t segment_length = |
| IsFixedArray(*elem_segment_raw) |
| ? Cast<FixedArray>(elem_segment_raw)->ulength().value() |
| : module_elem_segment->element_count; |
| if (!base::IsInBounds<size_t>(offset, length, segment_length)) { |
| return ThrowWasmError( |
| isolate, MessageTemplate::kWasmTrapElementSegmentOutOfBounds); |
| } |
| DirectHandle<Object> result = |
| isolate->factory()->NewWasmArrayFromElementSegment( |
| trusted_instance_data, segment_index, offset, length, rtt, |
| allocation, element_type); |
| if (IsSmi(*result)) { |
| return ThrowWasmError( |
| isolate, static_cast<MessageTemplate>(Cast<Smi>(*result).value())); |
| } else { |
| return *result; |
| } |
| } |
| } |
| |
| namespace { |
| DISABLE_TSAN Tagged<Object> WasmArrayInitSegmentImpl( |
| Isolate* isolate, |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data, |
| uint32_t segment_index, DirectHandle<WasmArray> array, uint32_t array_index, |
| uint32_t segment_offset, uint32_t length) { |
| wasm::CanonicalValueType element_type = |
| array->map()->wasm_type_info()->element_type(); |
| |
| if (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_type.value_kind_size(); |
| |
| wasm::WireBytesRef segment_source = |
| trusted_instance_data->data_segments()->get(segment_index); |
| if (!base::IsInBounds<uint32_t>(segment_offset, length_in_bytes, |
| segment_source.length())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapDataSegmentOutOfBounds); |
| } |
| |
| base::Vector<const uint8_t> wire_bytes = |
| trusted_instance_data->native_module()->wire_bytes(); |
| const uint8_t* source = |
| wire_bytes.begin() + segment_source.offset() + segment_offset; |
| void* dest = reinterpret_cast<void*>(array->ElementAddress(array_index)); |
| #if V8_TARGET_BIG_ENDIAN |
| MemCopyAndSwitchEndianness(dest, source, length, |
| element_type.value_kind_size()); |
| #else |
| MemCopy(dest, source, length_in_bytes); |
| #endif |
| return *isolate->factory()->undefined_value(); |
| } else { |
| DirectHandle<Object> elem_segment_raw( |
| 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. |
| uint32_t segment_length = |
| IsFixedArray(*elem_segment_raw) |
| ? Cast<FixedArray>(elem_segment_raw)->ulength().value() |
| : 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. |
| std::optional<MessageTemplate> opt_error = wasm::InitializeElementSegment( |
| isolate, trusted_instance_data, segment_index); |
| if (opt_error.has_value()) { |
| return ThrowWasmError(isolate, opt_error.value()); |
| } |
| |
| auto elements = direct_handle( |
| Cast<FixedArray>( |
| 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(); |
| } |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_WasmArrayInitSegment) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(6, args.length()); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate); |
| uint32_t segment_index = args.positive_smi_value_at(1); |
| DirectHandle<WasmArray> array(Cast<WasmArray>(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); |
| return WasmArrayInitSegmentImpl(isolate, trusted_instance_data, segment_index, |
| array, array_index, segment_offset, length); |
| } |
| |
| // 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); |
| DirectHandle<WasmSuspenderObject> suspender = |
| isolate->factory()->NewWasmSuspenderObjectInitialized(); |
| |
| // Update the stack state. |
| std::unique_ptr<wasm::StackMemory> target_stack = |
| isolate->stack_pool().GetOrAllocate(); |
| target_stack->jmpbuf()->parent = nullptr; |
| target_stack->jmpbuf()->stack_limit = target_stack->jslimit(); |
| target_stack->jmpbuf()->sp = target_stack->base(); |
| target_stack->jmpbuf()->fp = kNullAddress; |
| target_stack->jmpbuf()->state = wasm::JumpBuffer::Suspended; |
| target_stack->jmpbuf()->is_on_central_stack = false; |
| if (v8_flags.wasm_wasmfx) { |
| // For now JSPI does not use the WasmStackObject, and it is only set here |
| // because it is expected by WasmFX. |
| // TODO(thibaudm): We could consider using this object for JSPI too as an |
| // indirection between the WasmSuspenderObjects and the StackMemory. This |
| // would have roughly the same benefits as for WasmFX: |
| // - We would only need to allocate and manage a single EPT entry per |
| // StackMemory, |
| // - It would be easier to track ownership of the StackMemory and ensure |
| // that there is no UAF. In particular the StackMemory could track its |
| // (unique) EPT entry via {EPT::ManagedResource} and zap it when the |
| // resource is freed. |
| target_stack->set_stack_obj( |
| *isolate->factory()->NewWasmStackObject(target_stack.get())); |
| } |
| |
| // Update the suspender state. |
| Tagged<WasmSuspenderObject> active_suspender = |
| isolate->isolate_data()->active_suspender(); |
| if (v8_flags.wasm_wasmfx) { |
| // The active suspender is about to become inactive. Record the currently |
| // active stack (which may have changed due to WasmFX) for when we |
| // return to this suspender. |
| active_suspender->set_stack(isolate, |
| isolate->isolate_data()->active_stack()); |
| } |
| suspender->set_parent(active_suspender); |
| suspender->set_stack(isolate, target_stack.get()); |
| // The active stack is updated in {Isolate::SwitchStacks}. |
| isolate->isolate_data()->set_active_suspender(*suspender); |
| |
| target_stack->set_index(isolate->wasm_stacks().size()); |
| isolate->wasm_stacks().emplace_back(std::move(target_stack)); |
| for (size_t i = 0; i < isolate->wasm_stacks().size(); ++i) { |
| SLOW_DCHECK(isolate->wasm_stacks()[i]->index() == i); |
| } |
| |
| // Stack limit will be updated in WasmReturnPromiseOnSuspendAsm builtin. |
| return *suspender; |
| } |
| |
| namespace { |
| int GetWasmFrameCount(Isolate* isolate, Tagged<WasmSuspenderObject> suspender) { |
| int count = 0; |
| for (StackFrameIterator it(isolate); !it.done(); it.Advance()) { |
| StackFrame* frame = it.frame(); |
| #ifdef DEBUG |
| Tagged<WasmSuspenderObject> parent = suspender->parent(); |
| wasm::StackMemory* end_stack = parent->stack(); |
| DCHECK_NOT_NULL(end_stack); |
| bool suspender_contains_frame = false; |
| for (wasm::StackMemory* stack = isolate->isolate_data()->active_stack(); |
| stack != end_stack; stack = stack->jmpbuf()->parent) { |
| if (stack->Contains(frame->fp())) { |
| suspender_contains_frame = true; |
| break; |
| } |
| } |
| #endif |
| if (frame->is_wasm()) { |
| WasmFrame* wasm_frame = WasmFrame::cast(frame); |
| count += wasm_frame->Summarize().size(); |
| DCHECK(suspender_contains_frame); |
| } else if (frame->is_javascript()) { |
| // By construction, the first JS frame must be the JSPI entry point and is |
| // outside of the captured stack. Stop the count. |
| DCHECK(!suspender_contains_frame); |
| break; |
| } |
| } |
| return count; |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_WasmSuspended) { |
| HandleScope scope(isolate); |
| DirectHandle<JSPromise> awaited_promise(Cast<JSPromise>(args[0]), isolate); |
| DirectHandle<WasmSuspenderObject> suspender( |
| TrustedCast<WasmSuspenderObject>(args[1]), isolate); |
| |
| DirectHandle<JSPromise> throwaway = |
| isolate->factory()->NewJSPromiseWithoutHook(); |
| int skip_frame_count = GetWasmFrameCount(isolate, *suspender); |
| isolate->OnAsyncFunctionSuspended(throwaway, awaited_promise, |
| skip_frame_count); |
| throwaway->set_has_handler(true); |
| |
| if (isolate->debug()->is_active()) { |
| Tagged<Object> promise_obj = suspender->promise(); |
| if (IsJSPromise(promise_obj)) { |
| DirectHandle<JSPromise> outer_promise(Cast<JSPromise>(promise_obj), |
| isolate); |
| Object::SetProperty(isolate, throwaway, |
| isolate->factory()->promise_handled_by_symbol(), |
| outer_promise, StoreOrigin::kMaybeKeyed, |
| Just(ShouldThrow::kThrowOnError)) |
| .Check(); |
| } |
| } |
| return *throwaway; |
| } |
| |
| // Helper function needed for the stress stack switching mode. |
| // This is a runtime function to avoid writing trusted space memory from |
| // generated code. |
| RUNTIME_FUNCTION(Runtime_ClearWasmSuspenderResumeField) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| // Should only be used in stress stack switching mode. |
| CHECK(v8_flags.stress_wasm_stack_switching); |
| |
| DCHECK_EQ(1, args.length()); |
| Tagged<WasmSuspenderObject> suspender = |
| TrustedCast<WasmSuspenderObject>(args[0]); |
| suspender->set_resume(ReadOnlyRoots(isolate).undefined_value()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| class PrototypesSetup : public wasm::Decoder { |
| public: |
| struct Method { |
| enum Kind : uint8_t { kMethod = 0, kGetter = 1, kSetter = 2 }; |
| |
| Kind kind; |
| bool is_static; |
| base::Vector<const uint8_t> name; |
| }; |
| |
| PrototypesSetup(Isolate* isolate, base::Vector<const uint8_t> data) |
| : Decoder(data), isolate_(isolate) { |
| // kLength == 0 may look weird, but it's what you'd get for |
| // function wrapper(...args) { return wasm_func(this, ...args); }. |
| static constexpr int kLength = 0; |
| method_wrapper_ = isolate_->factory()->NewSharedFunctionInfoForBuiltin( |
| {}, Builtin::kWasmMethodWrapper, kLength, kDontAdapt); |
| method_wrapper_->set_native(true); |
| method_wrapper_->set_language_mode(LanguageMode::kStrict); |
| } |
| |
| MaybeDirectHandle<String> ReadUtf8String(base::Vector<const uint8_t> bytes) { |
| DirectHandle<String> result; |
| if (!isolate() |
| ->factory() |
| ->NewStringFromUtf8(bytes, unibrow::Utf8Variant::kUtf8) |
| .ToHandle(&result)) { |
| DCHECK(isolate()->has_exception()); |
| return {}; |
| } |
| return isolate()->factory()->InternalizeString(result); |
| } |
| |
| Tagged<Object> SetupPrototypes(DirectHandle<Object> constructors) { |
| uint32_t num_prototypes = consume_u32v("number of prototypes"); |
| FOR_WITH_HANDLE_SCOPE(isolate(), uint32_t proto_index = 0, proto_index, |
| proto_index < num_prototypes && ok(), proto_index++) { |
| // We have to support {null} prototypes until we try to do something |
| // with them. To avoid needing casts all over the following code, we |
| // handle that edge case separately: {prototype.is_null()} (i.e. empty |
| // handle) usually means "error", *unless* {prototype_is_null = true}, |
| // in which case it means "prototype is {null}". |
| bool prototype_is_null = false; |
| DirectHandle<JSReceiver> prototype; |
| if (!NextPrototype(&prototype_is_null).To(&prototype) && |
| !prototype_is_null) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| |
| bool fast_path = CheckFastPathEligibility(prototype); |
| |
| uint32_t has_constructor = consume_u32v("constructor"); |
| if (!ok()) break; |
| |
| if (has_constructor == 1) { |
| if (V8_UNLIKELY(prototype_is_null)) { |
| isolate()->Throw(*isolate()->factory()->NewTypeError( |
| MessageTemplate::kNonObjectPropertyStoreWithProperty, |
| isolate()->factory()->null_value(), |
| isolate()->factory()->constructor_string())); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| DirectHandle<JSFunction> constructor; |
| if (!NextFunction().To(&constructor)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| uint32_t name_length = consume_u32v("constructor name length"); |
| if (!ok()) break; |
| const uint8_t* name_start = pc(); |
| consume_bytes(name_length); |
| if (!ok()) break; |
| DirectHandle<JSFunction> wrapped_constructor = InstallConstructor( |
| prototype, constructor, {name_start, name_length}, constructors); |
| if (wrapped_constructor.is_null()) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| uint32_t num_statics = consume_u32v("number of statics"); |
| if (!ok()) break; |
| if (num_statics != 0) { |
| if (num_statics > wasm::kMaxConfigureAllStaticProperties) { |
| isolate()->Throw(*isolate()->factory()->NewRangeError( |
| MessageTemplate::kOutOfMemory, |
| isolate()->factory()->NewStringFromAsciiChecked( |
| "too many constructor properties in configureAll"))); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| ToDictionaryMode(wrapped_constructor, num_statics); |
| for (uint32_t i = 0; i < num_statics; i++) { |
| Method method = NextMethod(true); |
| if (!ok()) break; |
| DirectHandle<JSFunction> function; |
| if (!NextFunction().To(&function) || |
| !InstallMethod(wrapped_constructor, method, function)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| } |
| if (!ok()) break; |
| } |
| } else if (has_constructor > 1) { |
| // Contrary to other uses of the Decoder, the built-in offset reporting |
| // is not usable here, so we have to hand-roll it. |
| errorf(0u, "invalid constructor count %d at data+%u", has_constructor, |
| pc_offset() - 1); |
| break; |
| } |
| |
| uint32_t num_methods = consume_u32v("number of methods"); |
| if (!ok()) break; |
| if (num_methods > wasm::kMaxConfigureAllMethods) { |
| isolate()->Throw(*isolate()->factory()->NewRangeError( |
| MessageTemplate::kOutOfMemory, |
| isolate()->factory()->NewStringFromAsciiChecked( |
| "too many methods in configureAll"))); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| DirectHandle<NameDictionary> dictionary; |
| if (!prototype_is_null) { |
| if (IsJSObject(*prototype)) { |
| DirectHandle<JSObject> proto = Cast<JSObject>(prototype); |
| ToDictionaryMode(proto, num_methods); |
| if (fast_path) { |
| dictionary = handle(proto->property_dictionary(), isolate_); |
| } |
| } |
| } else if (num_methods > 0) { |
| DCHECK(prototype_is_null); |
| isolate()->Throw(*isolate()->factory()->NewTypeError( |
| MessageTemplate::kNonObjectPropertyStoreWithProperty, |
| isolate()->factory()->null_value(), |
| isolate()->factory()->constructor_string())); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| base::Vector<const uint8_t> last_name; |
| DirectHandle<JSFunction> getter; |
| DirectHandle<JSFunction> setter; |
| for (uint32_t i = 0; i < num_methods; i++) { |
| Method method = NextMethod(false); |
| if (!ok()) break; |
| DirectHandle<JSFunction> function; |
| if (!NextFunction().To(&function)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| |
| // Wrap the function to pass the JS receiver as first Wasm param. |
| DirectHandle<Context> context = isolate_->factory()->NewBuiltinContext( |
| isolate_->native_context(), wasm::kMethodWrapperContextLength); |
| context->SetNoCell(wasm::kMethodWrapperContextSlot, *function); |
| function = |
| Factory::JSFunctionBuilder{isolate_, method_wrapper(), context} |
| .set_map( |
| isolate_->strict_function_with_readonly_prototype_map()) |
| .Build(); |
| // For nicer stack traces, we could call |
| // JSFunction::SetName(..., function, name, ...); |
| // here, but that would have a performance cost, so for now we choose |
| // to hide the wrappers on stack traces instead. |
| // Note to our future selves: if we wanted names, the most performant |
| // way would likely be to add a pre-created |
| // strict_function_with_NAME_AND_readonly_prototype_map |
| // and use that for these wrappers, to avoid needing individual SFIs |
| // for storing their names. |
| |
| if (fast_path) { |
| bool bailout = MayBeArrayIndex(method.name); |
| // If we have pending accessors, and we're moving on to something |
| // else, install them now. |
| if ((!getter.is_null() || !setter.is_null()) && |
| (method.name != last_name || method.kind == Method::kMethod || |
| bailout)) { |
| if (!FastPath_AddAccessorProperty(prototype, dictionary, last_name, |
| getter, setter) |
| .ToHandle(&dictionary)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| getter = {}; |
| setter = {}; |
| } |
| if (bailout) { |
| fast_path = false; |
| prototype->SetProperties(*dictionary); |
| if (!InstallMethod(prototype, method, function)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| continue; |
| } |
| if (method.kind == Method::kMethod) { |
| if (!FastPath_AddDataProperty(prototype, dictionary, method.name, |
| function) |
| .ToHandle(&dictionary)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| } else if (method.kind == Method::kGetter) { |
| getter = function; |
| last_name = method.name; |
| } else if (method.kind == Method::kSetter) { |
| setter = function; |
| last_name = method.name; |
| } |
| } else { |
| if (!InstallMethod(prototype, method, function)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| } |
| } |
| // If we used the fast path, then the extended property dictionary |
| // hasn't been installed yet, and we may still have pending accessors. |
| if (fast_path) { |
| if (!getter.is_null() || !setter.is_null()) { |
| if (!FastPath_AddAccessorProperty(prototype, dictionary, last_name, |
| getter, setter) |
| .ToHandle(&dictionary)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| } |
| prototype->SetProperties(*dictionary); |
| } |
| |
| if (!ok()) break; |
| |
| uint32_t parent_idx_offset = pc_offset(); |
| int32_t parent_idx = consume_i32v("parentidx"); |
| if (!ok()) break; |
| if (parent_idx >= 0 && static_cast<uint32_t>(parent_idx) < proto_index) { |
| if (prototype_is_null) { |
| isolate()->Throw(*isolate()->factory()->NewTypeError( |
| MessageTemplate::kCalledOnNullOrUndefined, |
| isolate()->factory()->NewStringFromAsciiChecked( |
| "Object.setPrototypeOf"))); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| // Previous entries in the prototypes array may have been overwritten |
| // since we last saw them, so we cannot make any assumptions about them. |
| DirectHandle<Object> parent = |
| PrototypeByIndex(static_cast<uint32_t>(parent_idx)); |
| if (!JSReceiver::SetPrototype(isolate(), prototype, parent, true, |
| ShouldThrow::kThrowOnError) |
| .FromMaybe(false)) { |
| DCHECK(isolate()->has_exception()); |
| return ReadOnlyRoots(isolate()).exception(); |
| } |
| } else if (parent_idx == -1) { |
| // No parent requested. |
| } else { |
| errorf(0u, "invalid parentidx %d at data+%u", parent_idx, |
| parent_idx_offset); |
| break; |
| } |
| } |
| if (HasMoreFunctions()) error("unconsumed functions"); |
| if (HasMorePrototypes()) error("unconsumed prototypes"); |
| if (more()) error("unconsumed data"); |
| if (!ok()) { |
| DirectHandle<String> message = |
| isolate() |
| ->factory() |
| ->NewStringFromUtf8(base::VectorOf(error().message())) |
| .ToHandleChecked(); |
| DirectHandle<JSObject> error = isolate()->factory()->NewError( |
| isolate()->wasm_runtime_error_function(), message); |
| return isolate()->Throw(*error); |
| } |
| |
| return ReadOnlyRoots(isolate()).undefined_value(); |
| } |
| |
| Method NextMethod(bool is_static) { |
| uint8_t kind = consume_u8("kind"); |
| if (kind > 2) { |
| errorf(0u, "invalid method kind %u at data+%u", kind, pc_offset() - 1); |
| return {}; |
| } |
| uint32_t name_length = consume_u32v("name length"); |
| if (!ok()) return {}; |
| const uint8_t* name_start = pc(); |
| consume_bytes(name_length); |
| if (!ok()) return {}; |
| return {.kind = static_cast<Method::Kind>(kind), |
| .is_static = is_static, |
| .name = {name_start, name_length}}; |
| } |
| |
| MaybeDirectHandle<JSFunction> NextFunction() { |
| DirectHandle<Object> maybe_func = NextFunctionInternal(); |
| if (maybe_func.is_null()) { |
| ThrowWasmError(isolate_, MessageTemplate::kWasmTrapArrayOutOfBounds); |
| return {}; |
| } |
| // TODO(jkummerow): Can we tighten the spec to require non-nullable arrays? |
| if (IsWasmNull(*maybe_func)) { |
| ThrowWasmError(isolate_, MessageTemplate::kWasmTrapNullFunc); |
| return {}; |
| } |
| DirectHandle<WasmFuncRef> funcref = Cast<WasmFuncRef>(maybe_func); |
| DirectHandle<WasmInternalFunction> internal_function( |
| funcref->internal(isolate_), isolate_); |
| return Cast<JSFunction>( |
| WasmInternalFunction::GetOrCreateExternal(internal_function)); |
| } |
| |
| MaybeDirectHandle<JSReceiver> NextPrototype(bool* value_was_null) { |
| DirectHandle<Object> maybe_proto = NextPrototypeInternal(); |
| if (maybe_proto.is_null()) { |
| ThrowWasmError(isolate_, MessageTemplate::kWasmTrapArrayOutOfBounds); |
| return {}; |
| } |
| if (IsHeapObject(*maybe_proto)) { |
| DirectHandle<HeapObject> heap_proto = Cast<HeapObject>(maybe_proto); |
| if (IsJSObject(*heap_proto)) { |
| if (HeapLayout::InWritableSharedSpace(*heap_proto)) { |
| DCHECK(v8_flags.harmony_struct); |
| // Shared JS structs are not supported as prototypes, and probably |
| // never will be: we cannot change their maps to add new properties, |
| // and we cannot add pointers to non-shared methods to them. |
| ThrowWasmError(isolate_, MessageTemplate::kWasmTrapIllegalCast); |
| return {}; |
| } |
| DirectHandle<JSObject> proto = Cast<JSObject>(heap_proto); |
| if (!proto->map()->is_prototype_map()) { |
| // Important for correctness: switch to a non-shared map. |
| // Important for performance: switch to dictionary mode. |
| JSObject::OptimizeAsPrototype(proto); |
| } else { |
| // Prepare the fast path by invalidating any ICs that might already |
| // rely on this prototype. |
| JSObject::InvalidatePrototypeChains(proto->map()); |
| } |
| return proto; |
| } |
| if (IsJSReceiver(*heap_proto)) { |
| return Cast<JSReceiver>(heap_proto); |
| } |
| if (IsNull(*heap_proto)) { |
| *value_was_null = true; |
| return {}; |
| } |
| } |
| isolate_->Throw(*isolate_->factory()->NewTypeError( |
| MessageTemplate::kProtoObjectOrNull, maybe_proto)); |
| return {}; |
| } |
| |
| // Adding multiple properties is more efficient when the prototype |
| // object is in dictionary mode. ICs will transition it back to |
| // "fast" (but slow to modify) properties. |
| void ToDictionaryMode(DirectHandle<JSObject> object, int num_properties) { |
| if (!object->HasFastProperties()) return; |
| if (IsJSGlobalProxy(*object)) return; |
| JSObject::NormalizeProperties(isolate_, Cast<JSObject>(object), |
| KEEP_INOBJECT_PROPERTIES, num_properties, |
| "Wasm prototype setup"); |
| } |
| |
| bool InstallMethod(DirectHandle<JSReceiver> receiver, Method method, |
| DirectHandle<JSFunction> function) { |
| DirectHandle<String> name; |
| if (!ReadUtf8String(method.name).To(&name)) return false; |
| PropertyDescriptor prop; |
| prop.set_enumerable(false); |
| prop.set_configurable(true); |
| if (method.kind == Method::kMethod) { |
| prop.set_writable(true); |
| prop.set_value(function); |
| } else if (method.kind == Method::kGetter) { |
| prop.set_get(function); |
| } else if (method.kind == Method::kSetter) { |
| prop.set_set(function); |
| } else { |
| UNREACHABLE(); // Ruled out by validation. |
| } |
| return JSReceiver::DefineOwnProperty(isolate_, receiver, name, &prop, |
| Just(ShouldThrow::kThrowOnError)) |
| .FromMaybe(false); |
| } |
| |
| // Returns the wrapped constructor on success. |
| DirectHandle<JSFunction> InstallConstructor( |
| DirectHandle<JSReceiver> prototype, |
| DirectHandle<JSFunction> wasm_function, |
| base::Vector<const uint8_t> name_vec, |
| DirectHandle<Object> all_constructors) { |
| DirectHandle<String> name; |
| if (!ReadUtf8String(name_vec).To(&name)) return {}; |
| if (!IsJSReceiver(*all_constructors)) { |
| isolate_->Throw(*isolate_->factory()->NewTypeError( |
| MessageTemplate::kNonObjectPropertyStoreWithProperty, |
| all_constructors, name)); |
| return {}; |
| } |
| DirectHandle<Context> context = isolate_->factory()->NewBuiltinContext( |
| isolate_->native_context(), wasm::kConstructorFunctionContextLength); |
| context->SetNoCell(wasm::kConstructorFunctionContextSlot, *wasm_function); |
| Builtin code = Builtin::kWasmConstructorWrapper; |
| const uint32_t length = wasm_function->length(); |
| DirectHandle<SharedFunctionInfo> sfi = |
| isolate_->factory()->NewSharedFunctionInfoForBuiltin( |
| name, code, static_cast<int>(length), kDontAdapt); |
| sfi->set_native(true); |
| sfi->set_language_mode(LanguageMode::kStrict); |
| DirectHandle<JSFunction> constructor = |
| Factory::JSFunctionBuilder{isolate_, sfi, context} |
| .set_map(isolate_->strict_function_with_readonly_prototype_map()) |
| .Build(); |
| constructor->set_prototype_or_initial_map(*prototype, kReleaseStore); |
| |
| PropertyDescriptor constructor_prop; |
| constructor_prop.set_enumerable(false); |
| constructor_prop.set_configurable(true); |
| constructor_prop.set_writable(true); |
| constructor_prop.set_value(constructor); |
| if (!JSReceiver::DefineOwnProperty( |
| isolate_, prototype, isolate_->factory()->constructor_string(), |
| &constructor_prop, Just(ShouldThrow::kThrowOnError)) |
| .FromMaybe(false)) { |
| return {}; |
| } |
| |
| PropertyDescriptor prop; |
| prop.set_enumerable(true); |
| prop.set_configurable(true); |
| prop.set_writable(true); |
| prop.set_value(constructor); |
| if (!JSReceiver::DefineOwnProperty(isolate_, |
| Cast<JSReceiver>(all_constructors), name, |
| &prop, Just(ShouldThrow::kThrowOnError)) |
| .FromMaybe(false)) { |
| return {}; |
| } |
| return constructor; |
| } |
| |
| // Fast path, skipping the LookupIterator. |
| bool CheckFastPathEligibility(DirectHandle<JSReceiver> prototype) { |
| if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { |
| // Support for swiss name dictionaries isn't implemented here yet. |
| UNIMPLEMENTED(); |
| } |
| if (prototype.is_null()) return false; |
| Tagged<Map> map = prototype->map(); |
| if (map->instance_type() != JS_OBJECT_TYPE) return false; |
| if (!map->is_extensible()) return false; |
| // JS_OBJECT_TYPE implies no access checks or interceptors. |
| DCHECK(!map->is_access_check_needed()); |
| DCHECK(!map->has_indexed_interceptor()); |
| DCHECK(!map->has_named_interceptor()); |
| DCHECK(!IsJSGlobalObject(*prototype)); |
| if (map->is_dictionary_map()) { |
| if (Cast<JSObject>(prototype) |
| ->property_dictionary() |
| ->NumberOfElements() != 0) { |
| return false; |
| } |
| } else { |
| if (map->NumberOfOwnDescriptors() != 0) return false; |
| } |
| return true; |
| } |
| |
| bool MayBeArrayIndex(base::Vector<const uint8_t> name) { |
| // The fast path can't handle elements (i.e. names that are string |
| // representations of array indices). To save time, we approximate |
| // detection of such names by only looking at the first character. |
| if (name.size() == 0) return false; |
| char first = name.at(0); |
| return first >= '0' && first <= '9'; |
| } |
| |
| // This is not fully generic: it doesn't need to handle overwriting arbitrary |
| // properties, only those that the same fast path put there before. |
| MaybeDirectHandle<NameDictionary> FastPath_AddDataProperty( |
| DirectHandle<JSReceiver> receiver, |
| DirectHandle<NameDictionary> property_dictionary, |
| base::Vector<const uint8_t> name_vec, DirectHandle<Object> value) { |
| DirectHandle<String> name; |
| if (!ReadUtf8String(name_vec).To(&name)) return {}; |
| PropertyDetails details(PropertyKind::kData, DONT_ENUM, |
| PropertyCellType::kConstant); |
| InternalIndex entry = property_dictionary->FindEntry(isolate_, name); |
| if (entry.is_found()) [[unlikely]] { |
| // This isn't expected to happen in practice, so from a performance |
| // perspective it'd be fine to just bail out of the fast path; but the |
| // code ends up being simpler if we just handle this and carry on. |
| PropertyDetails old_details = property_dictionary->DetailsAt(entry); |
| details = details.set_index(old_details.dictionary_index()); |
| details = details.set_cell_type(PropertyCellType::kMutable); |
| property_dictionary->DetailsAtPut(entry, details); |
| property_dictionary->ValueAtPut(entry, *value); |
| return property_dictionary; |
| } |
| DCHECK(entry.is_not_found()); |
| if (!NameDictionary::Add(isolate_, property_dictionary, name, value, |
| details) |
| .ToHandle(&property_dictionary)) { |
| return {}; |
| } |
| LookupIterator::UpdateProtector(isolate_, receiver, name, value); |
| if (name->IsInteresting(isolate_)) { |
| property_dictionary->set_may_have_interesting_properties(true); |
| } |
| return property_dictionary; |
| } |
| |
| // This is not fully generic: it doesn't need to handle overwriting arbitrary |
| // properties, only those that the same fast path put there before. |
| MaybeDirectHandle<NameDictionary> FastPath_AddAccessorProperty( |
| DirectHandle<JSReceiver> receiver, |
| DirectHandle<NameDictionary> property_dictionary, |
| base::Vector<const uint8_t> name_vec, DirectHandle<JSFunction> getter, |
| DirectHandle<JSFunction> setter) { |
| DirectHandle<String> name; |
| if (!ReadUtf8String(name_vec).To(&name)) return {}; |
| PropertyDetails details(PropertyKind::kAccessor, DONT_ENUM, |
| PropertyCellType::kMutable); |
| DirectHandle<AccessorPair> pair = isolate_->factory()->NewAccessorPair(); |
| if (!getter.is_null()) pair->set_getter(*getter); |
| if (!setter.is_null()) pair->set_setter(*setter); |
| InternalIndex entry = property_dictionary->FindEntry(isolate_, name); |
| if (entry.is_found()) [[unlikely]] { |
| // This isn't expected to happen in practice, so from a performance |
| // perspective it'd be fine to just bail out of the fast path; but the |
| // code ends up being simpler if we just handle this and carry on. |
| PropertyDetails old_details = property_dictionary->DetailsAt(entry); |
| details = details.set_index(old_details.dictionary_index()); |
| // When setting only one accessor, keep the other if it's already present. |
| if (getter.is_null() || setter.is_null()) { |
| Tagged<Object> old_value = property_dictionary->ValueAt(entry); |
| if (IsAccessorPair(old_value)) { |
| Tagged<AccessorPair> old_pair = Cast<AccessorPair>(old_value); |
| if (getter.is_null() && !IsNull(old_pair->getter())) { |
| pair->set_getter(old_pair->getter()); |
| } |
| if (setter.is_null() && !IsNull(old_pair->setter())) { |
| pair->set_setter(old_pair->setter()); |
| } |
| } |
| } |
| property_dictionary->DetailsAtPut(entry, details); |
| property_dictionary->ValueAtPut(entry, *pair); |
| return property_dictionary; |
| } |
| DCHECK(entry.is_not_found()); |
| if (!NameDictionary::Add(isolate_, property_dictionary, name, pair, details) |
| .ToHandle(&property_dictionary)) { |
| return {}; |
| } |
| LookupIterator::UpdateProtector(isolate_, receiver, name, pair); |
| if (name->IsInteresting(isolate_)) { |
| property_dictionary->set_may_have_interesting_properties(true); |
| } |
| return property_dictionary; |
| } |
| |
| protected: |
| Isolate* isolate() { return isolate_; } |
| virtual DirectHandle<Object> NextFunctionInternal() = 0; |
| virtual bool HasMoreFunctions() = 0; |
| virtual DirectHandle<Object> NextPrototypeInternal() = 0; |
| virtual bool HasMorePrototypes() = 0; |
| virtual DirectHandle<Object> PrototypeByIndex(uint32_t index) = 0; |
| DirectHandle<SharedFunctionInfo> method_wrapper() { return method_wrapper_; } |
| |
| private: |
| Isolate* isolate_; |
| DirectHandle<SharedFunctionInfo> method_wrapper_; |
| }; |
| |
| class PrototypesSetup_Arrays : public PrototypesSetup { |
| public: |
| PrototypesSetup_Arrays(Isolate* isolate, base::Vector<const uint8_t> data, |
| DirectHandle<WasmArray> prototypes, |
| DirectHandle<WasmArray> functions) |
| : PrototypesSetup(isolate, data), |
| prototypes_(prototypes), |
| functions_(functions) {} |
| |
| protected: |
| DirectHandle<Object> NextFunctionInternal() override { |
| if (function_index_ >= functions_->length()) return {}; |
| return WasmArray::GetElement(isolate(), functions_, function_index_++); |
| } |
| |
| bool HasMoreFunctions() override { |
| return function_index_ < functions_->length(); |
| } |
| |
| DirectHandle<Object> NextPrototypeInternal() override { |
| if (prototype_index_ >= prototypes_->length()) return {}; |
| return WasmArray::GetElement(isolate(), prototypes_, prototype_index_++); |
| } |
| |
| bool HasMorePrototypes() override { |
| return prototype_index_ < prototypes_->length(); |
| } |
| |
| DirectHandle<Object> PrototypeByIndex(uint32_t index) override { |
| DCHECK_LT(index, prototypes_->length()); |
| return WasmArray::GetElement(isolate(), prototypes_, index); |
| } |
| |
| private: |
| DirectHandle<WasmArray> prototypes_; |
| DirectHandle<WasmArray> functions_; |
| uint32_t prototype_index_{0}; |
| uint32_t function_index_{0}; |
| }; |
| |
| class PrototypesSetup_Sections : public PrototypesSetup { |
| public: |
| PrototypesSetup_Sections(Isolate* isolate, base::Vector<const uint8_t> data, |
| DirectHandle<FixedArray> prototypes, |
| uint32_t prototypes_start_index, |
| uint32_t prototypes_length, |
| DirectHandle<FixedArray> functions, |
| uint32_t functions_start_index, |
| uint32_t functions_length) |
| : PrototypesSetup(isolate, data), |
| prototypes_(prototypes), |
| functions_(functions), |
| prototype_start_index_(prototypes_start_index), |
| prototype_index_(prototypes_start_index), |
| prototypes_end_(prototypes_start_index + prototypes_length), |
| function_index_(functions_start_index), |
| functions_end_(functions_start_index + functions_length) {} |
| |
| protected: |
| DirectHandle<Object> NextFunctionInternal() override { |
| if (function_index_ >= functions_end_) return {}; |
| return direct_handle(functions_->get(function_index_++), isolate()); |
| } |
| |
| bool HasMoreFunctions() override { return function_index_ < functions_end_; } |
| |
| DirectHandle<Object> NextPrototypeInternal() override { |
| if (prototype_index_ >= prototypes_end_) return {}; |
| return direct_handle(prototypes_->get(prototype_index_++), isolate()); |
| } |
| |
| bool HasMorePrototypes() override { |
| return prototype_index_ < prototypes_end_; |
| } |
| |
| DirectHandle<Object> PrototypeByIndex(uint32_t index) override { |
| index += prototype_start_index_; |
| DCHECK_LT(index, prototypes_end_); |
| return direct_handle(prototypes_->get(index), isolate()); |
| } |
| |
| private: |
| DirectHandle<FixedArray> prototypes_; |
| DirectHandle<FixedArray> functions_; |
| uint32_t prototype_start_index_; |
| uint32_t prototype_index_; |
| uint32_t prototypes_end_; |
| uint32_t function_index_; |
| uint32_t functions_end_; |
| }; |
| |
| MaybeDirectHandle<FixedArray> GetElementSegment( |
| Isolate* isolate, DirectHandle<WasmTrustedInstanceData> instance, |
| uint32_t segment_index) { |
| Tagged<Object> segment_raw = instance->element_segments()->get(segment_index); |
| if (IsFixedArray(segment_raw)) { |
| return {Cast<FixedArray>(segment_raw), isolate}; |
| } |
| |
| std::optional<MessageTemplate> opt_error = wasm::InitializeElementSegment( |
| isolate, instance, segment_index, wasm::kPrecreateExternal); |
| if (opt_error.has_value()) { |
| ThrowWasmError(isolate, opt_error.value()); |
| return {}; |
| } |
| return {Cast<FixedArray>(instance->element_segments()->get(segment_index)), |
| isolate}; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_WasmConfigureAllPrototypes) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| |
| MessageTemplate illegal_cast = MessageTemplate::kWasmTrapIllegalCast; |
| if (!IsWasmArray(args[0])) return ThrowWasmError(isolate, illegal_cast); |
| if (!IsWasmArray(args[1])) return ThrowWasmError(isolate, illegal_cast); |
| if (!IsWasmArray(args[2])) return ThrowWasmError(isolate, illegal_cast); |
| DirectHandle<WasmArray> prototypes(Cast<WasmArray>(args[0]), isolate); |
| DirectHandle<WasmArray> functions(Cast<WasmArray>(args[1]), isolate); |
| DirectHandle<WasmArray> data(Cast<WasmArray>(args[2]), isolate); |
| DirectHandle<Object> constructors(args[3], isolate); |
| { |
| Tagged<Object> expected_prototypes_map = |
| MakeStrong(isolate->heap()->wasm_canonical_rtts()->get( |
| wasm::TypeCanonicalizer::kPredefinedArrayExternRefIndex.index)); |
| Tagged<Object> expected_functions_map = |
| MakeStrong(isolate->heap()->wasm_canonical_rtts()->get( |
| wasm::TypeCanonicalizer::kPredefinedArrayFuncRefIndex.index)); |
| Tagged<Object> expected_data_map = |
| MakeStrong(isolate->heap()->wasm_canonical_rtts()->get( |
| wasm::TypeCanonicalizer::kPredefinedArrayI8Index.index)); |
| if (prototypes->map() != expected_prototypes_map || |
| functions->map() != expected_functions_map || |
| data->map() != expected_data_map) { |
| return ThrowWasmError(isolate, illegal_cast); |
| } |
| } |
| |
| // Arrays on the heap can move on GC, so we create an immovable copy of |
| // the data we'll need to decode. |
| uint32_t length = data->length(); |
| base::OwnedVector<uint8_t> immovable_data; |
| if (length) { |
| immovable_data = base::OwnedCopyOf( |
| reinterpret_cast<const uint8_t*>(data->ElementAddress(0)), length); |
| } |
| |
| PrototypesSetup_Arrays decoder(isolate, immovable_data.as_vector(), |
| prototypes, functions); |
| return decoder.SetupPrototypes(constructors); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmConfigureAllPrototypesOpt) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| |
| uint32_t* stack_buffer = reinterpret_cast<uint32_t*>(args[0].ptr()); |
| DirectHandle<Object> constructors(args[1], isolate); |
| DirectHandle<WasmTrustedInstanceData> instance( |
| TrustedCast<WasmTrustedInstanceData>(args[2]), isolate); |
| |
| uint32_t prototypes_start = stack_buffer[0]; |
| uint32_t prototypes_length = stack_buffer[1]; |
| uint32_t prototypes_segment_index = stack_buffer[2]; |
| uint32_t functions_start = stack_buffer[3]; |
| uint32_t functions_length = stack_buffer[4]; |
| uint32_t functions_segment_index = stack_buffer[5]; |
| uint32_t data_start = stack_buffer[6]; |
| uint32_t data_length = stack_buffer[7]; |
| uint32_t data_segment_index = stack_buffer[8]; |
| |
| DirectHandle<FixedArray> prototypes_segment; |
| if (!GetElementSegment(isolate, instance, prototypes_segment_index) |
| .ToHandle(&prototypes_segment)) { |
| DCHECK(isolate->has_exception()); |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| if (!base::IsInBounds<size_t>(prototypes_start, prototypes_length, |
| prototypes_segment->ulength().value())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapElementSegmentOutOfBounds); |
| } |
| |
| DirectHandle<FixedArray> functions_segment; |
| if (!GetElementSegment(isolate, instance, functions_segment_index) |
| .ToHandle(&functions_segment)) { |
| DCHECK(isolate->has_exception()); |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| if (!base::IsInBounds<size_t>(functions_start, functions_length, |
| functions_segment->ulength().value())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapElementSegmentOutOfBounds); |
| } |
| |
| wasm::WireBytesRef segment_source = |
| instance->data_segments()->get(data_segment_index); |
| if (!base::IsInBounds<uint32_t>(data_start, data_length, |
| segment_source.length())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapDataSegmentOutOfBounds); |
| } |
| |
| base::Vector<const uint8_t> data = |
| instance->native_module()->wire_bytes().SubVector( |
| segment_source.offset() + data_start, |
| segment_source.offset() + data_start + data_length); |
| |
| PrototypesSetup_Sections decoder( |
| isolate, data, prototypes_segment, prototypes_start, prototypes_length, |
| functions_segment, functions_start, functions_length); |
| return decoder.SetupPrototypes(constructors); |
| } |
| |
| #define RETURN_RESULT_OR_TRAP(call) \ |
| do { \ |
| DirectHandle<Object> result; \ |
| if (!(call).ToHandle(&result)) { \ |
| DCHECK(isolate->has_exception()); \ |
| /* Mark any exception as uncatchable by Wasm. */ \ |
| DirectHandle<JSObject> exception(Cast<JSObject>(isolate->exception()), \ |
| isolate); \ |
| DirectHandle<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() || \ |
| IsTerminationException(isolate->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) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| |
| int bits = args.smi_value_at(1); |
| int shared = args.smi_value_at(2); |
| 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 = Cast<WasmArray>(args[0]); |
| wasm::CanonicalTypeIndex expected = |
| bits == 8 |
| ? (shared ? wasm::TypeCanonicalizer::kPredefinedArrayI8SharedIndex |
| : wasm::TypeCanonicalizer::kPredefinedArrayI8Index) |
| : (shared ? wasm::TypeCanonicalizer::kPredefinedArrayI16SharedIndex |
| : wasm::TypeCanonicalizer::kPredefinedArrayI16Index); |
| Tagged<Object> expected_map = |
| MakeStrong(isolate->heap()->wasm_canonical_rtts()->get(expected.index)); |
| // If the expected_map has been cleared or never even created, then there's |
| // no chance of a match anyway. |
| if (obj->map() != expected_map) { |
| 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) { |
| DCHECK_EQ(5, args.length()); |
| HandleScope scope(isolate); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| uint32_t memory = args.positive_smi_value_at(1); |
| UnicodeConfig config(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]); |
| |
| 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}; |
| MaybeDirectHandle<v8::internal::String> result_string = |
| isolate->factory()->NewStringFromUtf8(bytes, config); |
| if (config.variant() == unibrow::Utf8Variant::kUtf8NoTrap) { |
| // If the input was invalid, then the decoder has failed silently, and |
| // the string.new_utf8_try instruction should return null. |
| if (result_string.is_null() && !isolate->has_exception()) { |
| return *isolate->factory()->wasm_null(); |
| } |
| // Fall through in case of a valid result, and in case of a pending |
| // exception because the requested string was too large. |
| } |
| RETURN_RESULT_OR_TRAP(result_string); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8Array) { |
| DCHECK_EQ(4, args.length()); |
| HandleScope scope(isolate); |
| UnicodeConfig config(args.positive_smi_value_at(0)); |
| DirectHandle<WasmArray> array(Cast<WasmArray>(args[1]), isolate); |
| uint32_t start = NumberToUint32(args[2]); |
| uint32_t end = NumberToUint32(args[3]); |
| |
| TSAN_ACQUIRE(array->address()); |
| |
| MaybeDirectHandle<v8::internal::String> result_string = |
| isolate->factory()->NewStringFromUtf8(array, start, end, config); |
| if (config.variant() == unibrow::Utf8Variant::kUtf8NoTrap) { |
| // If the input was invalid, then the decoder has failed silently, and |
| // the string.new_utf8_array_try instruction should return null. |
| if (result_string.is_null() && !isolate->has_exception()) { |
| return *isolate->factory()->wasm_null(); |
| } |
| // Fall through in case of a valid result, and in case of a pending |
| // exception because the requested string was too large. |
| } |
| RETURN_RESULT_OR_TRAP(result_string); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16) { |
| DCHECK_EQ(5, args.length()); |
| HandleScope scope(isolate); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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]); |
| UnicodeConfig config(args.positive_smi_value_at(4)); |
| |
| 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}, config)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16Array) { |
| DCHECK_EQ(4, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<WasmArray> array(Cast<WasmArray>(args[0]), isolate); |
| uint32_t start = NumberToUint32(args[1]); |
| uint32_t end = NumberToUint32(args[2]); |
| UnicodeConfig config(args.positive_smi_value_at(3)); |
| |
| TSAN_ACQUIRE(array->address()); |
| |
| RETURN_RESULT_OR_TRAP( |
| isolate->factory()->NewStringFromUtf16(array, start, end, config)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmSubstring) { |
| DCHECK_EQ(3, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| uint32_t start = args.positive_smi_value_at(1); |
| uint32_t length = args.positive_smi_value_at(2); |
| |
| string = String::Flatten(isolate, string); |
| return *isolate->factory()->NewCopiedSubstring(string, start, length); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmSubstringShared) { |
| DCHECK_EQ(3, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| uint32_t start = args.positive_smi_value_at(1); |
| uint32_t length = args.positive_smi_value_at(2); |
| |
| // TSAN_ACQUIRE has been called in the Torque builtin. |
| |
| return *isolate->factory()->NewCopiedSubstringShared(string, start, length); |
| } |
| |
| // Returns the new string if the operation succeeds. Otherwise traps. |
| RUNTIME_FUNCTION(Runtime_WasmStringConst) { |
| DCHECK_EQ(2, args.length()); |
| HandleScope scope(isolate); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(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) { |
| DCHECK_EQ(5, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(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)); |
| |
| wasm::WireBytesRef segment_source = |
| trusted_instance_data->data_segments()->get(segment_index); |
| if (!base::IsInBounds<uint32_t>(offset, length, segment_source.length())) { |
| return ThrowWasmError(isolate, |
| MessageTemplate::kWasmTrapDataSegmentOutOfBounds); |
| } |
| |
| base::Vector<const uint8_t> source = |
| trusted_instance_data->native_module()->wire_bytes().SubVector( |
| segment_source.offset() + offset, |
| segment_source.offset() + offset + length); |
| |
| MaybeDirectHandle<String> result = |
| isolate->factory()->NewStringFromUtf8(source, variant); |
| if (variant == unibrow::Utf8Variant::kUtf8NoTrap) { |
| // If the input was invalid, then the decoder has failed silently, and |
| // the string.new_utf8_array_try instruction should return null. |
| // Only instructions from the stringref proposal can set variant |
| // kUtf8NoTrap, so WasmNull is appropriate here. |
| if (result.is_null() && !isolate->has_exception()) { |
| return *isolate->factory()->wasm_null(); |
| } |
| // Fall through in case of a valid result, and in case of a pending |
| // exception because the requested string was too large. |
| } |
| RETURN_RESULT_OR_FAILURE(isolate, result); |
| } |
| |
| namespace { |
| // TODO(12868): Consider unifying with api.cc:String::Utf8Length. |
| template <typename T> |
| uint32_t MeasureWtf8(base::Vector<const T> wtf16) { |
| int previous = unibrow::Utf16::kNoPreviousCharacter; |
| uint32_t 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; |
| } |
| uint32_t MeasureWtf8(Isolate* isolate, DirectHandle<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()); |
| } |
| 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(); |
| } |
| |
| bool write_null = false; |
| unibrow::Utf8::EncodingResult result = |
| unibrow::Utf8::Encode(wtf16, bytes.begin() + offset, |
| bytes.size() - offset, write_null, replace_invalid); |
| DCHECK_EQ(result.characters_processed, wtf16.size()); |
| DCHECK_LE(result.bytes_written, kMaxInt); |
| return static_cast<int>(result.bytes_written); |
| } |
| template <typename GetWritableBytes> |
| Tagged<Object> EncodeWtf8(Isolate* isolate, unibrow::Utf8Variant variant, |
| DirectHandle<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 |
| |
| // Used for storing the name of a string-constants imports module off the heap. |
| // Defined here to be able to make use of the helper functions above. |
| void ToUtf8Lossy(Isolate* isolate, DirectHandle<String> string, |
| std::string& out) { |
| uint32_t utf8_length = MeasureWtf8(isolate, string); |
| DisallowGarbageCollection no_gc; |
| out.resize(utf8_length); |
| String::FlatContent content = string->GetFlatContent(no_gc); |
| DCHECK(content.IsFlat()); |
| static constexpr unibrow::Utf8Variant variant = |
| unibrow::Utf8Variant::kLossyUtf8; |
| MessageTemplate* error_cant_happen = nullptr; |
| MessageTemplate oob_cant_happen = MessageTemplate::kInvalid; |
| if (content.IsOneByte()) { |
| EncodeWtf8({out.data(), out.size()}, 0, content.ToOneByteVector(), variant, |
| error_cant_happen, oob_cant_happen); |
| } else { |
| EncodeWtf8({out.data(), out.size()}, 0, content.ToUC16Vector(), variant, |
| error_cant_happen, oob_cant_happen); |
| } |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringMeasureUtf8) { |
| DCHECK_EQ(1, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| |
| TSAN_ACQUIRE(string->address()); |
| |
| string = String::Flatten(isolate, string); |
| uint32_t 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())) { |
| return Smi::FromInt(-1); |
| } else { |
| length = MeasureWtf8(code_units); |
| } |
| } |
| } |
| return *isolate->factory()->NewNumberFromUint(length); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringMeasureWtf8) { |
| DCHECK_EQ(1, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| |
| TSAN_ACQUIRE(string->address()); |
| |
| uint32_t length = MeasureWtf8(isolate, string); |
| return *isolate->factory()->NewNumberFromUint(length); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf8) { |
| DCHECK_EQ(5, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate); |
| uint32_t memory = args.positive_smi_value_at(1); |
| uint32_t utf8_variant_value = args.positive_smi_value_at(2); |
| DirectHandle<String> string(Cast<String>(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)); |
| |
| auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value); |
| auto get_writable_bytes = |
| [&](const DisallowGarbageCollection&) -> base::Vector<char> { |
| return {reinterpret_cast<char*>(trusted_instance_data->memory_base(memory)), |
| trusted_instance_data->memory_size(memory)}; |
| }; |
| return EncodeWtf8(isolate, utf8_variant, string, get_writable_bytes, offset, |
| MessageTemplate::kWasmTrapMemOutOfBounds); |
| } |
| |
| namespace { |
| DISABLE_TSAN Tagged<Object> WasmStringEncodeWtf8ArrayImpl( |
| Isolate* isolate, uint32_t utf8_variant_value, DirectHandle<String> string, |
| DirectHandle<WasmArray> array, uint32_t start) { |
| 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); |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf8Array) { |
| DCHECK_EQ(4, args.length()); |
| HandleScope scope(isolate); |
| uint32_t utf8_variant_value = args.positive_smi_value_at(0); |
| DirectHandle<String> string(Cast<String>(args[1]), isolate); |
| DirectHandle<WasmArray> array(Cast<WasmArray>(args[2]), isolate); |
| uint32_t start = NumberToUint32(args[3]); |
| |
| return WasmStringEncodeWtf8ArrayImpl(isolate, utf8_variant_value, string, |
| array, start); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringToUtf8Array) { |
| DCHECK_EQ(2, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| int32_t shared = args.smi_value_at(1); |
| |
| TSAN_ACQUIRE(string->address()); |
| |
| uint32_t length = MeasureWtf8(isolate, string); |
| constexpr int kElemSize = wasm::kWasmI8.value_kind_size(); |
| if (length > static_cast<uint32_t>(WasmArray::MaxLength(kElemSize))) { |
| return ThrowWasmError(isolate, MessageTemplate::kWasmTrapArrayTooLarge); |
| } |
| wasm::WasmValue initial_value(int8_t{0}); |
| Tagged<WeakFixedArray> 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. |
| DirectHandle<Map> map( |
| Cast<Map>( |
| rtts->get( |
| shared |
| ? wasm::TypeCanonicalizer::kPredefinedArrayI8SharedIndex |
| .index |
| : wasm::TypeCanonicalizer::kPredefinedArrayI8Index.index) |
| .GetHeapObjectAssumeWeak()), |
| isolate); |
| AllocationType allocation = |
| shared ? AllocationType::kSharedOld : AllocationType::kYoung; |
| DirectHandle<WasmArray> array = |
| isolate->factory()->NewWasmArray(wasm::kWasmI8, length, initial_value, |
| map, allocation, SKIP_WRITE_BARRIER); |
| 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::NumberValue(write_result) == length); |
| USE(write_result); |
| return *array; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringEncodeWtf16) { |
| DCHECK_EQ(6, args.length()); |
| HandleScope scope(isolate); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| uint32_t memory = args.positive_smi_value_at(1); |
| Tagged<String> string = Cast<String>(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); |
| |
| TSAN_ACQUIRE(string.address()); |
| |
| 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); |
| |
| return Smi::zero(); // Unused. |
| #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 |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringAsWtf8) { |
| DCHECK_EQ(1, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> string(Cast<String>(args[0]), isolate); |
| |
| TSAN_ACQUIRE(string->address()); |
| |
| uint32_t wtf8_length = MeasureWtf8(isolate, string); |
| DirectHandle<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()), wtf8_length}; |
| }; |
| EncodeWtf8(isolate, utf8_variant, string, get_writable_bytes, 0, |
| MessageTemplate::kWasmTrapArrayOutOfBounds); |
| return *array; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringViewWtf8Encode) { |
| DCHECK_EQ(7, args.length()); |
| HandleScope scope(isolate); |
| Tagged<WasmTrustedInstanceData> trusted_instance_data = |
| TrustedCast<WasmTrustedInstanceData>(args[0]); |
| uint32_t utf8_variant_value = args.positive_smi_value_at(1); |
| DirectHandle<ByteArray> array(Cast<ByteArray>(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->ulength().value())); |
| |
| 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) { |
| DCHECK_EQ(3, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<ByteArray> array(Cast<ByteArray>(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->ulength().value())); |
| |
| // 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(); |
| } |
| |
| #ifdef V8_ENABLE_DRUMBRAKE |
| RUNTIME_FUNCTION(Runtime_WasmTraceBeginExecution) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK(v8_flags.slow_histograms && !v8_flags.wasm_jitless && |
| v8_flags.wasm_enable_exec_time_histograms); |
| DCHECK_EQ(0, args.length()); |
| |
| wasm::WasmExecutionTimer* timer = isolate->wasm_execution_timer(); |
| timer->Start(); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTraceEndExecution) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK(v8_flags.slow_histograms && !v8_flags.wasm_jitless && |
| v8_flags.wasm_enable_exec_time_histograms); |
| DCHECK_EQ(0, args.length()); |
| |
| wasm::WasmExecutionTimer* timer = isolate->wasm_execution_timer(); |
| timer->Stop(); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| #endif // V8_ENABLE_DRUMBRAKE |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringFromCodePoint) { |
| DCHECK_EQ(2, args.length()); |
| HandleScope scope(isolate); |
| |
| uint32_t code_point = NumberToUint32(args[0]); |
| int shared = args.smi_value_at(1); |
| |
| // `LookupSingleCharacterStringFromCode` might return an internalized string |
| // if `code_point` is not one-byte, which is not shared. |
| if ((!shared && code_point <= unibrow::Utf16::kMaxNonSurrogateCharCode) || |
| (shared && code_point <= String::kMaxOneByteCharCode)) { |
| 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)}); |
| } |
| |
| if (shared && code_point <= unibrow::Utf16::kMaxNonSurrogateCharCode) { |
| DirectHandle<SeqTwoByteString> result = |
| isolate->factory()->NewRawSharedTwoByteString(1).ToHandleChecked(); |
| DisallowGarbageCollection no_gc; |
| CopyChars(result->GetChars(no_gc), &code_point, 1); |
| return *result; |
| } |
| |
| base::uc16 char_buffer[] = { |
| unibrow::Utf16::LeadSurrogate(code_point), |
| unibrow::Utf16::TrailSurrogate(code_point), |
| }; |
| DirectHandle<SeqTwoByteString> result = |
| (shared ? isolate->factory()->NewRawSharedTwoByteString( |
| arraysize(char_buffer)) |
| : 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) { |
| SealHandleScope seal_handle_scope(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK_EQ(1, args.length()); |
| Tagged<String> string(Cast<String>(args[0])); |
| uint32_t hash = string->EnsureHash(); |
| return Smi::FromInt(static_cast<int>(hash)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmStringAdd_NoMapCheck_Shared) { |
| DCHECK_EQ(2, args.length()); |
| HandleScope scope(isolate); |
| |
| DirectHandle<String> left(Cast<String>(args[0]), isolate); |
| DirectHandle<String> right(Cast<String>(args[1]), isolate); |
| |
| TSAN_ACQUIRE(left->address()); |
| TSAN_ACQUIRE(right->address()); |
| |
| DirectHandle<String> result; |
| if (isolate->factory()->WasmStringAddShared(left, right).ToHandle(&result)) { |
| return *result; |
| } else { |
| DCHECK(isolate->has_exception()); |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| } |
| |
| // For cont.new: this initializes the continuation with a new stack and with the |
| // given function reference, such that calling "resume" on it will call the |
| // function on the new stack. |
| RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation) { |
| DCHECK_EQ(3, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<WasmTrustedInstanceData> trusted_instance_data( |
| TrustedCast<WasmTrustedInstanceData>(args[0]), isolate); |
| DirectHandle<WasmFuncRef> func_ref(Cast<WasmFuncRef>(args[1]), isolate); |
| wasm::CanonicalTypeIndex sig_id{static_cast<uint32_t>(args.smi_value_at(2))}; |
| std::unique_ptr<wasm::StackMemory> stack = |
| isolate->stack_pool().GetOrAllocate(); |
| DirectHandle<WasmStackObject> stack_obj = |
| isolate->factory()->NewWasmStackObject(stack.get()); |
| const wasm::CanonicalSig* sig = |
| wasm::GetTypeCanonicalizer()->LookupFunctionSignature(sig_id); |
| auto [arg_buffer_size, alignment] = |
| GetBufferSizeAndAlignmentFor(sig->parameters()); |
| #if V8_TARGET_ARCH_ARM64 |
| // For stack alignment. |
| alignment = RoundUp(alignment, 2 * kSystemPointerSize); |
| #endif |
| stack->jmpbuf()->fp = kNullAddress; |
| stack->jmpbuf()->sp = RoundDown(stack->base() - arg_buffer_size, alignment); |
| Address arg_buffer = stack->jmpbuf()->sp; |
| stack->set_arg_buffer(arg_buffer); |
| stack->jmpbuf()->state = wasm::JumpBuffer::Suspended; |
| stack->jmpbuf()->stack_limit = stack->jslimit(); |
| stack->jmpbuf()->is_on_central_stack = false; |
| stack->jmpbuf()->parent = nullptr; |
| stack->set_index(isolate->wasm_stacks().size()); |
| // TODO(thibaudm): Store the WasmCodePointer instead. |
| wasm::StackEntryWrapperCacheKey key{sig}; |
| std::shared_ptr<wasm::WasmWrapperHandle> wrapper = |
| wasm::GetWasmStackEntryWrapperCache()->GetCompiled(isolate, key); |
| stack->jmpbuf()->pc = wrapper->code()->instruction_start(); |
| trusted_instance_data->native_module()->RegisterStackEntryWrapper( |
| std::move(wrapper)); |
| stack->set_param_types(sig->parameters()); |
| stack->set_signature_id(sig->index()); |
| DirectHandle<WasmContinuationObject> cont = |
| isolate->factory()->NewWasmContinuationObject(stack_obj); |
| // Set the references after the heap allocation, so that they are not |
| // immediately stale from a potential GC. |
| stack->set_func_ref(*func_ref); |
| stack->set_current_continuation(*cont); |
| stack->set_stack_obj(*stack_obj); |
| isolate->wasm_stacks().emplace_back(std::move(stack)); |
| return *cont; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTypeAssertionFailed) { |
| DCHECK_EQ(0, args.length()); |
| // The "FuzzerSecurityIssueHigh" is needed to label crashes of this as |
| // security issues in ClusterFuzz. |
| FATAL("[FuzzerSecurityIssueHigh] Wasm type assertion violation"); |
| } |
| |
| #ifdef V8_IS_TSAN |
| // Since TSAN does not know about release fences, we must manually define the |
| // synchronization between object initialization and reads from that object. We |
| // add a TSAN_RELEASE after the object-initialization release fence, and a |
| // TSAN_ACQUIRE at the beginning of each read-only builtin (future work: also, |
| // before every read of a shared object in generated code). Explainer: |
| // https://docs.google.com/document/d/17RLOdAFJ2HFA4hE83wSsTYdRwHV4ZBiX_jtOUp0qatw/edit?usp=sharing |
| RUNTIME_FUNCTION(Runtime_TsanAcquireForInitializationFence) { |
| DCHECK_EQ(1, args.length()); |
| TSAN_ACQUIRE(Cast<HeapObject>(args[0]).address()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| #endif // V8_IS_TSAN |
| |
| #undef RuntimeArguments |
| |
| } // namespace v8::internal |