current commit
261015.17 · deps: update V8 to 15.6.75.13
// 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/logging.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/objects/managed-inl.h"
#include "src/objects/map-inl.h"
#include "src/wasm/decoder.h"
#include "src/wasm/wasm-objects-inl.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;
  CanonicalValueType type =
      CanonicalValueType::Ref(module_->canonical_type_id(sig_index),
                              SharedFlag{false}, 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_, 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);
  result->runtime_value =
      trusted_instance_data_->GetGlobalValue(isolate_, global);
}

DirectHandle<Map> ConstantExpressionInterface::GetRtt(
    ModuleTypeIndex index, const TypeDefinition& type,
    const Value& descriptor) {
  if (!type.has_descriptor()) {
    return direct_handle(
        Cast<Map>(
            trusted_instance_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 (IsWasmNull(*maybe_obj)) {
    error_ = MessageTemplate::kWasmTrapNullDereference;
    return {};
  }
  DCHECK(IsWasmStruct(*maybe_obj));
  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;

  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(imm.index, type, descriptor);
  if (rtt.is_null()) return;  // Trap (descriptor was null).

  DirectHandle<WasmStruct> obj;
  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_, trusted_instance_data_, imm.index, rtt, first_field);
  } else {
    // Pretenure, because we expect values in globals to be long-lived.
    obj = isolate_->factory()->NewWasmStructUninitialized(
        struct_type, rtt,
        type.is_shared ? AllocationType::kSharedOld : AllocationType::kOld);
  }

  DisallowGarbageCollection no_gc;  // Must initialize fields first.
  SharedObjectConditionalSafePublishGuard publish_guard(*obj, type.is_shared);

  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());
    }
  }

  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();
  }
  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;

  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(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_, trusted_instance_data_, imm.index, rtt, first_field);
  } else {
    // Pretenure, because we expect values in globals to be long-lived.
    obj = isolate_->factory()->NewWasmStructUninitialized(
        struct_type, rtt,
        type.is_shared ? AllocationType::kSharedOld : AllocationType::kOld);
  }

  DisallowGarbageCollection no_gc;  // Must initialize fields first.
  SharedObjectConditionalSafePublishGuard publish_guard(*obj, type.is_shared);

  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 {
      // No write barrier needed, as read-only-space objects never move.
      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;
  ArrayNewImpl(decoder, imm, length, initial_value, result,
               UPDATE_WRITE_BARRIER);
}

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_);
  // We can skip the write barriers because default values live in RO-space.
  return ArrayNewImpl(decoder, imm, length, initial_value, result,
                      SKIP_WRITE_BARRIER);
}

void ConstantExpressionInterface::ArrayNewImpl(
    FullDecoder* decoder, const ArrayIndexImmediate& imm, const Value& length,
    const Value& initial_value, Value* result, WriteBarrierMode write_barrier) {
  DirectHandle<Map> rtt{
      Cast<Map>(
          trusted_instance_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;
  }
  AllocationType allocation =
      imm.shared ? AllocationType::kSharedOld : AllocationType::kOld;

  DirectHandle<WasmArray> obj = isolate_->factory()->NewWasmArray(
      imm.array_type->element_type(), length.runtime_value.to_u32(),
      initial_value.runtime_value, rtt, allocation, write_barrier);

  result->runtime_value = WasmValue(
      obj,
      decoder->module_->canonical_type(
          ValueType::Ref(imm.heap_type()).AsExactIfEnabled(decoder->enabled_)));
}

void ConstantExpressionInterface::ArrayNewFixed(
    FullDecoder* decoder, const ArrayIndexImmediate& array_imm,
    const IndexImmediate& length_imm, const Value elements[], Value* result) {
  if (!generate_value()) return;
  DirectHandle<Map> rtt{
      Cast<Map>(trusted_instance_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;
  }
  AllocationType allocation =
      array_imm.shared ? AllocationType::kSharedOld : AllocationType::kOld;

  DirectHandle<WasmArray> obj = isolate_->factory()->NewWasmArrayFromElements(
      array_imm.array_type, element_values, rtt, allocation);
  SharedObjectConditionalSafePublishGuard publish_guard(*obj, array_imm.shared);
  result->runtime_value =
      WasmValue(obj, 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<Map> rtt{
      Cast<Map>(trusted_instance_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_);
  AllocationType allocation =
      array_imm.shared ? AllocationType::kSharedOld : AllocationType::kOld;
  if (element_type.is_numeric()) {
    uint32_t length_in_bytes =
        length * array_imm.array_type->element_type().value_kind_size();
    WireBytesRef segment_source =
        trusted_instance_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 =
        trusted_instance_data_->native_module()->wire_bytes() + offset;
    DirectHandle<WasmArray> array_value =
        isolate_->factory()->NewWasmArrayFromMemory(length, rtt, allocation,
                                                    element_type, source);
    SharedObjectConditionalSafePublishGuard publish_guard(*array_value,
                                                          array_imm.shared);
    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_, segment_imm.index, offset, length, rtt,
            allocation, element_type);
    if (IsSmi(*array_object)) {
      // A smi result stands for an error code.
      error_ = static_cast<MessageTemplate>(Cast<Smi>(*array_object).value());
    } else {
      SharedObjectConditionalSafePublishGuard publish_guard(
          Cast<WasmArray>(*array_object), array_imm.shared);
      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::WaitqueueNew(FullDecoder* decoder,
                                               Value* result) {
  if (!generate_value()) return;

  // 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);
  result->runtime_value = WasmValue(managed, kWasmWaitqueueRef.AsNonNull());
}

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;
  }
}

}  // namespace wasm
}  // namespace internal
}  // namespace v8