a421022b77
Rationale: Code had no guard on running out of registers https://github.com/dart-lang/sdk/issues/36977 Change-Id: Ifec1b7ef626e6a8720c437f0f25d119956dfb63a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106429 Commit-Queue: Aart Bik <ajcbik@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
2182 lines
74 KiB
C++
2182 lines
74 KiB
C++
// Copyright (c) 2016, 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/globals.h" // Needed here to get TARGET_ARCH_DBC.
|
|
#if defined(TARGET_ARCH_DBC)
|
|
|
|
#include "vm/compiler/backend/il.h"
|
|
|
|
#include "vm/compiler/backend/flow_graph.h"
|
|
#include "vm/compiler/backend/flow_graph_compiler.h"
|
|
#include "vm/compiler/backend/locations.h"
|
|
#include "vm/compiler/backend/range_analysis.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/cpu.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/simulator.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/stub_code.h"
|
|
#include "vm/symbols.h"
|
|
|
|
#define __ compiler->assembler()->
|
|
|
|
namespace dart {
|
|
|
|
DECLARE_FLAG(bool, emit_edge_counters);
|
|
DECLARE_FLAG(int, optimization_counter_threshold);
|
|
|
|
// List of instructions that are still unimplemented by DBC backend.
|
|
#define FOR_EACH_UNIMPLEMENTED_INSTRUCTION(M) \
|
|
M(BinaryInt32Op) \
|
|
M(BinaryUint32Op) \
|
|
M(CheckCondition) \
|
|
M(DoubleToInteger) \
|
|
M(ExtractNthOutput) \
|
|
M(GuardFieldClass) \
|
|
M(GuardFieldLength) \
|
|
M(GuardFieldType) \
|
|
M(IfThenElse) \
|
|
M(Int32ToDouble) \
|
|
M(LoadCodeUnits) \
|
|
M(ShiftUint32Op) \
|
|
M(SpeculativeShiftUint32Op) \
|
|
M(TruncDivMod) \
|
|
M(UnaryUint32Op) \
|
|
M(IntConverter)
|
|
|
|
// List of instructions that are not used by DBC.
|
|
// Things we aren't planning to implement for DBC:
|
|
// - Unboxed SIMD,
|
|
// - Unboxed Mint,
|
|
// - Optimized RegExps,
|
|
// - Precompilation.
|
|
#define FOR_EACH_UNREACHABLE_INSTRUCTION(M) \
|
|
M(CaseInsensitiveCompare) \
|
|
M(GenericCheckBound) \
|
|
M(IndirectGoto) \
|
|
M(Int64ToDouble) \
|
|
M(BinaryInt64Op) \
|
|
M(ShiftInt64Op) \
|
|
M(SpeculativeShiftInt64Op) \
|
|
M(UnaryInt64Op) \
|
|
M(CheckedSmiOp) \
|
|
M(CheckedSmiComparison) \
|
|
M(SimdOp) \
|
|
M(NativeReturn)
|
|
|
|
// Location summaries actually are not used by the unoptimizing DBC compiler
|
|
// because we don't allocate any registers.
|
|
static LocationSummary* CreateLocationSummary(
|
|
Zone* zone,
|
|
intptr_t num_inputs,
|
|
Location output = Location::NoLocation(),
|
|
LocationSummary::ContainsCall contains_call = LocationSummary::kNoCall,
|
|
intptr_t num_temps = 0) {
|
|
LocationSummary* locs =
|
|
new (zone) LocationSummary(zone, num_inputs, num_temps, contains_call);
|
|
ASSERT(contains_call == LocationSummary::kNoCall ||
|
|
num_inputs <= kMaxNumberOfFixedInputRegistersUsedByIL);
|
|
for (intptr_t i = 0; i < num_inputs; i++) {
|
|
locs->set_in(i, (contains_call == LocationSummary::kNoCall)
|
|
? Location::RequiresRegister()
|
|
: Location::RegisterLocation(i));
|
|
}
|
|
for (intptr_t i = 0; i < num_temps; i++) {
|
|
locs->set_temp(i, Location::RequiresRegister());
|
|
}
|
|
if (!output.IsInvalid()) {
|
|
// For instructions that call we default to returning result in R0.
|
|
locs->set_out(0, output);
|
|
}
|
|
return locs;
|
|
}
|
|
|
|
#define DEFINE_MAKE_LOCATION_SUMMARY(Name, ...) \
|
|
LocationSummary* Name##Instr::MakeLocationSummary(Zone* zone, bool opt) \
|
|
const { \
|
|
return CreateLocationSummary(zone, __VA_ARGS__); \
|
|
}
|
|
|
|
#define EMIT_NATIVE_CODE(Name, ...) \
|
|
DEFINE_MAKE_LOCATION_SUMMARY(Name, __VA_ARGS__); \
|
|
void Name##Instr::EmitNativeCode(FlowGraphCompiler* compiler)
|
|
|
|
#define DEFINE_UNIMPLEMENTED_MAKE_LOCATION_SUMMARY(Name) \
|
|
LocationSummary* Name##Instr::MakeLocationSummary(Zone* zone, bool opt) \
|
|
const { \
|
|
if (!opt) UNIMPLEMENTED(); \
|
|
return NULL; \
|
|
}
|
|
|
|
#define DEFINE_UNREACHABLE_MAKE_LOCATION_SUMMARY(Name) \
|
|
LocationSummary* Name##Instr::MakeLocationSummary(Zone* zone, bool opt) \
|
|
const { \
|
|
UNREACHABLE(); \
|
|
return NULL; \
|
|
}
|
|
|
|
#define DEFINE_UNIMPLEMENTED_EMIT_NATIVE_CODE(Name) \
|
|
void Name##Instr::EmitNativeCode(FlowGraphCompiler* compiler) { \
|
|
UNIMPLEMENTED(); \
|
|
}
|
|
|
|
#define DEFINE_UNREACHABLE_EMIT_NATIVE_CODE(Name) \
|
|
void Name##Instr::EmitNativeCode(FlowGraphCompiler* compiler) { \
|
|
UNREACHABLE(); \
|
|
}
|
|
|
|
#define DEFINE_UNIMPLEMENTED_EMIT_BRANCH_CODE(Name) \
|
|
void Name##Instr::EmitBranchCode(FlowGraphCompiler*, BranchInstr*) { \
|
|
UNIMPLEMENTED(); \
|
|
} \
|
|
Condition Name##Instr::EmitComparisonCode(FlowGraphCompiler*, \
|
|
BranchLabels) { \
|
|
UNIMPLEMENTED(); \
|
|
return NEXT_IS_TRUE; \
|
|
}
|
|
|
|
#define DEFINE_UNIMPLEMENTED(Name) \
|
|
DEFINE_UNIMPLEMENTED_MAKE_LOCATION_SUMMARY(Name) \
|
|
DEFINE_UNIMPLEMENTED_EMIT_NATIVE_CODE(Name)
|
|
|
|
FOR_EACH_UNIMPLEMENTED_INSTRUCTION(DEFINE_UNIMPLEMENTED)
|
|
|
|
#undef DEFINE_UNIMPLEMENTED
|
|
|
|
#define DEFINE_UNREACHABLE(Name) \
|
|
DEFINE_UNREACHABLE_MAKE_LOCATION_SUMMARY(Name) \
|
|
DEFINE_UNREACHABLE_EMIT_NATIVE_CODE(Name)
|
|
|
|
FOR_EACH_UNREACHABLE_INSTRUCTION(DEFINE_UNREACHABLE)
|
|
|
|
#undef DEFINE_UNREACHABLE
|
|
|
|
// Only used in AOT compilation.
|
|
DEFINE_UNIMPLEMENTED_EMIT_BRANCH_CODE(CheckedSmiComparison)
|
|
|
|
EMIT_NATIVE_CODE(InstanceOf,
|
|
3,
|
|
Location::SameAsFirstInput(),
|
|
LocationSummary::kCall) {
|
|
SubtypeTestCache& test_cache = SubtypeTestCache::Handle();
|
|
if (!type().IsVoidType() && type().IsInstantiated()) {
|
|
test_cache = SubtypeTestCache::New();
|
|
}
|
|
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg()); // Value.
|
|
__ Push(locs()->in(1).reg()); // Instantiator type arguments.
|
|
__ Push(locs()->in(2).reg()); // Function type arguments.
|
|
}
|
|
|
|
__ PushConstant(type());
|
|
__ PushConstant(test_cache);
|
|
__ InstanceOf();
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(AssertAssignable,
|
|
3,
|
|
Location::SameAsFirstInput(),
|
|
LocationSummary::kCall);
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(AssertSubtype,
|
|
2,
|
|
Location::NoLocation(),
|
|
LocationSummary::kCall);
|
|
|
|
EMIT_NATIVE_CODE(AssertBoolean,
|
|
1,
|
|
Location::SameAsFirstInput(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
}
|
|
__ AssertBoolean(0);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
if (compiler->is_optimizing()) {
|
|
__ Drop1();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(PolymorphicInstanceCall,
|
|
0,
|
|
Location::RegisterLocation(0),
|
|
LocationSummary::kCall) {
|
|
const Array& arguments_descriptor =
|
|
Array::Handle(instance_call()->GetArgumentsDescriptor());
|
|
const intptr_t argdesc_kidx = __ AddConstant(arguments_descriptor);
|
|
|
|
// Push the target onto the stack.
|
|
const intptr_t length = targets_.length();
|
|
if (!Utils::IsUint(8, length)) {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
bool using_ranges = false;
|
|
for (intptr_t i = 0; i < length; i++) {
|
|
if (!targets_[i].IsSingleCid()) {
|
|
using_ranges = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (using_ranges) {
|
|
__ PushPolymorphicInstanceCallByRange(
|
|
instance_call()->ArgumentCountWithoutTypeArgs(), length);
|
|
} else {
|
|
__ PushPolymorphicInstanceCall(
|
|
instance_call()->ArgumentCountWithoutTypeArgs(), length);
|
|
}
|
|
for (intptr_t i = 0; i < length; i++) {
|
|
const Function& target = *targets_.TargetAt(i)->target;
|
|
|
|
__ Nop(compiler->ToEmbeddableCid(targets_[i].cid_start, this));
|
|
if (using_ranges) {
|
|
__ Nop(compiler->ToEmbeddableCid(1 + targets_[i].Extent(), this));
|
|
}
|
|
__ Nop(__ AddConstant(target));
|
|
}
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptPolymorphicInstanceCallTestFail,
|
|
0);
|
|
|
|
// Call the function.
|
|
__ StaticCall(instance_call()->ArgumentCount(), argdesc_kidx);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
instance_call()->token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadIndexedUnsafe, 1, Location::RegisterLocation(0)) {
|
|
ASSERT(base_reg() == FPREG);
|
|
|
|
ASSERT(offset_ % kWordSize == 0);
|
|
const intptr_t slot_offset = offset_ / kWordSize;
|
|
ASSERT(Utils::IsInt(8, slot_offset));
|
|
|
|
if (compiler->is_optimizing()) {
|
|
const Register index = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ LoadFpRelativeSlotOpt(result, index, slot_offset);
|
|
} else {
|
|
__ LoadFpRelativeSlot(slot_offset);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StoreIndexedUnsafe, 2, Location::RegisterLocation(0)) {
|
|
ASSERT(base_reg() == FPREG);
|
|
|
|
ASSERT(offset_ % kWordSize == 0);
|
|
const intptr_t slot_offset = offset_ / kWordSize;
|
|
ASSERT(Utils::IsInt(8, slot_offset));
|
|
|
|
if (compiler->is_optimizing()) {
|
|
const Register index = locs()->in(kIndexPos).reg();
|
|
const Register value = locs()->in(kValuePos).reg();
|
|
__ StoreFpRelativeSlotOpt(value, index, slot_offset);
|
|
} else {
|
|
__ StoreFpRelativeSlot(slot_offset);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(TailCall,
|
|
1,
|
|
Location::NoLocation(),
|
|
LocationSummary::kNoCall,
|
|
1) {
|
|
if (compiler->is_optimizing()) {
|
|
const Register arg_desc = locs()->in(0).reg();
|
|
const Register temp = locs()->temp(0).reg();
|
|
__ LoadConstant(temp, code());
|
|
__ TailCallOpt(arg_desc, temp);
|
|
} else {
|
|
__ PushConstant(code());
|
|
__ TailCall();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Stop, 0) {
|
|
__ Stop(message());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckStackOverflow,
|
|
0,
|
|
Location::NoLocation(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->ForceSlowPathForStackOverflow()) {
|
|
__ CheckStackAlwaysExit();
|
|
} else {
|
|
__ CheckStack();
|
|
}
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kNoResult);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(PushArgument, 1) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadLocal, 0) {
|
|
ASSERT(!compiler->is_optimizing());
|
|
const intptr_t slot_index =
|
|
compiler::target::frame_layout.FrameSlotForVariable(&local());
|
|
__ Push(LocalVarIndex(0, slot_index));
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StoreLocal, 0) {
|
|
ASSERT(!compiler->is_optimizing());
|
|
const intptr_t slot_index =
|
|
compiler::target::frame_layout.FrameSlotForVariable(&local());
|
|
if (HasTemp()) {
|
|
__ StoreLocal(LocalVarIndex(0, slot_index));
|
|
} else {
|
|
__ PopLocal(LocalVarIndex(0, slot_index));
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadClassId, 1, Location::RequiresRegister()) {
|
|
if (compiler->is_optimizing()) {
|
|
__ LoadClassId(locs()->out(0).reg(), locs()->in(0).reg());
|
|
} else {
|
|
__ LoadClassIdTOS();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Constant, 0, Location::RequiresRegister()) {
|
|
if (compiler->is_optimizing()) {
|
|
if (locs()->out(0).IsRegister()) {
|
|
__ LoadConstant(locs()->out(0).reg(), value());
|
|
}
|
|
} else {
|
|
__ PushConstant(value());
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(UnboxedConstant, 0, Location::RequiresRegister()) {
|
|
// The register allocator drops constant definitions that have no uses.
|
|
if (locs()->out(0).IsInvalid()) {
|
|
return;
|
|
}
|
|
if (representation_ != kUnboxedDouble) {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
const Register result = locs()->out(0).reg();
|
|
if (Utils::DoublesBitEqual(Double::Cast(value()).value(), 0.0)) {
|
|
__ BitXor(result, result, result);
|
|
} else {
|
|
__ LoadConstant(result, value());
|
|
__ UnboxDouble(result, result);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Return, 1) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Return(locs()->in(0).reg());
|
|
} else {
|
|
__ ReturnTOS();
|
|
}
|
|
}
|
|
|
|
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(
|
|
|
|
Zone* zone,
|
|
bool opt) const {
|
|
const intptr_t kNumInputs = 1;
|
|
const intptr_t kNumTemps = 1;
|
|
LocationSummary* locs = new (zone)
|
|
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
|
|
for (intptr_t i = 0; i < kNumInputs; i++) {
|
|
locs->set_in(i, Location::RequiresRegister());
|
|
}
|
|
for (intptr_t i = 0; i < kNumTemps; i++) {
|
|
locs->set_temp(i, Location::RequiresRegister());
|
|
}
|
|
return locs;
|
|
}
|
|
|
|
void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
if (compiler->is_optimizing()) {
|
|
__ LoadConstant(locs()->temp(0).reg(),
|
|
Field::ZoneHandle(field().Original()));
|
|
__ StoreField(locs()->temp(0).reg(),
|
|
Field::static_value_offset() / kWordSize,
|
|
locs()->in(0).reg());
|
|
} else {
|
|
const intptr_t kidx = __ AddConstant(field());
|
|
__ StoreStaticTOS(kidx);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadStaticField, 1, Location::RequiresRegister()) {
|
|
if (compiler->is_optimizing()) {
|
|
__ LoadField(locs()->out(0).reg(), locs()->in(0).reg(),
|
|
Field::static_value_offset() / kWordSize);
|
|
} else {
|
|
const intptr_t kidx = __ AddConstant(StaticField());
|
|
__ PushStatic(kidx);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(InitStaticField,
|
|
1,
|
|
Location::NoLocation(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
__ InitStaticTOS();
|
|
} else {
|
|
__ InitStaticTOS();
|
|
}
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kNoResult);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(ClosureCall,
|
|
1,
|
|
Location::RegisterLocation(0),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
}
|
|
|
|
const Array& arguments_descriptor =
|
|
Array::ZoneHandle(GetArgumentsDescriptor());
|
|
const intptr_t argdesc_kidx =
|
|
compiler->assembler()->AddConstant(arguments_descriptor);
|
|
__ StaticCall(ArgumentCount(), argdesc_kidx);
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
static void EmitBranchOnCondition(FlowGraphCompiler* compiler,
|
|
Condition true_condition,
|
|
BranchLabels labels) {
|
|
if (true_condition == NEXT_IS_TRUE) {
|
|
// NEXT_IS_TRUE indicates that the preceeding test expects the true case
|
|
// to be in the subsequent instruction, which it skips if the test fails.
|
|
__ Jump(labels.true_label);
|
|
if (labels.fall_through != labels.false_label) {
|
|
// The preceeding Jump instruction will be skipped if the test fails.
|
|
// If we aren't falling through to the false case, then we have to do
|
|
// a Jump to it here.
|
|
__ Jump(labels.false_label);
|
|
}
|
|
} else {
|
|
ASSERT(true_condition == NEXT_IS_FALSE);
|
|
// NEXT_IS_FALSE indicates that the preceeding test has been flipped and
|
|
// expects the false case to be in the subsequent instruction, which it
|
|
// skips if the test succeeds.
|
|
__ Jump(labels.false_label);
|
|
if (labels.fall_through != labels.true_label) {
|
|
// The preceeding Jump instruction will be skipped if the test succeeds.
|
|
// If we aren't falling through to the true case, then we have to do
|
|
// a Jump to it here.
|
|
__ Jump(labels.true_label);
|
|
}
|
|
}
|
|
}
|
|
|
|
Condition StrictCompareInstr::GetNextInstructionCondition(
|
|
FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
return (labels.fall_through == labels.false_label) ? NEXT_IS_TRUE
|
|
: NEXT_IS_FALSE;
|
|
}
|
|
|
|
Condition StrictCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
ASSERT((kind() == Token::kNE_STRICT) || (kind() == Token::kEQ_STRICT));
|
|
|
|
Token::Kind comparison;
|
|
Condition condition;
|
|
if (labels.fall_through == labels.false_label) {
|
|
condition = NEXT_IS_TRUE;
|
|
comparison = kind();
|
|
} else {
|
|
// Flip comparison to save a jump.
|
|
condition = NEXT_IS_FALSE;
|
|
comparison =
|
|
(kind() == Token::kEQ_STRICT) ? Token::kNE_STRICT : Token::kEQ_STRICT;
|
|
}
|
|
|
|
if (!compiler->is_optimizing()) {
|
|
const SimulatorBytecode::Opcode eq_op =
|
|
needs_number_check() ? SimulatorBytecode::kIfEqStrictNumTOS
|
|
: SimulatorBytecode::kIfEqStrictTOS;
|
|
const SimulatorBytecode::Opcode ne_op =
|
|
needs_number_check() ? SimulatorBytecode::kIfNeStrictNumTOS
|
|
: SimulatorBytecode::kIfNeStrictTOS;
|
|
__ Emit(comparison == Token::kEQ_STRICT ? eq_op : ne_op);
|
|
} else {
|
|
const SimulatorBytecode::Opcode eq_op =
|
|
needs_number_check() ? SimulatorBytecode::kIfEqStrictNum
|
|
: SimulatorBytecode::kIfEqStrict;
|
|
const SimulatorBytecode::Opcode ne_op =
|
|
needs_number_check() ? SimulatorBytecode::kIfNeStrictNum
|
|
: SimulatorBytecode::kIfNeStrict;
|
|
__ Emit(SimulatorBytecode::Encode(
|
|
(comparison == Token::kEQ_STRICT) ? eq_op : ne_op, locs()->in(0).reg(),
|
|
locs()->in(1).reg()));
|
|
}
|
|
|
|
if (needs_number_check() && token_pos().IsReal()) {
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kRuntimeCall, deopt_id_,
|
|
token_pos());
|
|
}
|
|
|
|
return condition;
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(StrictCompare,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
needs_number_check() ? LocationSummary::kCall
|
|
: LocationSummary::kNoCall)
|
|
|
|
void ComparisonInstr::EmitBranchCode(FlowGraphCompiler* compiler,
|
|
BranchInstr* branch) {
|
|
BranchLabels labels = compiler->CreateBranchLabels(branch);
|
|
Condition true_condition = EmitComparisonCode(compiler, labels);
|
|
if (true_condition != INVALID_CONDITION) {
|
|
EmitBranchOnCondition(compiler, true_condition, labels);
|
|
}
|
|
}
|
|
|
|
void ComparisonInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
Label is_true, is_false;
|
|
BranchLabels labels = {&is_true, &is_false, &is_false};
|
|
Condition true_condition =
|
|
this->GetNextInstructionCondition(compiler, labels);
|
|
if (true_condition == INVALID_CONDITION || !compiler->is_optimizing() ||
|
|
is_true.IsLinked() || is_false.IsLinked()) {
|
|
Condition actual_condition = EmitComparisonCode(compiler, labels);
|
|
ASSERT(actual_condition == true_condition);
|
|
if (true_condition != INVALID_CONDITION) {
|
|
EmitBranchOnCondition(compiler, true_condition, labels);
|
|
}
|
|
Label done;
|
|
__ Bind(&is_false);
|
|
__ PushConstant(Bool::False());
|
|
__ Jump(&done);
|
|
__ Bind(&is_true);
|
|
__ PushConstant(Bool::True());
|
|
__ Bind(&done);
|
|
} else {
|
|
const Register result = this->locs()->out(0).reg();
|
|
bool next_is_true = true_condition == NEXT_IS_TRUE;
|
|
__ LoadConstant(result, Bool::Get(!next_is_true));
|
|
Condition actual_condition = EmitComparisonCode(compiler, labels);
|
|
ASSERT(actual_condition == true_condition);
|
|
// Although we have a condition to branch on, the comparison code may also
|
|
// have contained a direct branch to one of the labels, so they may need to
|
|
// be bound.
|
|
if (next_is_true && is_true.IsLinked()) {
|
|
__ Bind(&is_true);
|
|
} else if (!next_is_true && is_false.IsLinked()) {
|
|
__ Bind(&is_false);
|
|
}
|
|
// This instruction is conditionally skipped by EmitComparisonCode.
|
|
__ LoadConstant(result, Bool::Get(next_is_true));
|
|
// If the other label is linked we need to bind it and emit code that loads
|
|
// the correct boolean.
|
|
if ((next_is_true && is_false.IsLinked()) ||
|
|
(!next_is_true && is_true.IsLinked())) {
|
|
Label done;
|
|
__ Jump(&done);
|
|
__ Bind(next_is_true ? &is_false : &is_true);
|
|
__ LoadConstant(result, Bool::Get(!next_is_true));
|
|
__ Bind(&done);
|
|
}
|
|
}
|
|
}
|
|
|
|
LocationSummary* BranchInstr::MakeLocationSummary(
|
|
|
|
Zone* zone,
|
|
bool opt) const {
|
|
comparison()->InitializeLocationSummary(zone, opt);
|
|
if (!comparison()->HasLocs()) {
|
|
return NULL;
|
|
}
|
|
// Branches don't produce a result.
|
|
comparison()->locs()->set_out(0, Location::NoLocation());
|
|
return comparison()->locs();
|
|
}
|
|
|
|
void BranchInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
comparison()->EmitBranchCode(compiler, this);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Goto, 0) {
|
|
if (!compiler->is_optimizing()) {
|
|
// Add a deoptimization descriptor for deoptimizing instructions that
|
|
// may be inserted before this instruction.
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, GetDeoptId(),
|
|
TokenPosition::kNoSource);
|
|
}
|
|
if (HasParallelMove()) {
|
|
compiler->parallel_move_resolver()->EmitNativeCode(parallel_move());
|
|
}
|
|
// We can fall through if the successor is the next block in the list.
|
|
// Otherwise, we need a jump.
|
|
if (!compiler->CanFallThroughTo(successor())) {
|
|
__ Jump(compiler->GetJumpLabel(successor()));
|
|
}
|
|
}
|
|
|
|
Condition TestSmiInstr::GetNextInstructionCondition(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
ASSERT((kind() == Token::kEQ) || (kind() == Token::kNE));
|
|
return (kind() == Token::kEQ) ? NEXT_IS_TRUE : NEXT_IS_FALSE;
|
|
}
|
|
|
|
Condition TestSmiInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
ASSERT((kind() == Token::kEQ) || (kind() == Token::kNE));
|
|
Register left = locs()->in(0).reg();
|
|
Register right = locs()->in(1).reg();
|
|
__ TestSmi(left, right);
|
|
return (kind() == Token::kEQ) ? NEXT_IS_TRUE : NEXT_IS_FALSE;
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(TestSmi,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kNoCall)
|
|
|
|
Condition TestCidsInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
ASSERT((kind() == Token::kIS) || (kind() == Token::kISNOT));
|
|
const Register value = locs()->in(0).reg();
|
|
const intptr_t true_result = (kind() == Token::kIS) ? 1 : 0;
|
|
|
|
const ZoneGrowableArray<intptr_t>& data = cid_results();
|
|
const intptr_t num_cases = data.length() / 2;
|
|
ASSERT(num_cases <= 255);
|
|
__ TestCids(value, num_cases);
|
|
|
|
bool result = false;
|
|
for (intptr_t i = 0; i < data.length(); i += 2) {
|
|
const intptr_t test_cid = data[i];
|
|
result = data[i + 1] == true_result;
|
|
__ Nop(result ? 1 : 0, compiler->ToEmbeddableCid(test_cid, this));
|
|
}
|
|
|
|
// No match found, deoptimize or default action.
|
|
if (CanDeoptimize()) {
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptTestCids,
|
|
licm_hoisted_ ? ICData::kHoisted : 0);
|
|
} else {
|
|
// If the cid is not in the list, jump to the opposite label from the cids
|
|
// that are in the list. These must be all the same (see asserts in the
|
|
// constructor).
|
|
Label* target = result ? labels.false_label : labels.true_label;
|
|
__ Jump(target);
|
|
}
|
|
|
|
return NEXT_IS_TRUE;
|
|
}
|
|
|
|
Condition TestCidsInstr::GetNextInstructionCondition(
|
|
FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
return NEXT_IS_TRUE;
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(TestCids,
|
|
1,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kNoCall)
|
|
|
|
EMIT_NATIVE_CODE(CreateArray,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
const Register length = locs()->in(kLengthPos).reg();
|
|
const Register type_arguments = locs()->in(kElementTypePos).reg();
|
|
const Register out = locs()->out(0).reg();
|
|
__ CreateArrayOpt(out, length, type_arguments);
|
|
__ Push(type_arguments);
|
|
__ Push(length);
|
|
__ CreateArrayTOS();
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
__ PopLocal(out);
|
|
} else {
|
|
__ CreateArrayTOS();
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kHasResult);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StoreIndexed,
|
|
3,
|
|
Location::NoLocation(),
|
|
LocationSummary::kNoCall,
|
|
1) {
|
|
if (!compiler->is_optimizing()) {
|
|
ASSERT(class_id() == kArrayCid);
|
|
__ StoreIndexedTOS();
|
|
return;
|
|
}
|
|
const Register array = locs()->in(kArrayPos).reg();
|
|
const Register index = locs()->in(kIndexPos).reg();
|
|
const Register value = locs()->in(kValuePos).reg();
|
|
const Register temp = locs()->temp(0).reg();
|
|
switch (class_id()) {
|
|
case kArrayCid:
|
|
__ StoreIndexed(array, index, value);
|
|
break;
|
|
case kTypedDataUint8ArrayCid:
|
|
case kTypedDataInt8ArrayCid:
|
|
case kExternalOneByteStringCid:
|
|
case kExternalTypedDataUint8ArrayCid:
|
|
ASSERT(index_scale() == 1);
|
|
if (IsExternal()) {
|
|
__ StoreIndexedUntaggedUint8(array, index, value);
|
|
} else {
|
|
__ StoreIndexedUint8(array, index, value);
|
|
}
|
|
break;
|
|
case kOneByteStringCid:
|
|
ASSERT(index_scale() == 1);
|
|
__ StoreIndexedOneByteString(array, index, value);
|
|
break;
|
|
case kTypedDataInt32ArrayCid:
|
|
case kTypedDataUint32ArrayCid: {
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedUntaggedUint32(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedUntaggedUint32(array, temp, value);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedUint32(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedUint32(array, temp, value);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case kTypedDataFloat32ArrayCid:
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedUntaggedFloat32(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedUntaggedFloat32(array, temp, value);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedFloat32(array, index, value);
|
|
} else if (index_scale() == 4) {
|
|
__ StoreIndexed4Float32(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedFloat32(array, temp, value);
|
|
}
|
|
}
|
|
break;
|
|
case kTypedDataFloat64ArrayCid:
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedUntaggedFloat64(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedUntaggedFloat64(array, temp, value);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ StoreIndexedFloat64(array, index, value);
|
|
} else if (index_scale() == 8) {
|
|
__ StoreIndexed8Float64(array, index, value);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ StoreIndexedFloat64(array, temp, value);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadIndexed,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kNoCall,
|
|
1) {
|
|
if (compiler->is_optimizing()) {
|
|
ASSERT(compiler->is_optimizing());
|
|
const Register array = locs()->in(0).reg();
|
|
const Register index = locs()->in(1).reg();
|
|
const Register temp = locs()->temp(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
switch (class_id()) {
|
|
case kArrayCid:
|
|
case kImmutableArrayCid:
|
|
__ LoadIndexed(result, array, index);
|
|
break;
|
|
case kTypedDataUint8ArrayCid:
|
|
case kTypedDataUint8ClampedArrayCid:
|
|
case kExternalOneByteStringCid:
|
|
case kExternalTypedDataUint8ArrayCid:
|
|
case kExternalTypedDataUint8ClampedArrayCid:
|
|
ASSERT(index_scale() == 1);
|
|
if (IsExternal()) {
|
|
__ LoadIndexedUntaggedUint8(result, array, index);
|
|
} else {
|
|
__ LoadIndexedUint8(result, array, index);
|
|
}
|
|
break;
|
|
case kTypedDataInt8ArrayCid:
|
|
ASSERT(index_scale() == 1);
|
|
if (IsExternal()) {
|
|
__ LoadIndexedUntaggedInt8(result, array, index);
|
|
} else {
|
|
__ LoadIndexedInt8(result, array, index);
|
|
}
|
|
break;
|
|
case kOneByteStringCid:
|
|
ASSERT(index_scale() == 1);
|
|
__ LoadIndexedOneByteString(result, array, index);
|
|
break;
|
|
case kTwoByteStringCid:
|
|
if (index_scale() != 2) {
|
|
// TODO(zra): Fix-up index.
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
if (IsExternal()) {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
__ LoadIndexedTwoByteString(result, array, index);
|
|
break;
|
|
case kTypedDataInt32ArrayCid:
|
|
ASSERT(representation() == kUnboxedInt32);
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedUntaggedInt32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedUntaggedInt32(result, array, temp);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedInt32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedInt32(result, array, temp);
|
|
}
|
|
}
|
|
break;
|
|
case kTypedDataUint32ArrayCid:
|
|
ASSERT(representation() == kUnboxedUint32);
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedUntaggedUint32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedUntaggedUint32(result, array, temp);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedUint32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedUint32(result, array, temp);
|
|
}
|
|
}
|
|
break;
|
|
case kTypedDataFloat32ArrayCid:
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedUntaggedFloat32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedUntaggedFloat32(result, array, temp);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedFloat32(result, array, index);
|
|
} else if (index_scale() == 4) {
|
|
__ LoadIndexed4Float32(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedFloat32(result, array, temp);
|
|
}
|
|
}
|
|
break;
|
|
case kTypedDataFloat64ArrayCid:
|
|
if (IsExternal()) {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedUntaggedFloat64(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedUntaggedFloat64(result, array, temp);
|
|
}
|
|
} else {
|
|
if (index_scale() == 1) {
|
|
__ LoadIndexedFloat64(result, array, index);
|
|
} else if (index_scale() == 8) {
|
|
__ LoadIndexed8Float64(result, array, index);
|
|
} else {
|
|
__ ShlImm(temp, index, Utils::ShiftForPowerOfTwo(index_scale()));
|
|
__ LoadIndexedFloat64(result, array, temp);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
} else {
|
|
switch (class_id()) {
|
|
case kArrayCid:
|
|
case kImmutableArrayCid:
|
|
__ LoadIndexedTOS();
|
|
break;
|
|
default:
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StringInterpolate,
|
|
1,
|
|
Location::RegisterLocation(0),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
}
|
|
const intptr_t kTypeArgsLen = 0;
|
|
const intptr_t kArgumentCount = 1;
|
|
const Array& arguments_descriptor = Array::Handle(ArgumentsDescriptor::New(
|
|
kTypeArgsLen, kArgumentCount, Object::null_array()));
|
|
__ PushConstant(CallFunction());
|
|
const intptr_t argdesc_kidx = __ AddConstant(arguments_descriptor);
|
|
__ StaticCall(kArgumentCount, argdesc_kidx);
|
|
// Note: can't use RecordAfterCall here because
|
|
// StringInterpolateInstr::ArgumentCount() is 0. However
|
|
// internally it does a call with 1 argument which needs to
|
|
// be reflected in the lazy deoptimization environment.
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCallHelper(token_pos(), deopt_id(), kArgumentCount,
|
|
FlowGraphCompiler::kHasResult, locs());
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
const Representation result_rep =
|
|
compiler::ffi::ResultHostRepresentation(signature_);
|
|
// TODO(36809): In 32 bit we'll need a result location as well.
|
|
const TypedData& signature_descriptor =
|
|
TypedData::Handle(compiler::ffi::FfiSignatureDescriptor::New(
|
|
*arg_host_locations_, result_rep));
|
|
|
|
const intptr_t sigdesc_kidx = __ AddConstant(signature_descriptor);
|
|
|
|
__ FfiCall(sigdesc_kidx);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCallHelper(token_pos(), deopt_id(), 0,
|
|
FlowGraphCompiler::kHasResult, locs());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(NativeCall,
|
|
0,
|
|
Location::NoLocation(),
|
|
LocationSummary::kCall) {
|
|
SetupNative();
|
|
|
|
const intptr_t argc_tag = NativeArguments::ComputeArgcTag(function());
|
|
|
|
NativeFunctionWrapper trampoline;
|
|
NativeFunction function;
|
|
if (link_lazily()) {
|
|
trampoline = &NativeEntry::BootstrapNativeCallWrapper;
|
|
function = reinterpret_cast<NativeFunction>(&NativeEntry::LinkNativeCall);
|
|
} else {
|
|
if (is_bootstrap_native()) {
|
|
trampoline = &NativeEntry::BootstrapNativeCallWrapper;
|
|
} else if (is_auto_scope()) {
|
|
trampoline = &NativeEntry::AutoScopeNativeCallWrapper;
|
|
} else {
|
|
trampoline = &NativeEntry::NoScopeNativeCallWrapper;
|
|
}
|
|
function = native_c_function();
|
|
}
|
|
|
|
const ExternalLabel trampoline_label(reinterpret_cast<uword>(trampoline));
|
|
const intptr_t trampoline_kidx =
|
|
__ object_pool_builder().FindNativeFunctionWrapper(
|
|
&trampoline_label, ObjectPool::Patchability::kPatchable);
|
|
const ExternalLabel label(reinterpret_cast<uword>(function));
|
|
const intptr_t target_kidx = __ object_pool_builder().FindNativeFunction(
|
|
&label, ObjectPool::Patchability::kPatchable);
|
|
const intptr_t argc_tag_kidx =
|
|
__ object_pool_builder().FindImmediate(static_cast<uword>(argc_tag));
|
|
__ NativeCall(trampoline_kidx, target_kidx, argc_tag_kidx);
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(OneByteStringFromCharCode,
|
|
1,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kNoCall) {
|
|
ASSERT(compiler->is_optimizing());
|
|
const Register char_code = locs()->in(0).reg(); // Char code is a smi.
|
|
const Register result = locs()->out(0).reg();
|
|
__ OneByteStringFromCharCode(result, char_code);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StringToCharCode,
|
|
1,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kNoCall) {
|
|
ASSERT(cid_ == kOneByteStringCid);
|
|
const Register str = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg(); // Result char code is a smi.
|
|
__ StringToCharCode(result, str);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(AllocateObject,
|
|
0,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
if (ArgumentCount() == 1) {
|
|
// Allocate with type arguments.
|
|
if (compiler->is_optimizing()) {
|
|
// If we're optimizing, try a streamlined fastpath.
|
|
const intptr_t instance_size = cls().instance_size();
|
|
Isolate* isolate = Isolate::Current();
|
|
if (Heap::IsAllocatableInNewSpace(instance_size) &&
|
|
!cls().TraceAllocation(isolate)) {
|
|
uint32_t tags = 0;
|
|
tags = RawObject::SizeTag::update(instance_size, tags);
|
|
ASSERT(cls().id() != kIllegalCid);
|
|
tags = RawObject::ClassIdTag::update(cls().id(), tags);
|
|
if (Smi::IsValid(tags)) {
|
|
const intptr_t tags_kidx =
|
|
__ AddConstant(Smi::Handle(Smi::New(tags)));
|
|
__ AllocateTOpt(locs()->out(0).reg(), tags_kidx);
|
|
__ Nop(cls().type_arguments_field_offset());
|
|
}
|
|
}
|
|
__ PushConstant(cls());
|
|
__ AllocateT();
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
compiler->RecordSafepoint(locs());
|
|
__ PopLocal(locs()->out(0).reg());
|
|
} else {
|
|
__ PushConstant(cls());
|
|
__ AllocateT();
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
compiler->RecordSafepoint(locs());
|
|
}
|
|
} else if (compiler->is_optimizing()) {
|
|
// If we're optimizing, try a streamlined fastpath.
|
|
const intptr_t instance_size = cls().instance_size();
|
|
Isolate* isolate = Isolate::Current();
|
|
if (Heap::IsAllocatableInNewSpace(instance_size) &&
|
|
!cls().TraceAllocation(isolate)) {
|
|
uword tags = 0;
|
|
tags = RawObject::SizeTag::update(instance_size, tags);
|
|
ASSERT(cls().id() != kIllegalCid);
|
|
tags = RawObject::ClassIdTag::update(cls().id(), tags);
|
|
// tags also has the initial zero hash code on 64 bit.
|
|
if (Smi::IsValid(tags)) {
|
|
const intptr_t tags_kidx = __ AddConstant(Smi::Handle(Smi::New(tags)));
|
|
__ AllocateOpt(locs()->out(0).reg(), tags_kidx);
|
|
}
|
|
}
|
|
const intptr_t kidx = __ AddConstant(cls());
|
|
__ Allocate(kidx);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
compiler->RecordSafepoint(locs());
|
|
__ PopLocal(locs()->out(0).reg());
|
|
} else {
|
|
const intptr_t kidx = __ AddConstant(cls());
|
|
__ Allocate(kidx);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
compiler->RecordSafepoint(locs());
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StoreInstanceField, 2) {
|
|
ASSERT(OffsetInBytes() % kWordSize == 0);
|
|
if (compiler->is_optimizing()) {
|
|
const Register value = locs()->in(1).reg();
|
|
const Register instance = locs()->in(0).reg();
|
|
if (Utils::IsInt(8, OffsetInBytes() / kWordSize)) {
|
|
__ StoreField(instance, OffsetInBytes() / kWordSize, value);
|
|
} else {
|
|
__ StoreFieldExt(instance, value);
|
|
__ Nop(OffsetInBytes() / kWordSize);
|
|
}
|
|
} else {
|
|
__ StoreFieldTOS(OffsetInBytes() / kWordSize);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadField, 1, Location::RequiresRegister()) {
|
|
ASSERT(OffsetInBytes() % kWordSize == 0);
|
|
if (compiler->is_optimizing()) {
|
|
const Register result = locs()->out(0).reg();
|
|
const Register instance = locs()->in(0).reg();
|
|
if (Utils::IsInt(8, OffsetInBytes() / kWordSize)) {
|
|
__ LoadField(result, instance, OffsetInBytes() / kWordSize);
|
|
} else {
|
|
__ LoadFieldExt(result, instance);
|
|
__ Nop(OffsetInBytes() / kWordSize);
|
|
}
|
|
} else {
|
|
__ LoadFieldTOS(OffsetInBytes() / kWordSize);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(LoadUntagged, 1, Location::RequiresRegister()) {
|
|
const Register obj = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
if (object()->definition()->representation() == kUntagged) {
|
|
__ LoadUntagged(result, obj, offset() / kWordSize);
|
|
} else {
|
|
ASSERT(object()->definition()->representation() == kTagged);
|
|
__ LoadField(result, obj, offset() / kWordSize);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(StoreUntagged, 1, Location::RequiresRegister()) {
|
|
const Register obj = locs()->in(0).reg();
|
|
const Register value = locs()->out(0).reg();
|
|
const auto offset_in_words = offset() / kWordSize;
|
|
if (object()->definition()->representation() == kUntagged) {
|
|
__ StoreUntagged(obj, offset_in_words, value);
|
|
} else {
|
|
ASSERT(object()->definition()->representation() == kTagged);
|
|
__ StoreField(obj, offset_in_words, value);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(BooleanNegate, 1, Location::RequiresRegister()) {
|
|
if (compiler->is_optimizing()) {
|
|
__ BooleanNegate(locs()->out(0).reg(), locs()->in(0).reg());
|
|
} else {
|
|
__ BooleanNegateTOS();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(AllocateContext,
|
|
0,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
ASSERT(!compiler->is_optimizing());
|
|
__ AllocateContext(num_context_variables());
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(AllocateUninitializedContext,
|
|
0,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
ASSERT(compiler->is_optimizing());
|
|
__ AllocateUninitializedContext(locs()->out(0).reg(),
|
|
num_context_variables());
|
|
__ AllocateContext(num_context_variables());
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CloneContext,
|
|
1,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg());
|
|
}
|
|
__ CloneContext();
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CatchBlockEntry, 0) {
|
|
__ Bind(compiler->GetJumpLabel(this));
|
|
compiler->AddExceptionHandler(catch_try_index(), try_index(),
|
|
compiler->assembler()->CodeSize(),
|
|
handler_token_pos(), is_generated(),
|
|
catch_handler_types_, needs_stacktrace());
|
|
// On lazy deoptimization we patch the optimized code here to enter the
|
|
// deoptimization stub.
|
|
const intptr_t deopt_id = DeoptId::ToDeoptAfter(GetDeoptId());
|
|
if (compiler->is_optimizing()) {
|
|
compiler->AddDeoptIndexAtCall(deopt_id);
|
|
} else {
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kDeopt, deopt_id,
|
|
TokenPosition::kNoSource);
|
|
}
|
|
if (HasParallelMove()) {
|
|
compiler->parallel_move_resolver()->EmitNativeCode(parallel_move());
|
|
}
|
|
__ SetFrame(compiler->StackSize());
|
|
|
|
if (!compiler->is_optimizing()) {
|
|
if (raw_exception_var_ != nullptr) {
|
|
__ MoveSpecial(
|
|
LocalVarIndex(0, compiler::target::frame_layout.FrameSlotForVariable(
|
|
raw_exception_var_)),
|
|
Simulator::kExceptionSpecialIndex);
|
|
}
|
|
if (raw_stacktrace_var_ != nullptr) {
|
|
__ MoveSpecial(
|
|
LocalVarIndex(0, compiler::target::frame_layout.FrameSlotForVariable(
|
|
raw_stacktrace_var_)),
|
|
Simulator::kStackTraceSpecialIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Throw, 0, Location::NoLocation(), LocationSummary::kCall) {
|
|
__ Throw(0);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kNoResult);
|
|
__ Trap();
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(ReThrow, 0, Location::NoLocation(), LocationSummary::kCall) {
|
|
compiler->SetNeedsStackTrace(catch_try_index());
|
|
__ Throw(1);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
compiler->RecordAfterCall(this, FlowGraphCompiler::kNoResult);
|
|
__ Trap();
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(InstantiateType,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg()); // Instantiator type arguments.
|
|
__ Push(locs()->in(1).reg()); // Function type arguments.
|
|
}
|
|
__ InstantiateType(__ AddConstant(type()));
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(InstantiateTypeArguments,
|
|
2,
|
|
Location::RequiresRegister(),
|
|
LocationSummary::kCall) {
|
|
if (compiler->is_optimizing()) {
|
|
__ Push(locs()->in(0).reg()); // Instantiator type arguments.
|
|
__ Push(locs()->in(1).reg()); // Function type arguments.
|
|
}
|
|
__ InstantiateTypeArgumentsTOS(
|
|
type_arguments().IsRawWhenInstantiatedFromRaw(type_arguments().Length()),
|
|
__ AddConstant(type_arguments()));
|
|
compiler->RecordSafepoint(locs());
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, deopt_id(),
|
|
token_pos());
|
|
if (compiler->is_optimizing()) {
|
|
__ PopLocal(locs()->out(0).reg());
|
|
}
|
|
}
|
|
|
|
void DebugStepCheckInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
#ifdef PRODUCT
|
|
UNREACHABLE();
|
|
#else
|
|
__ DebugStep();
|
|
compiler->AddCurrentDescriptor(stub_kind_, deopt_id_, token_pos());
|
|
#endif
|
|
}
|
|
|
|
void GraphEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
BlockEntryInstr* entry = normal_entry();
|
|
if (entry != nullptr) {
|
|
if (!compiler->CanFallThroughTo(entry)) {
|
|
FATAL("Checked function entry must have no offset");
|
|
}
|
|
} else {
|
|
entry = osr_entry();
|
|
if (!compiler->CanFallThroughTo(entry)) {
|
|
__ Jump(compiler->GetJumpLabel(entry));
|
|
}
|
|
}
|
|
}
|
|
|
|
LocationSummary* Instruction::MakeCallSummary(Zone* zone) {
|
|
LocationSummary* result =
|
|
new (zone) LocationSummary(zone, 0, 0, LocationSummary::kCall);
|
|
// TODO(vegorov) support allocating out registers for calls.
|
|
// Currently we require them to be fixed.
|
|
ASSERT(0 < kMaxNumberOfFixedInputRegistersUsedByIL);
|
|
result->set_out(0, Location::RegisterLocation(0));
|
|
return result;
|
|
}
|
|
|
|
CompileType BinaryUint32OpInstr::ComputeType() const {
|
|
return CompileType::Int();
|
|
}
|
|
|
|
CompileType ShiftUint32OpInstr::ComputeType() const {
|
|
return CompileType::Int();
|
|
}
|
|
|
|
CompileType SpeculativeShiftUint32OpInstr::ComputeType() const {
|
|
return CompileType::Int();
|
|
}
|
|
|
|
CompileType UnaryUint32OpInstr::ComputeType() const {
|
|
return CompileType::Int();
|
|
}
|
|
|
|
CompileType LoadIndexedInstr::ComputeType() const {
|
|
switch (class_id_) {
|
|
case kArrayCid:
|
|
case kImmutableArrayCid:
|
|
return CompileType::Dynamic();
|
|
|
|
case kTypedDataFloat32ArrayCid:
|
|
case kTypedDataFloat64ArrayCid:
|
|
return CompileType::FromCid(kDoubleCid);
|
|
case kTypedDataFloat32x4ArrayCid:
|
|
return CompileType::FromCid(kFloat32x4Cid);
|
|
case kTypedDataInt32x4ArrayCid:
|
|
return CompileType::FromCid(kInt32x4Cid);
|
|
case kTypedDataFloat64x2ArrayCid:
|
|
return CompileType::FromCid(kFloat64x2Cid);
|
|
|
|
case kTypedDataInt8ArrayCid:
|
|
case kTypedDataUint8ArrayCid:
|
|
case kTypedDataUint8ClampedArrayCid:
|
|
case kExternalTypedDataUint8ArrayCid:
|
|
case kExternalTypedDataUint8ClampedArrayCid:
|
|
|
|
case kOneByteStringCid:
|
|
case kTwoByteStringCid:
|
|
case kExternalOneByteStringCid:
|
|
return CompileType::FromCid(kSmiCid);
|
|
|
|
case kTypedDataInt32ArrayCid:
|
|
case kTypedDataUint32ArrayCid:
|
|
return CompileType::Int();
|
|
|
|
// These are unsupported on DBC and will cause a bailout during
|
|
// EmitNativeCode.
|
|
case kTypedDataInt16ArrayCid:
|
|
case kTypedDataUint16ArrayCid:
|
|
case kExternalTwoByteStringCid:
|
|
return CompileType::FromCid(kSmiCid);
|
|
|
|
default:
|
|
UNREACHABLE();
|
|
return CompileType::Dynamic();
|
|
}
|
|
}
|
|
|
|
Representation LoadIndexedInstr::representation() const {
|
|
switch (class_id_) {
|
|
case kArrayCid:
|
|
case kImmutableArrayCid:
|
|
return kTagged;
|
|
case kOneByteStringCid:
|
|
case kTwoByteStringCid:
|
|
case kTypedDataInt8ArrayCid:
|
|
case kTypedDataUint8ArrayCid:
|
|
case kTypedDataUint8ClampedArrayCid:
|
|
case kExternalOneByteStringCid:
|
|
case kExternalTypedDataUint8ArrayCid:
|
|
case kExternalTypedDataUint8ClampedArrayCid:
|
|
return kUnboxedIntPtr;
|
|
case kTypedDataInt32ArrayCid:
|
|
return kUnboxedInt32;
|
|
case kTypedDataUint32ArrayCid:
|
|
return kUnboxedUint32;
|
|
case kTypedDataFloat32ArrayCid:
|
|
case kTypedDataFloat64ArrayCid:
|
|
return kUnboxedDouble;
|
|
case kTypedDataInt32x4ArrayCid:
|
|
return kUnboxedInt32x4;
|
|
case kTypedDataFloat32x4ArrayCid:
|
|
return kUnboxedFloat32x4;
|
|
case kTypedDataFloat64x2ArrayCid:
|
|
return kUnboxedFloat64x2;
|
|
|
|
// These are unsupported on DBC and will cause a bailout during
|
|
// EmitNativeCode.
|
|
case kTypedDataInt16ArrayCid:
|
|
case kTypedDataUint16ArrayCid:
|
|
case kExternalTwoByteStringCid:
|
|
return kUnboxedIntPtr;
|
|
|
|
default:
|
|
UNREACHABLE();
|
|
return kTagged;
|
|
}
|
|
}
|
|
|
|
Representation StoreIndexedInstr::RequiredInputRepresentation(
|
|
intptr_t idx) const {
|
|
// Array can be a Dart object or a pointer to external data.
|
|
if (idx == 0) {
|
|
return kNoRepresentation; // Flexible input representation.
|
|
}
|
|
if (idx == 1) {
|
|
return kTagged; // Index is a smi.
|
|
}
|
|
ASSERT(idx == 2);
|
|
switch (class_id_) {
|
|
case kArrayCid:
|
|
return kTagged;
|
|
case kOneByteStringCid:
|
|
case kTypedDataInt8ArrayCid:
|
|
case kTypedDataUint8ArrayCid:
|
|
case kExternalOneByteStringCid:
|
|
case kExternalTypedDataUint8ArrayCid:
|
|
return kUnboxedIntPtr;
|
|
case kTypedDataInt32ArrayCid:
|
|
return kUnboxedInt32;
|
|
case kTypedDataUint32ArrayCid:
|
|
return kUnboxedUint32;
|
|
case kTypedDataFloat32ArrayCid:
|
|
case kTypedDataFloat64ArrayCid:
|
|
return kUnboxedDouble;
|
|
case kTypedDataFloat32x4ArrayCid:
|
|
return kUnboxedFloat32x4;
|
|
case kTypedDataInt32x4ArrayCid:
|
|
return kUnboxedInt32x4;
|
|
case kTypedDataFloat64x2ArrayCid:
|
|
return kUnboxedFloat64x2;
|
|
|
|
// These are unsupported on DBC and will cause a bailout during
|
|
// EmitNativeCode.
|
|
case kTypedDataUint8ClampedArrayCid:
|
|
case kExternalTypedDataUint8ClampedArrayCid:
|
|
case kTypedDataInt16ArrayCid:
|
|
case kTypedDataUint16ArrayCid:
|
|
return kUnboxedIntPtr;
|
|
default:
|
|
UNREACHABLE();
|
|
return kTagged;
|
|
}
|
|
}
|
|
|
|
void Environment::DropArguments(intptr_t argc) {
|
|
#if defined(DEBUG)
|
|
// Check that we are in the backend - register allocation has been run.
|
|
ASSERT(locations_ != NULL);
|
|
|
|
// Check that we are only dropping a valid number of instructions from the
|
|
// environment.
|
|
ASSERT(argc <= values_.length());
|
|
#endif
|
|
values_.TruncateTo(values_.length() - argc);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckSmi, 1) {
|
|
__ CheckSmi(locs()->in(0).reg());
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptCheckSmi,
|
|
licm_hoisted_ ? ICData::kHoisted : 0);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckEitherNonSmi, 2) {
|
|
const Register left = locs()->in(0).reg();
|
|
const Register right = locs()->in(1).reg();
|
|
__ CheckEitherNonSmi(left, right);
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptBinaryDoubleOp,
|
|
licm_hoisted_ ? ICData::kHoisted : 0);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckClassId, 1) {
|
|
if (cids_.IsSingleCid()) {
|
|
__ CheckClassId(locs()->in(0).reg(),
|
|
compiler->ToEmbeddableCid(cids_.cid_start, this));
|
|
} else {
|
|
__ CheckClassIdRange(locs()->in(0).reg(),
|
|
compiler->ToEmbeddableCid(cids_.cid_start, this));
|
|
__ Nop(compiler->ToEmbeddableCid(cids_.Extent(), this));
|
|
}
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptCheckClass);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckClass, 1) {
|
|
const Register value = locs()->in(0).reg();
|
|
if (IsNullCheck()) {
|
|
ASSERT(IsDeoptIfNull() || IsDeoptIfNotNull());
|
|
if (IsDeoptIfNull()) {
|
|
__ IfEqNull(value);
|
|
} else {
|
|
__ IfNeNull(value);
|
|
}
|
|
} else {
|
|
ASSERT(!cids_.IsMonomorphic() || !cids_.HasClassId(kSmiCid));
|
|
const intptr_t may_be_smi = cids_.HasClassId(kSmiCid) ? 1 : 0;
|
|
bool is_bit_test = false;
|
|
intptr_t cid_mask = 0;
|
|
if (IsBitTest()) {
|
|
cid_mask = ComputeCidMask();
|
|
is_bit_test = Smi::IsValid(cid_mask);
|
|
}
|
|
if (is_bit_test) {
|
|
intptr_t min = cids_.ComputeLowestCid();
|
|
__ CheckBitTest(value, may_be_smi);
|
|
__ Nop(compiler->ToEmbeddableCid(min, this));
|
|
__ Nop(__ AddConstant(Smi::Handle(Smi::New(cid_mask))));
|
|
} else {
|
|
bool using_ranges = false;
|
|
int smi_adjustment = 0;
|
|
int length = cids_.length();
|
|
for (intptr_t i = 0; i < length; i++) {
|
|
if (!cids_[i].IsSingleCid()) {
|
|
using_ranges = true;
|
|
} else if (cids_[i].cid_start == kSmiCid) {
|
|
ASSERT(cids_[i].cid_end == kSmiCid); // We are in the else clause.
|
|
ASSERT(smi_adjustment == 0);
|
|
smi_adjustment = 1;
|
|
}
|
|
}
|
|
|
|
if (!Utils::IsUint(8, length)) {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
if (using_ranges) {
|
|
__ CheckCidsByRange(value, may_be_smi, (length - smi_adjustment) * 2);
|
|
} else {
|
|
__ CheckCids(value, may_be_smi, length - smi_adjustment);
|
|
}
|
|
for (intptr_t i = 0; i < length; i++) {
|
|
intptr_t cid_start = cids_[i].cid_start;
|
|
intptr_t cid_end = cids_[i].cid_end;
|
|
if (cid_start == kSmiCid && cid_end == kSmiCid) {
|
|
ASSERT(smi_adjustment == 1);
|
|
continue;
|
|
}
|
|
__ Nop(compiler->ToEmbeddableCid(cid_start, this));
|
|
if (using_ranges) {
|
|
__ Nop(compiler->ToEmbeddableCid(1 + cids_[i].Extent(), this));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptCheckClass,
|
|
licm_hoisted_ ? ICData::kHoisted : 0);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(CheckNull, 1) {
|
|
if (compiler->is_optimizing()) {
|
|
const Register value = locs()->in(0).reg();
|
|
__ IfEqNull(value);
|
|
} else {
|
|
__ IfEqNullTOS();
|
|
}
|
|
__ NullError();
|
|
CheckNullInstr::AddMetadataForRuntimeCall(this, compiler);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(BinarySmiOp, 2, Location::RequiresRegister()) {
|
|
if (compiler->is_optimizing()) {
|
|
const Register left = locs()->in(0).reg();
|
|
const Register right = locs()->in(1).reg();
|
|
const Register out = locs()->out(0).reg();
|
|
const bool can_deopt = CanDeoptimize();
|
|
bool needs_nop = false;
|
|
switch (op_kind()) {
|
|
case Token::kADD:
|
|
__ Add(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
case Token::kSUB:
|
|
__ Sub(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
case Token::kMUL:
|
|
__ Mul(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
case Token::kTRUNCDIV:
|
|
ASSERT(can_deopt);
|
|
__ Div(out, left, right);
|
|
break;
|
|
case Token::kBIT_AND:
|
|
ASSERT(!can_deopt);
|
|
__ BitAnd(out, left, right);
|
|
break;
|
|
case Token::kBIT_OR:
|
|
ASSERT(!can_deopt);
|
|
__ BitOr(out, left, right);
|
|
break;
|
|
case Token::kBIT_XOR:
|
|
ASSERT(!can_deopt);
|
|
__ BitXor(out, left, right);
|
|
break;
|
|
case Token::kMOD:
|
|
__ Mod(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
case Token::kSHR:
|
|
__ Shr(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
case Token::kSHL:
|
|
__ Shl(out, left, right);
|
|
needs_nop = true;
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
if (can_deopt) {
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptBinarySmiOp);
|
|
} else if (needs_nop) {
|
|
__ Nop(0);
|
|
}
|
|
} else {
|
|
switch (op_kind()) {
|
|
case Token::kADD:
|
|
__ SmiAddTOS();
|
|
break;
|
|
case Token::kSUB:
|
|
__ SmiSubTOS();
|
|
break;
|
|
case Token::kMUL:
|
|
__ SmiMulTOS();
|
|
break;
|
|
case Token::kBIT_AND:
|
|
__ SmiBitAndTOS();
|
|
break;
|
|
default:
|
|
UNIMPLEMENTED();
|
|
}
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(UnarySmiOp, 1, Location::RequiresRegister()) {
|
|
switch (op_kind()) {
|
|
case Token::kNEGATE: {
|
|
__ Neg(locs()->out(0).reg(), locs()->in(0).reg());
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptUnaryOp);
|
|
break;
|
|
}
|
|
case Token::kBIT_NOT:
|
|
__ BitNot(locs()->out(0).reg(), locs()->in(0).reg());
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void BoxInstr::EmitAllocateBox(FlowGraphCompiler* compiler) {
|
|
const Register out = locs()->out(0).reg();
|
|
const Class& box_class = compiler->BoxClassFor(from_representation());
|
|
const intptr_t instance_size = box_class.instance_size();
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(Heap::IsAllocatableInNewSpace(instance_size));
|
|
if (!box_class.TraceAllocation(isolate)) {
|
|
uword tags = 0;
|
|
tags = RawObject::SizeTag::update(instance_size, tags);
|
|
tags = RawObject::ClassIdTag::update(box_class.id(), tags);
|
|
// tags also has the initial zero hash code on 64 bit.
|
|
if (Smi::IsValid(tags)) {
|
|
const intptr_t tags_kidx = __ AddConstant(Smi::Handle(Smi::New(tags)));
|
|
__ AllocateOpt(out, tags_kidx);
|
|
}
|
|
}
|
|
const intptr_t kidx = __ AddConstant(box_class);
|
|
__ Allocate(kidx);
|
|
compiler->AddCurrentDescriptor(RawPcDescriptors::kOther, DeoptId::kNone,
|
|
token_pos());
|
|
compiler->RecordSafepoint(locs());
|
|
__ PopLocal(out);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Box, 1, Location::RequiresRegister(), LocationSummary::kCall) {
|
|
ASSERT(from_representation() == kUnboxedDouble ||
|
|
from_representation() == kUnboxedFloat);
|
|
const Register value = locs()->in(0).reg();
|
|
const Register out = locs()->out(0).reg();
|
|
if (from_representation() == kUnboxedFloat) {
|
|
__ FloatToDouble(value, value);
|
|
}
|
|
EmitAllocateBox(compiler);
|
|
__ WriteIntoDouble(out, value);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(Unbox, 1, Location::RequiresRegister()) {
|
|
if (representation() == kUnboxedInt64) {
|
|
EmitLoadInt64FromBoxOrSmi(compiler);
|
|
return;
|
|
}
|
|
ASSERT(representation() == kUnboxedDouble ||
|
|
representation() == kUnboxedFloat);
|
|
const intptr_t value_cid = value()->Type()->ToCid();
|
|
const intptr_t box_cid = BoxCid();
|
|
const Register box = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
if (value_cid == box_cid ||
|
|
(speculative_mode() == kNotSpeculative && value_cid != kSmiCid)) {
|
|
__ UnboxDouble(result, box);
|
|
} else if (CanConvertSmi() && (value_cid == kSmiCid)) {
|
|
__ SmiToDouble(result, box);
|
|
} else if ((value()->Type()->ToNullableCid() == box_cid) &&
|
|
value()->Type()->is_nullable()) {
|
|
__ IfEqNull(box);
|
|
ASSERT(CanDeoptimize());
|
|
compiler->EmitDeopt(GetDeoptId(), ICData::kDeoptCheckClass);
|
|
__ UnboxDouble(result, box);
|
|
} else {
|
|
__ CheckedUnboxDouble(result, box);
|
|
ASSERT(CanDeoptimize());
|
|
compiler->EmitDeopt(GetDeoptId(), ICData::kDeoptCheckClass);
|
|
}
|
|
if (representation() == kUnboxedFloat) {
|
|
__ DoubleToFloat(result, result);
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(UnboxInteger32, 1, Location::RequiresRegister()) {
|
|
#if defined(ARCH_IS_64_BIT)
|
|
const Register out = locs()->out(0).reg();
|
|
const Register value = locs()->in(0).reg();
|
|
const bool may_truncate = is_truncating() || !CanDeoptimize();
|
|
__ UnboxInt32(out, value, may_truncate);
|
|
if (CanDeoptimize()) {
|
|
compiler->EmitDeopt(GetDeoptId(), ICData::kDeoptUnboxInteger);
|
|
} else {
|
|
__ Nop(0);
|
|
}
|
|
#else
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
#endif // defined(ARCH_IS_64_BIT)
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(BoxInteger32, 1, Location::RequiresRegister()) {
|
|
#if defined(ARCH_IS_64_BIT)
|
|
const Register out = locs()->out(0).reg();
|
|
const Register value = locs()->in(0).reg();
|
|
if (from_representation() == kUnboxedInt32) {
|
|
__ BoxInt32(out, value);
|
|
} else {
|
|
ASSERT(from_representation() == kUnboxedUint32);
|
|
__ BoxUint32(out, value);
|
|
}
|
|
#else
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
#endif // defined(ARCH_IS_64_BIT)
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(BoxInt64, 1, Location::RequiresRegister()) {
|
|
#if defined(ARCH_IS_64_BIT)
|
|
Label done;
|
|
const Register value = locs()->in(0).reg();
|
|
const Register out = locs()->out(0).reg();
|
|
__ BoxInt64(out, value);
|
|
__ Jump(&done);
|
|
EmitAllocateBox(compiler);
|
|
__ WriteIntoMint(out, value);
|
|
__ Bind(&done);
|
|
#else
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
#endif // defined(ARCH_IS_64_BIT)
|
|
}
|
|
|
|
void UnboxInstr::EmitLoadInt64FromBoxOrSmi(FlowGraphCompiler* compiler) {
|
|
#if defined(ARCH_IS_64_BIT)
|
|
const Register out = locs()->out(0).reg();
|
|
const Register value = locs()->in(0).reg();
|
|
__ UnboxInt64(out, value);
|
|
#else
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
#endif // defined(ARCH_IS_64_BIT)
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(UnboxedWidthExtender, 1, Location::RequiresRegister()) {
|
|
const Register out = locs()->out(0).reg();
|
|
const Register value = locs()->in(0).reg();
|
|
__ UnboxedWidthExtender(out, value, from_representation());
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(DoubleToSmi, 1, Location::RequiresRegister()) {
|
|
const Register value = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ DoubleToSmi(result, value);
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptDoubleToSmi);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(SmiToDouble, 1, Location::RequiresRegister()) {
|
|
const Register value = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ SmiToDouble(result, value);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(BinaryDoubleOp, 2, Location::RequiresRegister()) {
|
|
const Register left = locs()->in(0).reg();
|
|
const Register right = locs()->in(1).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
switch (op_kind()) {
|
|
case Token::kADD:
|
|
__ DAdd(result, left, right);
|
|
break;
|
|
case Token::kSUB:
|
|
__ DSub(result, left, right);
|
|
break;
|
|
case Token::kMUL:
|
|
__ DMul(result, left, right);
|
|
break;
|
|
case Token::kDIV:
|
|
__ DDiv(result, left, right);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
Condition DoubleTestOpInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
ASSERT(compiler->is_optimizing());
|
|
const Register value = locs()->in(0).reg();
|
|
switch (op_kind()) {
|
|
case MethodRecognizer::kDouble_getIsNaN:
|
|
__ DoubleIsNaN(value);
|
|
break;
|
|
case MethodRecognizer::kDouble_getIsInfinite:
|
|
__ DoubleIsInfinite(value);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
const bool is_negated = kind() != Token::kEQ;
|
|
return is_negated ? NEXT_IS_FALSE : NEXT_IS_TRUE;
|
|
}
|
|
|
|
Condition DoubleTestOpInstr::GetNextInstructionCondition(
|
|
FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
const bool is_negated = kind() != Token::kEQ;
|
|
return is_negated ? NEXT_IS_FALSE : NEXT_IS_TRUE;
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(DoubleTestOp, 1, Location::RequiresRegister())
|
|
|
|
EMIT_NATIVE_CODE(UnaryDoubleOp, 1, Location::RequiresRegister()) {
|
|
const Register value = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ DNeg(result, value);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(MathUnary, 1, Location::RequiresRegister()) {
|
|
const Register value = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
if (kind() == MathUnaryInstr::kSqrt) {
|
|
__ DSqrt(result, value);
|
|
} else if (kind() == MathUnaryInstr::kDoubleSquare) {
|
|
__ DMul(result, value, value);
|
|
} else {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(DoubleToDouble, 1, Location::RequiresRegister()) {
|
|
const Register in = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
switch (recognized_kind()) {
|
|
case MethodRecognizer::kDoubleTruncate:
|
|
__ DTruncate(result, in);
|
|
break;
|
|
case MethodRecognizer::kDoubleFloor:
|
|
__ DFloor(result, in);
|
|
break;
|
|
case MethodRecognizer::kDoubleCeil:
|
|
__ DCeil(result, in);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(DoubleToFloat, 1, Location::RequiresRegister()) {
|
|
const Register in = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ DoubleToFloat(result, in);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(FloatToDouble, 1, Location::RequiresRegister()) {
|
|
const Register in = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
__ FloatToDouble(result, in);
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(InvokeMathCFunction,
|
|
InputCount(),
|
|
Location::RequiresRegister()) {
|
|
const Register left = locs()->in(0).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
if (recognized_kind() == MethodRecognizer::kMathDoublePow) {
|
|
const Register right = locs()->in(1).reg();
|
|
__ DPow(result, left, right);
|
|
} else if (recognized_kind() == MethodRecognizer::kDoubleMod) {
|
|
const Register right = locs()->in(1).reg();
|
|
__ DMod(result, left, right);
|
|
} else if (recognized_kind() == MethodRecognizer::kMathSin) {
|
|
__ DSin(result, left);
|
|
} else if (recognized_kind() == MethodRecognizer::kMathCos) {
|
|
__ DCos(result, left);
|
|
} else {
|
|
Unsupported(compiler);
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
EMIT_NATIVE_CODE(MathMinMax, 2, Location::RequiresRegister()) {
|
|
ASSERT((op_kind() == MethodRecognizer::kMathMin) ||
|
|
(op_kind() == MethodRecognizer::kMathMax));
|
|
const Register left = locs()->in(0).reg();
|
|
const Register right = locs()->in(1).reg();
|
|
const Register result = locs()->out(0).reg();
|
|
if (result_cid() == kDoubleCid) {
|
|
if (op_kind() == MethodRecognizer::kMathMin) {
|
|
__ DMin(result, left, right);
|
|
} else {
|
|
__ DMax(result, left, right);
|
|
}
|
|
} else {
|
|
ASSERT(result_cid() == kSmiCid);
|
|
if (op_kind() == MethodRecognizer::kMathMin) {
|
|
__ Min(result, left, right);
|
|
} else {
|
|
__ Max(result, left, right);
|
|
}
|
|
}
|
|
}
|
|
|
|
static SimulatorBytecode::Opcode OpcodeForSmiCondition(Token::Kind kind) {
|
|
switch (kind) {
|
|
case Token::kEQ:
|
|
return SimulatorBytecode::kIfEqStrict;
|
|
case Token::kNE:
|
|
return SimulatorBytecode::kIfNeStrict;
|
|
case Token::kLT:
|
|
return SimulatorBytecode::kIfLt;
|
|
case Token::kGT:
|
|
return SimulatorBytecode::kIfGt;
|
|
case Token::kLTE:
|
|
return SimulatorBytecode::kIfLe;
|
|
case Token::kGTE:
|
|
return SimulatorBytecode::kIfGe;
|
|
default:
|
|
UNREACHABLE();
|
|
return SimulatorBytecode::kTrap;
|
|
}
|
|
}
|
|
|
|
static SimulatorBytecode::Opcode OpcodeForDoubleCondition(Token::Kind kind) {
|
|
switch (kind) {
|
|
case Token::kEQ:
|
|
return SimulatorBytecode::kIfDEq;
|
|
case Token::kNE:
|
|
return SimulatorBytecode::kIfDNe;
|
|
case Token::kLT:
|
|
return SimulatorBytecode::kIfDLt;
|
|
case Token::kGT:
|
|
return SimulatorBytecode::kIfDGt;
|
|
case Token::kLTE:
|
|
return SimulatorBytecode::kIfDLe;
|
|
case Token::kGTE:
|
|
return SimulatorBytecode::kIfDGe;
|
|
default:
|
|
UNREACHABLE();
|
|
return SimulatorBytecode::kTrap;
|
|
}
|
|
}
|
|
|
|
static Condition EmitSmiComparisonOp(FlowGraphCompiler* compiler,
|
|
LocationSummary* locs,
|
|
Token::Kind kind,
|
|
BranchLabels labels) {
|
|
Token::Kind comparison = kind;
|
|
Condition condition = NEXT_IS_TRUE;
|
|
if (labels.fall_through != labels.false_label) {
|
|
// If we aren't falling through to the false label, we can save a Jump
|
|
// instruction in the case that the true case is the fall through by
|
|
// negating the sense of the test such that the instruction following the
|
|
// test is the Jump to the false label. In the case where both labels are
|
|
// null we don't negate the sense of the test.
|
|
condition = NEXT_IS_FALSE;
|
|
comparison = Token::NegateComparison(kind);
|
|
}
|
|
if (compiler->is_optimizing()) {
|
|
const Register left = locs->in(0).reg();
|
|
const Register right = locs->in(1).reg();
|
|
__ Emit(SimulatorBytecode::Encode(OpcodeForSmiCondition(comparison), left,
|
|
right));
|
|
return condition;
|
|
} else {
|
|
switch (kind) {
|
|
case Token::kEQ:
|
|
__ IfEqStrictTOS();
|
|
break;
|
|
case Token::kNE:
|
|
__ IfNeStrictTOS();
|
|
break;
|
|
case Token::kLT:
|
|
__ IfSmiLtTOS();
|
|
break;
|
|
case Token::kLTE:
|
|
__ IfSmiLeTOS();
|
|
break;
|
|
case Token::kGT:
|
|
__ IfSmiGtTOS();
|
|
break;
|
|
case Token::kGTE:
|
|
__ IfSmiGeTOS();
|
|
break;
|
|
default:
|
|
UNIMPLEMENTED();
|
|
}
|
|
return condition;
|
|
}
|
|
}
|
|
|
|
static Condition EmitDoubleComparisonOp(FlowGraphCompiler* compiler,
|
|
LocationSummary* locs,
|
|
Token::Kind kind) {
|
|
const Register left = locs->in(0).reg();
|
|
const Register right = locs->in(1).reg();
|
|
Token::Kind comparison = kind;
|
|
// For double comparisons we can't flip the condition like with smi
|
|
// comparisons because of NaN which will compare false for all except !=
|
|
// operations.
|
|
// TODO(fschneider): Change the block order instead in DBC so that the
|
|
// false block in always the fall-through block.
|
|
Condition condition = NEXT_IS_TRUE;
|
|
__ Emit(SimulatorBytecode::Encode(OpcodeForDoubleCondition(comparison), left,
|
|
right));
|
|
return condition;
|
|
}
|
|
|
|
Condition EqualityCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
if (operation_cid() == kSmiCid) {
|
|
return EmitSmiComparisonOp(compiler, locs(), kind(), labels);
|
|
} else {
|
|
ASSERT(operation_cid() == kDoubleCid);
|
|
return EmitDoubleComparisonOp(compiler, locs(), kind());
|
|
}
|
|
}
|
|
|
|
Condition EqualityCompareInstr::GetNextInstructionCondition(
|
|
FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
if (operation_cid() == kSmiCid) {
|
|
return (labels.fall_through != labels.false_label) ? NEXT_IS_FALSE
|
|
: NEXT_IS_TRUE;
|
|
} else {
|
|
ASSERT(operation_cid() == kDoubleCid);
|
|
return NEXT_IS_TRUE;
|
|
}
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(EqualityCompare, 2, Location::RequiresRegister());
|
|
|
|
Condition RelationalOpInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
if (operation_cid() == kSmiCid) {
|
|
return EmitSmiComparisonOp(compiler, locs(), kind(), labels);
|
|
} else {
|
|
ASSERT(operation_cid() == kDoubleCid);
|
|
return EmitDoubleComparisonOp(compiler, locs(), kind());
|
|
}
|
|
}
|
|
|
|
Condition RelationalOpInstr::GetNextInstructionCondition(
|
|
FlowGraphCompiler* compiler,
|
|
BranchLabels labels) {
|
|
if (operation_cid() == kSmiCid) {
|
|
return (labels.fall_through != labels.false_label) ? NEXT_IS_FALSE
|
|
: NEXT_IS_TRUE;
|
|
} else {
|
|
ASSERT(operation_cid() == kDoubleCid);
|
|
return NEXT_IS_TRUE;
|
|
}
|
|
}
|
|
|
|
DEFINE_MAKE_LOCATION_SUMMARY(RelationalOp, 2, Location::RequiresRegister())
|
|
|
|
EMIT_NATIVE_CODE(CheckArrayBound, 2) {
|
|
const Register length = locs()->in(kLengthPos).reg();
|
|
const Register index = locs()->in(kIndexPos).reg();
|
|
const intptr_t index_cid = this->index()->Type()->ToCid();
|
|
if (index_cid != kSmiCid) {
|
|
__ CheckSmi(index);
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptCheckArrayBound,
|
|
(generalized_ ? ICData::kGeneralized : 0) |
|
|
(licm_hoisted_ ? ICData::kHoisted : 0));
|
|
}
|
|
__ IfULe(length, index);
|
|
compiler->EmitDeopt(deopt_id(), ICData::kDeoptCheckArrayBound,
|
|
(generalized_ ? ICData::kGeneralized : 0) |
|
|
(licm_hoisted_ ? ICData::kHoisted : 0));
|
|
}
|
|
|
|
void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
} // namespace dart
|
|
|
|
#endif // defined TARGET_ARCH_DBC
|