| // Copyright (c) 2022, 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_COMPILER_BACKEND_IL_SERIALIZER_H_ |
| #define RUNTIME_VM_COMPILER_BACKEND_IL_SERIALIZER_H_ |
| |
| #if defined(DART_PRECOMPILED_RUNTIME) |
| #error "AOT runtime should not use compiler sources (including header files)" |
| #endif // defined(DART_PRECOMPILED_RUNTIME) |
| |
| #include <utility> // For std::move. |
| |
| #include "platform/globals.h" |
| #include "vm/allocation.h" |
| #include "vm/compiler/backend/locations.h" |
| |
| namespace dart { |
| |
| class AliasIdentity; |
| class BlockEntryInstr; |
| class CallTargets; |
| class CatchBlockEntryInstr; |
| struct CidRangeValue; |
| class Cids; |
| class Code; |
| class ComparisonInstr; |
| class CompileType; |
| class Definition; |
| class Environment; |
| class FunctionEntryInstr; |
| class Instruction; |
| class FlowGraph; |
| class GraphEntryInstr; |
| class Heap; |
| class IndirectEntryInstr; |
| class JoinEntryInstr; |
| class LocalVariable; |
| class LocationSummary; |
| class MoveOperands; |
| class NonStreamingWriteStream; |
| class OsrEntryInstr; |
| class ParsedFunction; |
| class ParallelMoveInstr; |
| class PhiInstr; |
| class Range; |
| class ReadStream; |
| class TargetEntryInstr; |
| class TokenPosition; |
| |
| namespace compiler { |
| struct TableSelector; |
| |
| namespace ffi { |
| class CallbackMarshaller; |
| class CallMarshaller; |
| class NativeCallingConvention; |
| } // namespace ffi |
| } // namespace compiler |
| |
| // The list of types which are handled by flow graph serializer/deserializer. |
| // For each type there is a corresponding Write<T>(T) and Read<T>() methods. |
| // |
| // This list includes all types of fields of IL instructions |
| // which are serialized via DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS macro, |
| // except enum types which are unwrapped with serializable_type_t. |
| // |
| // The list is sorted alphabetically by type name. |
| #define IL_SERIALIZABLE_TYPE_LIST(V) \ |
| V(AliasIdentity) \ |
| V(const AbstractType&) \ |
| V(const AbstractType*) \ |
| V(const Array&) \ |
| V(bool) \ |
| V(const compiler::ffi::CallbackMarshaller&) \ |
| V(const compiler::ffi::CallMarshaller&) \ |
| V(const CallTargets&) \ |
| V(const char*) \ |
| V(CidRangeValue) \ |
| V(const Cids&) \ |
| V(const Class&) \ |
| V(const Code&) \ |
| V(ComparisonInstr*) \ |
| V(CompileType*) \ |
| V(ConstantInstr*) \ |
| V(Definition*) \ |
| V(double) \ |
| V(Environment*) \ |
| V(const Field&) \ |
| V(const ICData*) \ |
| V(int8_t) \ |
| V(int16_t) \ |
| V(int32_t) \ |
| V(int64_t) \ |
| V(const Function&) \ |
| V(const FunctionType&) \ |
| V(Instruction*) \ |
| V(const LocalVariable&) \ |
| V(LocationSummary*) \ |
| V(MoveOperands*) \ |
| V(const compiler::ffi::NativeCallingConvention&) \ |
| V(const Object&) \ |
| V(ParallelMoveInstr*) \ |
| V(PhiInstr*) \ |
| V(Range*) \ |
| V(Representation) \ |
| V(const Slot&) \ |
| V(const Slot*) \ |
| V(const String&) \ |
| V(const compiler::TableSelector*) \ |
| V(TokenPosition) \ |
| V(const TypeArguments&) \ |
| V(const TypeParameters&) \ |
| V(uint8_t) \ |
| V(uint16_t) \ |
| V(uint32_t) \ |
| V(uint64_t) \ |
| V(Value*) |
| |
| // List of types serializable as references. |
| #define IL_SERIALIZABLE_REF_TYPE_LIST(V) \ |
| V(BlockEntryInstr*) \ |
| V(CatchBlockEntryInstr*) \ |
| V(Definition*) \ |
| V(FunctionEntryInstr*) \ |
| V(IndirectEntryInstr*) \ |
| V(JoinEntryInstr*) \ |
| V(OsrEntryInstr*) \ |
| V(TargetEntryInstr*) |
| |
| // Serializes flow graph, including constants and references |
| // to objects of program structure. |
| // |
| // Each IL instruction is serialized in 2 step: |
| // - the main step (T::WriteTo / T::T()) serializes |
| // instruction fields, basically everything required to |
| // re-create instruction object. |
| // - the extra step (T::WriteExtra / T::ReadExtra) serializes |
| // references to other instructions, including inputs, |
| // environments, locations (may reference constants) and successors. |
| // |
| class FlowGraphSerializer : public ValueObject { |
| public: |
| explicit FlowGraphSerializer(NonStreamingWriteStream* stream); |
| ~FlowGraphSerializer(); |
| |
| // Writes [flow_graph] into the stream. |
| // The graph should be compacted via CompactSSA(). |
| // [detached_defs] should contain all definitions which are |
| // detached from the graph but can still be referenced from |
| // environments. |
| void WriteFlowGraph(const FlowGraph& flow_graph, |
| const ZoneGrowableArray<Definition*>& detached_defs); |
| |
| // Default implementation of 'Write' method, when |
| // specialization for a particular type is not provided. |
| // This struct is used for the partial template instantiations below. |
| template <typename T> |
| struct WriteTrait { |
| using ArgType = T; |
| }; |
| |
| template <typename T> |
| struct WriteTrait<GrowableArray<T>> { |
| using ArgType = const GrowableArray<T>&; |
| static void Write(FlowGraphSerializer* s, ArgType x) { |
| const intptr_t len = x.length(); |
| s->Write<intptr_t>(len); |
| for (intptr_t i = 0; i < len; ++i) { |
| s->Write<T>(x[i]); |
| } |
| } |
| }; |
| |
| template <typename T> |
| struct WriteTrait<const GrowableArray<T>&> { |
| using ArgType = const GrowableArray<T>&; |
| static void Write(FlowGraphSerializer* s, ArgType x) { |
| WriteTrait<GrowableArray<T>>::Write(s, x); |
| } |
| }; |
| |
| template <typename T> |
| struct WriteTrait<ZoneGrowableArray<T>*> { |
| using ArgType = const ZoneGrowableArray<T>*; |
| static void Write(FlowGraphSerializer* s, ArgType x) { |
| if (x == nullptr) { |
| s->Write<intptr_t>(-1); |
| return; |
| } |
| const intptr_t len = x->length(); |
| s->Write<intptr_t>(len); |
| for (intptr_t i = 0; i < len; ++i) { |
| s->Write<T>((*x)[i]); |
| } |
| } |
| }; |
| |
| template <typename T> |
| struct WriteTrait<const ZoneGrowableArray<T>&> { |
| using ArgType = const ZoneGrowableArray<T>&; |
| static void Write(FlowGraphSerializer* s, ArgType x) { |
| WriteTrait<ZoneGrowableArray<T>*>::Write(s, &x); |
| } |
| }; |
| |
| // Specialization in case intptr_t is not mapped to intN_t. |
| template <> |
| struct WriteTrait<intptr_t> { |
| using ArgType = intptr_t; |
| static void Write(FlowGraphSerializer* s, intptr_t x) { |
| #ifdef ARCH_IS_64_BIT |
| s->Write<int64_t>(x); |
| #else |
| s->Write<int32_t>(x); |
| #endif |
| } |
| }; |
| |
| // Specialization in case uintptr_t is not mapped to uintN_t. |
| template <> |
| struct WriteTrait<uintptr_t> { |
| using ArgType = uintptr_t; |
| static void Write(FlowGraphSerializer* s, uintptr_t x) { |
| #ifdef ARCH_IS_64_BIT |
| s->Write<uint64_t>(x); |
| #else |
| s->Write<uint32_t>(x); |
| #endif |
| } |
| }; |
| |
| template <typename T> |
| void Write(typename WriteTrait<T>::ArgType x) { |
| WriteTrait<T>::Write(this, x); |
| } |
| |
| #define DECLARE_WRITE_METHOD(type) \ |
| template <> \ |
| void Write<type>(type x); |
| IL_SERIALIZABLE_TYPE_LIST(DECLARE_WRITE_METHOD) |
| #undef DECLARE_WRITE_METHOD |
| |
| template <typename T> |
| void WriteRef(T x); |
| |
| #define DECLARE_WRITE_REF_METHOD(type) \ |
| template <> \ |
| void WriteRef<type>(type x); |
| IL_SERIALIZABLE_REF_TYPE_LIST(DECLARE_WRITE_REF_METHOD) |
| #undef DECLARE_WRITE_REF_METHOD |
| |
| template <typename T> |
| void WriteGrowableArrayOfRefs(const GrowableArray<T>& array) { |
| const intptr_t len = array.length(); |
| Write<intptr_t>(len); |
| for (intptr_t i = 0; i < len; ++i) { |
| WriteRef<T>(array[i]); |
| } |
| } |
| |
| BaseWriteStream* stream() const { return stream_; } |
| IsolateGroup* isolate_group() const { return isolate_group_; } |
| bool can_write_refs() const { return can_write_refs_; } |
| |
| private: |
| void WriteObjectImpl(const Object& x, intptr_t cid, intptr_t object_index); |
| |
| // Used to track scopes of recursive types during serialization. |
| struct TypeScope { |
| TypeScope(FlowGraphSerializer* serializer, bool is_recursive) |
| : serializer_(serializer), |
| is_recursive_(is_recursive), |
| was_writing_recursive_type_(serializer->writing_recursive_type_) { |
| serializer->writing_recursive_type_ = is_recursive; |
| } |
| |
| ~TypeScope() { |
| serializer_->writing_recursive_type_ = was_writing_recursive_type_; |
| } |
| |
| // Returns true if type of the current scope can be canonicalized |
| // during deserialization. Recursive types which were not |
| // fully deserialized should not be canonicalized. |
| bool CanBeCanonicalized() const { |
| return !is_recursive_ || !was_writing_recursive_type_; |
| } |
| |
| FlowGraphSerializer* const serializer_; |
| const bool is_recursive_; |
| const bool was_writing_recursive_type_; |
| }; |
| |
| NonStreamingWriteStream* stream_; |
| Zone* zone_; |
| IsolateGroup* isolate_group_; |
| Heap* heap_; |
| intptr_t object_counter_ = 0; |
| bool can_write_refs_ = false; |
| bool writing_recursive_type_ = false; |
| }; |
| |
| // Deserializes flow graph. |
| // All constants and types are canonicalized during deserialization. |
| class FlowGraphDeserializer : public ValueObject { |
| public: |
| FlowGraphDeserializer(const ParsedFunction& parsed_function, |
| ReadStream* stream); |
| |
| const ParsedFunction& parsed_function() const { return parsed_function_; } |
| |
| Zone* zone() const { return zone_; } |
| ReadStream* stream() const { return stream_; } |
| Thread* thread() const { return thread_; } |
| IsolateGroup* isolate_group() const { return isolate_group_; } |
| |
| GraphEntryInstr* graph_entry() const { return graph_entry_; } |
| void set_graph_entry(GraphEntryInstr* entry) { graph_entry_ = entry; } |
| |
| BlockEntryInstr* current_block() const { return current_block_; } |
| void set_current_block(BlockEntryInstr* block) { current_block_ = block; } |
| |
| BlockEntryInstr* block(intptr_t block_id) const { |
| BlockEntryInstr* b = blocks_[block_id]; |
| ASSERT(b != nullptr); |
| return b; |
| } |
| void set_block(intptr_t block_id, BlockEntryInstr* block) { |
| ASSERT(blocks_[block_id] == nullptr); |
| blocks_[block_id] = block; |
| } |
| |
| Definition* definition(intptr_t ssa_temp_index) const { |
| Definition* def = definitions_[ssa_temp_index]; |
| ASSERT(def != nullptr); |
| return def; |
| } |
| void set_definition(intptr_t ssa_temp_index, Definition* def) { |
| ASSERT(definitions_[ssa_temp_index] == nullptr); |
| definitions_[ssa_temp_index] = def; |
| } |
| |
| FlowGraph* ReadFlowGraph(); |
| |
| // Default implementation of 'Read' method, when |
| // specialization for a particular type is not provided. |
| // This struct is used for the partial template instantiations below. |
| template <typename T> |
| struct ReadTrait {}; |
| |
| template <typename T> |
| struct ReadTrait<GrowableArray<T>> { |
| static GrowableArray<T> Read(FlowGraphDeserializer* d) { |
| const intptr_t len = d->Read<intptr_t>(); |
| GrowableArray<T> array(len); |
| for (int i = 0; i < len; ++i) { |
| array.Add(d->Read<T>()); |
| } |
| return array; |
| } |
| }; |
| |
| template <typename T> |
| struct ReadTrait<const GrowableArray<T>&> { |
| static const GrowableArray<T>& Read(FlowGraphDeserializer* d) { |
| return ReadTrait<GrowableArray<T>>::Read(d); |
| } |
| }; |
| |
| template <typename T> |
| struct ReadTrait<ZoneGrowableArray<T>*> { |
| static ZoneGrowableArray<T>* Read(FlowGraphDeserializer* d) { |
| const intptr_t len = d->Read<intptr_t>(); |
| if (len < 0) { |
| return nullptr; |
| } |
| auto* array = new (d->zone()) ZoneGrowableArray<T>(d->zone(), len); |
| for (int i = 0; i < len; ++i) { |
| array->Add(d->Read<T>()); |
| } |
| return array; |
| } |
| }; |
| |
| template <typename T> |
| struct ReadTrait<const ZoneGrowableArray<T>&> { |
| static const ZoneGrowableArray<T>& Read(FlowGraphDeserializer* d) { |
| return *ReadTrait<ZoneGrowableArray<T>*>::Read(d); |
| } |
| }; |
| |
| // Specialization in case intptr_t is not mapped to intN_t. |
| template <> |
| struct ReadTrait<intptr_t> { |
| static intptr_t Read(FlowGraphDeserializer* d) { |
| #ifdef ARCH_IS_64_BIT |
| return d->Read<int64_t>(); |
| #else |
| return d->Read<int32_t>(); |
| #endif |
| } |
| }; |
| |
| // Specialization in case uintptr_t is not mapped to uintN_t. |
| template <> |
| struct ReadTrait<uintptr_t> { |
| static uintptr_t Read(FlowGraphDeserializer* d) { |
| #ifdef ARCH_IS_64_BIT |
| return d->Read<uint64_t>(); |
| #else |
| return d->Read<uint32_t>(); |
| #endif |
| } |
| }; |
| |
| template <typename T> |
| T Read() { |
| return ReadTrait<T>::Read(this); |
| } |
| |
| #define DECLARE_READ_METHOD(type) \ |
| template <> \ |
| type Read<type>(); |
| IL_SERIALIZABLE_TYPE_LIST(DECLARE_READ_METHOD) |
| #undef DECLARE_READ_METHOD |
| |
| template <typename T> |
| T ReadRef(); |
| |
| #define DECLARE_READ_REF_METHOD(type) \ |
| template <> \ |
| type ReadRef<type>(); |
| IL_SERIALIZABLE_REF_TYPE_LIST(DECLARE_READ_REF_METHOD) |
| #undef DECLARE_READ_REF_METHOD |
| |
| template <typename T> |
| GrowableArray<T> ReadGrowableArrayOfRefs() { |
| const intptr_t len = Read<intptr_t>(); |
| GrowableArray<T> array(len); |
| for (int i = 0; i < len; ++i) { |
| array.Add(ReadRef<T>()); |
| } |
| return std::move(array); |
| } |
| |
| private: |
| ClassPtr GetClassById(classid_t id) const; |
| const Object& ReadObjectImpl(intptr_t cid, intptr_t object_index); |
| void SetObjectAt(intptr_t object_index, const Object& object); |
| |
| InstancePtr MaybeCanonicalize(const Instance& obj, |
| intptr_t object_index, |
| bool can_be_canonicalized); |
| |
| const ParsedFunction& parsed_function_; |
| ReadStream* stream_; |
| Zone* zone_; |
| Thread* thread_; |
| IsolateGroup* isolate_group_; |
| |
| // Deserialized objects. |
| GraphEntryInstr* graph_entry_ = nullptr; |
| BlockEntryInstr* current_block_ = nullptr; |
| GrowableArray<BlockEntryInstr*> blocks_; |
| GrowableArray<Definition*> definitions_; |
| GrowableArray<const Object*> objects_; |
| intptr_t object_counter_ = 0; |
| GrowableArray<intptr_t> pending_canonicalization_; |
| }; |
| |
| } // namespace dart |
| |
| #endif // RUNTIME_VM_COMPILER_BACKEND_IL_SERIALIZER_H_ |