// Copyright (c) 2012, 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_CODE_DESCRIPTORS_H_ #define RUNTIME_VM_CODE_DESCRIPTORS_H_ #include "vm/datastream.h" #include "vm/globals.h" #include "vm/growable_array.h" #include "vm/log.h" #include "vm/runtime_entry.h" #include "vm/token_position.h" namespace dart { static constexpr intptr_t kInvalidTryIndex = -1; class DescriptorList : public ZoneObject { public: explicit DescriptorList( Zone* zone, const GrowableArray* inline_id_to_function = nullptr); ~DescriptorList() {} void AddDescriptor(UntaggedPcDescriptors::Kind kind, intptr_t pc_offset, intptr_t deopt_id, TokenPosition token_pos, intptr_t try_index, intptr_t yield_index); PcDescriptorsPtr FinalizePcDescriptors(uword entry_point); private: static constexpr intptr_t kInitialStreamSize = 64; const Function& function_; const Script& script_; ZoneWriteStream encoded_data_; intptr_t prev_pc_offset; intptr_t prev_deopt_id; int32_t prev_token_pos; DISALLOW_COPY_AND_ASSIGN(DescriptorList); }; class CompressedStackMapsBuilder : public ZoneObject { public: explicit CompressedStackMapsBuilder(Zone* zone) : encoded_bytes_(zone, kInitialStreamSize) {} void AddEntry(intptr_t pc_offset, BitmapBuilder* bitmap, intptr_t spill_slot_bit_count); CompressedStackMapsPtr Finalize() const; private: static constexpr intptr_t kInitialStreamSize = 16; ZoneWriteStream encoded_bytes_; intptr_t last_pc_offset_ = 0; DISALLOW_COPY_AND_ASSIGN(CompressedStackMapsBuilder); }; class ExceptionHandlerList : public ZoneObject { public: struct HandlerDesc { intptr_t outer_try_index; // Try block in which this try block is nested. intptr_t pc_offset; // Handler PC offset value. bool is_generated; // False if this is directly from Dart code. const Array* handler_types; // Catch clause guards. bool needs_stacktrace; }; explicit ExceptionHandlerList(const Function& function) : list_(), has_async_handler_( (function.IsAsyncFunction() || function.IsAsyncGenerator()) && !function.is_declared_in_bytecode()) {} intptr_t Length() const { return list_.length(); } void AddPlaceHolder() { struct HandlerDesc data; data.outer_try_index = -1; data.pc_offset = ExceptionHandlers::kInvalidPcOffset; data.is_generated = true; data.handler_types = nullptr; data.needs_stacktrace = false; list_.Add(data); } void AddHandler(intptr_t try_index, intptr_t outer_try_index, intptr_t pc_offset, bool is_generated, const Array& handler_types, bool needs_stacktrace) { ASSERT(try_index >= 0); while (Length() <= try_index) { AddPlaceHolder(); } list_[try_index].outer_try_index = outer_try_index; ASSERT(list_[try_index].pc_offset == ExceptionHandlers::kInvalidPcOffset); list_[try_index].pc_offset = pc_offset; list_[try_index].is_generated = is_generated; DEBUG_ASSERT(handler_types.IsNotTemporaryScopedHandle()); list_[try_index].handler_types = &handler_types; list_[try_index].needs_stacktrace |= needs_stacktrace; } // Called by rethrows, to mark their enclosing handlers. void SetNeedsStackTrace(intptr_t try_index) { // Rethrows can be generated outside a try by the compiler. if (try_index == kInvalidTryIndex) { return; } ASSERT(try_index >= 0); while (Length() <= try_index) { AddPlaceHolder(); } list_[try_index].needs_stacktrace = true; } static bool ContainsCatchAllType(const Array& array) { auto& type = AbstractType::Handle(); for (intptr_t i = 0; i < array.Length(); i++) { type ^= array.At(i); if (type.IsCatchAllType()) { return true; } } return false; } ExceptionHandlersPtr FinalizeExceptionHandlers(uword entry_point) const; private: GrowableArray list_; const bool has_async_handler_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerList); }; #if !defined(DART_PRECOMPILED_RUNTIME) // Used to construct CatchEntryMoves for the AOT mode of compilation. class CatchEntryMovesMapBuilder : public ZoneObject { public: CatchEntryMovesMapBuilder(); void NewMapping(intptr_t pc_offset); void Append(const CatchEntryMove& move); void EndMapping(); TypedDataPtr FinalizeCatchEntryMovesMap(); private: class TrieNode; Zone* zone_; TrieNode* root_; intptr_t current_pc_offset_; GrowableArray moves_; ZoneWriteStream stream_; DISALLOW_COPY_AND_ASSIGN(CatchEntryMovesMapBuilder); }; #endif // !defined(DART_PRECOMPILED_RUNTIME) // Instructions have two pieces of information needed to get accurate source // locations: the token position and the inlining id. The inlining id tells us // which function, and thus which script, to use for this instruction and the // token position, when real, tells us the position in the source for the // script for the instruction. // // Thus, we bundle the two pieces of information in InstructionSource structs // when copying or retrieving to lower the likelihood that the token position // is used without the appropriate inlining id. struct InstructionSource { // Treat an instruction source without inlining id information as unset. InstructionSource() : InstructionSource(TokenPosition::kNoSource) {} explicit InstructionSource(TokenPosition pos) : InstructionSource(pos, -1) {} InstructionSource(TokenPosition pos, intptr_t inlining_id) : token_pos(pos), inlining_id(inlining_id) {} const TokenPosition token_pos; const intptr_t inlining_id; DISALLOW_ALLOCATION(); }; struct CodeSourceMapOps : AllStatic { static constexpr uint8_t kChangePosition = 0; static constexpr uint8_t kAdvancePC = 1; static constexpr uint8_t kPushFunction = 2; static constexpr uint8_t kPopFunction = 3; static constexpr uint8_t kNullCheck = 4; static uint8_t Read(ReadStream* stream, int32_t* arg1, int32_t* arg2 = nullptr); static void Write(BaseWriteStream* stream, uint8_t op, int32_t arg1 = 0, int32_t arg2 = 0); private: static constexpr intptr_t kOpBits = 3; using OpField = BitField; using ArgField = BitField; static constexpr int32_t kMaxArgValue = Utils::NBitMask(ArgField::bitsize() - 1); static constexpr int32_t kMinArgValue = ~kMaxArgValue; static constexpr int32_t kSignBits = static_cast(kMinArgValue) << 1; }; // A CodeSourceMap maps from pc offsets to a stack of inlined functions and // their positions. This is encoded as a little bytecode that pushes and pops // functions and changes the top function's position as the PC advances. // Decoding happens by running this bytecode until we reach the desired PC. // // The implementation keeps track of two sets of state: one written to the byte // stream and one that is buffered. On the JIT, this buffering effectively gives // us a peephole optimization that merges adjacent advance PC bytecodes. On AOT, // this allows to skip encoding our position until we reach a PC where we might // throw. class CodeSourceMapBuilder : public ZoneObject { public: CodeSourceMapBuilder( Zone* zone, bool stack_traces_only, const GrowableArray& caller_inline_id, const GrowableArray& inline_id_to_token_pos, const GrowableArray& inline_id_to_function); // The position at which a function implicitly starts, for both the root and // after a push bytecode. We use the classifying position kDartCodePrologue // since it is the most common. static const TokenPosition& kInitialPosition; void BeginCodeSourceRange(int32_t pc_offset, const InstructionSource& source); void EndCodeSourceRange(int32_t pc_offset, const InstructionSource& source); void NoteDescriptor(UntaggedPcDescriptors::Kind kind, int32_t pc_offset, const InstructionSource& source); void NoteNullCheck(int32_t pc_offset, const InstructionSource& source, intptr_t name_index); void WriteFunctionEntrySourcePosition(const InstructionSource& source); // If source is from an inlined call, returns the token position of the // original call in the root function, otherwise the source's token position. TokenPosition RootPosition(const InstructionSource& source); ArrayPtr InliningIdToFunction(); CodeSourceMapPtr Finalize(); const GrowableArray& inline_id_to_function() const { return inline_id_to_function_; } private: intptr_t GetFunctionId(intptr_t inline_id); void StartInliningInterval(int32_t pc_offset, const InstructionSource& source); void BufferChangePosition(TokenPosition pos); void WriteChangePosition(TokenPosition pos); void BufferAdvancePC(int32_t distance) { buffered_pc_offset_ += distance; } void WriteAdvancePC(int32_t distance) { CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kAdvancePC, distance); written_pc_offset_ += distance; } void BufferPush(intptr_t inline_id) { buffered_inline_id_stack_.Add(inline_id); buffered_token_pos_stack_.Add(kInitialPosition); } void WritePush(intptr_t inline_id) { CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kPushFunction, GetFunctionId(inline_id)); written_inline_id_stack_.Add(inline_id); written_token_pos_stack_.Add(kInitialPosition); } void BufferPop() { buffered_inline_id_stack_.RemoveLast(); buffered_token_pos_stack_.RemoveLast(); } void WritePop() { CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kPopFunction); written_inline_id_stack_.RemoveLast(); written_token_pos_stack_.RemoveLast(); } void WriteNullCheck(int32_t name_index) { CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kNullCheck, name_index); } void FlushBuffer(); bool IsOnBufferedStack(intptr_t inline_id) { for (intptr_t i = 0; i < buffered_inline_id_stack_.length(); i++) { if (buffered_inline_id_stack_[i] == inline_id) return true; } return false; } Zone* const zone_; intptr_t buffered_pc_offset_; GrowableArray buffered_inline_id_stack_; GrowableArray buffered_token_pos_stack_; intptr_t written_pc_offset_; GrowableArray written_inline_id_stack_; GrowableArray written_token_pos_stack_; const GrowableArray& caller_inline_id_; const GrowableArray& inline_id_to_token_pos_; const GrowableArray& inline_id_to_function_; const GrowableObjectArray& inlined_functions_; Script& script_; ZoneWriteStream stream_; const bool stack_traces_only_; DISALLOW_COPY_AND_ASSIGN(CodeSourceMapBuilder); }; class CodeSourceMapReader : public ValueObject { public: CodeSourceMapReader(const CodeSourceMap& map, const Array& functions, const Function& root) : map_(map), functions_(functions), root_(root) {} void GetInlinedFunctionsAt(int32_t pc_offset, GrowableArray* function_stack, GrowableArray* token_positions); NOT_IN_PRODUCT(void PrintJSONInlineIntervals(JSONObject* jsobj)); void DumpInlineIntervals(uword start); void DumpSourcePositions(uword start); intptr_t GetNullCheckNameIndexAt(int32_t pc_offset); private: static const TokenPosition& InitialPosition() { if (FLAG_precompiled_mode) { // In precompiled mode, the CodeSourceMap stores lines instead of // real token positions and uses kNoSourcePos for no line information. return TokenPosition::kNoSource; } else { return CodeSourceMapBuilder::kInitialPosition; } } // Reads a TokenPosition value from a CSM, handling the different encoding for // when non-symbolic stack traces are enabled. static TokenPosition ReadPosition(ReadStream* stream); const CodeSourceMap& map_; const Array& functions_; const Function& root_; DISALLOW_COPY_AND_ASSIGN(CodeSourceMapReader); }; } // namespace dart #endif // RUNTIME_VM_CODE_DESCRIPTORS_H_