dart-sdk/runtime/vm/code_descriptors.h
Alexander Markov 5f985eb3e6 Reland "[vm/compiler] Initial implementation of IL binary serialization"
This is a reland of commit 9700458975

Original change's description:
> [vm/compiler] Initial implementation of IL binary serialization
>
> This change adds binary serialization/deserialization of flow graphs.
> It supports all IL instructions and certain objects which can be
> referenced from IL instructions. IL binary serialization is a useful
> machanism which would allow us to split compilation into multiple parts
> in order to parallelize AOT compilation.
>
> The program structure (libraries/classes/functions/fields) is not
> serialized. It is assumed that reader and writer use the same
> program structure.
>
> Caveats:
> * FFI callbacks are not supported yet.
> * Closure functions are not re-created when reading flow graph.
> * Flow graph should be in SSA form (unoptimized flow graphs are not
>   supported).
> * JIT mode is not supported (serializer currently assumes lazy
>   linking of native methods and empty ICData).
>
> In order to test IL serialization, --test_il_serialization VM option is
> added to serialize and deserialize flow graph before generating code.

TEST=vm/dart/splay_test now runs with --test_il_serialization.
TEST=Manual run of vm-kernel-precomp-linux-debug-x64-try with
--test_il_serialization enabled (only ffi tests failed).
TEST=gcc build on dart-sdk-linux-try bot.

Issue: https://github.com/dart-lang/sdk/issues/43299

> Change-Id: I7bbfd9e3a301e00c9cfbffa06b8f1f6c78a78470
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254941
> Reviewed-by: Ryan Macnak <rmacnak@google.com>
> Commit-Queue: Alexander Markov <alexmarkov@google.com>
> Reviewed-by: Slava Egorov <vegorov@google.com>

Change-Id: I64ff9747f761496a096371e490ef070a14023256
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255840
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2022-08-22 15:07:47 +00:00

372 lines
12 KiB
C++

// 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 const intptr_t kInvalidTryIndex = -1;
class DescriptorList : public ZoneAllocated {
public:
explicit DescriptorList(
Zone* zone,
const GrowableArray<const Function*>* 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 ZoneAllocated {
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 ZoneAllocated {
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()) {}
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 = NULL;
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;
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<struct HandlerDesc> 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 ZoneAllocated {
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<CatchEntryMove> 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 id)
: token_pos(pos), inlining_id(id) {}
const TokenPosition token_pos;
const intptr_t inlining_id;
DISALLOW_ALLOCATION();
};
struct CodeSourceMapOps : AllStatic {
static const uint8_t kChangePosition = 0;
static const uint8_t kAdvancePC = 1;
static const uint8_t kPushFunction = 2;
static const uint8_t kPopFunction = 3;
static const 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<int32_t, uint8_t, 0, kOpBits>;
using ArgField = BitField<int32_t, int32_t, OpField::kNextBit>;
static constexpr int32_t kMaxArgValue =
Utils::NBitMask<int32_t>(ArgField::bitsize() - 1);
static constexpr int32_t kMinArgValue = ~kMaxArgValue;
static constexpr int32_t kSignBits = static_cast<uint32_t>(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 ZoneAllocated {
public:
CodeSourceMapBuilder(
Zone* zone,
bool stack_traces_only,
const GrowableArray<intptr_t>& caller_inline_id,
const GrowableArray<TokenPosition>& inline_id_to_token_pos,
const GrowableArray<const Function*>& 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<const Function*>& 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<intptr_t> buffered_inline_id_stack_;
GrowableArray<TokenPosition> buffered_token_pos_stack_;
intptr_t written_pc_offset_;
GrowableArray<intptr_t> written_inline_id_stack_;
GrowableArray<TokenPosition> written_token_pos_stack_;
const GrowableArray<intptr_t>& caller_inline_id_;
const GrowableArray<TokenPosition>& inline_id_to_token_pos_;
const GrowableArray<const Function*>& 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<const Function*>* function_stack,
GrowableArray<TokenPosition>* 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_