e64bd9a01a
Rationale: The "unoptimized code" representation is really a stack machine that pushes and pops arguments. The intermediate representation did not show the correct labeling for "temporaries", in particular around push arguments. This CL fixes that bug, making the human readable form of the intermediate much easier to understand. Also, it allows for proper stack depth computations. First use: Sharpen condition on OSR points a bit. Simple control flow collections will now allow OSR. https://github.com/dart-lang/sdk/issues/36421 https://github.com/dart-lang/sdk/issues/36409 Change-Id: I8be707af08462a22687b355b747ba85e42431ad5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98500 Commit-Queue: Aart Bik <ajcbik@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
1878 lines
61 KiB
C++
1878 lines
61 KiB
C++
// Copyright (c) 2018, 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.
|
|
|
|
#include "vm/compiler/frontend/bytecode_flow_graph_builder.h"
|
|
|
|
#include "vm/compiler/backend/il_printer.h"
|
|
#include "vm/compiler/frontend/bytecode_reader.h"
|
|
#include "vm/compiler/frontend/prologue_builder.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/stack_frame_kbc.h"
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#define B (flow_graph_builder_)
|
|
#define Z (zone_)
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool,
|
|
print_flow_graph_from_bytecode,
|
|
false,
|
|
"Print flow graph constructed from bytecode");
|
|
|
|
namespace kernel {
|
|
|
|
// 8-bit unsigned operand at bits 8-15.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandA() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeA(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
// 8-bit unsigned operand at bits 16-23.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandB() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeB(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
// 8-bit unsigned operand at bits 24-31.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandC() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeC(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
// 16-bit unsigned operand at bits 16-31.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandD() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeD(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
// 16-bit signed operand at bits 16-31.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandX() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeX(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
// 24-bit signed operand at bits 8-31.
|
|
BytecodeFlowGraphBuilder::Operand BytecodeFlowGraphBuilder::DecodeOperandT() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
intptr_t value = KernelBytecode::DecodeT(bytecode_instr_);
|
|
return Operand(value);
|
|
}
|
|
}
|
|
|
|
KBCInstr BytecodeFlowGraphBuilder::InstructionAt(
|
|
intptr_t pc,
|
|
KernelBytecode::Opcode expect_opcode) {
|
|
ASSERT(!is_generating_interpreter());
|
|
ASSERT((0 <= pc) && (pc < bytecode_length_));
|
|
|
|
const KBCInstr instr = raw_bytecode_[pc];
|
|
if (KernelBytecode::DecodeOpcode(instr) != expect_opcode) {
|
|
FATAL3("Expected bytecode instruction %s, but found %s at %" Pd "",
|
|
KernelBytecode::NameOf(KernelBytecode::Encode(expect_opcode)),
|
|
KernelBytecode::NameOf(instr), pc);
|
|
}
|
|
|
|
return instr;
|
|
}
|
|
|
|
BytecodeFlowGraphBuilder::Constant BytecodeFlowGraphBuilder::ConstantAt(
|
|
Operand entry_index,
|
|
intptr_t add_index) {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
const Object& value = Object::ZoneHandle(
|
|
Z, object_pool_.ObjectAt(entry_index.value() + add_index));
|
|
return Constant(Z, value);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::PushConstant(Constant constant) {
|
|
if (is_generating_interpreter()) {
|
|
B->Push(constant.definition());
|
|
} else {
|
|
code_ += B->Constant(constant.value());
|
|
}
|
|
}
|
|
|
|
BytecodeFlowGraphBuilder::Constant BytecodeFlowGraphBuilder::PopConstant() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
ASSERT(!IsStackEmpty());
|
|
const Object& value = B->stack_->definition()->AsConstant()->value();
|
|
code_ += B->Drop();
|
|
return Constant(Z, value);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::LoadStackSlots(intptr_t num_slots) {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
ASSERT(GetStackDepth() >= num_slots);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::AllocateLocalVariables(
|
|
Operand frame_size,
|
|
intptr_t num_param_locals) {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
// TODO(alexmarkov): Make table of local variables in bytecode and
|
|
// propagate type, name and positions.
|
|
|
|
ASSERT(local_vars_.is_empty());
|
|
|
|
const intptr_t num_bytecode_locals = frame_size.value();
|
|
ASSERT(num_bytecode_locals >= 0);
|
|
|
|
intptr_t num_locals = num_bytecode_locals;
|
|
if (exception_var_ != nullptr) {
|
|
++num_locals;
|
|
}
|
|
if (stacktrace_var_ != nullptr) {
|
|
++num_locals;
|
|
}
|
|
if (scratch_var_ != nullptr) {
|
|
++num_locals;
|
|
}
|
|
if (parsed_function()->has_arg_desc_var()) {
|
|
++num_locals;
|
|
}
|
|
|
|
if (num_locals == 0) {
|
|
return;
|
|
}
|
|
|
|
local_vars_.EnsureLength(num_bytecode_locals, nullptr);
|
|
for (intptr_t i = num_param_locals; i < num_bytecode_locals; ++i) {
|
|
String& name =
|
|
String::ZoneHandle(Z, Symbols::NewFormatted(thread(), "var%" Pd, i));
|
|
LocalVariable* local = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
name, Object::dynamic_type());
|
|
local->set_index(VariableIndex(-i));
|
|
local_vars_[i] = local;
|
|
}
|
|
|
|
intptr_t idx = num_bytecode_locals;
|
|
if (exception_var_ != nullptr) {
|
|
exception_var_->set_index(VariableIndex(-idx));
|
|
++idx;
|
|
}
|
|
if (stacktrace_var_ != nullptr) {
|
|
stacktrace_var_->set_index(VariableIndex(-idx));
|
|
++idx;
|
|
}
|
|
if (scratch_var_ != nullptr) {
|
|
scratch_var_->set_index(VariableIndex(-idx));
|
|
++idx;
|
|
}
|
|
if (parsed_function()->has_arg_desc_var()) {
|
|
parsed_function()->arg_desc_var()->set_index(VariableIndex(-idx));
|
|
++idx;
|
|
}
|
|
ASSERT(idx == num_locals);
|
|
|
|
ASSERT(parsed_function()->node_sequence() == nullptr);
|
|
parsed_function()->AllocateBytecodeVariables(num_locals);
|
|
}
|
|
}
|
|
|
|
LocalVariable* BytecodeFlowGraphBuilder::AllocateParameter(
|
|
intptr_t param_index,
|
|
VariableIndex var_index) {
|
|
const String& name =
|
|
String::ZoneHandle(Z, function().ParameterNameAt(param_index));
|
|
const AbstractType& type =
|
|
AbstractType::ZoneHandle(Z, function().ParameterTypeAt(param_index));
|
|
|
|
LocalVariable* param_var = new (Z) LocalVariable(
|
|
TokenPosition::kNoSource, TokenPosition::kNoSource, name, type);
|
|
param_var->set_index(var_index);
|
|
|
|
if (var_index.value() <= 0) {
|
|
local_vars_[-var_index.value()] = param_var;
|
|
}
|
|
|
|
return param_var;
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::AllocateFixedParameters() {
|
|
if (is_generating_interpreter()) {
|
|
return;
|
|
}
|
|
|
|
ASSERT(!function().HasOptionalParameters());
|
|
|
|
const intptr_t num_fixed_params = function().num_fixed_parameters();
|
|
auto parameters =
|
|
new (Z) ZoneGrowableArray<LocalVariable*>(Z, num_fixed_params);
|
|
|
|
for (intptr_t i = 0; i < num_fixed_params; ++i) {
|
|
LocalVariable* param_var =
|
|
AllocateParameter(i, VariableIndex(num_fixed_params - i));
|
|
parameters->Add(param_var);
|
|
}
|
|
|
|
parsed_function()->SetRawParameters(parameters);
|
|
}
|
|
|
|
LocalVariable* BytecodeFlowGraphBuilder::LocalVariableAt(intptr_t local_index) {
|
|
ASSERT(!is_generating_interpreter());
|
|
if (local_index < 0) {
|
|
// Parameter
|
|
ASSERT(!function().HasOptionalParameters());
|
|
const intptr_t param_index = local_index +
|
|
function().num_fixed_parameters() +
|
|
kKBCParamEndSlotFromFp;
|
|
ASSERT((0 <= param_index) &&
|
|
(param_index < function().num_fixed_parameters()));
|
|
return parsed_function()->RawParameterVariable(param_index);
|
|
} else {
|
|
return local_vars_.At(local_index);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::StoreLocal(Operand local_index) {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
LocalVariable* local_var = LocalVariableAt(local_index.value());
|
|
code_ += B->StoreLocalRaw(position_, local_var);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::LoadLocal(Operand local_index) {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
} else {
|
|
LocalVariable* local_var = LocalVariableAt(local_index.value());
|
|
code_ += B->LoadLocal(local_var);
|
|
}
|
|
}
|
|
|
|
Value* BytecodeFlowGraphBuilder::Pop() {
|
|
LoadStackSlots(1);
|
|
return B->Pop();
|
|
}
|
|
|
|
intptr_t BytecodeFlowGraphBuilder::GetStackDepth() const {
|
|
ASSERT(!is_generating_interpreter());
|
|
return B->GetStackDepth();
|
|
}
|
|
|
|
bool BytecodeFlowGraphBuilder::IsStackEmpty() const {
|
|
ASSERT(!is_generating_interpreter());
|
|
return B->GetStackDepth() == 0;
|
|
}
|
|
|
|
ArgumentArray BytecodeFlowGraphBuilder::GetArguments(int count) {
|
|
ArgumentArray arguments =
|
|
new (Z) ZoneGrowableArray<PushArgumentInstr*>(Z, count);
|
|
arguments->SetLength(count);
|
|
for (intptr_t i = count - 1; i >= 0; --i) {
|
|
ASSERT(!IsStackEmpty());
|
|
Definition* arg_def = B->stack_->definition();
|
|
ASSERT(!arg_def->HasSSATemp());
|
|
ASSERT(arg_def->temp_index() >= i);
|
|
|
|
PushArgumentInstr* argument = new (Z) PushArgumentInstr(Pop());
|
|
|
|
if (code_.current == arg_def) {
|
|
code_ <<= argument;
|
|
} else {
|
|
Instruction* next = arg_def->next();
|
|
ASSERT(next != nullptr);
|
|
arg_def->LinkTo(argument);
|
|
argument->LinkTo(next);
|
|
}
|
|
|
|
arguments->data()[i] = argument;
|
|
}
|
|
return arguments;
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::PropagateStackState(intptr_t target_pc) {
|
|
if (is_generating_interpreter() || IsStackEmpty()) {
|
|
return;
|
|
}
|
|
|
|
Value* current_stack = B->stack_;
|
|
Value* target_stack = stack_states_.Lookup(target_pc);
|
|
|
|
if (target_stack != nullptr) {
|
|
// Control flow join should observe the same stack state from
|
|
// all incoming branches.
|
|
RELEASE_ASSERT(target_stack == current_stack);
|
|
} else {
|
|
// Stack state propagation is supported for forward branches only.
|
|
RELEASE_ASSERT(target_pc > pc_);
|
|
stack_states_.Insert(target_pc, current_stack);
|
|
}
|
|
}
|
|
|
|
// Drop values from the stack unless they are used in control flow joins
|
|
// which are not generated yet (dartbug.com/36374).
|
|
void BytecodeFlowGraphBuilder::DropUnusedValuesFromStack() {
|
|
intptr_t drop_depth = GetStackDepth();
|
|
auto it = stack_states_.GetIterator();
|
|
for (const auto* current = it.Next(); current != nullptr;
|
|
current = it.Next()) {
|
|
if (current->key > pc_) {
|
|
Value* used_value = current->value;
|
|
Value* value = B->stack_;
|
|
// Find if a value on the expression stack is used in a propagated
|
|
// stack state, and adjust [drop_depth] to preserve it.
|
|
for (intptr_t i = 0; i < drop_depth; ++i) {
|
|
if (value == used_value) {
|
|
drop_depth = i;
|
|
break;
|
|
}
|
|
value = value->next_use();
|
|
}
|
|
}
|
|
}
|
|
for (intptr_t i = 0; i < drop_depth; ++i) {
|
|
B->Pop();
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildInstruction(KernelBytecode::Opcode opcode) {
|
|
switch (opcode) {
|
|
#define BUILD_BYTECODE_CASE(name, encoding, op1, op2, op3) \
|
|
case KernelBytecode::k##name: \
|
|
Build##name(); \
|
|
break;
|
|
|
|
PUBLIC_KERNEL_BYTECODES_LIST(BUILD_BYTECODE_CASE)
|
|
|
|
#undef BUILD_BYTECODE_CASE
|
|
default:
|
|
FATAL1("Unsupported bytecode instruction %s\n",
|
|
KernelBytecode::NameOf(bytecode_instr_));
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildEntry() {
|
|
AllocateLocalVariables(DecodeOperandD());
|
|
AllocateFixedParameters();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildEntryFixed() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const intptr_t num_fixed_params = DecodeOperandA().value();
|
|
ASSERT(num_fixed_params == function().num_fixed_parameters());
|
|
|
|
AllocateLocalVariables(DecodeOperandD());
|
|
AllocateFixedParameters();
|
|
|
|
Fragment check_args;
|
|
|
|
ASSERT(throw_no_such_method_ == nullptr);
|
|
throw_no_such_method_ = B->BuildThrowNoSuchMethod();
|
|
|
|
check_args += B->LoadArgDescriptor();
|
|
check_args +=
|
|
B->LoadNativeField(Slot::ArgumentsDescriptor_positional_count());
|
|
check_args += B->IntConstant(num_fixed_params);
|
|
TargetEntryInstr *success1, *fail1;
|
|
check_args += B->BranchIfEqual(&success1, &fail1);
|
|
check_args = Fragment(check_args.entry, success1);
|
|
|
|
check_args += B->LoadArgDescriptor();
|
|
check_args += B->LoadNativeField(Slot::ArgumentsDescriptor_count());
|
|
check_args += B->IntConstant(num_fixed_params);
|
|
TargetEntryInstr *success2, *fail2;
|
|
check_args += B->BranchIfEqual(&success2, &fail2);
|
|
check_args = Fragment(check_args.entry, success2);
|
|
|
|
Fragment(fail1) + B->Goto(throw_no_such_method_);
|
|
Fragment(fail2) + B->Goto(throw_no_such_method_);
|
|
|
|
ASSERT(IsStackEmpty());
|
|
|
|
if (!B->IsInlining() && !B->IsCompiledForOsr()) {
|
|
code_ += check_args;
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildEntryOptional() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const intptr_t num_fixed_params = DecodeOperandA().value();
|
|
const intptr_t num_opt_pos_params = DecodeOperandB().value();
|
|
const intptr_t num_opt_named_params = DecodeOperandC().value();
|
|
ASSERT(num_fixed_params == function().num_fixed_parameters());
|
|
ASSERT(num_opt_pos_params == function().NumOptionalPositionalParameters());
|
|
ASSERT(num_opt_named_params == function().NumOptionalNamedParameters());
|
|
|
|
ASSERT((num_opt_pos_params == 0) || (num_opt_named_params == 0));
|
|
const intptr_t num_load_const = num_opt_pos_params + 2 * num_opt_named_params;
|
|
|
|
const KBCInstr frame_instr =
|
|
InstructionAt(pc_ + 1 + num_load_const, KernelBytecode::kFrame);
|
|
const intptr_t num_extra_locals = KernelBytecode::DecodeD(frame_instr);
|
|
const intptr_t num_params =
|
|
num_fixed_params + num_opt_pos_params + num_opt_named_params;
|
|
const intptr_t total_locals = num_params + num_extra_locals;
|
|
|
|
AllocateLocalVariables(Operand(total_locals), num_params);
|
|
|
|
ZoneGrowableArray<const Instance*>* default_values =
|
|
new (Z) ZoneGrowableArray<const Instance*>(
|
|
Z, num_opt_pos_params + num_opt_named_params);
|
|
ZoneGrowableArray<LocalVariable*>* raw_parameters =
|
|
new (Z) ZoneGrowableArray<LocalVariable*>(Z, num_params);
|
|
LocalVariable* temp_var = nullptr;
|
|
|
|
intptr_t param = 0;
|
|
for (; param < num_fixed_params; ++param) {
|
|
LocalVariable* param_var = AllocateParameter(param, VariableIndex(-param));
|
|
raw_parameters->Add(param_var);
|
|
}
|
|
|
|
for (intptr_t i = 0; i < num_opt_pos_params; ++i, ++param) {
|
|
const KBCInstr load_value_instr =
|
|
InstructionAt(pc_ + 1 + i, KernelBytecode::kLoadConstant);
|
|
const Object& default_value =
|
|
ConstantAt(Operand(KernelBytecode::DecodeD(load_value_instr))).value();
|
|
ASSERT(KernelBytecode::DecodeA(load_value_instr) == param);
|
|
|
|
LocalVariable* param_var = AllocateParameter(param, VariableIndex(-param));
|
|
raw_parameters->Add(param_var);
|
|
default_values->Add(
|
|
&Instance::ZoneHandle(Z, Instance::RawCast(default_value.raw())));
|
|
}
|
|
|
|
if (num_opt_named_params > 0) {
|
|
default_values->EnsureLength(num_opt_named_params, nullptr);
|
|
raw_parameters->EnsureLength(num_params, nullptr);
|
|
|
|
ASSERT(scratch_var_ != nullptr);
|
|
temp_var = scratch_var_;
|
|
|
|
for (intptr_t i = 0; i < num_opt_named_params; ++i, ++param) {
|
|
const KBCInstr load_name_instr =
|
|
InstructionAt(pc_ + 1 + i * 2, KernelBytecode::kLoadConstant);
|
|
const KBCInstr load_value_instr =
|
|
InstructionAt(pc_ + 1 + i * 2 + 1, KernelBytecode::kLoadConstant);
|
|
const String& param_name = String::Cast(
|
|
ConstantAt(Operand(KernelBytecode::DecodeD(load_name_instr)))
|
|
.value());
|
|
ASSERT(param_name.IsSymbol());
|
|
const Object& default_value =
|
|
ConstantAt(Operand(KernelBytecode::DecodeD(load_value_instr)))
|
|
.value();
|
|
|
|
intptr_t param_index = num_fixed_params;
|
|
for (; param_index < num_params; ++param_index) {
|
|
if (function().ParameterNameAt(param_index) == param_name.raw()) {
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(param_index < num_params);
|
|
|
|
ASSERT(default_values->At(param_index - num_fixed_params) == nullptr);
|
|
(*default_values)[param_index - num_fixed_params] =
|
|
&Instance::ZoneHandle(Z, Instance::RawCast(default_value.raw()));
|
|
|
|
const intptr_t local_index = KernelBytecode::DecodeA(load_name_instr);
|
|
ASSERT(local_index == KernelBytecode::DecodeA(load_value_instr));
|
|
|
|
LocalVariable* param_var =
|
|
AllocateParameter(param_index, VariableIndex(-param));
|
|
ASSERT(raw_parameters->At(param_index) == nullptr);
|
|
(*raw_parameters)[param_index] = param_var;
|
|
}
|
|
}
|
|
|
|
parsed_function()->set_default_parameter_values(default_values);
|
|
parsed_function()->SetRawParameters(raw_parameters);
|
|
|
|
Fragment copy_args_prologue;
|
|
|
|
// Code generated for EntryOptional is considered a prologue code.
|
|
// Prologue should span a range of block ids, so start a new block at the
|
|
// beginning and end a block at the end.
|
|
JoinEntryInstr* prologue_entry = B->BuildJoinEntry();
|
|
copy_args_prologue += B->Goto(prologue_entry);
|
|
copy_args_prologue = Fragment(copy_args_prologue.entry, prologue_entry);
|
|
|
|
ASSERT(throw_no_such_method_ == nullptr);
|
|
throw_no_such_method_ = B->BuildThrowNoSuchMethod();
|
|
|
|
PrologueBuilder prologue_builder(parsed_function(), B->last_used_block_id_,
|
|
B->IsCompiledForOsr(), B->IsInlining());
|
|
|
|
copy_args_prologue += prologue_builder.BuildOptionalParameterHandling(
|
|
throw_no_such_method_, temp_var);
|
|
|
|
B->last_used_block_id_ = prologue_builder.last_used_block_id();
|
|
|
|
JoinEntryInstr* prologue_exit = B->BuildJoinEntry();
|
|
copy_args_prologue += B->Goto(prologue_exit);
|
|
copy_args_prologue.current = prologue_exit;
|
|
|
|
if (!B->IsInlining() && !B->IsCompiledForOsr()) {
|
|
code_ += copy_args_prologue;
|
|
}
|
|
|
|
prologue_info_ =
|
|
PrologueInfo(prologue_entry->block_id(), prologue_exit->block_id() - 1);
|
|
|
|
// Skip LoadConstant and Frame instructions.
|
|
pc_ += num_load_const + 1;
|
|
|
|
ASSERT(IsStackEmpty());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildLoadConstant() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
// Handled in EntryOptional instruction.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildFrame() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
// Handled in EntryOptional instruction.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCheckFunctionTypeArgs() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const intptr_t expected_num_type_args = DecodeOperandA().value();
|
|
LocalVariable* type_args_var = LocalVariableAt(DecodeOperandD().value());
|
|
ASSERT(function().IsGeneric());
|
|
|
|
if (throw_no_such_method_ == nullptr) {
|
|
throw_no_such_method_ = B->BuildThrowNoSuchMethod();
|
|
}
|
|
|
|
Fragment setup_type_args;
|
|
JoinEntryInstr* done = B->BuildJoinEntry();
|
|
|
|
// Type args are always optional, so length can always be zero.
|
|
// If expect_type_args, a non-zero length must match the declaration length.
|
|
TargetEntryInstr *then, *fail;
|
|
setup_type_args += B->LoadArgDescriptor();
|
|
setup_type_args +=
|
|
B->LoadNativeField(Slot::ArgumentsDescriptor_type_args_len());
|
|
|
|
if (expected_num_type_args != 0) {
|
|
JoinEntryInstr* join2 = B->BuildJoinEntry();
|
|
|
|
LocalVariable* len = B->MakeTemporary();
|
|
|
|
TargetEntryInstr* otherwise;
|
|
setup_type_args += B->LoadLocal(len);
|
|
setup_type_args += B->IntConstant(0);
|
|
setup_type_args += B->BranchIfEqual(&then, &otherwise);
|
|
|
|
TargetEntryInstr* then2;
|
|
Fragment check_len(otherwise);
|
|
check_len += B->LoadLocal(len);
|
|
check_len += B->IntConstant(expected_num_type_args);
|
|
check_len += B->BranchIfEqual(&then2, &fail);
|
|
|
|
Fragment null_type_args(then);
|
|
null_type_args += B->NullConstant();
|
|
null_type_args += B->StoreLocalRaw(TokenPosition::kNoSource, type_args_var);
|
|
null_type_args += B->Drop();
|
|
null_type_args += B->Goto(join2);
|
|
|
|
Fragment store_type_args(then2);
|
|
store_type_args += B->LoadArgDescriptor();
|
|
store_type_args += B->LoadNativeField(Slot::ArgumentsDescriptor_count());
|
|
store_type_args += B->LoadFpRelativeSlot(
|
|
kWordSize * (1 + compiler::target::frame_layout.param_end_from_fp));
|
|
store_type_args +=
|
|
B->StoreLocalRaw(TokenPosition::kNoSource, type_args_var);
|
|
store_type_args += B->Drop();
|
|
store_type_args += B->Goto(join2);
|
|
|
|
Fragment(join2) + B->Drop() + B->Goto(done);
|
|
Fragment(fail) + B->Goto(throw_no_such_method_);
|
|
} else {
|
|
setup_type_args += B->IntConstant(0);
|
|
setup_type_args += B->BranchIfEqual(&then, &fail);
|
|
Fragment(then) + B->Goto(done);
|
|
Fragment(fail) + B->Goto(throw_no_such_method_);
|
|
}
|
|
|
|
setup_type_args = Fragment(setup_type_args.entry, done);
|
|
ASSERT(IsStackEmpty());
|
|
|
|
if (expected_num_type_args != 0) {
|
|
parsed_function()->set_function_type_arguments(type_args_var);
|
|
parsed_function()->SetRawTypeArgumentsVariable(type_args_var);
|
|
}
|
|
|
|
if (!B->IsInlining() && !B->IsCompiledForOsr()) {
|
|
code_ += setup_type_args;
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCheckStack() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
const intptr_t loop_depth = DecodeOperandA().value();
|
|
if (loop_depth == 0) {
|
|
ASSERT(IsStackEmpty());
|
|
code_ += B->CheckStackOverflowInPrologue(position_);
|
|
} else {
|
|
// Avoid OSR points inside block-expressions.
|
|
// TODO(ajcbik): make sure OSR works inside BE too.
|
|
if (!IsStackEmpty()) {
|
|
code_ += B->CheckStackOverflow(position_, loop_depth);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushConstant() {
|
|
PushConstant(ConstantAt(DecodeOperandD()));
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushNull() {
|
|
code_ += B->NullConstant();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushTrue() {
|
|
code_ += B->Constant(Bool::True());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushFalse() {
|
|
code_ += B->Constant(Bool::False());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushInt() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
code_ += B->IntConstant(DecodeOperandX().value());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreLocal() {
|
|
LoadStackSlots(1);
|
|
const Operand local_index = DecodeOperandX();
|
|
StoreLocal(local_index);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPopLocal() {
|
|
BuildStoreLocal();
|
|
code_ += B->Drop();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPush() {
|
|
const Operand local_index = DecodeOperandX();
|
|
LoadLocal(local_index);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildIndirectStaticCall() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const ICData& icdata = ICData::Cast(PopConstant().value());
|
|
|
|
const Function& target = Function::ZoneHandle(Z, icdata.GetTargetAt(0));
|
|
const ArgumentsDescriptor arg_desc(
|
|
Array::Cast(ConstantAt(DecodeOperandD()).value()));
|
|
intptr_t argc = DecodeOperandA().value();
|
|
ASSERT(ic_data_array_->At(icdata.deopt_id())->Original() == icdata.raw());
|
|
|
|
ArgumentArray arguments = GetArguments(argc);
|
|
|
|
// TODO(alexmarkov): pass ICData::kSuper for super calls
|
|
// (need to distinguish them in bytecode).
|
|
StaticCallInstr* call = new (Z) StaticCallInstr(
|
|
position_, target, arg_desc.TypeArgsLen(),
|
|
Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), arguments,
|
|
*ic_data_array_, icdata.deopt_id(), ICData::kStatic);
|
|
|
|
// TODO(alexmarkov): add type info
|
|
// SetResultTypeForStaticCall(call, target, argument_count, result_type);
|
|
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildDirectCall() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const Function& target = Function::Cast(ConstantAt(DecodeOperandD()).value());
|
|
const intptr_t argc = DecodeOperandA().value();
|
|
|
|
// Recognize identical() call.
|
|
// Note: similar optimization is performed in AST flow graph builder - see
|
|
// StreamingFlowGraphBuilder::BuildStaticInvocation, special_case_identical.
|
|
// TODO(alexmarkov): find a better place for this optimization.
|
|
if (target.name() == Symbols::Identical().raw()) {
|
|
const auto& owner = Class::Handle(Z, target.Owner());
|
|
if (owner.IsTopLevel() && (owner.library() == Library::CoreLibrary())) {
|
|
ASSERT(argc == 2);
|
|
code_ += B->StrictCompare(Token::kEQ_STRICT, /*number_check=*/true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const Array& arg_desc_array =
|
|
Array::Cast(ConstantAt(DecodeOperandD(), 1).value());
|
|
const ArgumentsDescriptor arg_desc(arg_desc_array);
|
|
|
|
ArgumentArray arguments = GetArguments(argc);
|
|
|
|
// TODO(alexmarkov): pass ICData::kSuper for super calls
|
|
// (need to distinguish them in bytecode).
|
|
StaticCallInstr* call = new (Z) StaticCallInstr(
|
|
position_, target, arg_desc.TypeArgsLen(),
|
|
Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), arguments,
|
|
*ic_data_array_, B->GetNextDeoptId(), ICData::kStatic);
|
|
|
|
// TODO(alexmarkov): add type info
|
|
// SetResultTypeForStaticCall(call, target, argument_count, result_type);
|
|
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildInterfaceCall() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const String& name = String::Cast(ConstantAt(DecodeOperandD()).value());
|
|
ASSERT(name.IsSymbol());
|
|
|
|
const Array& arg_desc_array =
|
|
Array::Cast(ConstantAt(DecodeOperandD(), 1).value());
|
|
const ArgumentsDescriptor arg_desc(arg_desc_array);
|
|
|
|
const intptr_t argc = DecodeOperandA().value();
|
|
Token::Kind token_kind = MethodTokenRecognizer::RecognizeTokenKind(name);
|
|
|
|
intptr_t checked_argument_count = 1;
|
|
if ((token_kind != Token::kILLEGAL) ||
|
|
(name.raw() ==
|
|
Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()).raw())) {
|
|
intptr_t argument_count = arg_desc.Count();
|
|
ASSERT(argument_count <= 2);
|
|
checked_argument_count = argument_count;
|
|
} else if (name.raw() ==
|
|
Library::PrivateCoreLibName(Symbols::_instanceOf()).raw()) {
|
|
token_kind = Token::kIS;
|
|
}
|
|
|
|
const ArgumentArray arguments = GetArguments(argc);
|
|
|
|
// TODO(alexmarkov): store interface_target in bytecode and pass it here.
|
|
|
|
InstanceCallInstr* call = new (Z) InstanceCallInstr(
|
|
position_, name, token_kind, arguments, arg_desc.TypeArgsLen(),
|
|
Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), checked_argument_count,
|
|
*ic_data_array_, B->GetNextDeoptId());
|
|
|
|
// TODO(alexmarkov): add type info - call->SetResultType()
|
|
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildDynamicCall() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const ICData& icdata = ICData::Cast(ConstantAt(DecodeOperandD()).value());
|
|
ASSERT(ic_data_array_->At(icdata.deopt_id())->Original() == icdata.raw());
|
|
|
|
const intptr_t argc = DecodeOperandA().value();
|
|
const ArgumentsDescriptor arg_desc(
|
|
Array::Handle(Z, icdata.arguments_descriptor()));
|
|
|
|
const String& name = String::ZoneHandle(Z, icdata.target_name());
|
|
const Token::Kind token_kind =
|
|
MethodTokenRecognizer::RecognizeTokenKind(name);
|
|
|
|
const ArgumentArray arguments = GetArguments(argc);
|
|
|
|
// TODO(alexmarkov): store interface_target in bytecode and pass it here.
|
|
|
|
InstanceCallInstr* call = new (Z) InstanceCallInstr(
|
|
position_, name, token_kind, arguments, arg_desc.TypeArgsLen(),
|
|
Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), icdata.NumArgsTested(),
|
|
*ic_data_array_, icdata.deopt_id());
|
|
|
|
ASSERT(call->ic_data() != nullptr);
|
|
ASSERT(call->ic_data()->Original() == icdata.raw());
|
|
|
|
// TODO(alexmarkov): add type info - call->SetResultType()
|
|
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildNativeCall() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
ASSERT(function().is_native());
|
|
|
|
// TODO(alexmarkov): find a way to avoid code duplication with
|
|
// FlowGraphBuilder::NativeFunctionBody.
|
|
const MethodRecognizer::Kind kind =
|
|
MethodRecognizer::RecognizeKind(function());
|
|
switch (kind) {
|
|
case MethodRecognizer::kObjectEquals:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StrictCompare(Token::kEQ_STRICT);
|
|
break;
|
|
case MethodRecognizer::kStringBaseLength:
|
|
case MethodRecognizer::kStringBaseIsEmpty:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::String_length());
|
|
if (kind == MethodRecognizer::kStringBaseIsEmpty) {
|
|
code_ += B->IntConstant(0);
|
|
code_ += B->StrictCompare(Token::kEQ_STRICT);
|
|
}
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayLength:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::GrowableObjectArray_length());
|
|
break;
|
|
case MethodRecognizer::kObjectArrayLength:
|
|
case MethodRecognizer::kImmutableArrayLength:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::Array_length());
|
|
break;
|
|
case MethodRecognizer::kTypedListLength:
|
|
case MethodRecognizer::kTypedListViewLength:
|
|
case MethodRecognizer::kByteDataViewLength:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::TypedDataBase_length());
|
|
break;
|
|
case MethodRecognizer::kClassIDgetID:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadClassId();
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayCapacity:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::GrowableObjectArray_data());
|
|
code_ += B->LoadNativeField(Slot::Array_length());
|
|
break;
|
|
case MethodRecognizer::kListFactory: {
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric() &&
|
|
function().HasOptionalParameters());
|
|
ASSERT(scratch_var_ != nullptr);
|
|
// Generate code that performs:
|
|
//
|
|
// factory List<E>([int length]) {
|
|
// return (:arg_desc.positional_count == 2) ? new _List<E>(length)
|
|
// : new _GrowableList<E>(0);
|
|
// }
|
|
const auto& core_lib = Library::Handle(Z, Library::CoreLibrary());
|
|
|
|
TargetEntryInstr *allocate_non_growable, *allocate_growable;
|
|
|
|
code_ += B->Drop(); // Drop 'length'.
|
|
code_ += B->Drop(); // Drop 'type arguments'.
|
|
code_ += B->LoadArgDescriptor();
|
|
code_ += B->LoadNativeField(Slot::ArgumentsDescriptor_positional_count());
|
|
code_ += B->IntConstant(2);
|
|
code_ +=
|
|
B->BranchIfStrictEqual(&allocate_non_growable, &allocate_growable);
|
|
|
|
JoinEntryInstr* join = B->BuildJoinEntry();
|
|
|
|
{
|
|
const auto& cls = Class::Handle(
|
|
Z, core_lib.LookupClass(
|
|
Library::PrivateCoreLibName(Symbols::_List())));
|
|
ASSERT(!cls.IsNull());
|
|
const auto& func = Function::ZoneHandle(
|
|
Z, cls.LookupFactoryAllowPrivate(Symbols::_ListFactory()));
|
|
ASSERT(!func.IsNull());
|
|
|
|
code_ = Fragment(allocate_non_growable);
|
|
code_ += B->LoadLocal(LocalVariableAt(0));
|
|
code_ += B->LoadLocal(LocalVariableAt(1));
|
|
auto* call = new (Z) StaticCallInstr(
|
|
TokenPosition::kNoSource, func, 0, Array::null_array(),
|
|
GetArguments(2), *ic_data_array_, B->GetNextDeoptId(),
|
|
ICData::kStatic);
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
code_ += B->StoreLocal(TokenPosition::kNoSource, scratch_var_);
|
|
code_ += B->Drop();
|
|
code_ += B->Goto(join);
|
|
}
|
|
|
|
{
|
|
const auto& cls = Class::Handle(
|
|
Z, core_lib.LookupClass(
|
|
Library::PrivateCoreLibName(Symbols::_GrowableList())));
|
|
ASSERT(!cls.IsNull());
|
|
const auto& func = Function::ZoneHandle(
|
|
Z, cls.LookupFactoryAllowPrivate(Symbols::_GrowableListFactory()));
|
|
ASSERT(!func.IsNull());
|
|
|
|
code_ = Fragment(allocate_growable);
|
|
code_ += B->LoadLocal(LocalVariableAt(0));
|
|
code_ += B->IntConstant(0);
|
|
auto* call = new (Z) StaticCallInstr(
|
|
TokenPosition::kNoSource, func, 0, Array::null_array(),
|
|
GetArguments(2), *ic_data_array_, B->GetNextDeoptId(),
|
|
ICData::kStatic);
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
code_ += B->StoreLocal(TokenPosition::kNoSource, scratch_var_);
|
|
code_ += B->Drop();
|
|
code_ += B->Goto(join);
|
|
}
|
|
|
|
code_ = Fragment(join);
|
|
code_ += B->LoadLocal(scratch_var_);
|
|
break;
|
|
}
|
|
case MethodRecognizer::kObjectArrayAllocate:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->CreateArray();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getIndex:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::LinkedHashMap_index());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setIndex:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_index());
|
|
code_ += B->NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getData:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::LinkedHashMap_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setData:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_data());
|
|
code_ += B->NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getHashMask:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::LinkedHashMap_hash_mask());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setHashMask:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_hash_mask(),
|
|
kNoStoreBarrier);
|
|
code_ += B->NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getUsedData:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::LinkedHashMap_used_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_used_data(),
|
|
kNoStoreBarrier);
|
|
code_ += B->NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
|
ASSERT((function().NumParameters() == 1) && !function().IsGeneric());
|
|
code_ += B->LoadNativeField(Slot::LinkedHashMap_deleted_keys());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
|
ASSERT((function().NumParameters() == 2) && !function().IsGeneric());
|
|
code_ += B->StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_deleted_keys(),
|
|
kNoStoreBarrier);
|
|
code_ += B->NullConstant();
|
|
break;
|
|
default: {
|
|
B->InlineBailout("BytecodeFlowGraphBuilder::BuildNativeCall");
|
|
const auto& name = String::ZoneHandle(Z, function().native_name());
|
|
const intptr_t num_args =
|
|
function().NumParameters() + (function().IsGeneric() ? 1 : 0);
|
|
ArgumentArray arguments = GetArguments(num_args);
|
|
auto* call =
|
|
new (Z) NativeCallInstr(&name, &function(), FLAG_link_natives_lazily,
|
|
function().end_token_pos(), arguments);
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAllocate() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const Class& klass = Class::Cast(ConstantAt(DecodeOperandD()).value());
|
|
|
|
const ArgumentArray arguments =
|
|
new (Z) ZoneGrowableArray<PushArgumentInstr*>(Z, 0);
|
|
|
|
AllocateObjectInstr* allocate =
|
|
new (Z) AllocateObjectInstr(position_, klass, arguments);
|
|
|
|
code_ <<= allocate;
|
|
B->Push(allocate);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAllocateT() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const Class& klass = Class::Cast(PopConstant().value());
|
|
const ArgumentArray arguments = GetArguments(1);
|
|
|
|
AllocateObjectInstr* allocate =
|
|
new (Z) AllocateObjectInstr(position_, klass, arguments);
|
|
|
|
code_ <<= allocate;
|
|
B->Push(allocate);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAllocateContext() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const intptr_t context_id = DecodeOperandA().value();
|
|
const intptr_t num_context_vars = DecodeOperandD().value();
|
|
|
|
auto& context_variables = CompilerState::Current().GetDummyContextVariables(
|
|
context_id, num_context_vars);
|
|
code_ += B->AllocateContext(context_variables);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCloneContext() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(1);
|
|
const intptr_t context_id = DecodeOperandA().value();
|
|
const intptr_t num_context_vars = DecodeOperandD().value();
|
|
|
|
auto& context_variables = CompilerState::Current().GetDummyContextVariables(
|
|
context_id, num_context_vars);
|
|
CloneContextInstr* clone_instruction = new (Z) CloneContextInstr(
|
|
TokenPosition::kNoSource, Pop(), context_variables, B->GetNextDeoptId());
|
|
code_ <<= clone_instruction;
|
|
B->Push(clone_instruction);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCreateArrayTOS() {
|
|
LoadStackSlots(2);
|
|
code_ += B->CreateArray();
|
|
}
|
|
|
|
const Slot& ClosureSlotByField(const Field& field) {
|
|
const intptr_t offset = field.Offset();
|
|
if (offset == Closure::instantiator_type_arguments_offset()) {
|
|
return Slot::Closure_instantiator_type_arguments();
|
|
} else if (offset == Closure::function_type_arguments_offset()) {
|
|
return Slot::Closure_function_type_arguments();
|
|
} else if (offset == Closure::delayed_type_arguments_offset()) {
|
|
return Slot::Closure_delayed_type_arguments();
|
|
} else if (offset == Closure::function_offset()) {
|
|
return Slot::Closure_function();
|
|
} else if (offset == Closure::context_offset()) {
|
|
return Slot::Closure_context();
|
|
} else {
|
|
RELEASE_ASSERT(offset == Closure::hash_offset());
|
|
return Slot::Closure_hash();
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreFieldTOS() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(2);
|
|
Operand cp_index = DecodeOperandD();
|
|
|
|
const Field& field = Field::Cast(ConstantAt(cp_index, 1).value());
|
|
ASSERT(Smi::Cast(ConstantAt(cp_index).value()).Value() * kWordSize ==
|
|
field.Offset());
|
|
|
|
if (field.Owner() == isolate()->object_store()->closure_class()) {
|
|
// Stores to _Closure fields are lower-level.
|
|
code_ += B->StoreInstanceField(position_, ClosureSlotByField(field));
|
|
} else {
|
|
// The rest of the StoreFieldTOS are for field initializers.
|
|
// TODO(alexmarkov): Consider adding a flag to StoreFieldTOS or even
|
|
// adding a separate bytecode instruction.
|
|
code_ += B->StoreInstanceFieldGuarded(field,
|
|
/* is_initialization_store = */ true);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildLoadFieldTOS() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(1);
|
|
Operand cp_index = DecodeOperandD();
|
|
|
|
const Field& field = Field::Cast(ConstantAt(cp_index, 1).value());
|
|
ASSERT(Smi::Cast(ConstantAt(cp_index).value()).Value() * kWordSize ==
|
|
field.Offset());
|
|
|
|
if (field.Owner() == isolate()->object_store()->closure_class()) {
|
|
// Loads from _Closure fields are lower-level.
|
|
code_ += B->LoadNativeField(ClosureSlotByField(field));
|
|
} else {
|
|
code_ += B->LoadField(field);
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreContextParent() {
|
|
LoadStackSlots(2);
|
|
|
|
code_ += B->StoreInstanceField(position_, Slot::Context_parent());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildLoadContextParent() {
|
|
LoadStackSlots(1);
|
|
|
|
code_ += B->LoadNativeField(Slot::Context_parent());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreContextVar() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(2);
|
|
const intptr_t context_id = DecodeOperandA().value();
|
|
const intptr_t var_index = DecodeOperandD().value();
|
|
|
|
auto var =
|
|
CompilerState::Current().GetDummyCapturedVariable(context_id, var_index);
|
|
code_ += B->StoreInstanceField(
|
|
position_, Slot::GetContextVariableSlotFor(thread(), *var));
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildLoadContextVar() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(1);
|
|
const intptr_t context_id = DecodeOperandA().value();
|
|
const intptr_t var_index = DecodeOperandD().value();
|
|
|
|
auto var =
|
|
CompilerState::Current().GetDummyCapturedVariable(context_id, var_index);
|
|
code_ += B->LoadNativeField(Slot::GetContextVariableSlotFor(thread(), *var));
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildLoadTypeArgumentsField() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(1);
|
|
const intptr_t offset =
|
|
Smi::Cast(ConstantAt(DecodeOperandD()).value()).Value() * kWordSize;
|
|
|
|
code_ += B->LoadNativeField(Slot::GetTypeArgumentsSlotAt(thread(), offset));
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreStaticTOS() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(1);
|
|
Operand cp_index = DecodeOperandD();
|
|
|
|
const Field& field = Field::Cast(ConstantAt(cp_index).value());
|
|
|
|
code_ += B->StoreStaticField(position_, field);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildPushStatic() {
|
|
// Note: Field object is both pushed into the stack and
|
|
// available in constant pool entry D.
|
|
// TODO(alexmarkov): clean this up. If we stop pushing field object
|
|
// explicitly, we might need the following code to get it from constant
|
|
// pool: PushConstant(ConstantAt(DecodeOperandD()));
|
|
|
|
code_ += B->LoadStaticField();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildStoreIndexedTOS() {
|
|
LoadStackSlots(3);
|
|
code_ += B->StoreIndexed(kArrayCid);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildBooleanNegateTOS() {
|
|
LoadStackSlots(1);
|
|
code_ += B->BooleanNegate();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildInstantiateType() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const AbstractType& type =
|
|
AbstractType::Cast(ConstantAt(DecodeOperandD()).value());
|
|
|
|
LoadStackSlots(2);
|
|
code_ += B->InstantiateType(type);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildInstantiateTypeArgumentsTOS() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const TypeArguments& type_args =
|
|
TypeArguments::Cast(ConstantAt(DecodeOperandD()).value());
|
|
|
|
LoadStackSlots(2);
|
|
code_ += B->InstantiateTypeArguments(type_args);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAssertBoolean() {
|
|
LoadStackSlots(1);
|
|
code_ += B->AssertBool(position_);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAssertAssignable() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(5);
|
|
|
|
const String& dst_name = String::Cast(PopConstant().value());
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
const AbstractType& dst_type = AbstractType::Cast(PopConstant().value());
|
|
Value* value = Pop();
|
|
|
|
AssertAssignableInstr* instr = new (Z) AssertAssignableInstr(
|
|
position_, value, instantiator_type_args, function_type_args, dst_type,
|
|
dst_name, B->GetNextDeoptId());
|
|
|
|
code_ <<= instr;
|
|
|
|
B->Push(instr);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAssertSubtype() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(5);
|
|
|
|
const String& dst_name = String::Cast(PopConstant().value());
|
|
const AbstractType& super_type = AbstractType::Cast(PopConstant().value());
|
|
const AbstractType& sub_type = AbstractType::Cast(PopConstant().value());
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
|
|
AssertSubtypeInstr* instr = new (Z)
|
|
AssertSubtypeInstr(position_, instantiator_type_args, function_type_args,
|
|
sub_type, super_type, dst_name, B->GetNextDeoptId());
|
|
code_ <<= instr;
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJump() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
const intptr_t target_pc = pc_ + DecodeOperandT().value();
|
|
JoinEntryInstr* join = jump_targets_.Lookup(target_pc);
|
|
ASSERT(join != nullptr);
|
|
code_ += B->Goto(join);
|
|
PropagateStackState(target_pc);
|
|
B->stack_ = nullptr;
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfNoAsserts() {
|
|
ASSERT(IsStackEmpty());
|
|
if (!isolate()->asserts()) {
|
|
BuildJump();
|
|
// Skip all instructions up to the target PC, as they are all unreachable.
|
|
// If not skipped, some of the assert code may be considered reachable
|
|
// (if it contains jumps) and generated. The problem is that generated
|
|
// code may expect values left on the stack from unreachable
|
|
// (and not generated) code which immediately follows this Jump.
|
|
const intptr_t target_pc = pc_ + DecodeOperandT().value();
|
|
ASSERT(target_pc > pc_);
|
|
pc_ = target_pc - 1;
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfNotZeroTypeArgs() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
TargetEntryInstr *is_zero, *is_not_zero;
|
|
code_ += B->LoadArgDescriptor();
|
|
code_ += B->LoadNativeField(Slot::ArgumentsDescriptor_type_args_len());
|
|
code_ += B->IntConstant(0);
|
|
code_ += B->BranchIfEqual(&is_zero, &is_not_zero);
|
|
|
|
const intptr_t target_pc = pc_ + DecodeOperandT().value();
|
|
JoinEntryInstr* join = jump_targets_.Lookup(target_pc);
|
|
ASSERT(join != nullptr);
|
|
Fragment(is_not_zero) += B->Goto(join);
|
|
PropagateStackState(target_pc);
|
|
|
|
code_ = Fragment(code_.entry, is_zero);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfStrictCompare(Token::Kind cmp_kind) {
|
|
ASSERT((cmp_kind == Token::kEQ) || (cmp_kind == Token::kNE));
|
|
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LoadStackSlots(2);
|
|
|
|
TargetEntryInstr* eq_branch = nullptr;
|
|
TargetEntryInstr* ne_branch = nullptr;
|
|
code_ += B->BranchIfStrictEqual(&eq_branch, &ne_branch);
|
|
|
|
TargetEntryInstr* then_entry =
|
|
(cmp_kind == Token::kEQ) ? eq_branch : ne_branch;
|
|
TargetEntryInstr* else_entry =
|
|
(cmp_kind == Token::kEQ) ? ne_branch : eq_branch;
|
|
|
|
const intptr_t target_pc = pc_ + DecodeOperandT().value();
|
|
JoinEntryInstr* join = jump_targets_.Lookup(target_pc);
|
|
ASSERT(join != nullptr);
|
|
|
|
code_ = Fragment(then_entry);
|
|
code_ += B->Goto(join);
|
|
PropagateStackState(target_pc);
|
|
|
|
code_ = Fragment(else_entry);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfEqStrict() {
|
|
BuildJumpIfStrictCompare(Token::kEQ);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfNeStrict() {
|
|
BuildJumpIfStrictCompare(Token::kNE);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfTrue() {
|
|
code_ += B->Constant(Bool::True());
|
|
BuildJumpIfStrictCompare(Token::kEQ);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfFalse() {
|
|
code_ += B->Constant(Bool::False());
|
|
BuildJumpIfStrictCompare(Token::kEQ);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfNull() {
|
|
code_ += B->NullConstant();
|
|
BuildJumpIfStrictCompare(Token::kEQ);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildJumpIfNotNull() {
|
|
code_ += B->NullConstant();
|
|
BuildJumpIfStrictCompare(Token::kNE);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildDrop1() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
// AdjustSP(-1);
|
|
} else {
|
|
code_ += B->Drop();
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildReturnTOS() {
|
|
LoadStackSlots(1);
|
|
ASSERT(code_.is_open());
|
|
code_ += B->Return(position_);
|
|
ASSERT(IsStackEmpty());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildTrap() {
|
|
code_ += Fragment(new (Z) StopInstr("Bytecode Trap instruction")).closed();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildThrow() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
if (DecodeOperandA().value() == 0) {
|
|
// throw
|
|
LoadStackSlots(1);
|
|
code_ += B->PushArgument();
|
|
code_ += B->ThrowException(position_);
|
|
} else {
|
|
// rethrow
|
|
LoadStackSlots(2);
|
|
GetArguments(2);
|
|
code_ += Fragment(new (Z) ReThrowInstr(position_, kInvalidTryIndex,
|
|
B->GetNextDeoptId()))
|
|
.closed();
|
|
}
|
|
|
|
ASSERT(code_.is_closed());
|
|
|
|
if (!IsStackEmpty()) {
|
|
DropUnusedValuesFromStack();
|
|
B->stack_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildMoveSpecial() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
LocalVariable* special_var = nullptr;
|
|
switch (DecodeOperandA().value()) {
|
|
case KernelBytecode::kExceptionSpecialIndex:
|
|
ASSERT(exception_var_ != nullptr);
|
|
special_var = exception_var_;
|
|
break;
|
|
case KernelBytecode::kStackTraceSpecialIndex:
|
|
ASSERT(stacktrace_var_ != nullptr);
|
|
special_var = stacktrace_var_;
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
code_ += B->LoadLocal(special_var);
|
|
StoreLocal(DecodeOperandX());
|
|
code_ += B->Drop();
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildSetFrame() {
|
|
if (is_generating_interpreter()) {
|
|
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
|
|
}
|
|
|
|
// No-op in compiled code.
|
|
ASSERT(IsStackEmpty());
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildEqualsNull() {
|
|
ASSERT(scratch_var_ != nullptr);
|
|
LoadStackSlots(1);
|
|
|
|
TargetEntryInstr* true_branch = nullptr;
|
|
TargetEntryInstr* false_branch = nullptr;
|
|
code_ += B->BranchIfNull(&true_branch, &false_branch);
|
|
|
|
JoinEntryInstr* join = B->BuildJoinEntry();
|
|
|
|
code_ = Fragment(true_branch);
|
|
code_ += B->Constant(Bool::True());
|
|
code_ += B->StoreLocalRaw(position_, scratch_var_);
|
|
code_ += B->Drop();
|
|
code_ += B->Goto(join);
|
|
|
|
code_ = Fragment(false_branch);
|
|
code_ += B->Constant(Bool::False());
|
|
code_ += B->StoreLocalRaw(position_, scratch_var_);
|
|
code_ += B->Drop();
|
|
code_ += B->Goto(join);
|
|
|
|
code_ = Fragment(join);
|
|
code_ += B->LoadLocal(scratch_var_);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildIntOp(const String& name,
|
|
Token::Kind token_kind,
|
|
int num_args) {
|
|
ASSERT((num_args == 1) || (num_args == 2));
|
|
ASSERT(MethodTokenRecognizer::RecognizeTokenKind(name) == token_kind);
|
|
|
|
LoadStackSlots(num_args);
|
|
const ArgumentArray arguments = GetArguments(num_args);
|
|
|
|
InstanceCallInstr* call = new (Z) InstanceCallInstr(
|
|
position_, name, token_kind, arguments, 0, Array::null_array(), num_args,
|
|
*ic_data_array_, B->GetNextDeoptId());
|
|
|
|
code_ <<= call;
|
|
B->Push(call);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildNegateInt() {
|
|
BuildIntOp(Symbols::UnaryMinus(), Token::kNEGATE, 1);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildAddInt() {
|
|
BuildIntOp(Symbols::Plus(), Token::kADD, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildSubInt() {
|
|
BuildIntOp(Symbols::Minus(), Token::kSUB, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildMulInt() {
|
|
BuildIntOp(Symbols::Star(), Token::kMUL, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildTruncDivInt() {
|
|
BuildIntOp(Symbols::TruncDivOperator(), Token::kTRUNCDIV, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildModInt() {
|
|
BuildIntOp(Symbols::Percent(), Token::kMOD, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildBitAndInt() {
|
|
BuildIntOp(Symbols::Ampersand(), Token::kBIT_AND, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildBitOrInt() {
|
|
BuildIntOp(Symbols::BitOr(), Token::kBIT_OR, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildBitXorInt() {
|
|
BuildIntOp(Symbols::Caret(), Token::kBIT_XOR, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildShlInt() {
|
|
BuildIntOp(Symbols::LeftShiftOperator(), Token::kSHL, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildShrInt() {
|
|
BuildIntOp(Symbols::RightShiftOperator(), Token::kSHR, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCompareIntEq() {
|
|
BuildIntOp(Symbols::EqualOperator(), Token::kEQ, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCompareIntGt() {
|
|
BuildIntOp(Symbols::RAngleBracket(), Token::kGT, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCompareIntLt() {
|
|
BuildIntOp(Symbols::LAngleBracket(), Token::kLT, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCompareIntGe() {
|
|
BuildIntOp(Symbols::GreaterEqualOperator(), Token::kGTE, 2);
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::BuildCompareIntLe() {
|
|
BuildIntOp(Symbols::LessEqualOperator(), Token::kLTE, 2);
|
|
}
|
|
|
|
static bool IsICDataEntry(const ObjectPool& object_pool, intptr_t index) {
|
|
if (object_pool.TypeAt(index) != ObjectPool::EntryType::kTaggedObject) {
|
|
return false;
|
|
}
|
|
RawObject* entry = object_pool.ObjectAt(index);
|
|
return entry->IsHeapObject() && entry->IsICData();
|
|
}
|
|
|
|
// Read ICData entries in object pool, skip deopt_ids and
|
|
// pre-populate ic_data_array_.
|
|
void BytecodeFlowGraphBuilder::ProcessICDataInObjectPool(
|
|
const ObjectPool& object_pool) {
|
|
CompilerState& compiler_state = thread()->compiler_state();
|
|
ASSERT(compiler_state.deopt_id() == 0);
|
|
|
|
const intptr_t pool_length = object_pool.Length();
|
|
for (intptr_t i = 0; i < pool_length; ++i) {
|
|
if (IsICDataEntry(object_pool, i)) {
|
|
const ICData& icdata = ICData::CheckedHandle(Z, object_pool.ObjectAt(i));
|
|
const intptr_t deopt_id = compiler_state.GetNextDeoptId();
|
|
|
|
ASSERT(icdata.deopt_id() == deopt_id);
|
|
ASSERT(ic_data_array_->is_empty() ||
|
|
(ic_data_array_->At(deopt_id)->Original() == icdata.raw()));
|
|
}
|
|
}
|
|
|
|
if (ic_data_array_->is_empty()) {
|
|
const intptr_t len = compiler_state.deopt_id();
|
|
ic_data_array_->EnsureLength(len, nullptr);
|
|
for (intptr_t i = 0; i < pool_length; ++i) {
|
|
if (IsICDataEntry(object_pool, i)) {
|
|
const ICData& icdata =
|
|
ICData::CheckedHandle(Z, object_pool.ObjectAt(i));
|
|
(*ic_data_array_)[icdata.deopt_id()] = &icdata;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
intptr_t BytecodeFlowGraphBuilder::GetTryIndex(const PcDescriptors& descriptors,
|
|
intptr_t pc) {
|
|
const uword pc_offset =
|
|
KernelBytecode::BytecodePcToOffset(pc, /* is_return_address = */ true);
|
|
PcDescriptors::Iterator iter(descriptors, RawPcDescriptors::kAnyKind);
|
|
intptr_t try_index = kInvalidTryIndex;
|
|
while (iter.MoveNext()) {
|
|
const intptr_t current_try_index = iter.TryIndex();
|
|
const uword start_pc = iter.PcOffset();
|
|
if (pc_offset < start_pc) {
|
|
break;
|
|
}
|
|
const bool has_next = iter.MoveNext();
|
|
ASSERT(has_next);
|
|
const uword end_pc = iter.PcOffset();
|
|
if (start_pc <= pc_offset && pc_offset < end_pc) {
|
|
ASSERT(try_index < current_try_index);
|
|
try_index = current_try_index;
|
|
}
|
|
}
|
|
return try_index;
|
|
}
|
|
|
|
JoinEntryInstr* BytecodeFlowGraphBuilder::EnsureControlFlowJoin(
|
|
const PcDescriptors& descriptors,
|
|
intptr_t pc) {
|
|
ASSERT((0 <= pc) && (pc < bytecode_length_));
|
|
JoinEntryInstr* join = jump_targets_.Lookup(pc);
|
|
if (join == nullptr) {
|
|
join = B->BuildJoinEntry(GetTryIndex(descriptors, pc));
|
|
jump_targets_.Insert(pc, join);
|
|
}
|
|
return join;
|
|
}
|
|
|
|
bool BytecodeFlowGraphBuilder::RequiresScratchVar(KBCInstr instr) {
|
|
switch (KernelBytecode::DecodeOpcode(instr)) {
|
|
case KernelBytecode::kEntryOptional:
|
|
return KernelBytecode::DecodeC(instr) > 0;
|
|
case KernelBytecode::kEqualsNull:
|
|
return true;
|
|
case KernelBytecode::kNativeCall:
|
|
return MethodRecognizer::RecognizeKind(function()) ==
|
|
MethodRecognizer::kListFactory;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void BytecodeFlowGraphBuilder::CollectControlFlow(
|
|
const PcDescriptors& descriptors,
|
|
const ExceptionHandlers& handlers,
|
|
GraphEntryInstr* graph_entry) {
|
|
for (intptr_t pc = 0; pc < bytecode_length_; ++pc) {
|
|
const KBCInstr instr = raw_bytecode_[pc];
|
|
|
|
if (KernelBytecode::IsJumpOpcode(instr)) {
|
|
const intptr_t target = pc + KernelBytecode::DecodeT(instr);
|
|
EnsureControlFlowJoin(descriptors, target);
|
|
}
|
|
|
|
if ((scratch_var_ == nullptr) && RequiresScratchVar(instr)) {
|
|
scratch_var_ = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
Symbols::ExprTemp(), Object::dynamic_type());
|
|
}
|
|
}
|
|
|
|
PcDescriptors::Iterator iter(descriptors, RawPcDescriptors::kAnyKind);
|
|
while (iter.MoveNext()) {
|
|
const intptr_t start_pc = KernelBytecode::OffsetToBytecodePc(
|
|
iter.PcOffset(), /* is_return_address = */ true);
|
|
EnsureControlFlowJoin(descriptors, start_pc);
|
|
|
|
const bool has_next = iter.MoveNext();
|
|
ASSERT(has_next);
|
|
const intptr_t end_pc = KernelBytecode::OffsetToBytecodePc(
|
|
iter.PcOffset(), /* is_return_address = */ true);
|
|
EnsureControlFlowJoin(descriptors, end_pc);
|
|
}
|
|
|
|
if (handlers.num_entries() > 0) {
|
|
B->InlineBailout("kernel::BytecodeFlowGraphBuilder::CollectControlFlow");
|
|
|
|
exception_var_ = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
Symbols::ExceptionVar(), Object::dynamic_type());
|
|
stacktrace_var_ = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
Symbols::StackTraceVar(), Object::dynamic_type());
|
|
}
|
|
|
|
for (intptr_t try_index = 0; try_index < handlers.num_entries();
|
|
++try_index) {
|
|
ExceptionHandlerInfo handler_info;
|
|
handlers.GetHandlerInfo(try_index, &handler_info);
|
|
|
|
const intptr_t handler_pc = KernelBytecode::OffsetToBytecodePc(
|
|
handler_info.handler_pc_offset, /* is_return_address = */ false);
|
|
JoinEntryInstr* join = EnsureControlFlowJoin(descriptors, handler_pc);
|
|
|
|
// Make sure exception handler starts with SetFrame bytecode instruction.
|
|
InstructionAt(handler_pc, KernelBytecode::kSetFrame);
|
|
|
|
const Array& handler_types =
|
|
Array::ZoneHandle(Z, handlers.GetHandledTypes(try_index));
|
|
|
|
CatchBlockEntryInstr* entry = new (Z) CatchBlockEntryInstr(
|
|
TokenPosition::kNoSource, handler_info.is_generated,
|
|
B->AllocateBlockId(), handler_info.outer_try_index, graph_entry,
|
|
handler_types, try_index, handler_info.needs_stacktrace,
|
|
B->GetNextDeoptId(), nullptr, nullptr, exception_var_, stacktrace_var_);
|
|
graph_entry->AddCatchEntry(entry);
|
|
|
|
code_ = Fragment(entry);
|
|
code_ += B->Goto(join);
|
|
}
|
|
}
|
|
|
|
FlowGraph* BytecodeFlowGraphBuilder::BuildGraph() {
|
|
const Bytecode& bytecode = Bytecode::Handle(Z, function().bytecode());
|
|
|
|
object_pool_ = bytecode.object_pool();
|
|
raw_bytecode_ = reinterpret_cast<KBCInstr*>(bytecode.PayloadStart());
|
|
bytecode_length_ = bytecode.Size() / sizeof(KBCInstr);
|
|
|
|
ProcessICDataInObjectPool(object_pool_);
|
|
|
|
GraphEntryInstr* graph_entry =
|
|
new (Z) GraphEntryInstr(*parsed_function_, B->osr_id_);
|
|
|
|
auto normal_entry = B->BuildFunctionEntry(graph_entry);
|
|
graph_entry->set_normal_entry(normal_entry);
|
|
|
|
const PcDescriptors& descriptors =
|
|
PcDescriptors::Handle(Z, bytecode.pc_descriptors());
|
|
const ExceptionHandlers& handlers =
|
|
ExceptionHandlers::Handle(Z, bytecode.exception_handlers());
|
|
|
|
CollectControlFlow(descriptors, handlers, graph_entry);
|
|
|
|
kernel::BytecodeSourcePositionsIterator source_pos_iter(Z, bytecode);
|
|
bool update_position = source_pos_iter.MoveNext();
|
|
|
|
code_ = Fragment(normal_entry);
|
|
|
|
for (pc_ = 0; pc_ < bytecode_length_; ++pc_) {
|
|
bytecode_instr_ = raw_bytecode_[pc_];
|
|
|
|
JoinEntryInstr* join = jump_targets_.Lookup(pc_);
|
|
if (join != nullptr) {
|
|
Value* stack_state = stack_states_.Lookup(pc_);
|
|
if (code_.is_open()) {
|
|
if (stack_state != B->stack_) {
|
|
ASSERT(stack_state == nullptr);
|
|
stack_states_.Insert(pc_, B->stack_);
|
|
}
|
|
code_ += B->Goto(join);
|
|
} else {
|
|
ASSERT(IsStackEmpty());
|
|
B->stack_ = stack_state;
|
|
}
|
|
code_ = Fragment(join);
|
|
B->SetCurrentTryIndex(join->try_index());
|
|
} else {
|
|
// Unreachable bytecode is not allowed.
|
|
ASSERT(!code_.is_closed());
|
|
}
|
|
|
|
while (update_position &&
|
|
pc_ >= source_pos_iter.BytecodeInstructionIndex()) {
|
|
position_ = source_pos_iter.TokenPos();
|
|
update_position = source_pos_iter.MoveNext();
|
|
}
|
|
|
|
BuildInstruction(KernelBytecode::DecodeOpcode(bytecode_instr_));
|
|
|
|
if (code_.is_closed()) {
|
|
ASSERT(IsStackEmpty());
|
|
}
|
|
}
|
|
|
|
// When compiling for OSR, use a depth first search to find the OSR
|
|
// entry and make graph entry jump to it instead of normal entry.
|
|
// Catch entries are always considered reachable, even if they
|
|
// become unreachable after OSR.
|
|
if (B->IsCompiledForOsr()) {
|
|
graph_entry->RelinkToOsrEntry(Z, B->last_used_block_id_ + 1);
|
|
}
|
|
|
|
FlowGraph* flow_graph = new (Z) FlowGraph(
|
|
*parsed_function_, graph_entry, B->last_used_block_id_, prologue_info_);
|
|
|
|
if (FLAG_print_flow_graph_from_bytecode) {
|
|
FlowGraphPrinter::PrintGraph("Constructed from bytecode", flow_graph);
|
|
}
|
|
|
|
return flow_graph;
|
|
}
|
|
|
|
} // namespace kernel
|
|
} // namespace dart
|
|
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|