Commit 5a3a6daf authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Add new StringFromCharCode simplified operator.

We use StringFromCharCode to optimize calls to String.fromCharCode with
a single Number argument for now. We will use it to also implement the
charAt method on the String prototype.

R=jarin@chromium.org

Review-Url: https://codereview.chromium.org/2037453003
Cr-Commit-Position: refs/heads/master@{#36668}
parent a096aeed
......@@ -632,7 +632,7 @@ Node* CodeStubAssembler::AllocateSeqOneByteString(int length) {
StoreMapNoWriteBarrier(result, LoadRoot(Heap::kOneByteStringMapRootIndex));
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kLengthOffset,
SmiConstant(Smi::FromInt(length)));
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kHashFieldSlot,
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kHashFieldOffset,
IntPtrConstant(String::kEmptyHashField));
return result;
}
......@@ -659,7 +659,7 @@ Node* CodeStubAssembler::AllocateSeqOneByteString(Node* context, Node* length) {
StoreMapNoWriteBarrier(result, LoadRoot(Heap::kOneByteStringMapRootIndex));
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kLengthOffset,
SmiFromWord(length));
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kHashFieldSlot,
StoreObjectFieldNoWriteBarrier(result, SeqOneByteString::kHashFieldOffset,
IntPtrConstant(String::kEmptyHashField));
var_result.Bind(result);
Goto(&if_join);
......@@ -683,7 +683,7 @@ Node* CodeStubAssembler::AllocateSeqTwoByteString(int length) {
StoreMapNoWriteBarrier(result, LoadRoot(Heap::kStringMapRootIndex));
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kLengthOffset,
SmiConstant(Smi::FromInt(length)));
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kHashFieldSlot,
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kHashFieldOffset,
IntPtrConstant(String::kEmptyHashField));
return result;
}
......@@ -710,7 +710,7 @@ Node* CodeStubAssembler::AllocateSeqTwoByteString(Node* context, Node* length) {
StoreMapNoWriteBarrier(result, LoadRoot(Heap::kStringMapRootIndex));
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kLengthOffset,
SmiFromWord(length));
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kHashFieldSlot,
StoreObjectFieldNoWriteBarrier(result, SeqTwoByteString::kHashFieldOffset,
IntPtrConstant(String::kEmptyHashField));
var_result.Bind(result);
Goto(&if_join);
......
......@@ -364,6 +364,14 @@ FieldAccess AccessBuilder::ForMapPrototype() {
}
// static
FieldAccess AccessBuilder::ForNameHashField() {
FieldAccess access = {
kTaggedBase, Name::kHashFieldOffset, Handle<Name>(),
Type::Internal(), MachineType::Pointer(), kNoWriteBarrier};
return access;
}
// static
FieldAccess AccessBuilder::ForStringLength() {
FieldAccess access = {kTaggedBase,
......
......@@ -118,6 +118,9 @@ class AccessBuilder final : public AllStatic {
// Provides access to Map::prototype() field.
static FieldAccess ForMapPrototype();
// Provides access to Name::hash_field() field.
static FieldAccess ForNameHashField();
// Provides access to String::length() field.
static FieldAccess ForStringLength();
......
......@@ -408,6 +408,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node, Node** effect,
case IrOpcode::kObjectIsUndetectable:
state = LowerObjectIsUndetectable(node, *effect, *control);
break;
case IrOpcode::kStringFromCharCode:
state = LowerStringFromCharCode(node, *effect, *control);
break;
default:
return false;
}
......@@ -926,6 +929,125 @@ EffectControlLinearizer::LowerObjectIsUndetectable(Node* node, Node* effect,
return ValueEffectControl(value, effect, control);
}
EffectControlLinearizer::ValueEffectControl
EffectControlLinearizer::LowerStringFromCharCode(Node* node, Node* effect,
Node* control) {
Node* value = node->InputAt(0);
// Compute the character code.
Node* code =
graph()->NewNode(machine()->Word32And(), value,
jsgraph()->Int32Constant(String::kMaxUtf16CodeUnit));
// Check if the {code} is a one-byte char code.
Node* check0 =
graph()->NewNode(machine()->Int32LessThanOrEqual(), code,
jsgraph()->Int32Constant(String::kMaxOneByteCharCode));
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control);
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* etrue0 = effect;
Node* vtrue0;
{
// Load the isolate wide single character string cache.
Node* cache =
jsgraph()->HeapConstant(factory()->single_character_string_cache());
// Compute the {cache} index for {code}.
Node* index =
machine()->Is32() ? code : graph()->NewNode(
machine()->ChangeUint32ToUint64(), code);
// Check if we have an entry for the {code} in the single character string
// cache already.
Node* entry = etrue0 = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), cache,
index, etrue0, if_true0);
Node* check1 = graph()->NewNode(machine()->WordEqual(), entry,
jsgraph()->UndefinedConstant());
Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check1, if_true0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* etrue1 = etrue0;
Node* vtrue1;
{
// Allocate a new SeqOneByteString for {code}.
vtrue1 = etrue1 = graph()->NewNode(
simplified()->Allocate(NOT_TENURED),
jsgraph()->Int32Constant(SeqOneByteString::SizeFor(1)), etrue1,
if_true1);
etrue1 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForMap()), vtrue1,
jsgraph()->HeapConstant(factory()->one_byte_string_map()), etrue1,
if_true1);
etrue1 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForNameHashField()), vtrue1,
jsgraph()->IntPtrConstant(Name::kEmptyHashField), etrue1, if_true1);
etrue1 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForStringLength()), vtrue1,
jsgraph()->SmiConstant(1), etrue1, if_true1);
etrue1 = graph()->NewNode(
machine()->Store(StoreRepresentation(MachineRepresentation::kWord8,
kNoWriteBarrier)),
vtrue1, jsgraph()->IntPtrConstant(SeqOneByteString::kHeaderSize -
kHeapObjectTag),
code, etrue1, if_true1);
// Remember it in the {cache}.
etrue1 = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement()),
cache, index, vtrue1, etrue1, if_true1);
}
// Use the {entry} from the {cache}.
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1);
Node* efalse1 = etrue0;
Node* vfalse1 = entry;
if_true0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
etrue0 =
graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_true0);
vtrue0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue1, vfalse1, if_true0);
}
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0);
Node* efalse0 = effect;
Node* vfalse0;
{
// Allocate a new SeqTwoByteString for {code}.
vfalse0 = efalse0 =
graph()->NewNode(simplified()->Allocate(NOT_TENURED),
jsgraph()->Int32Constant(SeqTwoByteString::SizeFor(1)),
efalse0, if_false0);
efalse0 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForMap()), vfalse0,
jsgraph()->HeapConstant(factory()->string_map()), efalse0, if_false0);
efalse0 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForNameHashField()), vfalse0,
jsgraph()->IntPtrConstant(Name::kEmptyHashField), efalse0, if_false0);
efalse0 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForStringLength()), vfalse0,
jsgraph()->SmiConstant(1), efalse0, if_false0);
efalse0 = graph()->NewNode(
machine()->Store(StoreRepresentation(MachineRepresentation::kWord16,
kNoWriteBarrier)),
vfalse0, jsgraph()->IntPtrConstant(SeqTwoByteString::kHeaderSize -
kHeapObjectTag),
code, efalse0, if_false0);
}
control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue0, vfalse0, control);
return ValueEffectControl(value, effect, control);
}
EffectControlLinearizer::ValueEffectControl
EffectControlLinearizer::AllocateHeapNumberWithValue(Node* value, Node* effect,
Node* control) {
......@@ -987,6 +1109,14 @@ Node* EffectControlLinearizer::SmiShiftBitsConstant() {
return jsgraph()->IntPtrConstant(kSmiShiftSize + kSmiTagSize);
}
Factory* EffectControlLinearizer::factory() const {
return isolate()->factory();
}
Isolate* EffectControlLinearizer::isolate() const {
return jsgraph()->isolate();
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -77,6 +77,8 @@ class EffectControlLinearizer {
Node* control);
ValueEffectControl LowerObjectIsUndetectable(Node* node, Node* effect,
Node* control);
ValueEffectControl LowerStringFromCharCode(Node* node, Node* effect,
Node* control);
ValueEffectControl AllocateHeapNumberWithValue(Node* node, Node* effect,
Node* control);
......@@ -90,6 +92,8 @@ class EffectControlLinearizer {
Node* SmiMaxValueConstant();
Node* SmiShiftBitsConstant();
Factory* factory() const;
Isolate* isolate() const;
JSGraph* jsgraph() const { return js_graph_; }
Graph* graph() const;
Schedule* schedule() const { return schedule_; }
......
......@@ -216,6 +216,18 @@ Reduction JSBuiltinReducer::ReduceMathTrunc(Node* node) {
return NoChange();
}
// ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits )
Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) {
JSCallReduction r(node);
if (r.InputsMatchOne(Type::Number())) {
// String.fromCharCode(a:number) -> StringFromCharCode(a)
Node* value =
graph()->NewNode(simplified()->StringFromCharCode(), r.left());
return Replace(value);
}
return NoChange();
}
Reduction JSBuiltinReducer::Reduce(Node* node) {
Reduction reduction = NoChange();
JSCallReduction r(node);
......@@ -250,6 +262,9 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
case kMathTrunc:
reduction = ReduceMathTrunc(node);
break;
case kStringFromCharCode:
reduction = ReduceStringFromCharCode(node);
break;
default:
break;
}
......
......@@ -40,6 +40,7 @@ class JSBuiltinReducer final : public AdvancedReducer {
Reduction ReduceMathRound(Node* node);
Reduction ReduceMathSqrt(Node* node);
Reduction ReduceMathTrunc(Node* node);
Reduction ReduceStringFromCharCode(Node* node);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
......
......@@ -197,6 +197,7 @@
V(NumberToInt32) \
V(NumberToUint32) \
V(NumberIsHoleNaN) \
V(StringFromCharCode) \
V(StringToNumber) \
V(ChangeTaggedSignedToInt32) \
V(ChangeTaggedToInt32) \
......
......@@ -1420,7 +1420,7 @@ bool PipelineImpl::CreateGraph() {
// Run early optimization pass.
Run<EarlyOptimizationPhase>();
RunPrintAndVerify("Early optimized");
RunPrintAndVerify("Early optimized", true);
}
#ifdef DEBUG
......
......@@ -1170,6 +1170,11 @@ class RepresentationSelector {
}
break;
}
case IrOpcode::kStringFromCharCode: {
VisitUnop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kTagged);
break;
}
case IrOpcode::kStringToNumber: {
VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kTagged);
if (lower()) {
......
......@@ -203,6 +203,7 @@ Type* TypeOf(const Operator* op) {
V(NumberToInt32, Operator::kNoProperties, 1) \
V(NumberToUint32, Operator::kNoProperties, 1) \
V(NumberIsHoleNaN, Operator::kNoProperties, 1) \
V(StringFromCharCode, Operator::kNoProperties, 1) \
V(StringToNumber, Operator::kNoProperties, 1) \
V(ChangeTaggedSignedToInt32, Operator::kNoProperties, 1) \
V(ChangeTaggedToInt32, Operator::kNoProperties, 1) \
......
......@@ -162,6 +162,7 @@ class SimplifiedOperatorBuilder final : public ZoneObject {
const Operator* StringEqual();
const Operator* StringLessThan();
const Operator* StringLessThanOrEqual();
const Operator* StringFromCharCode();
const Operator* StringToNumber();
const Operator* ChangeTaggedSignedToInt32();
......
......@@ -272,6 +272,7 @@ class Typer::Visitor : public Reducer {
static Type* JSCallFunctionTyper(Type*, Typer*);
static Type* ReferenceEqualTyper(Type*, Type*, Typer*);
static Type* StringFromCharCodeTyper(Type*, Typer*);
Reduction UpdateType(Node* node, Type* current) {
if (NodeProperties::IsTyped(node)) {
......@@ -1814,6 +1815,23 @@ Type* Typer::Visitor::TypeStringLessThanOrEqual(Node* node) {
return Type::Boolean();
}
Type* Typer::Visitor::StringFromCharCodeTyper(Type* type, Typer* t) {
type = NumberToUint32(ToNumber(type, t), t);
Factory* f = t->isolate()->factory();
double min = type->Min();
double max = type->Max();
if (min == max) {
uint32_t code = static_cast<uint32_t>(min) & String::kMaxUtf16CodeUnitU;
Handle<String> string = f->LookupSingleCharacterStringFromCode(code);
return Type::Constant(string, t->zone());
}
return Type::String();
}
Type* Typer::Visitor::TypeStringFromCharCode(Node* node) {
return TypeUnaryOp(node, StringFromCharCodeTyper);
}
Type* Typer::Visitor::TypeStringToNumber(Node* node) {
return TypeUnaryOp(node, ToNumber);
}
......
......@@ -761,6 +761,11 @@ void Verifier::Visitor::Check(Node* node) {
CheckValueInputIs(node, 1, Type::String());
CheckUpperIs(node, Type::Boolean());
break;
case IrOpcode::kStringFromCharCode:
// Number -> String
CheckValueInputIs(node, 0, Type::Number());
CheckUpperIs(node, Type::String());
break;
case IrOpcode::kStringToNumber:
// String -> Number
CheckValueInputIs(node, 0, Type::String());
......
......@@ -49,6 +49,19 @@ class JSBuiltinReducerTest : public TypedGraphTest {
return HeapConstant(f);
}
Node* StringFunction(const char* name) {
Handle<Object> m =
JSObject::GetProperty(
isolate()->global_object(),
isolate()->factory()->NewStringFromAsciiChecked("String"))
.ToHandleChecked();
Handle<JSFunction> f = Handle<JSFunction>::cast(
Object::GetProperty(
m, isolate()->factory()->NewStringFromAsciiChecked(name))
.ToHandleChecked());
return HeapConstant(f);
}
JSOperatorBuilder* javascript() { return &javascript_; }
private:
......@@ -189,6 +202,28 @@ TEST_F(JSBuiltinReducerTest, MathFround) {
}
}
// -----------------------------------------------------------------------------
// String.fromCharCode
TEST_F(JSBuiltinReducerTest, StringFromCharCode) {
Node* function = StringFunction("fromCharCode");
Node* effect = graph()->start();
Node* control = graph()->start();
Node* context = UndefinedConstant();
Node* frame_state = graph()->start();
TRACED_FOREACH(Type*, t0, kNumberTypes) {
Node* p0 = Parameter(t0, 0);
Node* call = graph()->NewNode(javascript()->CallFunction(3), function,
UndefinedConstant(), p0, context, frame_state,
frame_state, effect, control);
Reduction r = Reduce(call);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsStringFromCharCode(p0));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -2260,6 +2260,7 @@ IS_UNOP_MATCHER(NumberToInt32)
IS_UNOP_MATCHER(NumberToUint32)
IS_UNOP_MATCHER(ObjectIsReceiver)
IS_UNOP_MATCHER(ObjectIsSmi)
IS_UNOP_MATCHER(StringFromCharCode)
IS_UNOP_MATCHER(Word32Clz)
IS_UNOP_MATCHER(Word32Ctz)
IS_UNOP_MATCHER(Word32Popcnt)
......
......@@ -211,6 +211,7 @@ Matcher<Node*> IsNumberShiftRightLogical(const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
Matcher<Node*> IsNumberImul(const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
Matcher<Node*> IsStringFromCharCode(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment