/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/wasm/constant-expression-interface.cc
525 строк
21 KB
Richard Lau
deps: V8: cherry-pick cf1bce40a5ef
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
cc5a331
Код
Авторство
О чём код?
// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/wasm/constant-expression-interface.h" #include "src/base/overflowing-math.h" #include "src/execution/isolate.h" #include "src/handles/handles-inl.h" #include "src/objects/fixed-array-inl.h" #include "src/wasm/decoder.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-objects.h" namespace v8 { namespace internal { namespace wasm { void ConstantExpressionInterface::I32Const(FullDecoder* decoder, Value* result, int32_t value) { if (generate_value()) result->runtime_value = WasmValue(value); } void ConstantExpressionInterface::I64Const(FullDecoder* decoder, Value* result, int64_t value) { if (generate_value()) result->runtime_value = WasmValue(value); } void ConstantExpressionInterface::F32Const(FullDecoder* decoder, Value* result, float value) { if (generate_value()) result->runtime_value = WasmValue(value); } void ConstantExpressionInterface::F64Const(FullDecoder* decoder, Value* result, double value) { if (generate_value()) result->runtime_value = WasmValue(value); } void ConstantExpressionInterface::S128Const(FullDecoder* decoder, const Simd128Immediate& imm, Value* result) { if (!generate_value()) return; #if V8_TARGET_BIG_ENDIAN // Globals are not little endian enforced, they use native byte order and we // need to reverse the bytes on big endian platforms. uint8_t value[kSimd128Size]; for (int i = 0; i < kSimd128Size; i++) { value[i] = imm.value[kSimd128Size - 1 - i]; } result->runtime_value = WasmValue(value, kWasmS128); #else result->runtime_value = WasmValue(imm.value, kWasmS128); #endif } void ConstantExpressionInterface::UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& input, Value* result) { if (!generate_value()) return; switch (opcode) { case kExprExternConvertAny: { result->runtime_value = WasmValue(WasmToJSObject(isolate_, input.runtime_value.to_ref()), CanonicalValueType::RefMaybeNull(kWasmExternRef, input.type.nullability())); break; } case kExprAnyConvertExtern: { const char* error_message = nullptr; result->runtime_value = WasmValue(JSToWasmObject(isolate_, input.runtime_value.to_ref(), kWasmAnyRef, &error_message) .ToHandleChecked(), CanonicalValueType::RefMaybeNull(kWasmAnyRef, input.type.nullability())); break; } default: UNREACHABLE(); } } void ConstantExpressionInterface::BinOp(FullDecoder* decoder, WasmOpcode opcode, const Value& lhs, const Value& rhs, Value* result) { if (!generate_value()) return; switch (opcode) { case kExprI32Add: result->runtime_value = WasmValue(base::AddWithWraparound( lhs.runtime_value.to_i32(), rhs.runtime_value.to_i32())); break; case kExprI32Sub: result->runtime_value = WasmValue(base::SubWithWraparound( lhs.runtime_value.to_i32(), rhs.runtime_value.to_i32())); break; case kExprI32Mul: result->runtime_value = WasmValue(base::MulWithWraparound( lhs.runtime_value.to_i32(), rhs.runtime_value.to_i32())); break; case kExprI64Add: result->runtime_value = WasmValue(base::AddWithWraparound( lhs.runtime_value.to_i64(), rhs.runtime_value.to_i64())); break; case kExprI64Sub: result->runtime_value = WasmValue(base::SubWithWraparound( lhs.runtime_value.to_i64(), rhs.runtime_value.to_i64())); break; case kExprI64Mul: result->runtime_value = WasmValue(base::MulWithWraparound( lhs.runtime_value.to_i64(), rhs.runtime_value.to_i64())); break; default: UNREACHABLE(); } } void ConstantExpressionInterface::RefNull(FullDecoder* decoder, ValueType type, Value* result) { if (!generate_value()) return; result->runtime_value = WasmValue( type.use_wasm_null() ? Cast<Object>(isolate_->factory()->wasm_null()) : Cast<Object>(isolate_->factory()->null_value()), decoder->module_->canonical_type(type)); } void ConstantExpressionInterface::RefFunc(FullDecoder* decoder, uint32_t function_index, Value* result) { if (isolate_ == nullptr) { outer_module_->functions[function_index].declared = true; return; } if (!generate_value()) return; ModuleTypeIndex sig_index = module_->functions[function_index].sig_index; bool function_is_shared = module_->type(sig_index).is_shared; CanonicalValueType type = CanonicalValueType::Ref(module_->canonical_type_id(sig_index), function_is_shared, RefTypeKind::kFunction); // Imported functions can be subtypes of their static import type; for // non-imported or exactly imported functions we can return an exact type. if (decoder->enabled_.has_custom_descriptors() && (function_index >= module_->num_imported_functions || module_->functions[function_index].exact)) { type = type.AsExact(); } DirectHandle<WasmFuncRef> func_ref = WasmTrustedInstanceData::GetOrCreateFuncRef( isolate_, function_is_shared ? shared_trusted_instance_data_ : trusted_instance_data_, function_index); result->runtime_value = WasmValue(func_ref, type); } void ConstantExpressionInterface::GlobalGet(FullDecoder* decoder, Value* result, const GlobalIndexImmediate& imm) { if (!generate_value()) return; const WasmGlobal& global = module_->globals[imm.index]; DCHECK(!global.mutability); DirectHandle<WasmTrustedInstanceData> data = global.shared ? shared_trusted_instance_data_ : trusted_instance_data_; CanonicalValueType type = module_->canonical_type(global.type); result->runtime_value = type.is_numeric() ? WasmValue(reinterpret_cast<uint8_t*>( data->untagged_globals_buffer()->backing_store()) + global.offset, type) : WasmValue( direct_handle(data->tagged_globals_buffer()->get(global.offset), isolate_), type); } DirectHandle<Map> ConstantExpressionInterface::GetRtt( DirectHandle<WasmTrustedInstanceData> data, ModuleTypeIndex index, const TypeDefinition& type, const Value& descriptor) { if (!type.has_descriptor()) { return direct_handle( Cast<Map>(data->managed_object_maps()->get(index.index)), isolate_); } DCHECK(type.has_descriptor()); WasmValue desc = descriptor.runtime_value; DirectHandle<Object> maybe_obj = desc.to_ref(); if (!IsWasmStruct(*maybe_obj)) { DCHECK(IsWasmNull(*maybe_obj)); error_ = MessageTemplate::kWasmTrapNullDereference; return {}; } DCHECK_EQ(desc.type().ref_index(), module_->canonical_type_id(type.descriptor)); return direct_handle(Cast<WasmStruct>(*maybe_obj)->described_rtt(), isolate_); } void ConstantExpressionInterface::StructNew(FullDecoder* decoder, const StructIndexImmediate& imm, const Value& descriptor, const Value args[], Value* result) { // Declaratively adding a prototype is only permitted if the initializer // ends with struct.new[_default]. Since no control flow is allowed in // initializers, an 'end' instruction indicates the end of the initializer. int offset_to_next_instr = 2 /* prefix, opcode */ + imm.length; ends_with_struct_new_ = decoder->lookahead(offset_to_next_instr, kExprEnd); if (!generate_value()) return; DirectHandle<WasmTrustedInstanceData> data = GetTrustedInstanceDataForTypeIndex(imm.index); const TypeDefinition& type = module_->type(imm.index); const StructType* struct_type = type.struct_type; DCHECK_EQ(struct_type, imm.struct_type); DirectHandle<Map> rtt = GetRtt(data, imm.index, type, descriptor); if (rtt.is_null()) return; // Trap (descriptor was null). DirectHandle<WasmStruct> obj; WriteBarrierMode mode = UPDATE_WRITE_BARRIER; if (type.is_descriptor()) { // TODO(14616): Support shared custom descriptors. if (type.is_shared) UNIMPLEMENTED(); DirectHandle<Object> first_field = struct_type->first_field_can_be_prototype() ? args[0].runtime_value.to_ref() : direct_handle(Smi::zero(), isolate_); obj = WasmStruct::AllocateDescriptorUninitialized(isolate_, data, imm.index, rtt, first_field); } else { obj = isolate_->factory()->NewWasmStructUninitialized( struct_type, rtt, type.is_shared ? AllocationType::kSharedOld : AllocationType::kYoung); if (!type.is_shared) mode = SKIP_WRITE_BARRIER; // Object is in new space. } DisallowGarbageCollection no_gc; // Must initialize fields first. for (uint32_t i = 0; i < struct_type->field_count(); i++) { int offset = struct_type->field_offset(i); if (struct_type->field(i).is_numeric()) { uint8_t* address = reinterpret_cast<uint8_t*>(obj->RawFieldAddress(offset)); args[i].runtime_value.Packed(struct_type->field(i)).CopyTo(address); } else { obj->SetTaggedFieldValue(offset, *args[i].runtime_value.to_ref(), mode); } } result->runtime_value = WasmValue( obj, decoder->module_->canonical_type( ValueType::Ref(imm.heap_type()).AsExactIfEnabled(decoder->enabled_))); } void ConstantExpressionInterface::StringConst(FullDecoder* decoder, const StringConstImmediate& imm, Value* result) { if (!generate_value()) return; static_assert(base::IsInRange(kV8MaxWasmStringLiterals, 0, Smi::kMaxValue)); DCHECK_LT(imm.index, module_->stringref_literals.size()); const wasm::WasmStringRefLiteral& literal = module_->stringref_literals[imm.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()); DirectHandle<String> string = isolate_->factory() ->NewStringFromUtf8(string_bytes, unibrow::Utf8Variant::kWtf8) .ToHandleChecked(); result->runtime_value = WasmValue(string, kWasmRefString); } namespace { WasmValue DefaultValueForType(ValueType type, Isolate* isolate, const WasmModule* module) { switch (type.kind()) { case kI32: case kI8: case kI16: return WasmValue(0); case kI64: return WasmValue(int64_t{0}); case kF16: case kF32: return WasmValue(0.0f); case kF64: return WasmValue(0.0); case kS128: return WasmValue(Simd128()); case kRefNull: return WasmValue(type.use_wasm_null() ? Cast<Object>(isolate->factory()->wasm_null()) : Cast<Object>(isolate->factory()->null_value()), module->canonical_type(type)); case kVoid: case kRef: case kTop: case kBottom: UNREACHABLE(); } } } // namespace void ConstantExpressionInterface::StructNewDefault( FullDecoder* decoder, const StructIndexImmediate& imm, const Value& descriptor, Value* result) { // Declaratively adding a prototype is only permitted if the initializer // ends with struct.new[_default]. Since no control flow is allowed in // initializers, an 'end' instruction indicates the end of the initializer. int offset_to_next_instr = 2 /* prefix, opcode */ + imm.length; ends_with_struct_new_ = decoder->lookahead(offset_to_next_instr, kExprEnd); if (!generate_value()) return; DirectHandle<WasmTrustedInstanceData> data = GetTrustedInstanceDataForTypeIndex(imm.index); const TypeDefinition& type = module_->type(imm.index); const StructType* struct_type = type.struct_type; DCHECK_EQ(struct_type, imm.struct_type); DirectHandle<Map> rtt = GetRtt(data, imm.index, type, descriptor); if (rtt.is_null()) return; // Trap (descriptor was null). DirectHandle<WasmStruct> obj; if (type.is_descriptor()) { // TODO(14616): Implement shared custom descriptors. if (type.is_shared) UNIMPLEMENTED(); DirectHandle<Object> first_field(Smi::zero(), isolate_); obj = WasmStruct::AllocateDescriptorUninitialized(isolate_, data, imm.index, rtt, first_field); } else { obj = isolate_->factory()->NewWasmStructUninitialized( struct_type, rtt, type.is_shared ? AllocationType::kSharedOld : AllocationType::kYoung); } DisallowGarbageCollection no_gc; // Must initialize fields first. for (uint32_t i = 0; i < struct_type->field_count(); i++) { int offset = struct_type->field_offset(i); ValueType ftype = struct_type->field(i); if (ftype.is_numeric()) { uint8_t* address = reinterpret_cast<uint8_t*>(obj->RawFieldAddress(offset)); DefaultValueForType(ftype, isolate_, module_) .Packed(ftype) .CopyTo(address); } else { TaggedField<Object, WasmStruct::kHeaderSize>::store( *obj, offset, *DefaultValueForType(ftype, isolate_, module_).to_ref()); } } result->runtime_value = WasmValue( obj, decoder->module_->canonical_type( ValueType::Ref(imm.heap_type()).AsExactIfEnabled(decoder->enabled_))); } void ConstantExpressionInterface::ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate& imm, const Value& length, const Value& initial_value, Value* result) { if (!generate_value()) return; DirectHandle<WasmTrustedInstanceData> data = GetTrustedInstanceDataForTypeIndex(imm.index); DirectHandle<Map> rtt{ Cast<Map>(data->managed_object_maps()->get(imm.index.index)), isolate_}; if (length.runtime_value.to_u32() > static_cast<uint32_t>(WasmArray::MaxLength(imm.array_type))) { error_ = MessageTemplate::kWasmTrapArrayTooLarge; return; } result->runtime_value = WasmValue( isolate_->factory()->NewWasmArray(imm.array_type->element_type(), length.runtime_value.to_u32(), initial_value.runtime_value, rtt), decoder->module_->canonical_type( ValueType::Ref(imm.heap_type()).AsExactIfEnabled(decoder->enabled_))); } void ConstantExpressionInterface::ArrayNewDefault( FullDecoder* decoder, const ArrayIndexImmediate& imm, const Value& length, Value* result) { if (!generate_value()) return; Value initial_value(decoder->pc(), imm.array_type->element_type()); initial_value.runtime_value = DefaultValueForType( imm.array_type->element_type(), isolate_, decoder->module_); return ArrayNew(decoder, imm, length, initial_value, result); } void ConstantExpressionInterface::ArrayNewFixed( FullDecoder* decoder, const ArrayIndexImmediate& array_imm, const IndexImmediate& length_imm, const Value elements[], Value* result) { if (!generate_value()) return; DirectHandle<WasmTrustedInstanceData> data = GetTrustedInstanceDataForTypeIndex(array_imm.index); DirectHandle<Map> rtt{ Cast<Map>(data->managed_object_maps()->get(array_imm.index.index)), isolate_}; base::Vector<WasmValue> element_values = decoder->zone_->AllocateVector<WasmValue>(length_imm.index); for (size_t i = 0; i < length_imm.index; i++) { element_values[i] = elements[i].runtime_value; } result->runtime_value = WasmValue(isolate_->factory()->NewWasmArrayFromElements( array_imm.array_type, element_values, rtt), decoder->module_->canonical_type( ValueType::Ref(array_imm.heap_type()) .AsExactIfEnabled(decoder->enabled_))); } // TODO(14034): These expressions are non-constant for now. There are plans to // make them constant in the future, so we retain the required infrastructure // here. void ConstantExpressionInterface::ArrayNewSegment( FullDecoder* decoder, const ArrayIndexImmediate& array_imm, const IndexImmediate& segment_imm, const Value& offset_value, const Value& length_value, Value* result) { if (!generate_value()) return; DirectHandle<WasmTrustedInstanceData> data = GetTrustedInstanceDataForTypeIndex(array_imm.index); DirectHandle<Map> rtt{ Cast<Map>(data->managed_object_maps()->get(array_imm.index.index)), isolate_}; DCHECK_EQ(rtt->wasm_type_info()->type_index(), decoder->module_->canonical_type_id(array_imm.index)); uint32_t length = length_value.runtime_value.to_u32(); uint32_t offset = offset_value.runtime_value.to_u32(); if (length > static_cast<uint32_t>(WasmArray::MaxLength(array_imm.array_type))) { error_ = MessageTemplate::kWasmTrapArrayTooLarge; return; } CanonicalValueType element_type = rtt->wasm_type_info()->element_type(); CanonicalValueType result_type = rtt->wasm_type_info()->type().AsExactIfEnabled(decoder->enabled_); if (element_type.is_numeric()) { uint32_t length_in_bytes = length * array_imm.array_type->element_type().value_kind_size(); WireBytesRef segment_source = data->data_segments()->get(segment_imm.index); if (!base::IsInBounds<uint32_t>(offset, length_in_bytes, segment_source.length())) { error_ = MessageTemplate::kWasmTrapDataSegmentOutOfBounds; return; } base::Vector<const uint8_t> source = data->native_module()->wire_bytes() + offset; DirectHandle<WasmArray> array_value = isolate_->factory()->NewWasmArrayFromMemory(length, rtt, element_type, source); result->runtime_value = WasmValue(array_value, result_type); } else { const wasm::WasmElemSegment* elem_segment = &decoder->module_->elem_segments[segment_imm.index]; // A constant expression should not observe if a passive segment_source is // dropped. However, it should consider active and declarative segments as // empty. if (!base::IsInBounds<size_t>( offset, length, elem_segment->status == WasmElemSegment::kStatusPassive ? elem_segment->element_count : 0)) { error_ = MessageTemplate::kWasmTrapElementSegmentOutOfBounds; return; } DirectHandle<Object> array_object = isolate_->factory()->NewWasmArrayFromElementSegment( trusted_instance_data_, shared_trusted_instance_data_, segment_imm.index, offset, length, rtt, element_type); if (IsSmi(*array_object)) { // A smi result stands for an error code. error_ = static_cast<MessageTemplate>(Cast<Smi>(*array_object).value()); } else { result->runtime_value = WasmValue(array_object, result_type); } } } void ConstantExpressionInterface::RefI31(FullDecoder* decoder, const Value& input, Value* result) { if (!generate_value()) return; Address raw = input.runtime_value.to_i32(); // We have to craft the Smi manually because we accept out-of-bounds inputs. // For 32-bit Smi builds, set the topmost bit to sign-extend the second bit. // This way, interpretation in JS (if this value escapes there) will be the // same as i31.get_s. static_assert((SmiValuesAre31Bits() ^ SmiValuesAre32Bits()) == 1); intptr_t shifted; if constexpr (SmiValuesAre31Bits()) { shifted = raw << (kSmiTagSize + kSmiShiftSize); } else { shifted = static_cast<intptr_t>(raw << (kSmiTagSize + kSmiShiftSize + 1)) >> 1; } result->runtime_value = WasmValue(direct_handle(Tagged<Smi>(shifted), isolate_), kWasmRefI31); } void ConstantExpressionInterface::DoReturn(FullDecoder* decoder, uint32_t /*drop_values*/) { end_found_ = true; // End decoding on "end". Note: We need this because we do not know the length // of a constant expression while decoding it. decoder->set_end(decoder->pc() + 1); if (generate_value()) { computed_value_ = decoder->stack_value(1)->runtime_value; } } DirectHandle<WasmTrustedInstanceData> ConstantExpressionInterface::GetTrustedInstanceDataForTypeIndex( ModuleTypeIndex index) { bool type_is_shared = module_->type(index).is_shared; return type_is_shared ? shared_trusted_instance_data_ : trusted_instance_data_; } } // namespace wasm } // namespace internal } // namespace v8