// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #ifndef RUNTIME_VM_BYTECODE_READER_H_ #define RUNTIME_VM_BYTECODE_READER_H_ #include "vm/globals.h" #if defined(DART_DYNAMIC_MODULES) #include "vm/bit_vector.h" #include "vm/constants_kbc.h" #include "vm/hash_table.h" #include "vm/object.h" namespace dart { namespace bytecode { class BytecodeComponentData; class BytecodeLoader { public: BytecodeLoader(Thread* thread, const TypedDataBase& binary); ~BytecodeLoader(); FunctionPtr LoadBytecode(); TypedDataBasePtr binary() const { return binary_.ptr(); } ArrayPtr bytecode_component_array() const { return bytecode_component_array_.ptr(); } void SetOffset(const Object& obj, intptr_t offset); intptr_t GetOffset(const Object& obj); private: Thread* thread_; const TypedDataBase& binary_; Array& bytecode_component_array_; Array& bytecode_offsets_map_; DISALLOW_COPY_AND_ASSIGN(BytecodeLoader); }; class Reader : public ValueObject { public: explicit Reader(const TypedDataBase& typed_data) : typed_data_(&typed_data) { Init(); } uint32_t ReadUInt32At(intptr_t offset) const { ASSERT((size_ >= 4) && (offset >= 0) && (offset <= size_ - 4)); uint32_t value = LoadUnaligned(reinterpret_cast(raw_buffer_ + offset)); // All supported platforms are little-endian, so there is no need to // convert from little-endian to host. return value; } uint32_t ReadUInt32() { uint32_t value = ReadUInt32At(offset_); offset_ += 4; return value; } uint32_t ReadUInt() { ASSERT((size_ >= 1) && (offset_ >= 0) && (offset_ <= size_ - 1)); const uint8_t* buffer = raw_buffer_; uword byte0 = buffer[offset_]; if ((byte0 & 0x80) == 0) { // 0... offset_++; return byte0; } else if ((byte0 & 0xc0) == 0x80) { // 10... ASSERT((size_ >= 2) && (offset_ >= 0) && (offset_ <= size_ - 2)); uint32_t value = ((byte0 & ~static_cast(0x80)) << 8) | (buffer[offset_ + 1]); offset_ += 2; return value; } else { // 11... ASSERT((size_ >= 4) && (offset_ >= 0) && (offset_ <= size_ - 4)); uint32_t value = ((byte0 & ~static_cast(0xc0)) << 24) | (buffer[offset_ + 1] << 16) | (buffer[offset_ + 2] << 8) | (buffer[offset_ + 3] << 0); offset_ += 4; return value; } } intptr_t ReadSLEB128() { ReadStream stream(raw_buffer_, size_, offset_); const intptr_t result = stream.ReadSLEB128(); offset_ = stream.Position(); return result; } int64_t ReadSLEB128AsInt64() { ReadStream stream(raw_buffer_, size_, offset_); const int64_t result = stream.ReadSLEB128(); offset_ = stream.Position(); return result; } /** * Read and return a TokenPosition from this reader. */ TokenPosition ReadPosition() { // Position is saved as unsigned, // but actually ranges from -1 and up (thus the -1) intptr_t value = ReadUInt() - 1; TokenPosition result = TokenPosition::Deserialize(value); return result; } intptr_t ReadListLength() { return ReadUInt(); } uint8_t ReadByte() { return raw_buffer_[offset_++]; } uint8_t PeekByte() { return raw_buffer_[offset_]; } void ReadBytes(uint8_t* buffer, uint8_t size) { for (int i = 0; i < size; i++) { buffer[i] = ReadByte(); } } const TypedDataBase* typed_data() { return typed_data_; } intptr_t offset() const { return offset_; } void set_offset(intptr_t offset) { ASSERT(offset <= size_); offset_ = offset; } intptr_t size() const { return size_; } TypedDataViewPtr ViewFromTo(intptr_t start, intptr_t end) { return typed_data_->ViewFromTo(start, end, Heap::kOld); } const uint8_t* BufferAt(intptr_t offset) { ASSERT((offset >= 0) && (offset < size_)); return &raw_buffer_[offset]; } private: friend class AlternativeReadingScope; void Init() { ASSERT(typed_data_->IsExternalOrExternalView()); raw_buffer_ = reinterpret_cast(typed_data_->DataAddr(0)); size_ = typed_data_->LengthInBytes(); offset_ = 0; } // A external typed data or a view on an external typed data. const TypedDataBase* typed_data_ = nullptr; // The raw data size/length of [typed_data_]. const uint8_t* raw_buffer_ = nullptr; intptr_t size_ = 0; intptr_t offset_ = 0; }; // A helper class that saves the current reader position, goes to another reader // position, and upon destruction, resets to the original reader position. class AlternativeReadingScope { public: AlternativeReadingScope(Reader* reader, intptr_t new_position) : reader_(reader), saved_offset_(reader_->offset_) { reader_->offset_ = new_position; } ~AlternativeReadingScope() { reader_->offset_ = saved_offset_; } private: Reader* const reader_; const intptr_t saved_offset_; DISALLOW_COPY_AND_ASSIGN(AlternativeReadingScope); }; // Helper class for reading bytecode. class BytecodeReaderHelper : public ValueObject { public: explicit BytecodeReaderHelper(Thread* thread, const TypedDataBase& typed_data); explicit BytecodeReaderHelper(Thread* thread, BytecodeComponentData* bytecode_component); Reader& reader() { return reader_; } void ReadCode(const Function& function, intptr_t code_offset); void ReadMembers(const Class& cls, bool discard_fields); void ReadFieldDeclarations(const Class& cls, bool discard_fields); void ReadFunctionDeclarations(const Class& cls); void ReadClassDeclaration(const Class& cls); void ReadLibraryDeclaration(const Library& library, const GrowableObjectArray& pending_classes); void ReadLibraryDeclarations(intptr_t num_libraries); LibraryPtr ReadMain(); ArrayPtr ReadBytecodeComponent(); void ResetObjects(); // Fills in [is_covariant] and [is_generic_covariant_impl] vectors // according to covariance attributes of [function] parameters. // // [function] should be declared in bytecode. // [is_covariant] and [is_generic_covariant_impl] should contain bitvectors // of function.NumParameters() length. void ReadParameterCovariance(const Function& function, intptr_t code_offset, BitVector* is_covariant, BitVector* is_generic_covariant_impl); // Read bytecode PackedObject. ObjectPtr ReadObject(); private: // These constants should match corresponding constants in class ObjectHandle // (pkg/dart2bytecode/lib/object_table.dart). static const int kReferenceBit = 1 << 0; static const int kIndexShift = 1; static const int kKindShift = 1; static const int kKindMask = 0x0f; static const int kFlagBit0 = 1 << 5; static const int kFlagBit1 = 1 << 6; static const int kFlagBit2 = 1 << 7; static const int kFlagBit3 = 1 << 8; static const int kFlagBit4 = 1 << 9; static const int kFlagBit5 = 1 << 10; static const int kTagMask = (kFlagBit0 | kFlagBit1 | kFlagBit2 | kFlagBit3); static const int kFlagIsNullable = kFlagBit4; static const int kFlagsMask = (kTagMask | kFlagBit4 | kFlagBit5); // Code flags, must be in sync with Code constants in // pkg/dart2bytecode/lib/declarations.dart. struct Code { static const int kHasExceptionsTableFlag = 1 << 0; static const int kHasSourcePositionsFlag = 1 << 1; static const int kHasNullableFieldsFlag = 1 << 2; static const int kHasClosuresFlag = 1 << 3; static const int kHasParameterFlagsFlag = 1 << 4; static const int kHasForwardingStubTargetFlag = 1 << 5; static const int kHasDefaultFunctionTypeArgsFlag = 1 << 6; static const int kHasLocalVariablesFlag = 1 << 7; }; // Closure code flags, must be in sync with ClosureCode constants in // pkg/dart2bytecode/lib/declarations.dart. struct ClosureCode { static const int kHasExceptionsTableFlag = 1 << 0; static const int kHasSourcePositionsFlag = 1 << 1; static const int kHasLocalVariablesFlag = 1 << 2; }; // Parameter flags, must be in sync with ParameterFlags constants in // pkg/dart2bytecode/lib/object_table.dart. struct Parameter { // Parameter flags in FunctionDeclaration, ClosureDeclaration and // FunctionType. static const int kIsRequiredFlag = 1 << 0; // Parameter flags in Code. static const int kIsCovariantFlag = 1 << 0; static const int kIsCovariantByClassFlag = 1 << 1; }; class FunctionTypeScope : public ValueObject { public: explicit FunctionTypeScope(BytecodeReaderHelper* bytecode_reader, const FunctionType& type) : bytecode_reader_(bytecode_reader) { bytecode_reader_->enclosing_function_types_.Add(&type); } ~FunctionTypeScope() { bytecode_reader_->enclosing_function_types_.RemoveLast(); } private: BytecodeReaderHelper* const bytecode_reader_; }; class FunctionScope : public ValueObject { public: FunctionScope(BytecodeReaderHelper* bytecode_reader, const Function& function, const String& name, const Class& cls) : bytecode_reader_(bytecode_reader) { ASSERT(bytecode_reader_->scoped_function_.IsNull()); ASSERT(bytecode_reader_->scoped_function_name_.IsNull()); ASSERT(bytecode_reader_->scoped_function_class_.IsNull()); ASSERT(name.IsSymbol()); bytecode_reader_->scoped_function_ = function.ptr(); bytecode_reader_->scoped_function_name_ = name.ptr(); bytecode_reader_->scoped_function_class_ = cls.ptr(); } ~FunctionScope() { bytecode_reader_->scoped_function_ = Function::null(); bytecode_reader_->scoped_function_name_ = String::null(); bytecode_reader_->scoped_function_class_ = Class::null(); } private: BytecodeReaderHelper* bytecode_reader_; }; void ReadClosureDeclaration(const Function& function, intptr_t closureIndex); FunctionTypePtr ReadFunctionSignature(const FunctionType& signature, bool has_optional_positional_params, bool has_optional_named_params, bool has_type_params, bool has_positional_param_names, bool has_parameter_flags); void ReadTypeParametersDeclaration( const Class& parameterized_class, const FunctionType& parameterized_signature); // Read portion of constant pool corresponding to one function/closure. // Start with [start_index], and stop when reaching EndClosureFunctionScope. // Return index of the last read constant pool entry. intptr_t ReadConstantPool(const Function& function, const ObjectPool& pool, intptr_t start_index); BytecodePtr ReadBytecode(const ObjectPool& pool); void ReadExceptionsTable(const Function& function, const Bytecode& bytecode, bool has_exceptions_table); void ReadSourcePositions(const Bytecode& bytecode, bool has_source_positions); void ReadLocalVariables(const Bytecode& bytecode, bool has_local_variables); StringPtr ConstructorName(const Class& cls, const String& name); ObjectPtr ReadObjectContents(uint32_t header); ObjectPtr ReadConstObject(intptr_t tag); ObjectPtr ReadType(intptr_t tag, Nullability nullability); StringPtr ReadString(bool is_canonical = true); TypeArgumentsPtr ReadTypeArguments(); void SetupFieldAccessorFunction(const Class& klass, const Function& function, const AbstractType& field_type); PatchClassPtr GetPatchClass(const Class& cls, const Script& script); InstancePtr Canonicalize(const Instance& instance); // Similar to cls.EnsureClassDeclaration, but may be more efficient if // class is from the current kernel binary. void LoadReferencedClass(const Class& cls); Reader reader_; Thread* const thread_; Zone* const zone_; BytecodeComponentData* bytecode_component_; Array* closures_ = nullptr; PatchClass* patch_class_ = nullptr; Array* functions_ = nullptr; intptr_t function_index_ = 0; GrowableArray enclosing_function_types_; Function& scoped_function_; String& scoped_function_name_; Class& scoped_function_class_; DISALLOW_COPY_AND_ASSIGN(BytecodeReaderHelper); }; class BytecodeComponentData : ValueObject { public: enum { kTypedData, kVersion, kStringsHeaderOffset, kStringsContentsOffset, kObjectOffsetsOffset, kNumObjects, kObjectsContentsOffset, kMainOffset, kNumLibraries, kLibraryIndexOffset, kLibrariesOffset, kNumClasses, kClassesOffset, kMembersOffset, kNumCodes, kCodesOffset, kSourcePositionsOffset, kSourceFilesOffset, kLineStartsOffset, kLocalVariablesOffset, kAnnotationsOffset, kNumFields }; explicit BytecodeComponentData(const Array& data) : data_(data) {} TypedDataBasePtr GetTypedData() const; intptr_t GetVersion() const; intptr_t GetStringsHeaderOffset() const; intptr_t GetStringsContentsOffset() const; intptr_t GetObjectOffsetsOffset() const; intptr_t GetNumObjects() const; intptr_t GetObjectsContentsOffset() const; intptr_t GetMainOffset() const; intptr_t GetNumLibraries() const; intptr_t GetLibraryIndexOffset() const; intptr_t GetLibrariesOffset() const; intptr_t GetNumClasses() const; intptr_t GetClassesOffset() const; intptr_t GetMembersOffset() const; intptr_t GetNumCodes() const; intptr_t GetCodesOffset() const; intptr_t GetSourcePositionsOffset() const; intptr_t GetSourceFilesOffset() const; intptr_t GetLineStartsOffset() const; intptr_t GetLocalVariablesOffset() const; intptr_t GetAnnotationsOffset() const; void SetObject(intptr_t index, const Object& obj) const; ObjectPtr GetObject(intptr_t index) const; bool IsNull() const { return data_.IsNull(); } static ArrayPtr New(Zone* zone, const TypedDataBase& typed_data, intptr_t version, intptr_t num_objects, intptr_t strings_header_offset, intptr_t strings_contents_offset, intptr_t object_offsets_offset, intptr_t objects_contents_offset, intptr_t main_offset, intptr_t num_libraries, intptr_t library_index_offset, intptr_t libraries_offset, intptr_t num_classes, intptr_t classes_offset, intptr_t members_offset, intptr_t num_codes, intptr_t codes_offset, intptr_t source_positions_offset, intptr_t source_files_offset, intptr_t line_starts_offset, intptr_t local_variables_offset, intptr_t annotations_offset, Heap::Space space); private: const Array& data_; }; class BytecodeReader : public AllStatic { public: // Read declaration of the given class. static void LoadClassDeclaration(const Class& cls); // Read members of the given class. static void FinishClassLoading(const Class& cls); static void ReadParameterCovariance(const Function& function, BitVector* is_covariant, BitVector* is_generic_covariant_impl); }; class BytecodeSourcePositionsIterator : ValueObject { public: // These constants should match corresponding constants in class // SourcePositions (pkg/dart2bytecode/lib/source_positions.dart). static const intptr_t kSyntheticCodeMarker = -1; static const intptr_t kYieldPointMarker = -2; BytecodeSourcePositionsIterator(Zone* zone, const Bytecode& bytecode) : reader_(TypedDataBase::Handle(zone, bytecode.binary())) { ASSERT(bytecode.HasSourcePositions()); reader_.set_offset(bytecode.source_positions_binary_offset()); pairs_remaining_ = reader_.ReadUInt(); } bool MoveNext() { if (pairs_remaining_ == 0) { return false; } ASSERT(pairs_remaining_ > 0); --pairs_remaining_; cur_bci_ += reader_.ReadUInt(); cur_token_pos_ += reader_.ReadSLEB128(); is_yield_point_ = false; if (cur_token_pos_ == kYieldPointMarker) { const bool result = MoveNext(); is_yield_point_ = true; return result; } return true; } uword PcOffset() const { return cur_bci_; } TokenPosition TokenPos() const { return (cur_token_pos_ == kSyntheticCodeMarker) ? TokenPosition::kNoSource : TokenPosition::Deserialize(cur_token_pos_); } bool IsYieldPoint() const { return is_yield_point_; } private: Reader reader_; intptr_t pairs_remaining_ = 0; intptr_t cur_bci_ = 0; intptr_t cur_token_pos_ = 0; bool is_yield_point_ = false; }; } // namespace bytecode } // namespace dart #endif // defined(DART_DYNAMIC_MODULES) #endif // RUNTIME_VM_BYTECODE_READER_H_