Commit 8bd0bd52 authored by jarin's avatar jarin Committed by Commit bot

[turbofan] Make the representation type component independent of the semantic component.

R=rossberg@chromium.org
BUG=

Review URL: https://codereview.chromium.org/904863002

Cr-Commit-Position: refs/heads/master@{#26621}
parent 30c71859
...@@ -436,14 +436,14 @@ void Typer::Decorator::Decorate(Node* node, bool incomplete) { ...@@ -436,14 +436,14 @@ void Typer::Decorator::Decorate(Node* node, bool incomplete) {
Bounds Typer::Visitor::TypeUnaryOp(Node* node, UnaryTyperFun f) { Bounds Typer::Visitor::TypeUnaryOp(Node* node, UnaryTyperFun f) {
Bounds input = Operand(node, 0); Bounds input = Operand(node, 0);
Type* upper = input.upper->Is(Type::None()) Type* upper =
? Type::None() input.upper->IsInhabited() ? f(input.upper, typer_) : Type::None();
: f(input.upper, typer_); Type* lower = input.lower->IsInhabited()
Type* lower = input.lower->Is(Type::None()) ? ((input.lower == input.upper || upper->IsConstant())
? Type::None() ? upper // TODO(neis): Extend this to Range(x,x),
: (input.lower == input.upper || upper->IsConstant()) // NaN, MinusZero, ...?
? upper // TODO(neis): Extend this to Range(x,x), NaN, MinusZero, ...? : f(input.lower, typer_))
: f(input.lower, typer_); : Type::None();
// TODO(neis): Figure out what to do with lower bound. // TODO(neis): Figure out what to do with lower bound.
return Bounds(lower, upper); return Bounds(lower, upper);
} }
...@@ -452,15 +452,16 @@ Bounds Typer::Visitor::TypeUnaryOp(Node* node, UnaryTyperFun f) { ...@@ -452,15 +452,16 @@ Bounds Typer::Visitor::TypeUnaryOp(Node* node, UnaryTyperFun f) {
Bounds Typer::Visitor::TypeBinaryOp(Node* node, BinaryTyperFun f) { Bounds Typer::Visitor::TypeBinaryOp(Node* node, BinaryTyperFun f) {
Bounds left = Operand(node, 0); Bounds left = Operand(node, 0);
Bounds right = Operand(node, 1); Bounds right = Operand(node, 1);
Type* upper = left.upper->Is(Type::None()) || right.upper->Is(Type::None()) Type* upper = left.upper->IsInhabited() && right.upper->IsInhabited()
? Type::None() ? f(left.upper, right.upper, typer_)
: f(left.upper, right.upper, typer_); : Type::None();
Type* lower = left.lower->Is(Type::None()) || right.lower->Is(Type::None()) Type* lower =
? Type::None() left.lower->IsInhabited() && right.lower->IsInhabited()
: ((left.lower == left.upper && right.lower == right.upper) || ? (((left.lower == left.upper && right.lower == right.upper) ||
upper->IsConstant()) upper->IsConstant())
? upper ? upper
: f(left.lower, right.lower, typer_); : f(left.lower, right.lower, typer_))
: Type::None();
// TODO(neis): Figure out what to do with lower bound. // TODO(neis): Figure out what to do with lower bound.
return Bounds(lower, upper); return Bounds(lower, upper);
} }
......
...@@ -15,7 +15,7 @@ namespace internal { ...@@ -15,7 +15,7 @@ namespace internal {
template <class T> template <class T>
HType HType::FromType(typename T::TypeHandle type) { HType HType::FromType(typename T::TypeHandle type) {
if (T::Any()->Is(type)) return HType::Any(); if (T::Any()->Is(type)) return HType::Any();
if (type->Is(T::None())) return HType::None(); if (!type->IsInhabited()) return HType::None();
if (type->Is(T::SignedSmall())) return HType::Smi(); if (type->Is(T::SignedSmall())) return HType::Smi();
if (type->Is(T::Number())) return HType::TaggedNumber(); if (type->Is(T::Number())) return HType::TaggedNumber();
if (type->Is(T::Null())) return HType::Null(); if (type->Is(T::Null())) return HType::Null();
......
...@@ -28,7 +28,6 @@ typename TypeImpl<Config>::Limits TypeImpl<Config>::Intersect( ...@@ -28,7 +28,6 @@ typename TypeImpl<Config>::Limits TypeImpl<Config>::Intersect(
Limits result(lhs); Limits result(lhs);
if (lhs.min < rhs.min) result.min = rhs.min; if (lhs.min < rhs.min) result.min = rhs.min;
if (lhs.max > rhs.max) result.max = rhs.max; if (lhs.max > rhs.max) result.max = rhs.max;
result.representation = lhs.representation & rhs.representation;
return result; return result;
} }
...@@ -43,10 +42,11 @@ template <class Config> ...@@ -43,10 +42,11 @@ template <class Config>
typename TypeImpl<Config>::Limits TypeImpl<Config>::Union(Limits lhs, typename TypeImpl<Config>::Limits TypeImpl<Config>::Union(Limits lhs,
Limits rhs) { Limits rhs) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
if (IsEmpty(lhs)) return rhs;
if (IsEmpty(rhs)) return lhs;
Limits result(lhs); Limits result(lhs);
if (lhs.min > rhs.min) result.min = rhs.min; if (lhs.min > rhs.min) result.min = rhs.min;
if (lhs.max < rhs.max) result.max = rhs.max; if (lhs.max < rhs.max) result.max = rhs.max;
result.representation = lhs.representation | rhs.representation;
return result; return result;
} }
...@@ -66,8 +66,7 @@ bool TypeImpl<Config>::Contains( ...@@ -66,8 +66,7 @@ bool TypeImpl<Config>::Contains(
typename TypeImpl<Config>::RangeType* lhs, typename TypeImpl<Config>::RangeType* lhs,
typename TypeImpl<Config>::RangeType* rhs) { typename TypeImpl<Config>::RangeType* rhs) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
return BitsetType::Is(rhs->Bound(), lhs->Bound()) && return lhs->Min() <= rhs->Min() && rhs->Max() <= lhs->Max();
lhs->Min() <= rhs->Min() && rhs->Max() <= lhs->Max();
} }
...@@ -76,7 +75,6 @@ bool TypeImpl<Config>::Contains(typename TypeImpl<Config>::RangeType* lhs, ...@@ -76,7 +75,6 @@ bool TypeImpl<Config>::Contains(typename TypeImpl<Config>::RangeType* lhs,
typename TypeImpl<Config>::ConstantType* rhs) { typename TypeImpl<Config>::ConstantType* rhs) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
return IsInteger(*rhs->Value()) && return IsInteger(*rhs->Value()) &&
BitsetType::Is(rhs->Bound()->AsBitset(), lhs->Bound()) &&
lhs->Min() <= rhs->Value()->Number() && lhs->Min() <= rhs->Value()->Number() &&
rhs->Value()->Number() <= lhs->Max(); rhs->Value()->Number() <= lhs->Max();
} }
...@@ -87,7 +85,6 @@ bool TypeImpl<Config>::Contains( ...@@ -87,7 +85,6 @@ bool TypeImpl<Config>::Contains(
typename TypeImpl<Config>::RangeType* range, i::Object* val) { typename TypeImpl<Config>::RangeType* range, i::Object* val) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
return IsInteger(val) && return IsInteger(val) &&
BitsetType::Is(BitsetType::Lub(val), range->Bound()) &&
range->Min() <= val->Number() && val->Number() <= range->Max(); range->Min() <= val->Number() && val->Number() <= range->Max();
} }
...@@ -97,7 +94,7 @@ bool TypeImpl<Config>::Contains( ...@@ -97,7 +94,7 @@ bool TypeImpl<Config>::Contains(
template<class Config> template<class Config>
double TypeImpl<Config>::Min() { double TypeImpl<Config>::Min() {
DCHECK(this->Is(Number())); DCHECK(this->SemanticIs(Number()));
if (this->IsBitset()) return BitsetType::Min(this->AsBitset()); if (this->IsBitset()) return BitsetType::Min(this->AsBitset());
if (this->IsUnion()) { if (this->IsUnion()) {
double min = +V8_INFINITY; double min = +V8_INFINITY;
...@@ -115,7 +112,7 @@ double TypeImpl<Config>::Min() { ...@@ -115,7 +112,7 @@ double TypeImpl<Config>::Min() {
template<class Config> template<class Config>
double TypeImpl<Config>::Max() { double TypeImpl<Config>::Max() {
DCHECK(this->Is(Number())); DCHECK(this->SemanticIs(Number()));
if (this->IsBitset()) return BitsetType::Max(this->AsBitset()); if (this->IsBitset()) return BitsetType::Max(this->AsBitset());
if (this->IsUnion()) { if (this->IsUnion()) {
double max = -V8_INFINITY; double max = -V8_INFINITY;
...@@ -140,23 +137,19 @@ template<class Config> ...@@ -140,23 +137,19 @@ template<class Config>
typename TypeImpl<Config>::bitset typename TypeImpl<Config>::bitset
TypeImpl<Config>::BitsetType::Glb(TypeImpl* type) { TypeImpl<Config>::BitsetType::Glb(TypeImpl* type) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
// Fast case.
if (type->IsBitset()) { if (type->IsBitset()) {
return type->AsBitset(); return type->AsBitset();
} else if (type->IsUnion()) { } else if (type->IsUnion()) {
SLOW_DCHECK(type->AsUnion()->Wellformed()); SLOW_DCHECK(type->AsUnion()->Wellformed());
return type->AsUnion()->Get(0)->BitsetGlb() | return type->AsUnion()->Get(0)->BitsetGlb() |
type->AsUnion()->Get(1)->BitsetGlb(); // Shortcut. SEMANTIC(type->AsUnion()->Get(1)->BitsetGlb()); // Shortcut.
} else if (type->IsRange()) { } else if (type->IsRange()) {
bitset glb = SEMANTIC( bitset glb = SEMANTIC(
BitsetType::Glb(type->AsRange()->Min(), type->AsRange()->Max())); BitsetType::Glb(type->AsRange()->Min(), type->AsRange()->Max()));
if (glb == 0) {
return kNone;
} else {
return glb | REPRESENTATION(type->BitsetLub()); return glb | REPRESENTATION(type->BitsetLub());
}
} else { } else {
// (The remaining BitsetGlb's are None anyway). return type->Representation();
return kNone;
} }
} }
...@@ -168,9 +161,12 @@ TypeImpl<Config>::BitsetType::Lub(TypeImpl* type) { ...@@ -168,9 +161,12 @@ TypeImpl<Config>::BitsetType::Lub(TypeImpl* type) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
if (type->IsBitset()) return type->AsBitset(); if (type->IsBitset()) return type->AsBitset();
if (type->IsUnion()) { if (type->IsUnion()) {
int bitset = kNone; // Take the representation from the first element, which is always
// a bitset.
int bitset = type->AsUnion()->Get(0)->BitsetLub();
for (int i = 0, n = type->AsUnion()->Length(); i < n; ++i) { for (int i = 0, n = type->AsUnion()->Length(); i < n; ++i) {
bitset |= type->AsUnion()->Get(i)->BitsetLub(); // Other elements only contribute their semantic part.
bitset |= SEMANTIC(type->AsUnion()->Get(i)->BitsetLub());
} }
return bitset; return bitset;
} }
...@@ -407,7 +403,7 @@ typename TypeImpl<Config>::bitset TypeImpl<Config>::BitsetType::Glb( ...@@ -407,7 +403,7 @@ typename TypeImpl<Config>::bitset TypeImpl<Config>::BitsetType::Glb(
template <class Config> template <class Config>
double TypeImpl<Config>::BitsetType::Min(bitset bits) { double TypeImpl<Config>::BitsetType::Min(bitset bits) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
DCHECK(Is(bits, kNumber)); DCHECK(Is(SEMANTIC(bits), kNumber));
const Boundary* mins = Boundaries(); const Boundary* mins = Boundaries();
bool mz = SEMANTIC(bits & kMinusZero); bool mz = SEMANTIC(bits & kMinusZero);
for (size_t i = 0; i < BoundariesSize(); ++i) { for (size_t i = 0; i < BoundariesSize(); ++i) {
...@@ -423,7 +419,7 @@ double TypeImpl<Config>::BitsetType::Min(bitset bits) { ...@@ -423,7 +419,7 @@ double TypeImpl<Config>::BitsetType::Min(bitset bits) {
template<class Config> template<class Config>
double TypeImpl<Config>::BitsetType::Max(bitset bits) { double TypeImpl<Config>::BitsetType::Max(bitset bits) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
DCHECK(Is(bits, kNumber)); DCHECK(Is(SEMANTIC(bits), kNumber));
const Boundary* mins = Boundaries(); const Boundary* mins = Boundaries();
bool mz = SEMANTIC(bits & kMinusZero); bool mz = SEMANTIC(bits & kMinusZero);
if (BitsetType::Is(SEMANTIC(mins[BoundariesSize() - 1].bits), bits)) { if (BitsetType::Is(SEMANTIC(mins[BoundariesSize() - 1].bits), bits)) {
...@@ -482,22 +478,55 @@ bool TypeImpl<Config>::SimplyEquals(TypeImpl* that) { ...@@ -482,22 +478,55 @@ bool TypeImpl<Config>::SimplyEquals(TypeImpl* that) {
} }
template <class Config>
typename TypeImpl<Config>::bitset TypeImpl<Config>::Representation() {
return REPRESENTATION(this->BitsetLub());
}
// Check if [this] <= [that]. // Check if [this] <= [that].
template<class Config> template<class Config>
bool TypeImpl<Config>::SlowIs(TypeImpl* that) { bool TypeImpl<Config>::SlowIs(TypeImpl* that) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
// Fast bitset cases
if (that->IsBitset()) { if (that->IsBitset()) {
return BitsetType::Is(this->BitsetLub(), that->AsBitset()); return BitsetType::Is(this->BitsetLub(), that->AsBitset());
} }
if (this->IsBitset()) { if (this->IsBitset()) {
return BitsetType::Is(this->AsBitset(), that->BitsetGlb()); return BitsetType::Is(this->AsBitset(), that->BitsetGlb());
} }
// Check the representations.
if (!BitsetType::Is(Representation(), that->Representation())) {
return false;
}
// Check the semantic part.
return SemanticIs(that);
}
// Check if SEMANTIC([this]) <= SEMANTIC([that]). The result of the method
// should be independent of the representation axis of the types.
template <class Config>
bool TypeImpl<Config>::SemanticIs(TypeImpl* that) {
DisallowHeapAllocation no_allocation;
if (this == that) return true;
if (that->IsBitset()) {
return BitsetType::Is(SEMANTIC(this->BitsetLub()), that->AsBitset());
}
if (this->IsBitset()) {
return BitsetType::Is(SEMANTIC(this->AsBitset()), that->BitsetGlb());
}
// (T1 \/ ... \/ Tn) <= T if (T1 <= T) /\ ... /\ (Tn <= T) // (T1 \/ ... \/ Tn) <= T if (T1 <= T) /\ ... /\ (Tn <= T)
if (this->IsUnion()) { if (this->IsUnion()) {
for (int i = 0, n = this->AsUnion()->Length(); i < n; ++i) { for (int i = 0, n = this->AsUnion()->Length(); i < n; ++i) {
if (!this->AsUnion()->Get(i)->Is(that)) return false; if (!this->AsUnion()->Get(i)->SemanticIs(that)) return false;
} }
return true; return true;
} }
...@@ -505,7 +534,7 @@ bool TypeImpl<Config>::SlowIs(TypeImpl* that) { ...@@ -505,7 +534,7 @@ bool TypeImpl<Config>::SlowIs(TypeImpl* that) {
// T <= (T1 \/ ... \/ Tn) if (T <= T1) \/ ... \/ (T <= Tn) // T <= (T1 \/ ... \/ Tn) if (T <= T1) \/ ... \/ (T <= Tn)
if (that->IsUnion()) { if (that->IsUnion()) {
for (int i = 0, n = that->AsUnion()->Length(); i < n; ++i) { for (int i = 0, n = that->AsUnion()->Length(); i < n; ++i) {
if (this->Is(that->AsUnion()->Get(i))) return true; if (this->SemanticIs(that->AsUnion()->Get(i)->unhandle())) return true;
if (i > 1 && this->IsRange()) return false; // Shortcut. if (i > 1 && this->IsRange()) return false; // Shortcut.
} }
return false; return false;
...@@ -558,10 +587,22 @@ template<class Config> ...@@ -558,10 +587,22 @@ template<class Config>
bool TypeImpl<Config>::Maybe(TypeImpl* that) { bool TypeImpl<Config>::Maybe(TypeImpl* that) {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
// Take care of the representation part (and also approximate
// the semantic part).
if (!BitsetType::IsInhabited(this->BitsetLub() & that->BitsetLub()))
return false;
return SemanticMaybe(that);
}
template <class Config>
bool TypeImpl<Config>::SemanticMaybe(TypeImpl* that) {
DisallowHeapAllocation no_allocation;
// (T1 \/ ... \/ Tn) overlaps T if (T1 overlaps T) \/ ... \/ (Tn overlaps T) // (T1 \/ ... \/ Tn) overlaps T if (T1 overlaps T) \/ ... \/ (Tn overlaps T)
if (this->IsUnion()) { if (this->IsUnion()) {
for (int i = 0, n = this->AsUnion()->Length(); i < n; ++i) { for (int i = 0, n = this->AsUnion()->Length(); i < n; ++i) {
if (this->AsUnion()->Get(i)->Maybe(that)) return true; if (this->AsUnion()->Get(i)->SemanticMaybe(that)) return true;
} }
return false; return false;
} }
...@@ -569,12 +610,12 @@ bool TypeImpl<Config>::Maybe(TypeImpl* that) { ...@@ -569,12 +610,12 @@ bool TypeImpl<Config>::Maybe(TypeImpl* that) {
// T overlaps (T1 \/ ... \/ Tn) if (T overlaps T1) \/ ... \/ (T overlaps Tn) // T overlaps (T1 \/ ... \/ Tn) if (T overlaps T1) \/ ... \/ (T overlaps Tn)
if (that->IsUnion()) { if (that->IsUnion()) {
for (int i = 0, n = that->AsUnion()->Length(); i < n; ++i) { for (int i = 0, n = that->AsUnion()->Length(); i < n; ++i) {
if (this->Maybe(that->AsUnion()->Get(i))) return true; if (this->SemanticMaybe(that->AsUnion()->Get(i)->unhandle())) return true;
} }
return false; return false;
} }
if (!BitsetType::IsInhabited(this->BitsetLub() & that->BitsetLub())) if (!BitsetType::SemanticIsInhabited(this->BitsetLub() & that->BitsetLub()))
return false; return false;
if (this->IsBitset() && that->IsBitset()) return true; if (this->IsBitset() && that->IsBitset()) return true;
...@@ -599,7 +640,7 @@ bool TypeImpl<Config>::Maybe(TypeImpl* that) { ...@@ -599,7 +640,7 @@ bool TypeImpl<Config>::Maybe(TypeImpl* that) {
} }
} }
if (that->IsRange()) { if (that->IsRange()) {
return that->Maybe(this); // This case is handled above. return that->SemanticMaybe(this); // This case is handled above.
} }
if (this->IsBitset() || that->IsBitset()) return true; if (this->IsBitset() || that->IsBitset()) return true;
...@@ -639,28 +680,27 @@ bool TypeImpl<Config>::UnionType::Wellformed() { ...@@ -639,28 +680,27 @@ bool TypeImpl<Config>::UnionType::Wellformed() {
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
// This checks the invariants of the union representation: // This checks the invariants of the union representation:
// 1. There are at least two elements. // 1. There are at least two elements.
// 2. At most one element is a bitset, and it must be the first one. // 2. The first element is a bitset, no other element is a bitset.
// 3. At most one element is a range, and it must be the second one // 3. At most one element is a range, and it must be the second one.
// (even when the first element is not a bitset).
// 4. No element is itself a union. // 4. No element is itself a union.
// 5. No element is a subtype of any other. // 5. No element (except the bitset) is a subtype of any other.
// 6. If there is a range, then the bitset type does not contain // 6. If there is a range, then the bitset type does not contain
// plain number bits. // plain number bits.
DCHECK(this->Length() >= 2); // (1) DCHECK(this->Length() >= 2); // (1)
DCHECK(this->Get(0)->IsBitset()); // (2a)
bitset number_bits = this->Get(0)->IsBitset()
? BitsetType::NumberBits(this->Get(0)->AsBitset()) : 0;
USE(number_bits);
for (int i = 0; i < this->Length(); ++i) { for (int i = 0; i < this->Length(); ++i) {
if (i != 0) DCHECK(!this->Get(i)->IsBitset()); // (2) if (i != 0) DCHECK(!this->Get(i)->IsBitset()); // (2b)
if (i != 1) DCHECK(!this->Get(i)->IsRange()); // (3) if (i != 1) DCHECK(!this->Get(i)->IsRange()); // (3)
DCHECK(!this->Get(i)->IsUnion()); // (4) DCHECK(!this->Get(i)->IsUnion()); // (4)
for (int j = 0; j < this->Length(); ++j) { for (int j = 0; j < this->Length(); ++j) {
if (i != j) DCHECK(!this->Get(i)->Is(this->Get(j))); // (5) if (i != j && i != 0)
DCHECK(!this->Get(i)->SemanticIs(this->Get(j)->unhandle())); // (5)
} }
} }
DCHECK(!this->Get(1)->IsRange() || (number_bits == 0)); // (6) DCHECK(!this->Get(1)->IsRange() ||
(BitsetType::NumberBits(this->Get(0)->AsBitset()) ==
BitsetType::kNone)); // (6)
return true; return true;
} }
...@@ -679,12 +719,10 @@ static bool AddIsSafe(int x, int y) { ...@@ -679,12 +719,10 @@ static bool AddIsSafe(int x, int y) {
template<class Config> template<class Config>
typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect( typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect(
TypeHandle type1, TypeHandle type2, Region* region) { TypeHandle type1, TypeHandle type2, Region* region) {
bitset bits = type1->BitsetGlb() & type2->BitsetGlb();
if (!BitsetType::IsInhabited(bits)) bits = BitsetType::kNone;
// Fast case: bit sets. // Fast case: bit sets.
if (type1->IsBitset() && type2->IsBitset()) { if (type1->IsBitset() && type2->IsBitset()) {
return BitsetType::New(bits, region); return BitsetType::New(type1->AsBitset() & type2->AsBitset(), region);
} }
// Fast case: top or bottom types. // Fast case: top or bottom types.
...@@ -696,6 +734,26 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect( ...@@ -696,6 +734,26 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect(
if (type2->Is(type1)) return type2; if (type2->Is(type1)) return type2;
// Slow case: create union. // Slow case: create union.
// Figure out the representation of the result first.
// The rest of the method should not change this representation and
// it should not make any decisions based on representations (i.e.,
// it should only use the semantic part of types).
const bitset representation =
type1->Representation() & type2->Representation();
// Semantic subtyping check - this is needed for consistency with the
// semi-fast case above - we should behave the same way regardless of
// representations. Intersection with a universal bitset should only update
// the representations.
if (type1->SemanticIs(type2->unhandle())) {
type2 = Any(region);
} else if (type2->SemanticIs(type1->unhandle())) {
type1 = Any(region);
}
bitset bits =
SEMANTIC(type1->BitsetGlb() & type2->BitsetGlb()) | representation;
int size1 = type1->IsUnion() ? type1->AsUnion()->Length() : 1; int size1 = type1->IsUnion() ? type1->AsUnion()->Length() : 1;
int size2 = type2->IsUnion() ? type2->AsUnion()->Length() : 1; int size2 = type2->IsUnion() ? type2->AsUnion()->Length() : 1;
if (!AddIsSafe(size1, size2)) return Any(region); if (!AddIsSafe(size1, size2)) return Any(region);
...@@ -707,8 +765,6 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect( ...@@ -707,8 +765,6 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect(
// Deal with bitsets. // Deal with bitsets.
result->Set(size++, BitsetType::New(bits, region)); result->Set(size++, BitsetType::New(bits, region));
// Insert a placeholder for the range.
result->Set(size++, None(region));
Limits lims = Limits::Empty(region); Limits lims = Limits::Empty(region);
size = IntersectAux(type1, type2, result, size, &lims, region); size = IntersectAux(type1, type2, result, size, &lims, region);
...@@ -716,14 +772,12 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect( ...@@ -716,14 +772,12 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect(
// If the range is not empty, then insert it into the union and // If the range is not empty, then insert it into the union and
// remove the number bits from the bitset. // remove the number bits from the bitset.
if (!IsEmpty(lims)) { if (!IsEmpty(lims)) {
size = UpdateRange(RangeType::New(lims, region), result, size, region); size = UpdateRange(RangeType::New(lims, representation, region), result,
size, region);
// Remove the number bits. // Remove the number bits.
bitset number_bits = BitsetType::NumberBits(bits); bitset number_bits = BitsetType::NumberBits(bits);
bits &= ~number_bits; bits &= ~number_bits;
if (SEMANTIC(bits) == BitsetType::kNone) {
bits = BitsetType::kNone;
}
result->Set(0, BitsetType::New(bits, region)); result->Set(0, BitsetType::New(bits, region));
} }
return NormalizeUnion(result, size); return NormalizeUnion(result, size);
...@@ -733,18 +787,17 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect( ...@@ -733,18 +787,17 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Intersect(
template<class Config> template<class Config>
int TypeImpl<Config>::UpdateRange( int TypeImpl<Config>::UpdateRange(
RangeHandle range, UnionHandle result, int size, Region* region) { RangeHandle range, UnionHandle result, int size, Region* region) {
TypeHandle old_range = result->Get(1); if (size == 1) {
DCHECK(old_range->IsRange() || old_range->IsNone()); result->Set(size++, range);
if (range->Is(old_range)) return size; } else {
if (!old_range->Is(range->unhandle())) { // Make space for the range.
range = RangeType::New( result->Set(size++, result->Get(1));
Union(Limits(range->AsRange()), Limits(old_range->AsRange())), region);
}
result->Set(1, range); result->Set(1, range);
}
// Remove any components that just got subsumed. // Remove any components that just got subsumed.
for (int i = 2; i < size; ) { for (int i = 2; i < size; ) {
if (result->Get(i)->Is(range->unhandle())) { if (result->Get(i)->SemanticIs(range->unhandle())) {
result->Set(i, result->Get(--size)); result->Set(i, result->Get(--size));
} else { } else {
++i; ++i;
...@@ -757,15 +810,13 @@ int TypeImpl<Config>::UpdateRange( ...@@ -757,15 +810,13 @@ int TypeImpl<Config>::UpdateRange(
template <class Config> template <class Config>
typename TypeImpl<Config>::Limits TypeImpl<Config>::ToLimits(bitset bits, typename TypeImpl<Config>::Limits TypeImpl<Config>::ToLimits(bitset bits,
Region* region) { Region* region) {
bitset representation = REPRESENTATION(bits);
bitset number_bits = BitsetType::NumberBits(bits); bitset number_bits = BitsetType::NumberBits(bits);
if (representation == BitsetType::kNone && number_bits == BitsetType::kNone) { if (number_bits == BitsetType::kNone) {
return Limits::Empty(region); return Limits::Empty(region);
} }
return Limits(BitsetType::Min(number_bits), BitsetType::Max(number_bits), return Limits(BitsetType::Min(number_bits), BitsetType::Max(number_bits));
representation);
} }
...@@ -797,7 +848,7 @@ int TypeImpl<Config>::IntersectAux(TypeHandle lhs, TypeHandle rhs, ...@@ -797,7 +848,7 @@ int TypeImpl<Config>::IntersectAux(TypeHandle lhs, TypeHandle rhs,
return size; return size;
} }
if (!BitsetType::IsInhabited(lhs->BitsetLub() & rhs->BitsetLub())) { if (!BitsetType::SemanticIsInhabited(lhs->BitsetLub() & rhs->BitsetLub())) {
return size; return size;
} }
...@@ -857,9 +908,8 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset( ...@@ -857,9 +908,8 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset(
// If the range is contained within the bitset, return an empty range // If the range is contained within the bitset, return an empty range
// (but make sure we take the representation). // (but make sure we take the representation).
bitset range_lub = range->BitsetLub(); bitset range_lub = SEMANTIC(range->BitsetLub());
if (BitsetType::Is(BitsetType::NumberBits(range_lub), *bits)) { if (BitsetType::Is(BitsetType::NumberBits(range_lub), *bits)) {
*bits |= range_lub;
return None(region); return None(region);
} }
...@@ -870,19 +920,10 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset( ...@@ -870,19 +920,10 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset(
double range_min = range->Min(); double range_min = range->Min();
double range_max = range->Max(); double range_max = range->Max();
bitset range_representation = REPRESENTATION(range->BitsetLub());
bitset bits_representation = REPRESENTATION(*bits);
bitset representation =
(bits_representation | range_representation) & BitsetType::kNumber;
// Remove the number bits from the bitset, they would just confuse us now. // Remove the number bits from the bitset, they would just confuse us now.
*bits &= ~number_bits; *bits &= ~number_bits;
if (bits_representation == *bits) {
*bits = BitsetType::kNone;
}
if (representation == range_representation && range_min <= bitset_min && if (range_min <= bitset_min && range_max >= bitset_max) {
range_max >= bitset_max) {
// Bitset is contained within the range, just return the range. // Bitset is contained within the range, just return the range.
return range; return range;
} }
...@@ -894,14 +935,13 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset( ...@@ -894,14 +935,13 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeRangeAndBitset(
range_max = bitset_max; range_max = bitset_max;
} }
return RangeType::New(range_min, range_max, return RangeType::New(range_min, range_max,
BitsetType::New(representation, region), region); BitsetType::New(BitsetType::kNone, region), region);
} }
template<class Config> template<class Config>
typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union( typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union(
TypeHandle type1, TypeHandle type2, Region* region) { TypeHandle type1, TypeHandle type2, Region* region) {
// Fast case: bit sets. // Fast case: bit sets.
if (type1->IsBitset() && type2->IsBitset()) { if (type1->IsBitset() && type2->IsBitset()) {
return BitsetType::New(type1->AsBitset() | type2->AsBitset(), region); return BitsetType::New(type1->AsBitset() | type2->AsBitset(), region);
...@@ -915,6 +955,13 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union( ...@@ -915,6 +955,13 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union(
if (type1->Is(type2)) return type2; if (type1->Is(type2)) return type2;
if (type2->Is(type1)) return type1; if (type2->Is(type1)) return type1;
// Figure out the representation of the result.
// The rest of the method should not change this representation and
// it should make any decisions based on representations (i.e.,
// it should only use the semantic part of types).
const bitset representation =
type1->Representation() | type2->Representation();
// Slow case: create union. // Slow case: create union.
int size1 = type1->IsUnion() ? type1->AsUnion()->Length() : 1; int size1 = type1->IsUnion() ? type1->AsUnion()->Length() : 1;
int size2 = type2->IsUnion() ? type2->AsUnion()->Length() : 1; int size2 = type2->IsUnion() ? type2->AsUnion()->Length() : 1;
...@@ -926,7 +973,7 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union( ...@@ -926,7 +973,7 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union(
size = 0; size = 0;
// Compute the new bitset. // Compute the new bitset.
bitset new_bitset = type1->BitsetGlb() | type2->BitsetGlb(); bitset new_bitset = SEMANTIC(type1->BitsetGlb() | type2->BitsetGlb());
// Deal with ranges. // Deal with ranges.
TypeHandle range = None(region); TypeHandle range = None(region);
...@@ -934,16 +981,17 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union( ...@@ -934,16 +981,17 @@ typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Union(
RangeType* range2 = type2->GetRange(); RangeType* range2 = type2->GetRange();
if (range1 != NULL && range2 != NULL) { if (range1 != NULL && range2 != NULL) {
Limits lims = Union(Limits(range1), Limits(range2)); Limits lims = Union(Limits(range1), Limits(range2));
RangeHandle union_range = RangeType::New(lims, region); RangeHandle union_range = RangeType::New(lims, representation, region);
range = NormalizeRangeAndBitset(union_range, &new_bitset, region); range = NormalizeRangeAndBitset(union_range, &new_bitset, region);
} else if (range1 != NULL) { } else if (range1 != NULL) {
range = NormalizeRangeAndBitset(handle(range1), &new_bitset, region); range = NormalizeRangeAndBitset(handle(range1), &new_bitset, region);
} else if (range2 != NULL) { } else if (range2 != NULL) {
range = NormalizeRangeAndBitset(handle(range2), &new_bitset, region); range = NormalizeRangeAndBitset(handle(range2), &new_bitset, region);
} }
new_bitset = SEMANTIC(new_bitset) | representation;
TypeHandle bits = BitsetType::New(new_bitset, region); TypeHandle bits = BitsetType::New(new_bitset, region);
result->Set(size++, bits); result->Set(size++, bits);
result->Set(size++, range); if (!range->IsNone()) result->Set(size++, range);
size = AddToUnion(type1, result, size, region); size = AddToUnion(type1, result, size, region);
size = AddToUnion(type2, result, size, region); size = AddToUnion(type2, result, size, region);
...@@ -964,7 +1012,7 @@ int TypeImpl<Config>::AddToUnion( ...@@ -964,7 +1012,7 @@ int TypeImpl<Config>::AddToUnion(
return size; return size;
} }
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
if (type->Is(result->Get(i))) return size; if (type->SemanticIs(result->Get(i)->unhandle())) return size;
} }
result->Set(size++, type); result->Set(size++, type);
return size; return size;
...@@ -974,22 +1022,47 @@ int TypeImpl<Config>::AddToUnion( ...@@ -974,22 +1022,47 @@ int TypeImpl<Config>::AddToUnion(
template<class Config> template<class Config>
typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeUnion( typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::NormalizeUnion(
UnionHandle unioned, int size) { UnionHandle unioned, int size) {
DCHECK(size >= 2); DCHECK(size >= 1);
// If range is subsumed by bitset, use its place for a different type. DCHECK(unioned->Get(0)->IsBitset());
if (unioned->Get(1)->Is(unioned->Get(0))) { // If the union has just one element, return it.
unioned->Set(1, unioned->Get(--size)); if (size == 1) {
return unioned->Get(0);
}
bitset bits = unioned->Get(0)->AsBitset();
// If the union only consists of a range, we can get rid of the union.
if (size == 2 && SEMANTIC(bits) == BitsetType::kNone) {
bitset representation = REPRESENTATION(bits);
if (representation == unioned->Get(1)->Representation()) {
return unioned->Get(1);
} }
// If bitset is None, use its place for a different type. // TODO(jarin) If the element at 1 is range of constant, slap
if (size >= 2 && unioned->Get(0)->IsNone()) { // the representation on it and return that.
unioned->Set(0, unioned->Get(--size));
} }
if (size == 1) return unioned->Get(0);
unioned->Shrink(size); unioned->Shrink(size);
SLOW_DCHECK(unioned->Wellformed()); SLOW_DCHECK(unioned->Wellformed());
return unioned; return unioned;
} }
// -----------------------------------------------------------------------------
// Component extraction
// static
template <class Config>
typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Representation(
TypeHandle t, Region* region) {
return BitsetType::New(t->Representation(), region);
}
// static
template <class Config>
typename TypeImpl<Config>::TypeHandle TypeImpl<Config>::Semantic(
TypeHandle t, Region* region) {
return Intersect(t, BitsetType::New(BitsetType::kSemantic, region), region);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Iteration. // Iteration.
......
...@@ -430,8 +430,11 @@ class TypeImpl : public Config::Base { ...@@ -430,8 +430,11 @@ class TypeImpl : public Config::Base {
return Of(*value, region); return Of(*value, region);
} }
// Predicates. // Extraction of components.
static TypeHandle Representation(TypeHandle t, Region* region);
static TypeHandle Semantic(TypeHandle t, Region* region);
// Predicates.
bool IsInhabited() { return BitsetType::IsInhabited(this->BitsetLub()); } bool IsInhabited() { return BitsetType::IsInhabited(this->BitsetLub()); }
bool Is(TypeImpl* that) { return this == that || this->SlowIs(that); } bool Is(TypeImpl* that) { return this == that || this->SlowIs(that); }
...@@ -563,12 +566,16 @@ class TypeImpl : public Config::Base { ...@@ -563,12 +566,16 @@ class TypeImpl : public Config::Base {
} }
UnionType* AsUnion() { return UnionType::cast(this); } UnionType* AsUnion() { return UnionType::cast(this); }
bitset Representation();
// Auxiliary functions. // Auxiliary functions.
bool SemanticMaybe(TypeImpl* that);
bitset BitsetGlb() { return BitsetType::Glb(this); } bitset BitsetGlb() { return BitsetType::Glb(this); }
bitset BitsetLub() { return BitsetType::Lub(this); } bitset BitsetLub() { return BitsetType::Lub(this); }
bool SlowIs(TypeImpl* that); bool SlowIs(TypeImpl* that);
bool SemanticIs(TypeImpl* that);
static bool IsInteger(double x) { static bool IsInteger(double x) {
return nearbyint(x) == x && !i::IsMinusZero(x); // Allows for infinities. return nearbyint(x) == x && !i::IsMinusZero(x); // Allows for infinities.
...@@ -580,16 +587,9 @@ class TypeImpl : public Config::Base { ...@@ -580,16 +587,9 @@ class TypeImpl : public Config::Base {
struct Limits { struct Limits {
double min; double min;
double max; double max;
bitset representation; Limits(double min, double max) : min(min), max(max) {}
Limits(double min, double max, bitset representation) explicit Limits(RangeType* range) : min(range->Min()), max(range->Max()) {}
: min(min), max(max), representation(representation) {} static Limits Empty(Region* region) { return Limits(1, 0); }
explicit Limits(RangeType* range)
: min(range->Min()),
max(range->Max()),
representation(REPRESENTATION(range->Bound())) {}
static Limits Empty(Region* region) {
return Limits(1, 0, BitsetType::kNone);
}
}; };
static bool IsEmpty(Limits lim); static bool IsEmpty(Limits lim);
...@@ -649,11 +649,13 @@ class TypeImpl<Config>::BitsetType : public TypeImpl<Config> { ...@@ -649,11 +649,13 @@ class TypeImpl<Config>::BitsetType : public TypeImpl<Config> {
if (FLAG_enable_slow_asserts) CheckNumberBits(bits); if (FLAG_enable_slow_asserts) CheckNumberBits(bits);
return Config::from_bitset(bits, region); return Config::from_bitset(bits, region);
} }
// TODO(neis): Eventually allow again for types with empty semantics
// part and modify intersection and possibly subtyping accordingly.
static bool IsInhabited(bitset bits) { static bool IsInhabited(bitset bits) {
return bits & kSemantic; return SEMANTIC(bits) != kNone && REPRESENTATION(bits) != kNone;
}
static bool SemanticIsInhabited(bitset bits) {
return SEMANTIC(bits) != kNone;
} }
static bool Is(bitset bits1, bitset bits2) { static bool Is(bitset bits1, bitset bits2) {
...@@ -855,8 +857,8 @@ class TypeImpl<Config>::RangeType : public TypeImpl<Config> { ...@@ -855,8 +857,8 @@ class TypeImpl<Config>::RangeType : public TypeImpl<Config> {
return Config::template cast<RangeType>(Config::from_range(range)); return Config::template cast<RangeType>(Config::from_range(range));
} }
static RangeHandle New(Limits lim, Region* region) { static RangeHandle New(Limits lim, bitset representation, Region* region) {
return New(lim.min, lim.max, BitsetType::New(lim.representation, region), return New(lim.min, lim.max, BitsetType::New(representation, region),
region); region);
} }
...@@ -866,7 +868,6 @@ class TypeImpl<Config>::RangeType : public TypeImpl<Config> { ...@@ -866,7 +868,6 @@ class TypeImpl<Config>::RangeType : public TypeImpl<Config> {
} }
}; };
// TODO(neis): Also cache min and max values. // TODO(neis): Also cache min and max values.
// TODO(neis): Allow restricting the representation.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
......
...@@ -88,7 +88,6 @@ ...@@ -88,7 +88,6 @@
'compiler/test-run-stackcheck.cc', 'compiler/test-run-stackcheck.cc',
'compiler/test-run-variables.cc', 'compiler/test-run-variables.cc',
'compiler/test-simplified-lowering.cc', 'compiler/test-simplified-lowering.cc',
'compiler/test-typer.cc',
'cctest.cc', 'cctest.cc',
'gay-fixed.cc', 'gay-fixed.cc',
'gay-precision.cc', 'gay-precision.cc',
......
...@@ -109,7 +109,8 @@ struct Tests : Rep { ...@@ -109,7 +109,8 @@ struct Tests : Rep {
: isolate(CcTest::i_isolate()), : isolate(CcTest::i_isolate()),
scope(isolate), scope(isolate),
zone(), zone(),
T(Rep::ToRegion(&zone, isolate), isolate) {} T(Rep::ToRegion(&zone, isolate), isolate,
isolate->random_number_generator()) {}
bool Equal(TypeHandle type1, TypeHandle type2) { bool Equal(TypeHandle type1, TypeHandle type2) {
return return
...@@ -234,14 +235,82 @@ struct Tests : Rep { ...@@ -234,14 +235,82 @@ struct Tests : Rep {
for (TypeIterator it2 = T.types.begin(); it2 != T.types.end(); ++it2) { for (TypeIterator it2 = T.types.begin(); it2 != T.types.end(); ++it2) {
TypeHandle type1 = *it1; TypeHandle type1 = *it1;
TypeHandle type2 = *it2; TypeHandle type2 = *it2;
TypeHandle intersect12 = T.Intersect(type1, type2);
if (this->IsBitset(type1) && this->IsBitset(type2)) { if (this->IsBitset(type1) && this->IsBitset(type2)) {
TypeHandle intersect12 = T.Intersect(type1, type2);
bitset bits = this->AsBitset(type1) & this->AsBitset(type2); bitset bits = this->AsBitset(type1) & this->AsBitset(type2);
CHECK( CHECK(bits == this->AsBitset(intersect12));
(Rep::BitsetType::IsInhabited(bits) ? bits : 0) == }
this->AsBitset(intersect12)); }
} }
} }
void PointwiseRepresentation() {
// Check we can decompose type into semantics and representation and
// then compose it back to get an equivalent type.
int counter = 0;
for (TypeIterator it1 = T.types.begin(); it1 != T.types.end(); ++it1) {
counter++;
printf("Counter: %i\n", counter);
fflush(stdout);
TypeHandle type1 = *it1;
TypeHandle representation = T.Representation(type1);
TypeHandle semantic = T.Semantic(type1);
TypeHandle composed = T.Union(representation, semantic);
CHECK(type1->Equals(composed));
}
// Pointwiseness of Union.
for (TypeIterator it1 = T.types.begin(); it1 != T.types.end(); ++it1) {
for (TypeIterator it2 = T.types.begin(); it2 != T.types.end(); ++it2) {
TypeHandle type1 = *it1;
TypeHandle type2 = *it2;
TypeHandle representation1 = T.Representation(type1);
TypeHandle semantic1 = T.Semantic(type1);
TypeHandle representation2 = T.Representation(type2);
TypeHandle semantic2 = T.Semantic(type2);
TypeHandle direct_union = T.Union(type1, type2);
TypeHandle representation_union =
T.Union(representation1, representation2);
TypeHandle semantic_union = T.Union(semantic1, semantic2);
TypeHandle composed_union =
T.Union(representation_union, semantic_union);
CHECK(direct_union->Equals(composed_union));
}
}
// Pointwiseness of Intersect.
for (TypeIterator it1 = T.types.begin(); it1 != T.types.end(); ++it1) {
for (TypeIterator it2 = T.types.begin(); it2 != T.types.end(); ++it2) {
TypeHandle type1 = *it1;
TypeHandle type2 = *it2;
TypeHandle representation1 = T.Representation(type1);
TypeHandle semantic1 = T.Semantic(type1);
TypeHandle representation2 = T.Representation(type2);
TypeHandle semantic2 = T.Semantic(type2);
TypeHandle direct_intersection = T.Intersect(type1, type2);
TypeHandle representation_intersection =
T.Intersect(representation1, representation2);
TypeHandle semantic_intersection = T.Intersect(semantic1, semantic2);
TypeHandle composed_intersection =
T.Union(representation_intersection, semantic_intersection);
CHECK(direct_intersection->Equals(composed_intersection));
}
}
// Pointwiseness of Is.
for (TypeIterator it1 = T.types.begin(); it1 != T.types.end(); ++it1) {
for (TypeIterator it2 = T.types.begin(); it2 != T.types.end(); ++it2) {
TypeHandle type1 = *it1;
TypeHandle type2 = *it2;
TypeHandle representation1 = T.Representation(type1);
TypeHandle semantic1 = T.Semantic(type1);
TypeHandle representation2 = T.Representation(type2);
TypeHandle semantic2 = T.Semantic(type2);
bool representation_is = representation1->Is(representation2);
bool semantic_is = semantic1->Is(semantic2);
bool direct_is = type1->Is(type2);
CHECK(direct_is == (semantic_is && representation_is));
}
} }
} }
...@@ -657,11 +726,11 @@ struct Tests : Rep { ...@@ -657,11 +726,11 @@ struct Tests : Rep {
} }
} }
// Rangification: If T->Is(Range(-inf,+inf)) and !T->Is(None), then // Rangification: If T->Is(Range(-inf,+inf)) and T is inhabited, then
// T->Is(Range(T->Min(), T->Max())). // T->Is(Range(T->Min(), T->Max())).
for (TypeIterator it = T.types.begin(); it != T.types.end(); ++it) { for (TypeIterator it = T.types.begin(); it != T.types.end(); ++it) {
TypeHandle type = *it; TypeHandle type = *it;
CHECK(!(type->Is(T.Integer) && !type->Is(T.None)) || CHECK(!type->Is(T.Integer) || !type->IsInhabited() ||
type->Is(T.Range(type->Min(), type->Max()))); type->Is(T.Range(type->Min(), type->Max())));
} }
} }
...@@ -801,7 +870,7 @@ struct Tests : Rep { ...@@ -801,7 +870,7 @@ struct Tests : Rep {
(type1->IsContext() && type2->IsContext()) || (type1->IsContext() && type2->IsContext()) ||
(type1->IsArray() && type2->IsArray()) || (type1->IsArray() && type2->IsArray()) ||
(type1->IsFunction() && type2->IsFunction()) || (type1->IsFunction() && type2->IsFunction()) ||
type1->Equals(T.None)); !type1->IsInhabited());
} }
} }
} }
...@@ -1305,7 +1374,7 @@ struct Tests : Rep { ...@@ -1305,7 +1374,7 @@ struct Tests : Rep {
CheckDisjoint(T.SignedFunction1, T.MethodFunction); CheckDisjoint(T.SignedFunction1, T.MethodFunction);
CheckOverlap(T.ObjectConstant1, T.ObjectClass); // !!! CheckOverlap(T.ObjectConstant1, T.ObjectClass); // !!!
CheckOverlap(T.ObjectConstant2, T.ObjectClass); // !!! CheckOverlap(T.ObjectConstant2, T.ObjectClass); // !!!
CheckOverlap(T.NumberClass, T.Intersect(T.Number, T.Untagged)); // !!! CheckOverlap(T.NumberClass, T.Intersect(T.Number, T.Tagged)); // !!!
} }
void Union1() { void Union1() {
...@@ -1694,24 +1763,24 @@ struct Tests : Rep { ...@@ -1694,24 +1763,24 @@ struct Tests : Rep {
// Bitset-class // Bitset-class
CheckEqual(T.Intersect(T.ObjectClass, T.Object), T.ObjectClass); CheckEqual(T.Intersect(T.ObjectClass, T.Object), T.ObjectClass);
CheckEqual(T.Intersect(T.ObjectClass, T.Array), T.None); CheckEqual(T.Semantic(T.Intersect(T.ObjectClass, T.Array)), T.None);
CheckEqual(T.Intersect(T.ObjectClass, T.Number), T.None); CheckEqual(T.Semantic(T.Intersect(T.ObjectClass, T.Number)), T.None);
// Bitset-array // Bitset-array
CheckEqual(T.Intersect(T.NumberArray, T.Object), T.NumberArray); CheckEqual(T.Intersect(T.NumberArray, T.Object), T.NumberArray);
CheckEqual(T.Intersect(T.AnyArray, T.Proxy), T.None); CheckEqual(T.Semantic(T.Intersect(T.AnyArray, T.Proxy)), T.None);
// Bitset-function // Bitset-function
CheckEqual(T.Intersect(T.MethodFunction, T.Object), T.MethodFunction); CheckEqual(T.Intersect(T.MethodFunction, T.Object), T.MethodFunction);
CheckEqual(T.Intersect(T.NumberFunction1, T.Proxy), T.None); CheckEqual(T.Semantic(T.Intersect(T.NumberFunction1, T.Proxy)), T.None);
// Bitset-union // Bitset-union
CheckEqual( CheckEqual(
T.Intersect(T.Object, T.Union(T.ObjectConstant1, T.ObjectClass)), T.Intersect(T.Object, T.Union(T.ObjectConstant1, T.ObjectClass)),
T.Union(T.ObjectConstant1, T.ObjectClass)); T.Union(T.ObjectConstant1, T.ObjectClass));
CHECK( CheckEqual(T.Semantic(T.Intersect(T.Union(T.ArrayClass, T.ObjectConstant1),
!T.Intersect(T.Union(T.ArrayClass, T.ObjectConstant1), T.Number) T.Number)),
->IsInhabited()); T.None);
// Class-constant // Class-constant
CHECK(T.Intersect(T.ObjectConstant1, T.ObjectClass)->IsInhabited()); // !!! CHECK(T.Intersect(T.ObjectConstant1, T.ObjectClass)->IsInhabited()); // !!!
...@@ -1867,8 +1936,9 @@ struct Tests : Rep { ...@@ -1867,8 +1936,9 @@ struct Tests : Rep {
template<class Type2, class TypeHandle2, class Region2, class Rep2> template<class Type2, class TypeHandle2, class Region2, class Rep2>
void Convert() { void Convert() {
Types<Type2, TypeHandle2, Region2> T2( Types<Type2, TypeHandle2, Region2> T2(Rep2::ToRegion(&zone, isolate),
Rep2::ToRegion(&zone, isolate), isolate); isolate,
isolate->random_number_generator());
for (TypeIterator it = T.types.begin(); it != T.types.end(); ++it) { for (TypeIterator it = T.types.begin(); it != T.types.end(); ++it) {
TypeHandle type1 = *it; TypeHandle type1 = *it;
TypeHandle2 type2 = T2.template Convert<Type>(type1); TypeHandle2 type2 = T2.template Convert<Type>(type1);
...@@ -1901,6 +1971,13 @@ TEST(IsSomeType) { ...@@ -1901,6 +1971,13 @@ TEST(IsSomeType) {
} }
TEST(PointwiseRepresentation) {
CcTest::InitializeVM();
// ZoneTests().PointwiseRepresentation();
HeapTests().PointwiseRepresentation();
}
TEST(BitsetType) { TEST(BitsetType) {
CcTest::InitializeVM(); CcTest::InitializeVM();
ZoneTests().Bitset(); ZoneTests().Bitset();
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#ifndef V8_TEST_CCTEST_TYPES_H_ #ifndef V8_TEST_CCTEST_TYPES_H_
#define V8_TEST_CCTEST_TYPES_H_ #define V8_TEST_CCTEST_TYPES_H_
#include "src/base/utils/random-number-generator.h"
#include "src/v8.h" #include "src/v8.h"
namespace v8 { namespace v8 {
...@@ -37,8 +38,8 @@ namespace internal { ...@@ -37,8 +38,8 @@ namespace internal {
template<class Type, class TypeHandle, class Region> template<class Type, class TypeHandle, class Region>
class Types { class Types {
public: public:
Types(Region* region, Isolate* isolate) Types(Region* region, Isolate* isolate, v8::base::RandomNumberGenerator* rng)
: region_(region), rng_(isolate->random_number_generator()) { : region_(region), rng_(rng) {
#define DECLARE_TYPE(name, value) \ #define DECLARE_TYPE(name, value) \
name = Type::name(region); \ name = Type::name(region); \
types.push_back(name); types.push_back(name);
...@@ -132,6 +133,10 @@ class Types { ...@@ -132,6 +133,10 @@ class Types {
#define DECLARE_TYPE(name, value) TypeHandle name; #define DECLARE_TYPE(name, value) TypeHandle name;
PROPER_BITSET_TYPE_LIST(DECLARE_TYPE) PROPER_BITSET_TYPE_LIST(DECLARE_TYPE)
#undef DECLARE_TYPE #undef DECLARE_TYPE
#define DECLARE_TYPE(name, value) TypeHandle Mask##name##ForTesting;
MASK_BITSET_TYPE_LIST(DECLARE_TYPE)
#undef DECLARE_TYPE
TypeHandle SignedSmall; TypeHandle SignedSmall;
TypeHandle UnsignedSmall; TypeHandle UnsignedSmall;
...@@ -212,10 +217,19 @@ class Types { ...@@ -212,10 +217,19 @@ class Types {
TypeHandle Union(TypeHandle t1, TypeHandle t2) { TypeHandle Union(TypeHandle t1, TypeHandle t2) {
return Type::Union(t1, t2, region_); return Type::Union(t1, t2, region_);
} }
TypeHandle Intersect(TypeHandle t1, TypeHandle t2) { TypeHandle Intersect(TypeHandle t1, TypeHandle t2) {
return Type::Intersect(t1, t2, region_); return Type::Intersect(t1, t2, region_);
} }
TypeHandle Representation(TypeHandle t) {
return Type::Representation(t, region_);
}
// TypeHandle Semantic(TypeHandle t) { return Intersect(t,
// MaskSemanticForTesting); }
TypeHandle Semantic(TypeHandle t) { return Type::Semantic(t, region_); }
template<class Type2, class TypeHandle2> template<class Type2, class TypeHandle2>
TypeHandle Convert(TypeHandle2 t) { TypeHandle Convert(TypeHandle2 t) {
return Type::template Convert<Type2>(t, region_); return Type::template Convert<Type2>(t, region_);
......
// Copyright 2014 the V8 project authors. All rights reserved. // Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
...@@ -7,27 +7,22 @@ ...@@ -7,27 +7,22 @@
#include "src/codegen.h" #include "src/codegen.h"
#include "src/compiler/js-operator.h" #include "src/compiler/js-operator.h"
#include "src/compiler/node-properties.h" #include "src/compiler/node-properties.h"
#include "src/compiler/typer.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/graph-builder-tester.h"
#include "test/cctest/types-fuzz.h" #include "test/cctest/types-fuzz.h"
#include "test/unittests/compiler/graph-unittest.h"
using namespace v8::internal; using namespace v8::internal;
using namespace v8::internal::compiler; using namespace v8::internal::compiler;
// TODO(titzer): generate a large set of deterministic inputs for these tests. // TODO(titzer): generate a large set of deterministic inputs for these tests.
class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { class TyperTest : public TypedGraphTest {
public: public:
TyperTester() TyperTest()
: GraphAndBuilders(main_zone()), : TypedGraphTest(3),
types_(main_zone(), isolate()), types_(zone(), isolate(), random_number_generator()),
typer_(isolate(), graph(), MaybeHandle<Context>()), javascript_(zone()) {
javascript_(main_zone()) {
Node* s = graph()->NewNode(common()->Start(3));
graph()->SetStart(s);
context_node_ = graph()->NewNode(common()->Parameter(2), graph()->start()); context_node_ = graph()->NewNode(common()->Parameter(2), graph()->start());
rng_ = isolate()->random_number_generator(); rng_ = random_number_generator();
integers.push_back(0); integers.push_back(0);
integers.push_back(0); integers.push_back(0);
...@@ -54,28 +49,19 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -54,28 +49,19 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
} }
Types<Type, Type*, Zone> types_; Types<Type, Type*, Zone> types_;
Typer typer_;
JSOperatorBuilder javascript_; JSOperatorBuilder javascript_;
Node* context_node_; Node* context_node_;
v8::base::RandomNumberGenerator* rng_; v8::base::RandomNumberGenerator* rng_;
std::vector<double> integers; std::vector<double> integers;
std::vector<double> int32s; std::vector<double> int32s;
Isolate* isolate() { return main_isolate(); }
Graph* graph() { return main_graph_; }
CommonOperatorBuilder* common() { return &main_common_; }
Node* Parameter(int index = 0) {
return graph()->NewNode(common()->Parameter(index), graph()->start());
}
Type* TypeBinaryOp(const Operator* op, Type* lhs, Type* rhs) { Type* TypeBinaryOp(const Operator* op, Type* lhs, Type* rhs) {
Node* p0 = Parameter(0); Node* p0 = Parameter(0);
Node* p1 = Parameter(1); Node* p1 = Parameter(1);
NodeProperties::SetBounds(p0, Bounds(lhs)); NodeProperties::SetBounds(p0, Bounds(lhs));
NodeProperties::SetBounds(p1, Bounds(rhs)); NodeProperties::SetBounds(p1, Bounds(rhs));
Node* n = graph()->NewNode( Node* n = graph()->NewNode(op, p0, p1, context_node_, graph()->start(),
op, p0, p1, context_node_, graph()->start(), graph()->start()); graph()->start());
return NodeProperties::GetBounds(n).upper; return NodeProperties::GetBounds(n).upper;
} }
...@@ -88,14 +74,17 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -88,14 +74,17 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
Type* NewRange(double i, double j) { Type* NewRange(double i, double j) {
if (i > j) std::swap(i, j); if (i > j) std::swap(i, j);
return Type::Range(i, j, main_zone()); return Type::Range(i, j, zone());
} }
double RandomInt(double min, double max) { double RandomInt(double min, double max) {
switch (rng_->NextInt(4)) { switch (rng_->NextInt(4)) {
case 0: return min; case 0:
case 1: return max; return min;
default: break; case 1:
return max;
default:
break;
} }
if (min == +V8_INFINITY) return +V8_INFINITY; if (min == +V8_INFINITY) return +V8_INFINITY;
if (max == -V8_INFINITY) return -V8_INFINITY; if (max == -V8_INFINITY) return -V8_INFINITY;
...@@ -130,8 +119,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -130,8 +119,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
for (int x2 = rmin; x2 < rmin + width; x2++) { for (int x2 = rmin; x2 < rmin + width; x2++) {
double result_value = opfun(x1, x2); double result_value = opfun(x1, x2);
Type* result_type = Type::Constant( Type* result_type = Type::Constant(
isolate()->factory()->NewNumber(result_value), main_zone()); isolate()->factory()->NewNumber(result_value), zone());
CHECK(result_type->Is(expected_type)); EXPECT_TRUE(result_type->Is(expected_type));
} }
} }
} }
...@@ -151,8 +140,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -151,8 +140,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
double x2 = RandomInt(r2); double x2 = RandomInt(r2);
double result_value = opfun(x1, x2); double result_value = opfun(x1, x2);
Type* result_type = Type::Constant( Type* result_type = Type::Constant(
isolate()->factory()->NewNumber(result_value), main_zone()); isolate()->factory()->NewNumber(result_value), zone());
CHECK(result_type->Is(expected_type)); EXPECT_TRUE(result_type->Is(expected_type));
} }
} }
} }
...@@ -170,8 +159,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -170,8 +159,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
Type* result_type = Type* result_type =
Type::Constant(result_value ? isolate()->factory()->true_value() Type::Constant(result_value ? isolate()->factory()->true_value()
: isolate()->factory()->false_value(), : isolate()->factory()->false_value(),
main_zone()); zone());
CHECK(result_type->Is(expected_type)); EXPECT_TRUE(result_type->Is(expected_type));
} }
} }
} }
...@@ -187,8 +176,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -187,8 +176,8 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
int32_t x2 = static_cast<int32_t>(RandomInt(r2)); int32_t x2 = static_cast<int32_t>(RandomInt(r2));
double result_value = opfun(x1, x2); double result_value = opfun(x1, x2);
Type* result_type = Type::Constant( Type* result_type = Type::Constant(
isolate()->factory()->NewNumber(result_value), main_zone()); isolate()->factory()->NewNumber(result_value), zone());
CHECK(result_type->Is(expected_type)); EXPECT_TRUE(result_type->Is(expected_type));
} }
} }
} }
...@@ -206,20 +195,26 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders { ...@@ -206,20 +195,26 @@ class TyperTester : public HandleAndZoneScope, public GraphAndBuilders {
Type* type1 = types_.Fuzz(); Type* type1 = types_.Fuzz();
Type* type2 = types_.Fuzz(); Type* type2 = types_.Fuzz();
Type* type = TypeBinaryOp(op, type1, type2); Type* type = TypeBinaryOp(op, type1, type2);
Type* subtype1 = RandomSubtype(type1);; Type* subtype1 = RandomSubtype(type1);
Type* subtype2 = RandomSubtype(type2);; ;
Type* subtype2 = RandomSubtype(type2);
;
Type* subtype = TypeBinaryOp(op, subtype1, subtype2); Type* subtype = TypeBinaryOp(op, subtype1, subtype2);
CHECK(subtype->Is(type)); EXPECT_TRUE(subtype->Is(type));
} }
} }
}; };
static int32_t shift_left(int32_t x, int32_t y) { return x << y; } namespace {
static int32_t shift_right(int32_t x, int32_t y) { return x >> y; }
static int32_t bit_or(int32_t x, int32_t y) { return x | y; } int32_t shift_left(int32_t x, int32_t y) { return x << y; }
static int32_t bit_and(int32_t x, int32_t y) { return x & y; } int32_t shift_right(int32_t x, int32_t y) { return x >> y; }
static int32_t bit_xor(int32_t x, int32_t y) { return x ^ y; } int32_t bit_or(int32_t x, int32_t y) { return x | y; }
int32_t bit_and(int32_t x, int32_t y) { return x & y; }
int32_t bit_xor(int32_t x, int32_t y) { return x ^ y; }
} // namespace
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
...@@ -229,115 +224,96 @@ static int32_t bit_xor(int32_t x, int32_t y) { return x ^ y; } ...@@ -229,115 +224,96 @@ static int32_t bit_xor(int32_t x, int32_t y) { return x ^ y; }
// to ranges as input types. // to ranges as input types.
TEST(TypeJSAdd) { TEST_F(TyperTest, TypeJSAdd) {
TyperTester t; TestBinaryArithOp(javascript_.Add(), std::plus<double>());
t.TestBinaryArithOp(t.javascript_.Add(), std::plus<double>());
} }
TEST(TypeJSSubtract) { TEST_F(TyperTest, TypeJSSubtract) {
TyperTester t; TestBinaryArithOp(javascript_.Subtract(), std::minus<double>());
t.TestBinaryArithOp(t.javascript_.Subtract(), std::minus<double>());
} }
TEST(TypeJSMultiply) { TEST_F(TyperTest, TypeJSMultiply) {
TyperTester t; TestBinaryArithOp(javascript_.Multiply(), std::multiplies<double>());
t.TestBinaryArithOp(t.javascript_.Multiply(), std::multiplies<double>());
} }
TEST(TypeJSDivide) { TEST_F(TyperTest, TypeJSDivide) {
TyperTester t; TestBinaryArithOp(javascript_.Divide(), std::divides<double>());
t.TestBinaryArithOp(t.javascript_.Divide(), std::divides<double>());
} }
TEST(TypeJSModulus) { TEST_F(TyperTest, TypeJSModulus) {
TyperTester t; TestBinaryArithOp(javascript_.Modulus(), modulo);
t.TestBinaryArithOp(t.javascript_.Modulus(), modulo);
} }
TEST(TypeJSBitwiseOr) { TEST_F(TyperTest, TypeJSBitwiseOr) {
TyperTester t; TestBinaryBitOp(javascript_.BitwiseOr(), bit_or);
t.TestBinaryBitOp(t.javascript_.BitwiseOr(), bit_or);
} }
TEST(TypeJSBitwiseAnd) { TEST_F(TyperTest, TypeJSBitwiseAnd) {
TyperTester t; TestBinaryBitOp(javascript_.BitwiseAnd(), bit_and);
t.TestBinaryBitOp(t.javascript_.BitwiseAnd(), bit_and);
} }
TEST(TypeJSBitwiseXor) { TEST_F(TyperTest, TypeJSBitwiseXor) {
TyperTester t; TestBinaryBitOp(javascript_.BitwiseXor(), bit_xor);
t.TestBinaryBitOp(t.javascript_.BitwiseXor(), bit_xor);
} }
TEST(TypeJSShiftLeft) { TEST_F(TyperTest, TypeJSShiftLeft) {
TyperTester t; TestBinaryBitOp(javascript_.ShiftLeft(), shift_left);
t.TestBinaryBitOp(t.javascript_.ShiftLeft(), shift_left);
} }
TEST(TypeJSShiftRight) { TEST_F(TyperTest, TypeJSShiftRight) {
TyperTester t; TestBinaryBitOp(javascript_.ShiftRight(), shift_right);
t.TestBinaryBitOp(t.javascript_.ShiftRight(), shift_right);
} }
TEST(TypeJSLessThan) { TEST_F(TyperTest, TypeJSLessThan) {
TyperTester t; TestBinaryCompareOp(javascript_.LessThan(), std::less<double>());
t.TestBinaryCompareOp(t.javascript_.LessThan(), std::less<double>());
} }
TEST(TypeJSLessThanOrEqual) { TEST_F(TyperTest, TypeJSLessThanOrEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.LessThanOrEqual(), std::less_equal<double>());
t.TestBinaryCompareOp(
t.javascript_.LessThanOrEqual(), std::less_equal<double>());
} }
TEST(TypeJSGreaterThan) { TEST_F(TyperTest, TypeJSGreaterThan) {
TyperTester t; TestBinaryCompareOp(javascript_.GreaterThan(), std::greater<double>());
t.TestBinaryCompareOp(t.javascript_.GreaterThan(), std::greater<double>());
} }
TEST(TypeJSGreaterThanOrEqual) { TEST_F(TyperTest, TypeJSGreaterThanOrEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.GreaterThanOrEqual(),
t.TestBinaryCompareOp( std::greater_equal<double>());
t.javascript_.GreaterThanOrEqual(), std::greater_equal<double>());
} }
TEST(TypeJSEqual) { TEST_F(TyperTest, TypeJSEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.Equal(), std::equal_to<double>());
t.TestBinaryCompareOp(t.javascript_.Equal(), std::equal_to<double>());
} }
TEST(TypeJSNotEqual) { TEST_F(TyperTest, TypeJSNotEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.NotEqual(), std::not_equal_to<double>());
t.TestBinaryCompareOp(t.javascript_.NotEqual(), std::not_equal_to<double>());
} }
// For numbers there's no difference between strict and non-strict equality. // For numbers there's no difference between strict and non-strict equality.
TEST(TypeJSStrictEqual) { TEST_F(TyperTest, TypeJSStrictEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.StrictEqual(), std::equal_to<double>());
t.TestBinaryCompareOp(t.javascript_.StrictEqual(), std::equal_to<double>());
} }
TEST(TypeJSStrictNotEqual) { TEST_F(TyperTest, TypeJSStrictNotEqual) {
TyperTester t; TestBinaryCompareOp(javascript_.StrictNotEqual(),
t.TestBinaryCompareOp( std::not_equal_to<double>());
t.javascript_.StrictNotEqual(), std::not_equal_to<double>());
} }
...@@ -369,9 +345,22 @@ TEST(TypeJSStrictNotEqual) { ...@@ -369,9 +345,22 @@ TEST(TypeJSStrictNotEqual) {
#define TEST_FUNC(name) \ #define TEST_FUNC(name) \
TEST(Monotonicity_##name) { \ TEST_F(TyperTest, Monotonicity_##name) { \
TyperTester t; \ TestBinaryMonotonicity(javascript_.name()); \
t.TestBinaryMonotonicity(t.javascript_.name()); \
} }
JSBINOP_LIST(TEST_FUNC) JSBINOP_LIST(TEST_FUNC)
#undef TEST_FUNC #undef TEST_FUNC
//------------------------------------------------------------------------------
// Regression tests
TEST_F(TyperTest, TypeRegressInt32Constant) {
int values[] = {-5, 10};
for (auto i : values) {
Node* c = graph()->NewNode(common()->Int32Constant(i));
Type* type = NodeProperties::GetBounds(c).upper;
EXPECT_TRUE(type->Is(NewRange(i, i)));
}
}
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
'compiler/scheduler-unittest.cc', 'compiler/scheduler-unittest.cc',
'compiler/simplified-operator-reducer-unittest.cc', 'compiler/simplified-operator-reducer-unittest.cc',
'compiler/simplified-operator-unittest.cc', 'compiler/simplified-operator-unittest.cc',
'compiler/typer-unittest.cc',
'compiler/value-numbering-reducer-unittest.cc', 'compiler/value-numbering-reducer-unittest.cc',
'compiler/zone-pool-unittest.cc', 'compiler/zone-pool-unittest.cc',
'libplatform/default-platform-unittest.cc', 'libplatform/default-platform-unittest.cc',
......
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