Add a smi-check instruction for arithmetic smi operations.

The flowgraph optimizer inserts smi checks for the inputs of
arithmetic smi operations:

      PushArgument v0
      PushArgument v1
v2 <- InstanceCall(+, v0, v1) IC[1: Smi, Smi]

becomes

      CheckSmi(v0)
      CheckSmi(v1)
v2 <- BinarySmiOp(+, v0, v1)


Each input operand is checked separately. This avoids using a temp
register for a combined smi check. It also allows us to easily eliminate
the checks for left and right input separately.

There are two ways to eliminate smi checks:
1. By common subexpression elimination: if the value checked is already
   checked for smi-ness before.
2. By class-id propagation: If the input value is guaranteed to be a smi.
Review URL: https://chromiumcodereview.appspot.com//10867012

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11216 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
fschneider@google.com 2012-08-23 08:24:29 +00:00
parent 5b27429411
commit e450dfb95a
7 changed files with 225 additions and 112 deletions

View file

@ -77,7 +77,8 @@ DECLARE_RUNTIME_ENTRY(TraceFunctionExit);
V(DeoptNoTypeFeedback) \
V(DeoptSAR) \
V(DeoptUnaryOp) \
V(DeoptCheckClass)
V(DeoptCheckClass) \
V(DeoptCheckSmi)
enum DeoptReasonId {
#define DEFINE_ENUM_LIST(name) k##name,

View file

@ -34,15 +34,18 @@ void FlowGraphOptimizer::OptimizeComputations() {
BindInstr* instr = it.Current()->AsBind();
if (instr != NULL) {
Definition* result = instr->computation()->TryReplace(instr);
if (result != NULL) {
// Replace uses and remove the current instructions via the iterator.
instr->ReplaceUsesWith(result);
it.RemoveCurrentFromGraph();
if (FLAG_trace_optimization) {
OS::Print("Replacing v%d with v%d\n",
instr->ssa_temp_index(),
result->ssa_temp_index());
if (result != instr) {
if (result != NULL) {
instr->ReplaceUsesWith(result);
if (FLAG_trace_optimization) {
OS::Print("Replacing v%d with v%d\n",
instr->ssa_temp_index(),
result->ssa_temp_index());
}
} else if (FLAG_trace_optimization) {
OS::Print("Removing v%d.\n", instr->ssa_temp_index());
}
it.RemoveCurrentFromGraph();
}
}
}
@ -194,6 +197,19 @@ static intptr_t ReceiverClassId(Computation* comp) {
}
// Insert a check computation before an instruction and set the environment
// of the check to the same as the instruction.
static void InsertCheckBefore(BindInstr* instr,
Computation* check,
Environment* env) {
BindInstr* check_instr = new BindInstr(BindInstr::kUnused, check);
check_instr->InsertBefore(instr);
ASSERT(env != NULL);
// Attach an environment to the check instruction.
check_instr->set_env(env);
}
static void AddCheckClass(BindInstr* instr,
InstanceCallComp* comp,
Value* value) {
@ -202,11 +218,10 @@ static void AddCheckClass(BindInstr* instr,
const ICData& unary_checks =
ICData::ZoneHandle(comp->ic_data()->AsUnaryClassChecks());
check->set_ic_data(&unary_checks);
BindInstr* check_instr = new BindInstr(BindInstr::kUnused, check);
ASSERT(instr->env() != NULL); // Always the case with SSA.
// Attach the original environment to the check instruction.
check_instr->set_env(instr->env());
check_instr->InsertBefore(instr);
InsertCheckBefore(instr, check, instr->env());
// Detach environment from the original instruction because it can't
// deoptimize.
instr->set_env(NULL);
}
@ -317,6 +332,14 @@ bool FlowGraphOptimizer::TryReplaceWithBinaryOp(BindInstr* instr,
ASSERT(operands_type == kSmiCid);
Value* left = comp->ArgumentAt(0)->value();
Value* right = comp->ArgumentAt(1)->value();
// Insert two smi checks and attach a copy of the original
// environment because the smi operation can still deoptimize.
InsertCheckBefore(instr,
new CheckSmiComp(left, comp),
instr->env()->Copy());
InsertCheckBefore(instr,
new CheckSmiComp(right, comp),
instr->env()->Copy());
BinarySmiOpComp* bin_op = new BinarySmiOpComp(op_kind,
comp,
left,
@ -391,8 +414,8 @@ bool FlowGraphOptimizer::TryInlineInstanceGetter(BindInstr* instr,
String::Handle(Field::NameFromGetter(comp->function_name()));
const Field& field = Field::Handle(GetField(class_ids[0], field_name));
ASSERT(!field.IsNull());
AddCheckClass(instr, comp, comp->ArgumentAt(0)->value());
instr->set_env(NULL);
LoadInstanceFieldComp* load =
new LoadInstanceFieldComp(field,
comp->ArgumentAt(0)->value(),
@ -573,7 +596,6 @@ bool FlowGraphOptimizer::TryInlineInstanceSetter(BindInstr* instr,
ASSERT(!field.IsNull());
AddCheckClass(instr, comp, comp->ArgumentAt(0)->value());
instr->set_env(NULL);
StoreInstanceFieldComp* store = new StoreInstanceFieldComp(
field,
comp->ArgumentAt(0)->value(),

View file

@ -408,6 +408,16 @@ void UnarySmiOpComp::PrintOperandsTo(BufferFormatter* f) const {
}
void CheckClassComp::PrintOperandsTo(BufferFormatter* f) const {
value()->PrintTo(f);
f->Print(", cid={");
for (intptr_t i = 0; i < ic_data()->NumberOfChecks(); i++) {
f->Print("%d ", ic_data()->GetReceiverClassIdAt(i));
}
f->Print("}");
}
void GraphEntryInstr::PrintTo(BufferFormatter* f) const {
f->Print("%2d: [graph]", block_id());
if (start_env_ != NULL) {

View file

@ -1000,6 +1000,18 @@ intptr_t BinarySmiOpComp::ResultCid() const {
}
bool BinarySmiOpComp::CanDeoptimize() const {
switch (op_kind()) {
case Token::kBIT_AND:
case Token::kBIT_OR:
case Token::kBIT_XOR:
return false;
default:
return true;
}
}
RawAbstractType* BinaryMintOpComp::CompileType() const {
ObjectStore* object_store = Isolate::Current()->object_store();
return object_store->mint_type();
@ -1047,33 +1059,52 @@ RawAbstractType* CheckClassComp::CompileType() const {
}
RawAbstractType* CheckSmiComp::CompileType() const {
return AbstractType::null();
}
// Optimizations that eliminate or simplify individual computations.
Definition* Computation::TryReplace(BindInstr* instr) const {
return instr;
}
Definition* StrictCompareComp::TryReplace(BindInstr* instr) const {
UseVal* left_use = left()->AsUse();
UseVal* right_use = right()->AsUse();
if ((right_use == NULL) || (left_use == NULL)) return instr;
if (!right_use->BindsToConstant()) return instr;
const Object& right_constant = right_use->BoundConstant();
Definition* left = left_use->definition();
// TODO(fschneider): Handle other cases: e === false and e !== true/false.
// Handles e === true.
if ((kind() == Token::kEQ_STRICT) &&
(right_constant.raw() == Bool::True()) &&
(left_use->ResultCid() == kBoolCid)) {
// Remove the constant from the graph.
BindInstr* right = right_use->definition()->AsBind();
if (right != NULL) {
right->set_use_list(NULL);
right->RemoveFromGraph();
}
// Return left subexpression as the replacement for this instruction.
return left;
}
return instr;
}
Definition* CheckSmiComp::TryReplace(BindInstr* instr) const {
return (value()->ResultCid() == kSmiCid) ? NULL : instr;
}
// Shared code generation methods (EmitNativeCode, MakeLocationSummary, and
// PrepareEntry). Only assembly code that can be shared across all architectures
// can be used. Machine specific register allocation and code generation
// is located in intermediate_language_<arch>.cc
// True iff. the arguments to a call will be properly pushed and can
// be popped after the call.
template <typename T> static bool VerifyCallComputation(T* comp) {
// Argument values should be consecutive temps.
//
// TODO(kmillikin): implement stack height tracking so we can also assert
// they are on top of the stack.
intptr_t previous = -1;
for (int i = 0; i < comp->ArgumentCount(); ++i) {
Value* val = comp->ArgumentAt(i);
if (!val->IsUse()) return false;
intptr_t current = val->AsUse()->definition()->temp_index();
if (i != 0) {
if (current != (previous + 1)) return false;
}
previous = current;
}
return true;
}
#define __ compiler->assembler()->
void GraphEntryInstr::PrepareEntry(FlowGraphCompiler* compiler) {
@ -1248,31 +1279,6 @@ void StoreContextComp::EmitNativeCode(FlowGraphCompiler* compiler) {
}
Definition* StrictCompareComp::TryReplace(BindInstr* instr) {
UseVal* left_use = left()->AsUse();
UseVal* right_use = right()->AsUse();
if ((right_use == NULL) || (left_use == NULL)) return NULL;
if (!right_use->BindsToConstant()) return NULL;
const Object& right_constant = right_use->BoundConstant();
Definition* left = left_use->definition();
// TODO(fschneider): Handle other cases: e === false and e !== true/false.
// Handles e === true.
if ((kind() == Token::kEQ_STRICT) &&
(right_constant.raw() == Bool::True()) &&
(left_use->ResultCid() == kBoolCid)) {
// Remove the constant from the graph.
BindInstr* right = right_use->definition()->AsBind();
if (right != NULL) {
right->set_use_list(NULL);
right->RemoveFromGraph();
}
// Return left subexpression as the replacement for this instruction.
return left;
}
return NULL;
}
LocationSummary* StrictCompareComp::MakeLocationSummary() const {
return LocationSummary::Make(2,
Location::SameAsFirstInput(),
@ -1528,6 +1534,20 @@ Environment::Environment(const GrowableArray<Definition*>& definitions,
}
Environment* Environment::Copy() const {
Environment* copy = new Environment(values().length(),
fixed_parameter_count());
GrowableArray<Value*>* values_copy = copy->values_ptr();
for (intptr_t i = 0; i < values().length(); ++i) {
Value* val = values()[i];
values_copy->Add(val->IsUse()
? UseDefinition(values()[i]->AsUse()->definition())
: val);
}
return copy;
}
#undef __
} // namespace dart

View file

@ -103,6 +103,7 @@ class LocalVariable;
M(DoubleToDouble, DoubleToDoubleComp) \
M(SmiToDouble, SmiToDoubleComp) \
M(CheckClass, CheckClassComp) \
M(CheckSmi, CheckSmiComp) \
M(Materialize, MaterializeComp)
@ -152,9 +153,10 @@ class Computation : public ZoneAllocated {
// Returns true, if this computation can deoptimize.
virtual bool CanDeoptimize() const = 0;
// Optimize this computation. Returns a replacement for the instruction
// that wraps this computation or NULL if nothing to replace.
virtual Definition* TryReplace(BindInstr* instr) { return NULL; }
// Returns a replacement for the instruction that wraps this computation.
// Returns NULL if instr can be eliminated.
// By default returns instr (input parameter) which means no change.
virtual Definition* TryReplace(BindInstr* instr) const;
// Compares two computations. Returns true, if:
// 1. They are of the same kind.
@ -772,7 +774,8 @@ class StrictCompareComp : public ComparisonComp {
virtual bool CanDeoptimize() const { return false; }
virtual Definition* TryReplace(BindInstr* instr);
virtual Definition* TryReplace(BindInstr* instr) const;
virtual intptr_t ResultCid() const { return kBoolCid; }
private:
@ -1675,7 +1678,8 @@ class BinarySmiOpComp : public TemplateComputation<2> {
DECLARE_COMPUTATION(BinarySmiOp)
virtual bool CanDeoptimize() const { return true; }
virtual bool CanDeoptimize() const;
virtual intptr_t ResultCid() const;
private:
@ -1890,6 +1894,8 @@ class CheckClassComp : public TemplateComputation<1> {
intptr_t deopt_id() const { return original_->deopt_id(); }
intptr_t try_index() const { return original_->try_index(); }
virtual void PrintOperandsTo(BufferFormatter* f) const;
private:
InstanceCallComp* original_;
@ -1897,6 +1903,36 @@ class CheckClassComp : public TemplateComputation<1> {
};
class CheckSmiComp : public TemplateComputation<1> {
public:
CheckSmiComp(Value* value, InstanceCallComp* original)
: original_(original) {
ASSERT(value != NULL);
inputs_[0] = value;
}
DECLARE_COMPUTATION(CheckSmi)
virtual bool CanDeoptimize() const { return true; }
virtual bool AttributesEqual(Computation* other) const { return true; }
virtual bool HasSideEffect() const { return false; }
virtual Definition* TryReplace(BindInstr* instr) const;
Value* value() const { return inputs_[0]; }
intptr_t deopt_id() const { return original_->deopt_id(); }
intptr_t try_index() const { return original_->try_index(); }
private:
InstanceCallComp* original_;
DISALLOW_COPY_AND_ASSIGN(CheckSmiComp);
};
#undef DECLARE_COMPUTATION
@ -3063,9 +3099,16 @@ class Environment : public ZoneAllocated {
return fixed_parameter_count_;
}
Environment* Copy() const;
void PrintTo(BufferFormatter* f) const;
private:
Environment(intptr_t length, intptr_t fixed_parameter_count)
: values_(length),
locations_(NULL),
fixed_parameter_count_(fixed_parameter_count) { }
GrowableArray<Value*> values_;
Location* locations_;
const intptr_t fixed_parameter_count_;

View file

@ -1422,13 +1422,12 @@ LocationSummary* BinarySmiOpComp::MakeLocationSummary() const {
summary->set_temp(2, Location::RequiresRegister());
return summary;
} else if (op_kind() == Token::kSHR) {
const intptr_t kNumTemps = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_in(1, Location::RegisterLocation(ECX));
summary->set_out(Location::SameAsFirstInput());
summary->set_temp(0, Location::RequiresRegister());
return summary;
} else if (op_kind() == Token::kSHL) {
// Two Smi operands can easily overflow into Mint.
@ -1442,13 +1441,12 @@ LocationSummary* BinarySmiOpComp::MakeLocationSummary() const {
summary->set_out(Location::RegisterLocation(EAX));
return summary;
} else {
const intptr_t kNumTemps = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_in(1, Location::RequiresRegister());
summary->set_out(Location::SameAsFirstInput());
summary->set_temp(0, Location::RequiresRegister());
return summary;
}
}
@ -1458,32 +1456,20 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
Register left = locs()->in(0).reg();
Register right = locs()->in(1).reg();
Register result = locs()->out().reg();
Register temp = locs()->temp(0).reg();
ASSERT(left == result);
const bool left_is_smi = this->left()->ResultCid() == kSmiCid;
const bool right_is_smi = this->right()->ResultCid() == kSmiCid;
bool can_deopt;
Label* deopt = NULL;
switch (op_kind()) {
case Token::kBIT_AND:
case Token::kBIT_OR:
case Token::kBIT_XOR:
can_deopt = !(right_is_smi && left_is_smi);
// Can't deoptimize. Arguments are already checked for smi.
break;
default:
can_deopt = true;
}
Label* deopt = NULL;
if (can_deopt) {
deopt = compiler->AddDeoptStub(instance_call()->deopt_id(),
instance_call()->try_index(),
kDeoptBinarySmiOp);
}
if (!left_is_smi || !right_is_smi) {
__ movl(temp, left);
__ orl(temp, right);
__ testl(temp, Immediate(kSmiTagMask));
__ j(NOT_ZERO, deopt);
deopt = compiler->AddDeoptStub(instance_call()->deopt_id(),
instance_call()->try_index(),
kDeoptBinarySmiOp);
}
switch (op_kind()) {
case Token::kADD: {
__ addl(left, right);
@ -1517,6 +1503,7 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
break;
}
case Token::kTRUNCDIV: {
Register temp = locs()->temp(0).reg();
// Handle divide by zero in runtime.
// Deoptimization requires that temp and right are preserved.
__ testl(right, right);
@ -1557,6 +1544,7 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
break;
}
case Token::kSHL: {
Register temp = locs()->temp(0).reg();
Label call_method, done;
// Check if count too large for handling it inlined.
__ movl(temp, left);
@ -2186,6 +2174,27 @@ void CheckClassComp::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Bind(&is_ok);
}
LocationSummary* CheckSmiComp::MakeLocationSummary() const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
return summary;
}
void CheckSmiComp::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Label* deopt = compiler->AddDeoptStub(deopt_id(),
try_index(),
kDeoptCheckSmi);
__ testl(value, Immediate(kSmiTagMask));
__ j(NOT_ZERO, deopt);
}
} // namespace dart
#undef __

View file

@ -1438,13 +1438,12 @@ LocationSummary* BinarySmiOpComp::MakeLocationSummary() const {
summary->set_temp(2, Location::RequiresRegister());
return summary;
} else if (op_kind() == Token::kSHR) {
const intptr_t kNumTemps = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_in(1, Location::RegisterLocation(RCX));
summary->set_out(Location::SameAsFirstInput());
summary->set_temp(0, Location::RequiresRegister());
return summary;
} else if (op_kind() == Token::kSHL) {
// Two Smi operands can easily overflow into Mint.
@ -1458,13 +1457,12 @@ LocationSummary* BinarySmiOpComp::MakeLocationSummary() const {
summary->set_temp(1, Location::RegisterLocation(RCX));
return summary;
} else {
const intptr_t kNumTemps = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_in(1, Location::RequiresRegister());
summary->set_out(Location::SameAsFirstInput());
summary->set_temp(0, Location::RequiresRegister());
return summary;
}
}
@ -1474,31 +1472,18 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
Register left = locs()->in(0).reg();
Register right = locs()->in(1).reg();
Register result = locs()->out().reg();
Register temp = locs()->temp(0).reg();
ASSERT(left == result);
const bool left_is_smi = this->left()->ResultCid() == kSmiCid;
const bool right_is_smi = this->right()->ResultCid() == kSmiCid;
bool can_deopt;
Label* deopt = NULL;
switch (op_kind()) {
case Token::kBIT_AND:
case Token::kBIT_OR:
case Token::kBIT_XOR:
can_deopt = !(right_is_smi && left_is_smi);
// Can't deoptimize. Arguments are already checked for smi.
break;
default:
can_deopt = true;
}
Label* deopt = NULL;
if (can_deopt) {
deopt = compiler->AddDeoptStub(instance_call()->deopt_id(),
instance_call()->try_index(),
kDeoptBinarySmiOp);
}
if (!left_is_smi || !right_is_smi) {
__ movq(temp, left);
__ orq(temp, right);
__ testq(temp, Immediate(kSmiTagMask));
__ j(NOT_ZERO, deopt);
deopt = compiler->AddDeoptStub(instance_call()->deopt_id(),
instance_call()->try_index(),
kDeoptBinarySmiOp);
}
switch (op_kind()) {
case Token::kADD: {
@ -1533,6 +1518,7 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
break;
}
case Token::kTRUNCDIV: {
Register temp = locs()->temp(0).reg();
// Handle divide by zero in runtime.
// Deoptimization requires that temp and right are preserved.
__ testq(right, right);
@ -1573,6 +1559,7 @@ void BinarySmiOpComp::EmitNativeCode(FlowGraphCompiler* compiler) {
break;
}
case Token::kSHL: {
Register temp = locs()->temp(0).reg();
Label call_method, done;
// Check if count too large for handling it inlined.
__ movq(temp, left);
@ -2199,6 +2186,27 @@ void CheckClassComp::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Bind(&is_ok);
}
LocationSummary* CheckSmiComp::MakeLocationSummary() const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary =
new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
return summary;
}
void CheckSmiComp::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Label* deopt = compiler->AddDeoptStub(deopt_id(),
try_index(),
kDeoptCheckSmi);
__ testq(value, Immediate(kSmiTagMask));
__ j(NOT_ZERO, deopt);
}
} // namespace dart
#undef __