[vm/compiler] Continued work on the IL serializer.

Adds support for:
  Constant
  Goto
  JoinEntry
  PushArgument
  StaticCall

Adds canonical name parsing, which allows us to parse Function
and Field values. Adds ImmutableList, Instance, and TypeParameter
parsing.

Results from compiling hello world program:

* Early round trip:
    * Contains unhandled instructions: 4070
    * Failed during deserialization: 2
    * Successful round trips: 106
* Late round trip:
    * Contains unhandled instructions: 3789
    * Failed during deserialization: 4
    * Successful round trips: 385

Bug: https://github.com/dart-lang/sdk/issues/36882
Change-Id: If9cb4c133e3ba8c62016e545f8471c67cc126290
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/113684
Commit-Queue: Teagan Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Aart Bik <ajcbik@google.com>
This commit is contained in:
Teagan Strickland 2019-09-03 09:14:00 +00:00 committed by commit-bot@chromium.org
parent 8fb0152af8
commit d2e99c4935
6 changed files with 706 additions and 126 deletions

View file

@ -1910,10 +1910,7 @@ class Definition : public Instruction {
explicit Definition(intptr_t deopt_id = DeoptId::kNone);
// Overridden by definitions that have call counts.
virtual intptr_t CallCount() const {
UNREACHABLE();
return -1;
}
virtual intptr_t CallCount() const { return -1; }
intptr_t temp_index() const { return temp_index_; }
void set_temp_index(intptr_t index) { temp_index_ = index; }
@ -3441,6 +3438,7 @@ class ClosureCallInstr : public TemplateDartCall<1> {
Code::EntryKind entry_kind() const { return entry_kind_; }
PRINT_OPERANDS_TO_SUPPORT
ADD_EXTRA_INFO_TO_S_EXPRESSION_SUPPORT
private:
const Code::EntryKind entry_kind_;
@ -4110,6 +4108,7 @@ class StaticCallInstr : public TemplateDartCall<0> {
PRINT_OPERANDS_TO_SUPPORT
ADD_OPERANDS_TO_S_EXPRESSION_SUPPORT
ADD_EXTRA_INFO_TO_S_EXPRESSION_SUPPORT
private:
const ICData* ic_data_;

View file

@ -24,18 +24,6 @@ DEFINE_FLAG(bool,
void FlowGraphDeserializer::RoundTripSerialization(CompilerPassState* state) {
auto const flow_graph = state->flow_graph;
auto const inst =
FlowGraphDeserializer::FirstUnhandledInstruction(flow_graph);
if (inst != nullptr) {
if (FLAG_trace_round_trip_serialization_skips) {
THR_Print("Cannot serialize graph due to instruction: %s\n",
inst->DebugName());
if (auto const const_inst = inst->AsConstant()) {
THR_Print("Constant value: %s\n", const_inst->value().ToCString());
}
}
return;
}
// The deserialized flow graph must be in the same zone as the original flow
// graph, to ensure it has the right lifetime. Thus, we leave an explicit
@ -55,6 +43,30 @@ void FlowGraphDeserializer::RoundTripSerialization(CompilerPassState* state) {
// that those VM parts mentioned can be passed an explicit zone.
Zone* const zone = flow_graph->zone();
GrowableArray<Instruction*> unhandled(zone, 2);
FlowGraphDeserializer::AllUnhandledInstructions(flow_graph, &unhandled);
if (!unhandled.is_empty()) {
if (FLAG_trace_round_trip_serialization_skips) {
THR_Print("Cannot serialize graph due to instruction: %s\n",
unhandled.At(0)->DebugName());
if (unhandled.length() > 1) {
CStringMap<intptr_t> count_map(zone);
for (auto inst : unhandled) {
auto const name = inst->DebugName();
auto const old_count = count_map.LookupValue(name);
count_map.Update({name, old_count + 1});
}
THR_Print("There are %" Pd " different unhandled instruction(s):\n",
count_map.Length());
auto count_it = count_map.GetIterator();
while (auto kv = count_it.Next()) {
THR_Print(" %s (%" Pd ")\n", kv->key, kv->value);
}
}
}
return;
}
auto const sexp = FlowGraphSerializer::SerializeToSExp(zone, flow_graph);
if (FLAG_trace_round_trip_serialization) {
THR_Print("----- Serialized flow graph:\n");
@ -84,54 +96,49 @@ void FlowGraphDeserializer::RoundTripSerialization(CompilerPassState* state) {
#define HANDLED_CASE(name) \
if (inst->Is##name()) return true;
static bool IsHandledInstruction(Instruction* inst) {
bool FlowGraphDeserializer::IsHandledInstruction(Instruction* inst) {
if (auto const const_inst = inst->AsConstant()) {
return IsHandledConstant(const_inst->value());
}
FOR_EACH_HANDLED_BLOCK_TYPE_IN_DESERIALIZER(HANDLED_CASE)
FOR_EACH_HANDLED_INSTRUCTION_IN_DESERIALIZER(HANDLED_CASE)
return false;
}
#undef HANDLED_CASE
Instruction* FlowGraphDeserializer::FirstUnhandledInstruction(
const FlowGraph* graph) {
void FlowGraphDeserializer::AllUnhandledInstructions(
const FlowGraph* graph,
GrowableArray<Instruction*>* unhandled) {
ASSERT(graph != nullptr);
ASSERT(unhandled != nullptr);
for (auto block_it = graph->reverse_postorder_iterator(); !block_it.Done();
block_it.Advance()) {
auto const entry = block_it.Current();
if (!IsHandledInstruction(entry)) return entry;
// The constant pool (the initial definitions of the graph entry block) is
// handled differently from other constant definitions, and there are no
// body instructions for a graph entry block. We should still make sure the
// values in the constant pool are serializable though.
if (auto const graph_entry = entry->AsGraphEntry()) {
auto const defs = graph_entry->initial_definitions();
for (intptr_t i = 0; i < defs->length(); i++) {
ASSERT(defs->At(i)->IsConstant());
auto const current = defs->At(i)->AsConstant();
if (!IsHandledConstant(current->value())) return current;
}
continue;
}
if (!IsHandledInstruction(entry)) unhandled->Add(entry);
// Don't check the Phi definitions in JoinEntrys, as those are now handled
// and also parsed differently from other definitions.
if (auto const def_block = entry->AsBlockEntryWithInitialDefs()) {
auto const defs = def_block->initial_definitions();
for (intptr_t i = 0; i < defs->length(); i++) {
auto const current = defs->At(i);
if (!IsHandledInstruction(current)) return current;
if (!IsHandledInstruction(current)) unhandled->Add(current);
}
}
for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
auto current = it.Current();
if (!IsHandledInstruction(current)) return current;
// We handle branches, so we need to check the comparison instruction.
if (current->IsBranch()) current = current->AsBranch()->comparison();
if (!IsHandledInstruction(current)) unhandled->Add(current);
}
}
return nullptr;
}
// Keep in sync with work in ParseDartValue. Right now, this is just a shallow
// check, not a deep one.
bool FlowGraphDeserializer::IsHandledConstant(const Object& obj) {
return obj.IsNull() || obj.IsBool() || obj.IsString() || obj.IsInteger() ||
obj.IsDouble() || obj.IsClass() || obj.IsType() ||
obj.IsTypeArguments();
if (obj.IsArray()) return Array::Cast(obj).IsImmutable();
if (obj.IsInstance()) return !obj.IsClosure();
return obj.IsNull() || obj.IsClass() || obj.IsFunction() || obj.IsField();
}
SExpression* FlowGraphDeserializer::Retrieve(SExpList* list, intptr_t index) {
@ -164,25 +171,15 @@ FlowGraph* FlowGraphDeserializer::ParseFlowGraph() {
auto const root = CheckTaggedList(root_sexp_, "FlowGraph");
if (root == nullptr) return nullptr;
auto const name_sexp = CheckSymbol(Retrieve(root, 1));
// TODO(sstrickl): If the FlowGraphDeserializer was constructed with a
// non-null ParsedFunction, we should check that the name matches here.
// If not, then we should create an appropriate ParsedFunction here.
if (name_sexp == nullptr) return nullptr;
intptr_t osr_id = Compiler::kNoOSRDeoptId;
if (auto const osr_id_sexp = CheckInteger(root->ExtraLookupValue("osr_id"))) {
osr_id = osr_id_sexp->value();
}
intptr_t deopt_id = DeoptId::kNone;
if (auto const deopt_id_sexp =
CheckInteger(root->ExtraLookupValue("deopt_id"))) {
deopt_id = deopt_id_sexp->value();
}
CommonEntryInfo common_info = {0, kInvalidTryIndex, deopt_id};
auto const graph = HandleGraphEntry(root, common_info);
auto const graph =
new (zone()) GraphEntryInstr(*parsed_function_, osr_id, deopt_id);
PrologueInfo pi(-1, -1);
flow_graph_ = new (zone()) FlowGraph(*parsed_function_, graph, 0, pi);
flow_graph_->CreateCommonConstants();
@ -307,36 +304,41 @@ BlockEntryInstr* FlowGraphDeserializer::ParseBlockHeader(SExpList* list,
try_index = try_int->value();
}
auto const old_block = block_map_.LookupValue(block_id);
BlockEntryInstr* block = nullptr;
CommonEntryInfo common_info = {block_id, try_index, deopt_id};
switch (kind) {
case FlowGraphSerializer::kTarget:
block = new (zone()) TargetEntryInstr(block_id, try_index, deopt_id);
block = HandleTargetEntry(list, common_info);
break;
case FlowGraphSerializer::kNormal:
// The Normal and Unchecked cases are the same except for the
// set_XXX_entry call, so combine them.
FALL_THROUGH;
case FlowGraphSerializer::kUnchecked: {
block = block_map_.LookupValue(block_id);
// These blocks were already created during ParseEntries, so just
// return the created block.
if (block != nullptr) {
ASSERT(block_id == block->block_id());
return block;
if (old_block != nullptr) {
ASSERT(old_block->block_id() == block_id);
ASSERT(old_block->IsFunctionEntry());
return old_block;
}
auto const graph = flow_graph_->graph_entry();
block =
new (zone()) FunctionEntryInstr(graph, block_id, try_index, deopt_id);
if (kind == FlowGraphSerializer::kUnchecked) {
graph->set_unchecked_entry(block->AsFunctionEntry());
} else {
block = HandleFunctionEntry(list, common_info);
if (block != nullptr) {
auto const graph = flow_graph_->graph_entry();
graph->set_normal_entry(block->AsFunctionEntry());
}
graph->AddDominatedBlock(block);
current_block_ = block;
if (!ParseInitialDefinitions(list)) return nullptr;
break;
case FlowGraphSerializer::kUnchecked: {
if (old_block != nullptr) {
ASSERT(old_block->block_id() == block_id);
ASSERT(old_block->IsFunctionEntry());
return old_block;
}
block = HandleFunctionEntry(list, common_info);
if (block != nullptr) {
auto const graph = flow_graph_->graph_entry();
graph->set_unchecked_entry(block->AsFunctionEntry());
}
break;
}
case FlowGraphSerializer::kJoin:
block = HandleJoinEntry(list, common_info);
break;
case FlowGraphSerializer::kInvalid:
StoreError(tag, "invalid block entry tag");
return nullptr;
@ -344,6 +346,12 @@ BlockEntryInstr* FlowGraphDeserializer::ParseBlockHeader(SExpList* list,
StoreError(tag, "unhandled block type");
return nullptr;
}
if (block == nullptr) return nullptr;
if (old_block != nullptr) {
// Any cases where this is not an error should have already returned.
StoreError(id_sexp, "duplicate definition of block");
return nullptr;
}
// For blocks with initial definitions, this needs to be done after those
// are parsed.
@ -353,24 +361,75 @@ BlockEntryInstr* FlowGraphDeserializer::ParseBlockHeader(SExpList* list,
env->DeepCopyTo(zone(), block);
}
if (block_map_.HasKey(block_id)) {
StoreError(id_sexp, "duplicate definition of block");
return nullptr;
}
block_map_.Insert(block_id, block);
return block;
}
intptr_t FlowGraphDeserializer::ParsePhis(SExpList* list, intptr_t pos) {
ASSERT(current_block_ != nullptr && current_block_->IsJoinEntry());
auto const join = current_block_->AsJoinEntry();
// All block S-expressions are of the form (Block B# inst...), so skip
// the first two entries and check for Phi definitions.
for (intptr_t i = 2, n = list->Length(); i < n; i++) {
auto const def_sexp = CheckTaggedList(Retrieve(list, i), "def");
if (def_sexp == nullptr) return i;
auto const phi_sexp = CheckTaggedList(Retrieve(def_sexp, 2), "Phi");
if (phi_sexp == nullptr) return i;
// Phi S-expressions are of the form (Phi value...). Since we use
// FlowGraph::AddPhi to create the Phi node, which takes exactly two
// definitions for the Phi inputs, error if we see more than two.
// We can change AddPhi to take a variable number of definition arguments
// if we ever run into the case where there are more than two.
if (phi_sexp->Length() > 3) {
StoreError(phi_sexp, "phi nodes with more than two inputs unhandled");
return -1;
}
intptr_t left_index;
if (!ParseSSATemp(CheckSymbol(Retrieve(phi_sexp, 1)), &left_index)) {
return -1;
}
bool has_pending_left = false;
Definition* left_def = definition_map_.LookupValue(left_index);
if (left_def == nullptr) {
left_def = flow_graph_->constant_null();
has_pending_left = true;
}
intptr_t right_index;
if (!ParseSSATemp(CheckSymbol(Retrieve(phi_sexp, 2)), &right_index)) {
return -1;
}
bool has_pending_right = false;
Definition* right_def = definition_map_.LookupValue(right_index);
if (right_def == nullptr) {
right_def = flow_graph_->constant_null();
has_pending_right = true;
}
auto const phi = flow_graph_->AddPhi(join, left_def, right_def);
if (has_pending_left) AddPendingValue(left_index, phi->InputAt(0));
if (has_pending_right) AddPendingValue(right_index, phi->InputAt(1));
if (!ParseDefinitionWithParsedBody(def_sexp, phi)) return -1;
}
StoreError(list, "block is empty or contains only Phi definitions");
return -1;
}
bool FlowGraphDeserializer::ParseBlockContents(SExpList* list) {
ASSERT(current_block_ != nullptr);
// All blocks are of the form (Block B# inst*), so the instructions start
// at the second position of the S-expression.
intptr_t pos = 2;
// TODO(sstrickl): Handle phis appropriately. Earlier attempts changed
// the serialization to separate them from the other definitions, but
// we can also just check for them since they always appear as the first
// definitions in a serialized JoinEntry block.
if (auto const join = current_block_->AsJoinEntry()) {
pos = ParsePhis(list, pos);
if (pos < 2) return false;
}
for (intptr_t i = pos; i < list->Length(); i++) {
auto const entry = CheckTaggedList(Retrieve(list, i));
@ -474,6 +533,82 @@ Instruction* FlowGraphDeserializer::ParseInstruction(SExpList* list) {
return inst;
}
FunctionEntryInstr* FlowGraphDeserializer::HandleFunctionEntry(
SExpList* sexp,
const CommonEntryInfo& info) {
ASSERT(flow_graph_ != nullptr);
auto const graph = flow_graph_->graph_entry();
auto const block = new (zone())
FunctionEntryInstr(graph, info.block_id, info.try_index, info.deopt_id);
graph->AddDominatedBlock(block);
current_block_ = block;
if (!ParseInitialDefinitions(sexp)) return nullptr;
return block;
}
GraphEntryInstr* FlowGraphDeserializer::HandleGraphEntry(
SExpList* sexp,
const CommonEntryInfo& info) {
auto const name_sexp = CheckSymbol(Retrieve(sexp, 1));
// TODO(sstrickl): If the FlowGraphDeserializer was constructed with a
// non-null ParsedFunction, we should check that the name matches here.
// If not, then we should create an appropriate ParsedFunction here.
if (name_sexp == nullptr) return nullptr;
intptr_t osr_id = Compiler::kNoOSRDeoptId;
if (auto const osr_id_sexp = CheckInteger(sexp->ExtraLookupValue("osr_id"))) {
osr_id = osr_id_sexp->value();
}
ASSERT(parsed_function_ != nullptr);
return new (zone()) GraphEntryInstr(*parsed_function_, osr_id, info.deopt_id);
}
JoinEntryInstr* FlowGraphDeserializer::HandleJoinEntry(
SExpList* sexp,
const CommonEntryInfo& info) {
return new (zone())
JoinEntryInstr(info.block_id, info.try_index, info.deopt_id);
}
TargetEntryInstr* FlowGraphDeserializer::HandleTargetEntry(
SExpList* sexp,
const CommonEntryInfo& info) {
return new (zone())
TargetEntryInstr(info.block_id, info.try_index, info.deopt_id);
}
BranchInstr* FlowGraphDeserializer::HandleBranch(SExpList* sexp,
const CommonInstrInfo& info) {
auto const comp_sexp = CheckTaggedList(Retrieve(sexp, 1));
auto const comp_inst = ParseInstruction(comp_sexp);
if (comp_inst == nullptr) return nullptr;
if (!comp_inst->IsComparison()) {
StoreError(sexp->At(1), "expected comparison instruction");
return nullptr;
}
auto const comparison = comp_inst->AsComparison();
auto const true_block = FetchBlock(CheckSymbol(Retrieve(sexp, 2)));
if (true_block == nullptr) return nullptr;
if (!true_block->IsTargetEntry()) {
StoreError(sexp->At(2), "true successor is not a target block");
return nullptr;
}
auto const false_block = FetchBlock(CheckSymbol(Retrieve(sexp, 3)));
if (false_block == nullptr) return nullptr;
if (!false_block->IsTargetEntry()) {
StoreError(sexp->At(3), "false successor is not a target block");
return nullptr;
}
auto const branch = new (zone()) BranchInstr(comparison, info.deopt_id);
*branch->true_successor_address() = true_block->AsTargetEntry();
*branch->false_successor_address() = false_block->AsTargetEntry();
return branch;
}
CheckStackOverflowInstr* FlowGraphDeserializer::HandleCheckStackOverflow(
SExpList* sexp,
const CommonInstrInfo& info) {
@ -499,6 +634,25 @@ CheckStackOverflowInstr* FlowGraphDeserializer::HandleCheckStackOverflow(
loop_depth, info.deopt_id, kind);
}
ConstantInstr* FlowGraphDeserializer::HandleConstant(
SExpList* sexp,
const CommonInstrInfo& info) {
Object& obj = Object::ZoneHandle(zone());
if (!ParseDartValue(Retrieve(sexp, 1), &obj)) return nullptr;
return new (zone()) ConstantInstr(obj, info.token_pos);
}
GotoInstr* FlowGraphDeserializer::HandleGoto(SExpList* sexp,
const CommonInstrInfo& info) {
auto const block = FetchBlock(CheckSymbol(Retrieve(sexp, 1)));
if (block == nullptr) return nullptr;
if (!block->IsJoinEntry()) {
StoreError(sexp->At(1), "target of goto must be join entry");
return nullptr;
}
return new (zone()) GotoInstr(block->AsJoinEntry(), info.deopt_id);
}
ParameterInstr* FlowGraphDeserializer::HandleParameter(
SExpList* sexp,
const CommonInstrInfo& info) {
@ -509,6 +663,16 @@ ParameterInstr* FlowGraphDeserializer::HandleParameter(
return nullptr;
}
PushArgumentInstr* FlowGraphDeserializer::HandlePushArgument(
SExpList* sexp,
const CommonInstrInfo& info) {
auto const val = ParseValue(Retrieve(sexp, 1));
if (val == nullptr) return nullptr;
auto const push = new (zone()) PushArgumentInstr(val);
pushed_stack_.Add(push);
return push;
}
ReturnInstr* FlowGraphDeserializer::HandleReturn(SExpList* list,
const CommonInstrInfo& info) {
Value* val = ParseValue(Retrieve(list, 1));
@ -531,6 +695,59 @@ SpecialParameterInstr* FlowGraphDeserializer::HandleSpecialParameter(
SpecialParameterInstr(kind, info.deopt_id, current_block_);
}
StaticCallInstr* FlowGraphDeserializer::HandleStaticCall(
SExpList* sexp,
const CommonInstrInfo& info) {
auto& function = Function::ZoneHandle(zone());
auto const function_sexp = CheckTaggedList(Retrieve(sexp, 1), "Function");
if (!ParseDartValue(function_sexp, &function)) return nullptr;
intptr_t type_args_len = 0;
if (auto const type_args_len_sexp =
CheckInteger(sexp->ExtraLookupValue("type_args_len"))) {
type_args_len = type_args_len_sexp->value();
}
Array& argument_names = Array::ZoneHandle(zone());
if (auto const arg_names_sexp =
CheckList(sexp->ExtraLookupValue("arg_names"))) {
argument_names = Array::New(arg_names_sexp->Length(), Heap::kOld);
for (intptr_t i = 0, n = arg_names_sexp->Length(); i < n; i++) {
auto name_sexp = CheckString(Retrieve(arg_names_sexp, i));
if (name_sexp == nullptr) return nullptr;
tmp_string_ = String::New(name_sexp->value(), Heap::kOld);
argument_names.SetAt(i, tmp_string_);
}
}
intptr_t args_len = 0;
if (auto const args_len_sexp =
CheckInteger(sexp->ExtraLookupValue("args_len"))) {
args_len = args_len_sexp->value();
}
auto const arguments = FetchPushedArguments(sexp, type_args_len + args_len);
if (arguments == nullptr) return nullptr;
intptr_t call_count = 0;
if (auto const call_count_sexp =
CheckInteger(sexp->ExtraLookupValue("call_count"))) {
call_count = call_count_sexp->value();
}
auto rebind_rule = ICData::kInstance;
if (auto const rebind_sexp =
CheckSymbol(sexp->ExtraLookupValue("rebind_rule"))) {
if (!ICData::RebindRuleFromCString(rebind_sexp->value(), &rebind_rule)) {
StoreError(rebind_sexp, "unknown rebind rule value");
return nullptr;
}
}
return new (zone())
StaticCallInstr(info.token_pos, function, type_args_len, argument_names,
arguments, info.deopt_id, call_count, rebind_rule);
}
Value* FlowGraphDeserializer::ParseValue(SExpression* sexp) {
auto name = sexp->AsSymbol();
CompileType* type = nullptr;
@ -549,7 +766,7 @@ Value* FlowGraphDeserializer::ParseValue(SExpression* sexp) {
auto const def = definition_map_.LookupValue(index);
Value* val;
if (def == nullptr) {
val = AddPendingValue(index);
val = AddNewPendingValue(index);
} else {
val = new (zone()) Value(def);
}
@ -619,8 +836,13 @@ Environment* FlowGraphDeserializer::ParseEnvironment(SExpList* list) {
StoreError(sym, "no definition found for environment use");
return nullptr;
}
} else if (ParseSymbolAsPrefixedInt(sym, 'a', &index)) {
if (index >= pushed_stack_.length()) {
StoreError(sym, "out of range index for pushed argument");
return nullptr;
}
def = pushed_stack_.At(index);
} else {
// TODO(sstrickl): Handle PushArgument references.
StoreError(sym, "unexpected name in env list");
return nullptr;
}
@ -635,10 +857,20 @@ bool FlowGraphDeserializer::ParseDartValue(SExpression* sexp, Object* out) {
if (sexp == nullptr) return false;
*out = Object::null();
// We'll use the null value in *out as a marker later, so go ahead and exit
// early if we parse one.
if (auto const sym = sexp->AsSymbol()) {
// We'll use the null value in *out as a marker later, so go ahead and exit
// early if we parse one.
if (strcmp(sym->value(), "null") == 0) return true;
// The only other symbols that should appear in Dart value position are
// names of constant definitions.
if (auto const val = ParseValue(sym)) {
if (!val->BindsToConstant()) {
StoreError(sym, "not a reference to a constant definition");
return false;
}
*out = val->BoundConstant().raw();
}
}
// Other instance values may need to be canonicalized, so do that before
@ -649,8 +881,8 @@ bool FlowGraphDeserializer::ParseDartValue(SExpression* sexp, Object* out) {
auto const cid_sexp = CheckInteger(Retrieve(list, 1));
if (cid_sexp == nullptr) return false;
ClassTable* table = thread()->isolate()->class_table();
if (!table->IsValidIndex(cid_sexp->value())) {
StoreError(cid_sexp, "no class found for cid");
if (!table->HasValidClassAt(cid_sexp->value())) {
StoreError(cid_sexp, "no valid class found for cid");
return false;
}
*out = table->At(cid_sexp->value());
@ -664,17 +896,52 @@ bool FlowGraphDeserializer::ParseDartValue(SExpression* sexp, Object* out) {
if (!ParseDartValue(ta_sexp, &type_args)) return false;
}
*out = Type::New(cls, type_args, TokenPosition::kNoSource, Heap::kOld);
// Need to set this for canonicalization.
// Need to set this for canonicalization. We ensure in the serializer
// that only finalized types are successfully serialized.
Type::Cast(*out).SetIsFinalized();
}
// TODO(sstrickl): Handle types not derived from classes.
} else if (strcmp(tag->value(), "TypeArguments") == 0) {
*out = TypeArguments::New(list->Length() - 1, Heap::kOld);
auto& typ = AbstractType::Handle(zone());
for (intptr_t i = 1; i < list->Length(); i++) {
if (!ParseDartValue(Retrieve(list, i), &typ)) return false;
TypeArguments::Cast(*out).SetTypeAt(i - 1, typ);
auto& type_args = TypeArguments::Cast(*out);
for (intptr_t i = 1, n = list->Length(); i < n; i++) {
if (!ParseDartValue(Retrieve(list, i), &value_type_)) return false;
type_args.SetTypeAt(i - 1, value_type_);
}
} else if (strcmp(tag->value(), "Field") == 0 ||
strcmp(tag->value(), "Function") == 0) {
auto const name_sexp = CheckSymbol(Retrieve(list, 1));
if (!ParseCanonicalName(name_sexp, out)) return false;
} else if (strcmp(tag->value(), "TypeParameter") == 0) {
ASSERT(parsed_function_ != nullptr);
auto const name_sexp = CheckSymbol(Retrieve(list, 1));
if (name_sexp == nullptr) return false;
const auto& func = parsed_function_->function();
tmp_string_ = String::New(name_sexp->value());
*out = func.LookupTypeParameter(tmp_string_, nullptr);
if (out->IsNull()) {
// Check the owning class for the function as well.
value_class_ = func.Owner();
*out = value_class_.LookupTypeParameter(tmp_string_);
}
// We'll want a more specific error message than the generic unhandled
// Dart value one if this failed.
if (out->IsNull()) {
StoreError(name_sexp, "no type parameter found for name");
return false;
}
} else if (strcmp(tag->value(), "ImmutableList") == 0) {
// Since arrays can contain arrays, we must allocate a new handle here.
auto& arr =
Array::Handle(zone(), Array::New(list->Length() - 1, Heap::kOld));
for (intptr_t i = 1; i < list->Length(); i++) {
if (!ParseDartValue(Retrieve(list, i), &value_object_)) return false;
arr.SetAt(i - 1, value_object_);
}
arr.MakeImmutable();
*out = arr.raw();
} else if (strcmp(tag->value(), "Instance") == 0) {
if (!ParseInstance(list, reinterpret_cast<Instance*>(out))) return false;
}
} else if (auto const b = sexp->AsBool()) {
*out = Bool::Get(b->value()).raw();
@ -712,6 +979,155 @@ bool FlowGraphDeserializer::ParseDartValue(SExpression* sexp, Object* out) {
return true;
}
bool FlowGraphDeserializer::ParseInstance(SExpList* list, Instance* out) {
auto const cid_sexp = CheckInteger(Retrieve(list, 1));
if (cid_sexp == nullptr) return false;
auto const table = thread()->isolate()->class_table();
if (!table->HasValidClassAt(cid_sexp->value())) {
StoreError(cid_sexp, "cid is not valid");
return false;
}
instance_class_ = table->At(cid_sexp->value());
*out = Instance::New(instance_class_, Heap::kOld);
if (list->Length() > 2) {
auto const fields_sexp = CheckTaggedList(Retrieve(list, 2), "Fields");
if (fields_sexp == nullptr) return false;
auto it = fields_sexp->ExtraIterator();
while (auto kv = it.Next()) {
tmp_string_ = String::New(kv->key);
instance_field_ = instance_class_.LookupFieldAllowPrivate(
tmp_string_, /*instance_only=*/true);
if (instance_field_.IsNull()) {
StoreError(list, "cannot find field %s", kv->key);
return false;
}
if (auto const inst = CheckTaggedList(kv->value, "Instance")) {
// Unsure if this will be necessary, so for now not doing fresh
// Instance/Class handle allocations unless it is.
StoreError(inst, "nested instances not handled yet");
return false;
}
if (!ParseDartValue(kv->value, &instance_object_)) return false;
out->SetField(instance_field_, instance_object_);
}
}
return true;
}
bool FlowGraphDeserializer::ParseCanonicalName(SExpSymbol* sym, Object* obj) {
if (sym == nullptr) return false;
auto const name = sym->value();
// TODO(sstrickl): No library URL, handle this better.
if (*name == ':') {
StoreError(sym, "expected non-empty library");
return false;
}
const char* lib_end = nullptr;
if (auto const first = strchr(name, ':')) {
lib_end = strchr(first + 1, ':');
if (lib_end == nullptr) lib_end = strchr(first + 1, '\0');
} else {
StoreError(sym, "malformed library");
return false;
}
tmp_string_ =
String::FromUTF8(reinterpret_cast<const uint8_t*>(name), lib_end - name);
name_library_ = Library::LookupLibrary(thread(), tmp_string_);
if (*lib_end == '\0') {
*obj = name_library_.raw();
return true;
}
const char* const class_start = lib_end + 1;
if (*class_start == '\0') {
StoreError(sym, "no class found after colon");
return false;
}
// If classes are followed by another part, it's either a function
// (separated by ':') or a field (separated by '.').
const char* class_end = strchr(class_start, ':');
if (class_end == nullptr) class_end = strchr(class_start, '.');
if (class_end == nullptr) class_end = strchr(class_start, '\0');
const bool empty_name = class_end == class_start;
name_class_ = Class::null();
if (empty_name) {
name_class_ = name_library_.toplevel_class();
} else {
tmp_string_ = String::FromUTF8(
reinterpret_cast<const uint8_t*>(class_start), class_end - class_start);
name_class_ = name_library_.LookupClassAllowPrivate(tmp_string_);
}
if (name_class_.IsNull()) {
StoreError(sym, "failure looking up class %s in library %s",
empty_name ? "at top level" : tmp_string_.ToCString(),
name_library_.ToCString());
return false;
}
if (*class_end == '\0') {
*obj = name_class_.raw();
return true;
}
if (*class_end == '.') {
if (class_end[1] == '\0') {
StoreError(sym, "no field name found after period");
return false;
}
const char* const field_start = class_end + 1;
const char* field_end = strchr(field_start, '\0');
tmp_string_ = String::FromUTF8(
reinterpret_cast<const uint8_t*>(field_start), field_end - field_start);
name_field_ = name_class_.LookupFieldAllowPrivate(tmp_string_);
if (name_field_.IsNull()) {
StoreError(sym, "failure looking up field %s in class %s",
tmp_string_.ToCString(),
empty_name ? "at top level" : name_class_.ToCString());
return false;
}
*obj = name_field_.raw();
return true;
}
if (class_end[1] == '\0') {
StoreError(sym, "no function name found after final colon");
return false;
}
const char* func_start = class_end + 1;
name_function_ = Function::null();
while (true) {
const char* func_end = strchr(func_start, ':');
// Special case for getters/setters, where they are prefixed with "get:"
// or "set:", as those colons should not be used as separators.
if ((func_end != nullptr) && (func_end - func_start == 3) &&
(strncmp(func_start, "get", 3) == 0 ||
strncmp(func_start, "set", 3) == 0)) {
func_end = strchr(func_end + 1, ':');
}
if (func_end == nullptr) func_end = strchr(func_start, '\0');
tmp_string_ = String::FromUTF8(reinterpret_cast<const uint8_t*>(func_start),
func_end - func_start);
if (!name_function_.IsNull()) {
StoreError(sym, "no handling for local functions");
return false;
}
name_function_ = name_class_.LookupFunctionAllowPrivate(tmp_string_);
if (name_function_.IsNull()) {
StoreError(sym, "failure looking up function %s in class %s",
tmp_string_.ToCString(), name_class_.ToCString());
return false;
}
if (func_end[0] == '\0') break;
if (func_end[1] == '\0') {
StoreError(sym, "no function name found after final colon");
return false;
}
func_start = func_end + 1;
}
*obj = name_function_.raw();
return true;
}
bool FlowGraphDeserializer::ParseBlockId(SExpSymbol* sym, intptr_t* out) {
return ParseSymbolAsPrefixedInt(sym, 'B', out);
}
@ -744,17 +1160,21 @@ bool FlowGraphDeserializer::ParseSymbolAsPrefixedInt(SExpSymbol* sym,
return true;
}
Value* FlowGraphDeserializer::AddPendingValue(intptr_t index) {
Value* FlowGraphDeserializer::AddNewPendingValue(intptr_t index) {
ASSERT(flow_graph_ != nullptr);
auto const val = new (zone()) Value(flow_graph_->constant_null());
AddPendingValue(index, val);
return val;
}
void FlowGraphDeserializer::AddPendingValue(intptr_t index, Value* val) {
ASSERT(!definition_map_.HasKey(index));
auto value_list = values_map_.LookupValue(index);
if (value_list == nullptr) {
value_list = new (zone()) ZoneGrowableArray<Value*>(zone(), 2);
values_map_.Insert(index, value_list);
}
auto const val = new (zone()) Value(flow_graph_->constant_null());
value_list->Add(val);
return val;
}
void FlowGraphDeserializer::FixPendingValues(intptr_t index, Definition* def) {
@ -767,6 +1187,32 @@ void FlowGraphDeserializer::FixPendingValues(intptr_t index, Definition* def) {
}
}
PushArgumentsArray* FlowGraphDeserializer::FetchPushedArguments(SExpList* list,
intptr_t len) {
auto const stack_len = pushed_stack_.length();
if (len > stack_len) {
StoreError(list, "expected %" Pd " pushed arguments, only %" Pd " on stack",
len, stack_len);
}
auto const arr = new (zone()) PushArgumentsArray(zone(), len);
for (intptr_t i = 0; i < len; i++) {
arr->InsertAt(0, pushed_stack_.RemoveLast());
}
return arr;
}
BlockEntryInstr* FlowGraphDeserializer::FetchBlock(SExpSymbol* sym) {
if (sym == nullptr) return nullptr;
intptr_t block_id;
if (!ParseBlockId(sym, &block_id)) return nullptr;
auto const entry = block_map_.LookupValue(block_id);
if (entry == nullptr) {
StoreError(sym, "reference to undefined block");
return nullptr;
}
return entry;
}
#define BASE_CHECK_DEF(name, type) \
SExp##name* FlowGraphDeserializer::Check##name(SExpression* sexp) { \
if (sexp == nullptr) return nullptr; \

View file

@ -22,15 +22,17 @@ namespace dart {
// Deserializes FlowGraphs from S-expressions.
class FlowGraphDeserializer : ValueObject {
public:
// Returns the first instruction that is guaranteed not to be handled by
// the current implementation of the FlowGraphDeserializer. This way,
// we can filter out graphs that are guaranteed not to be deserializable
// before going through the round-trip serialization process.
// Adds to the given array all the instructions in the flow graph that are
// guaranteed not to be handled by the current implementation of the
// FlowGraphDeserializer. This way, we can filter out graphs that are
// guaranteed not to be deserializable before going through the round-trip
// serialization process.
//
// Note that there may be other reasons that the deserializer may fail on
// a given flow graph, so getting back nullptr here is necessary, but not
// a given flow graph, so no new members of the array is necessary, but not
// sufficient, for a successful round-trip pass.
static Instruction* FirstUnhandledInstruction(const FlowGraph* graph);
static void AllUnhandledInstructions(const FlowGraph* graph,
GrowableArray<Instruction*>* out);
// Takes the FlowGraph from [state] and runs it through the serializer
// and deserializer. If the deserializer successfully deserializes the
@ -47,7 +49,20 @@ class FlowGraphDeserializer : ValueObject {
parsed_function_(pf),
block_map_(zone_),
definition_map_(zone_),
values_map_(zone_) {
values_map_(zone_),
pushed_stack_(zone_, 2),
instance_class_(Class::Handle(zone)),
instance_field_(Field::Handle(zone)),
instance_object_(Object::Handle(zone)),
name_class_(Class::Handle(zone)),
name_field_(Field::Handle(zone)),
name_function_(Function::Handle(zone)),
name_library_(Library::Handle(zone)),
value_class_(Class::Handle(zone)),
value_object_(Object::Handle(zone)),
value_type_(AbstractType::Handle(zone)),
value_type_args_(TypeArguments::Handle(zone)),
tmp_string_(String::Handle(zone)) {
// See canonicalization comment in ParseDartValue as to why this is
// currently necessary.
ASSERT(thread->zone() == zone);
@ -66,17 +81,22 @@ class FlowGraphDeserializer : ValueObject {
#define FOR_EACH_HANDLED_BLOCK_TYPE_IN_DESERIALIZER(M) \
M(FunctionEntry) \
M(GraphEntry) \
M(JoinEntry) \
M(TargetEntry)
#define FOR_EACH_HANDLED_INSTRUCTION_IN_DESERIALIZER(M) \
M(Branch) \
M(CheckStackOverflow) \
M(Constant) \
M(Goto) \
M(PushArgument) \
M(Parameter) \
M(Return) \
M(SpecialParameter)
M(SpecialParameter) \
M(StaticCall)
// Helper method for FirstUnhandledInstruction that returns whether a given
// object should be (de)serializable. Any work done on ParseDartValue may
// require changing this method.
// Helper methods for AllUnhandledInstructions.
static bool IsHandledInstruction(Instruction* inst);
static bool IsHandledConstant(const Object& obj);
// **GENERAL DESIGN NOTES FOR PARSING METHODS**
@ -103,6 +123,19 @@ class FlowGraphDeserializer : ValueObject {
bool ParseConstantPool(SExpList* pool);
bool ParseEntries(SExpList* list);
struct CommonEntryInfo {
intptr_t block_id;
intptr_t try_index;
intptr_t deopt_id;
};
#define HANDLER_DECL(name) \
name##Instr* Handle##name(SExpList* list, const CommonEntryInfo& info);
FOR_EACH_HANDLED_BLOCK_TYPE_IN_DESERIALIZER(HANDLER_DECL);
#undef HANDLER_DECL
// Block parsing is split into two passes. This pass checks the
// block ID and other extra information needed for certain block types.
// In addition, it parses initial definitions found in the entry list.
@ -112,6 +145,12 @@ class FlowGraphDeserializer : ValueObject {
// Expects [current_block_] to be set before calling.
bool ParseInitialDefinitions(SExpList* list);
// Expects [current_block_] to be set before calling.
// Takes the tagged list to parse and the index where parsing should start.
// Returns the index of the first non-Phi instruction or definition or -1 on
// error.
intptr_t ParsePhis(SExpList* list, intptr_t pos);
// Parses the instructions in the body of a block. [current_block_] must be
// set before calling.
bool ParseBlockContents(SExpList* list);
@ -161,7 +200,16 @@ class FlowGraphDeserializer : ValueObject {
// Parsing functions for which there are no good distinguished error
// values, so use out parameters and a boolean return instead.
// Parses a Dart value and returns a canonicalized result.
bool ParseDartValue(SExpression* sexp, Object* out);
// Helper function for ParseDartValue for parsing instances.
// Does not canonicalize (that is currently done in ParseDartValue), so
// do not call this method directly.
bool ParseInstance(SExpList* list, Instance* out);
bool ParseCanonicalName(SExpSymbol* sym, Object* out);
bool ParseBlockId(SExpSymbol* sym, intptr_t* out);
bool ParseSSATemp(SExpSymbol* sym, intptr_t* out);
bool ParseUse(SExpSymbol* sym, intptr_t* out);
@ -169,12 +217,26 @@ class FlowGraphDeserializer : ValueObject {
// Helper function for creating a placeholder value when the definition
// has not yet been seen.
Value* AddPendingValue(intptr_t index);
Value* AddNewPendingValue(intptr_t index);
// Similar helper, but where we already have a created value.
void AddPendingValue(intptr_t index, Value* val);
// Helper function for rebinding pending values once the definition has
// been located.
void FixPendingValues(intptr_t index, Definition* def);
// Creates a PushArgumentsArray of size [len] from [pushed_stack_] if there
// are enough and pops the fetched arguments from the stack.
//
// The [sexp] argument should be the serialized form of the instruction that
// needs the pushed arguments and is only used for error reporting.
PushArgumentsArray* FetchPushedArguments(SExpList* sexp, intptr_t len);
// Retrieves the block corresponding to the given block ID symbol from
// [block_map_]. Assumes all blocks have had their header parsed.
BlockEntryInstr* FetchBlock(SExpSymbol* sym);
// Utility functions for checking the shape of an S-expression.
// If these functions return nullptr for a non-null argument, they have the
// side effect of setting the stored error message.
@ -219,6 +281,26 @@ class FlowGraphDeserializer : ValueObject {
// values that were parsed prior to the corresponding definition being found.
IntMap<ZoneGrowableArray<Value*>*> values_map_;
// Stack of currently pushed arguments, used by environment parsing and calls.
GrowableArray<PushArgumentInstr*> pushed_stack_;
// Temporary handles used by functions that are not re-entrant or where the
// handle is not live after the re-entrant call. Comments show which handles
// are expected to only be used within a single method.
Class& instance_class_; // ParseInstance
Field& instance_field_; // ParseInstance
Object& instance_object_; // ParseInstance
Class& name_class_; // ParseCanonicalName
Field& name_field_; // ParseCanonicalName
Function& name_function_; // ParseCanonicalName
Library& name_library_; // ParseCanonicalName
Class& value_class_; // ParseDartValue
Object& value_object_; // ParseDartValue
AbstractType& value_type_; // ParseDartValue
TypeArguments& value_type_args_; // ParseDartValue
// Uses of string handles tend to be immediate, so we only need one.
String& tmp_string_;
// Stores a message appropriate to surfacing to the user when an error
// occurs.
const char* error_message_ = nullptr;

View file

@ -126,7 +126,9 @@ void FlowGraphSerializer::SerializeCanonicalName(TextBuffer* b,
ASSERT(!obj.IsNull());
if (obj.IsFunction()) {
const auto& function = Function::Cast(obj);
tmp_string_ = function.UserVisibleName();
tmp_string_ = function.name();
// We only want private keys removed, no other changes.
tmp_string_ = String::RemovePrivateKey(tmp_string_);
const char* function_name = tmp_string_.ToCString();
// If this function is an inner closure then the parent points to its
// containing function, which will also be part of the canonical name.
@ -857,8 +859,8 @@ void NativeCallInstr::AddOperandsToSExpression(SExpList* sexp,
}
}
template <>
void TemplateDartCall<0l>::AddExtraInfoToSExpression(
template <intptr_t kInputCount>
void TemplateDartCall<kInputCount>::AddExtraInfoToSExpression(
SExpList* sexp,
FlowGraphSerializer* s) const {
Instruction::AddExtraInfoToSExpression(sexp, s);
@ -866,17 +868,26 @@ void TemplateDartCall<0l>::AddExtraInfoToSExpression(
s->AddExtraInteger(sexp, "type_args_len", type_args_len());
}
s->AddExtraInteger(sexp, "args_len", ArgumentCountWithoutTypeArgs());
if (this->CallCount() > 0 || FLAG_verbose_flow_graph_serialization) {
s->AddExtraInteger(sexp, "call_count", this->CallCount());
}
const auto& arg_names = argument_names();
if (!arg_names.IsNull()) {
auto arg_names_sexp = new (s->zone()) SExpList(s->zone());
auto& str = String::Handle(s->zone());
for (intptr_t i = 0; i < arg_names.Length(); i++) {
str = String::RawCast(arg_names.At(i));
arg_names_sexp->Add(s->ObjectToSExp(str));
}
sexp->AddExtra("arg_names", arg_names_sexp);
}
}
template <>
void TemplateDartCall<1l>::AddExtraInfoToSExpression(
SExpList* sexp,
FlowGraphSerializer* s) const {
Instruction::AddExtraInfoToSExpression(sexp, s);
if (type_args_len() > 0 || FLAG_verbose_flow_graph_serialization) {
s->AddExtraInteger(sexp, "type_args_len", type_args_len());
}
s->AddExtraInteger(sexp, "args_len", ArgumentCountWithoutTypeArgs());
void ClosureCallInstr::AddExtraInfoToSExpression(SExpList* sexp,
FlowGraphSerializer* s) const {
// For now, just here to ensure TemplateDartCall<1>::AddExtraInfoToSExpression
// gets instantiated.
TemplateDartCall<1>::AddExtraInfoToSExpression(sexp, s);
}
void StaticCallInstr::AddOperandsToSExpression(SExpList* sexp,
@ -886,6 +897,18 @@ void StaticCallInstr::AddOperandsToSExpression(SExpList* sexp,
}
}
void StaticCallInstr::AddExtraInfoToSExpression(SExpList* sexp,
FlowGraphSerializer* s) const {
TemplateDartCall<0>::AddExtraInfoToSExpression(sexp, s);
if (rebind_rule_ != ICData::kInstance ||
FLAG_verbose_flow_graph_serialization) {
auto const str = ICData::RebindRuleToCString(rebind_rule_);
ASSERT(str != nullptr);
s->AddExtraSymbol(sexp, "rebind_rule", str);
}
}
void InstanceCallInstr::AddOperandsToSExpression(SExpList* sexp,
FlowGraphSerializer* s) const {
if (auto const target = s->DartValueToSExp(interface_target())) {
@ -1080,7 +1103,7 @@ SExpression* Environment::ToSExpression(FlowGraphSerializer* s) const {
for (intptr_t i = 0; i < values_.length(); ++i) {
if (values_[i]->definition()->IsPushArgument()) {
s->AddSymbol(sexp, OS::SCreate(s->zone(), "arg[%" Pd "]", arg_count++));
s->AddSymbol(sexp, OS::SCreate(s->zone(), "a%" Pd "", arg_count++));
} else {
sexp->Add(values_[i]->ToSExpression(s));
}

View file

@ -13624,6 +13624,29 @@ void ICData::AddDeoptReason(DeoptReasonId reason) const {
}
}
const char* ICData::RebindRuleToCString(RebindRule r) {
switch (r) {
#define RULE_CASE(Name) \
case RebindRule::k##Name: \
return #Name;
FOR_EACH_REBIND_RULE(RULE_CASE)
#undef RULE_CASE
default:
return nullptr;
}
}
bool ICData::RebindRuleFromCString(const char* str, RebindRule* out) {
#define RULE_CASE(Name) \
if (strcmp(str, #Name) == 0) { \
*out = RebindRule::k##Name; \
return true; \
}
FOR_EACH_REBIND_RULE(RULE_CASE)
#undef RULE_CASE
return false;
}
ICData::RebindRule ICData::rebind_rule() const {
return (ICData::RebindRule)RebindRuleBits::decode(raw_ptr()->state_bits_);
}

View file

@ -1783,15 +1783,22 @@ class ICData : public Object {
// Call site classification that is helpful for hot-reload. Call sites with
// different `RebindRule` have to be rebound differently.
#define FOR_EACH_REBIND_RULE(V) \
V(Instance) \
V(NoRebind) \
V(NSMDispatch) \
V(Optimized) \
V(Static) \
V(Super)
enum RebindRule {
kInstance,
kNoRebind,
kNSMDispatch,
kOptimized,
kStatic,
kSuper,
kNumRebindRules,
#define REBIND_ENUM_DEF(name) k##name,
FOR_EACH_REBIND_RULE(REBIND_ENUM_DEF)
#undef REBIND_ENUM_DEF
kNumRebindRules,
};
static const char* RebindRuleToCString(RebindRule r);
static bool RebindRuleFromCString(const char* str, RebindRule* out);
RebindRule rebind_rule() const;
void set_rebind_rule(uint32_t rebind_rule) const;