Initialized representations of computed values to None.

R=danno@chromium.org

Review URL: https://chromiumcodereview.appspot.com/14721009

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14982 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
verwaest@chromium.org 2013-06-06 14:21:35 +00:00
parent 27d01cf8b2
commit 16199c63d8
13 changed files with 204 additions and 48 deletions

View File

@ -5283,7 +5283,7 @@ class Internals {
static const int kNullValueRootIndex = 7;
static const int kTrueValueRootIndex = 8;
static const int kFalseValueRootIndex = 9;
static const int kEmptyStringRootIndex = 129;
static const int kEmptyStringRootIndex = 130;
static const int kNodeClassIdOffset = 1 * kApiPointerSize;
static const int kNodeFlagsOffset = 1 * kApiPointerSize + 3;

View File

@ -1086,11 +1086,13 @@ bool Genesis::InitializeGlobal(Handle<GlobalObject> inner_global,
CHECK_NOT_EMPTY_HANDLE(isolate,
JSObject::SetLocalPropertyIgnoreAttributes(
result, factory->length_string(),
factory->undefined_value(), DONT_ENUM));
factory->undefined_value(), DONT_ENUM,
Object::FORCE_TAGGED));
CHECK_NOT_EMPTY_HANDLE(isolate,
JSObject::SetLocalPropertyIgnoreAttributes(
result, factory->callee_string(),
factory->undefined_value(), DONT_ENUM));
factory->undefined_value(), DONT_ENUM,
Object::FORCE_TAGGED));
#ifdef DEBUG
LookupResult lookup(isolate);

View File

@ -199,8 +199,10 @@ DEFINE_bool(pretenuring_call_new, false, "pretenure call new")
DEFINE_bool(track_fields, true, "track fields with only smi values")
DEFINE_bool(track_double_fields, true, "track fields with double values")
DEFINE_bool(track_heap_object_fields, true, "track fields with heap values")
DEFINE_bool(track_computed_fields, true, "track computed boilerplate fields")
DEFINE_implication(track_double_fields, track_fields)
DEFINE_implication(track_heap_object_fields, track_fields)
DEFINE_implication(track_computed_fields, track_fields)
// Flags for data representation optimizations
DEFINE_bool(unbox_double_arrays, true, "automatically unbox arrays of doubles")

View File

@ -2845,6 +2845,13 @@ bool Heap::CreateInitialObjects() {
}
set_the_hole_value(Oddball::cast(obj));
{ MaybeObject* maybe_obj = CreateOddball("uninitialized",
Smi::FromInt(-1),
Oddball::kUninitialized);
if (!maybe_obj->ToObject(&obj)) return false;
}
set_uninitialized_value(Oddball::cast(obj));
{ MaybeObject* maybe_obj = CreateOddball("arguments_marker",
Smi::FromInt(-4),
Oddball::kArgumentMarker);

View File

@ -59,6 +59,7 @@ namespace internal {
V(Oddball, null_value, NullValue) \
V(Oddball, true_value, TrueValue) \
V(Oddball, false_value, FalseValue) \
V(Oddball, uninitialized_value, UninitializedValue) \
V(Map, global_property_cell_map, GlobalPropertyCellMap) \
V(Map, shared_function_info_map, SharedFunctionInfoMap) \
V(Map, meta_map, MetaMap) \

View File

@ -312,8 +312,9 @@ void JSObject::JSObjectVerify() {
Representation r = descriptors->GetDetails(i).representation();
int field = descriptors->GetFieldIndex(i);
Object* value = RawFastPropertyAt(field);
if (r.IsSmi()) ASSERT(value->IsSmi());
if (r.IsDouble()) ASSERT(value->IsHeapNumber());
if (value->IsUninitialized()) continue;
if (r.IsSmi()) ASSERT(value->IsSmi());
if (r.IsHeapObject()) ASSERT(value->IsHeapObject());
}
}

View File

@ -292,6 +292,9 @@ MaybeObject* Object::AllocateNewStorageFor(Heap* heap,
PretenureFlag tenure) {
if (!FLAG_track_double_fields) return this;
if (!representation.IsDouble()) return this;
if (IsUninitialized()) {
return heap->AllocateHeapNumber(0, tenure);
}
return heap->AllocateHeapNumber(Number(), tenure);
}
@ -530,6 +533,11 @@ bool MaybeObject::IsTheHole() {
}
bool MaybeObject::IsUninitialized() {
return !IsFailure() && ToObjectUnchecked()->IsUninitialized();
}
Failure* Failure::cast(MaybeObject* obj) {
ASSERT(HAS_FAILURE_TAG(obj));
return reinterpret_cast<Failure*>(obj);
@ -845,6 +853,11 @@ bool Object::IsTheHole() {
}
bool Object::IsUninitialized() {
return IsOddball() && Oddball::cast(this)->kind() == Oddball::kUninitialized;
}
bool Object::IsTrue() {
return IsOddball() && Oddball::cast(this)->kind() == Oddball::kTrue;
}
@ -1541,7 +1554,7 @@ MaybeObject* JSObject::MigrateInstance() {
// Converting any field to the most specific type will cause the
// GeneralizeFieldRepresentation algorithm to create the most general existing
// transition that matches the object. This achieves what is needed.
return GeneralizeFieldRepresentation(0, Representation::Smi());
return GeneralizeFieldRepresentation(0, Representation::None());
}
@ -2366,7 +2379,6 @@ void DescriptorArray::Set(int descriptor_number,
// Range check.
ASSERT(descriptor_number < number_of_descriptors());
ASSERT(!desc->GetDetails().representation().IsNone());
NoIncrementalWriteBarrierSet(this,
ToKeyIndex(descriptor_number),
desc->GetKey());
@ -2382,7 +2394,6 @@ void DescriptorArray::Set(int descriptor_number,
void DescriptorArray::Set(int descriptor_number, Descriptor* desc) {
// Range check.
ASSERT(descriptor_number < number_of_descriptors());
ASSERT(!desc->GetDetails().representation().IsNone());
set(ToKeyIndex(descriptor_number), desc->GetKey());
set(ToValueIndex(descriptor_number), desc->GetValue());
@ -3617,6 +3628,9 @@ bool Map::CanBeDeprecated() {
int descriptor = LastAdded();
for (int i = 0; i <= descriptor; i++) {
PropertyDetails details = instance_descriptors()->GetDetails(i);
if (FLAG_track_fields && details.representation().IsNone()) {
return true;
}
if (FLAG_track_fields && details.representation().IsSmi()) {
return true;
}

View File

@ -1817,7 +1817,8 @@ static bool IsIdentifier(UnicodeCache* cache, Name* name) {
MaybeObject* JSObject::AddFastProperty(Name* name,
Object* value,
PropertyAttributes attributes,
StoreFromKeyed store_mode) {
StoreFromKeyed store_mode,
ValueType value_type) {
ASSERT(!IsJSGlobalProxy());
ASSERT(DescriptorArray::kNotFound ==
map()->instance_descriptors()->Search(
@ -1843,8 +1844,8 @@ MaybeObject* JSObject::AddFastProperty(Name* name,
int index = map()->NextFreePropertyIndex();
// Allocate new instance descriptors with (name, index) added
Representation representation = IsJSContextExtensionObject()
? Representation::Tagged() : value->OptimalRepresentation();
if (IsJSContextExtensionObject()) value_type = FORCE_TAGGED;
Representation representation = value->OptimalRepresentation(value_type);
FieldDescriptor new_field(name, index, attributes, representation);
@ -1961,7 +1962,8 @@ MaybeObject* JSObject::AddProperty(Name* name,
PropertyAttributes attributes,
StrictModeFlag strict_mode,
JSReceiver::StoreFromKeyed store_mode,
ExtensibilityCheck extensibility_check) {
ExtensibilityCheck extensibility_check,
ValueType value_type) {
ASSERT(!IsJSGlobalProxy());
Map* map_of_this = map();
Heap* heap = GetHeap();
@ -1988,7 +1990,8 @@ MaybeObject* JSObject::AddProperty(Name* name,
JSFunction::cast(value),
attributes);
} else {
result = AddFastProperty(name, value, attributes, store_mode);
result = AddFastProperty(
name, value, attributes, store_mode, value_type);
}
} else {
// Normalize the object to prevent very large instance descriptors.
@ -2272,7 +2275,7 @@ bool Map::InstancesNeedRewriting(Map* target,
int limit = NumberOfOwnDescriptors();
for (int i = 0; i < limit; i++) {
if (new_desc->GetDetails(i).representation().IsDouble() &&
old_desc->GetDetails(i).representation().IsSmi()) {
!old_desc->GetDetails(i).representation().IsDouble()) {
return true;
}
}
@ -2343,8 +2346,9 @@ MaybeObject* JSObject::MigrateToMap(Map* new_map) {
? old_descriptors->GetValue(i)
: RawFastPropertyAt(old_descriptors->GetFieldIndex(i));
if (FLAG_track_double_fields &&
old_details.representation().IsSmi() &&
!old_details.representation().IsDouble() &&
details.representation().IsDouble()) {
if (old_details.representation().IsNone()) value = Smi::FromInt(0);
// Objects must be allocated in the old object space, since the
// overall number of HeapNumbers needed for the conversion might
// exceed the capacity of new space, and we would fail repeatedly
@ -2397,7 +2401,7 @@ MaybeObject* JSObject::GeneralizeFieldRepresentation(
MaybeObject* maybe_new_map =
map()->GeneralizeRepresentation(modify_index, new_representation);
if (!maybe_new_map->To(&new_map)) return maybe_new_map;
ASSERT(map() != new_map || new_map->FindRootMap()->is_deprecated());
if (map() == new_map) return this;
return MigrateToMap(new_map);
}
@ -2574,10 +2578,21 @@ MaybeObject* Map::GeneralizeRepresentation(int modify_index,
Representation old_representation =
old_descriptors->GetDetails(modify_index).representation();
if (old_representation.IsNone()) {
UNREACHABLE();
// It's fine to transition from None to anything but double without any
// modification to the object, because the default uninitialized value for
// representation None can be overwritten by both smi and tagged values.
// Doubles, however, would require a box allocation.
if (old_representation.IsNone() &&
!new_representation.IsNone() &&
!new_representation.IsDouble()) {
if (FLAG_trace_generalization) {
PrintF("initializing representation %i: %p -> %s\n",
modify_index,
static_cast<void*>(this),
new_representation.Mnemonic());
}
old_descriptors->SetRepresentation(modify_index, new_representation);
return this;
return old_map;
}
int descriptors = old_map->NumberOfOwnDescriptors();
@ -2603,7 +2618,7 @@ MaybeObject* Map::GeneralizeRepresentation(int modify_index,
updated_descriptors->GetDetails(modify_index).representation();
if (new_representation.fits_into(updated_representation)) {
if (FLAG_trace_generalization &&
!(modify_index == 0 && new_representation.IsSmi())) {
!(modify_index == 0 && new_representation.IsNone())) {
PropertyDetails old_details = old_descriptors->GetDetails(modify_index);
PrintF("migrating to existing map %p(%s) -> %p(%s)\n",
static_cast<void*>(this),
@ -2641,7 +2656,7 @@ MaybeObject* Map::GeneralizeRepresentation(int modify_index,
old_descriptors->GetKey(descriptor), new_descriptors);
if (FLAG_trace_generalization &&
!(modify_index == 0 && new_representation.IsSmi())) {
!(modify_index == 0 && new_representation.IsNone())) {
PrintF("migrating to new map %i: %p(%s) -> %p(%s) (%i steps)\n",
modify_index,
static_cast<void*>(this),
@ -3933,10 +3948,12 @@ Handle<Object> JSObject::SetLocalPropertyIgnoreAttributes(
Handle<JSObject> object,
Handle<Name> key,
Handle<Object> value,
PropertyAttributes attributes) {
PropertyAttributes attributes,
ValueType value_type) {
CALL_HEAP_FUNCTION(
object->GetIsolate(),
object->SetLocalPropertyIgnoreAttributes(*key, *value, attributes),
object->SetLocalPropertyIgnoreAttributes(
*key, *value, attributes, value_type),
Object);
}
@ -3944,7 +3961,8 @@ Handle<Object> JSObject::SetLocalPropertyIgnoreAttributes(
MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Name* name_raw,
Object* value_raw,
PropertyAttributes attributes) {
PropertyAttributes attributes,
ValueType value_type) {
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc;
@ -3970,13 +3988,16 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
return JSObject::cast(proto)->SetLocalPropertyIgnoreAttributes(
name_raw,
value_raw,
attributes);
attributes,
value_type);
}
// Check for accessor in prototype chain removed here in clone.
if (!lookup.IsFound()) {
// Neither properties nor transitions found.
return AddProperty(name_raw, value_raw, attributes, kNonStrictMode);
return AddProperty(
name_raw, value_raw, attributes, kNonStrictMode,
MAY_BE_STORE_FROM_KEYED, PERFORM_EXTENSIBILITY_CHECK, value_type);
}
// From this point on everything needs to be handlified.
@ -4003,9 +4024,11 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
}
case FIELD: {
Representation representation = lookup.representation();
if (!value->FitsRepresentation(representation)) {
Representation value_representation =
value->OptimalRepresentation(value_type);
if (!value_representation.fits_into(representation)) {
MaybeObject* maybe_failure = self->GeneralizeFieldRepresentation(
lookup.GetDescriptorIndex(), value->OptimalRepresentation());
lookup.GetDescriptorIndex(), value_representation);
if (maybe_failure->IsFailure()) return maybe_failure;
DescriptorArray* desc = self->map()->instance_descriptors();
int descriptor = lookup.GetDescriptorIndex();
@ -4046,9 +4069,11 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
if (details.type() == FIELD) {
if (attributes == details.attributes()) {
Representation representation = details.representation();
if (!value->FitsRepresentation(representation)) {
Representation value_representation =
value->OptimalRepresentation(value_type);
if (!value_representation.fits_into(representation)) {
MaybeObject* maybe_map = transition_map->GeneralizeRepresentation(
descriptor, value->OptimalRepresentation());
descriptor, value_representation);
if (!maybe_map->To(&transition_map)) return maybe_map;
Object* back = transition_map->GetBackPointer();
if (back->IsMap()) {

View File

@ -869,6 +869,7 @@ class MaybeObject BASE_EMBEDDED {
inline bool IsOutOfMemory();
inline bool IsException();
INLINE(bool IsTheHole());
INLINE(bool IsUninitialized());
inline bool ToObject(Object** obj) {
if (IsFailure()) return false;
*obj = reinterpret_cast<Object*>(this);
@ -1046,6 +1047,7 @@ class Object : public MaybeObject {
INLINE(bool IsUndefined());
INLINE(bool IsNull());
INLINE(bool IsTheHole()); // Shadows MaybeObject's implementation.
INLINE(bool IsUninitialized());
INLINE(bool IsTrue());
INLINE(bool IsFalse());
inline bool IsArgumentsMarker();
@ -1060,16 +1062,24 @@ class Object : public MaybeObject {
bool ToInt32(int32_t* value);
bool ToUint32(uint32_t* value);
inline Representation OptimalRepresentation() {
if (FLAG_track_fields && IsSmi()) {
// Indicates whether OptimalRepresentation can do its work, or whether it
// always has to return Representation::Tagged().
enum ValueType {
OPTIMAL_REPRESENTATION,
FORCE_TAGGED
};
inline Representation OptimalRepresentation(
ValueType type = OPTIMAL_REPRESENTATION) {
if (!FLAG_track_fields) return Representation::Tagged();
if (type == FORCE_TAGGED) return Representation::Tagged();
if (IsSmi()) {
return Representation::Smi();
} else if (FLAG_track_double_fields && IsHeapNumber()) {
return Representation::Double();
} else if (FLAG_track_heap_object_fields && !IsUndefined()) {
// Don't track undefined as heapobject because it's also used as temporary
// value for computed fields that may turn out to be Smi. That combination
// will go tagged, so go tagged immediately.
// TODO(verwaest): Change once we track computed boilerplate fields.
} else if (FLAG_track_computed_fields && IsUninitialized()) {
return Representation::None();
} else if (FLAG_track_heap_object_fields) {
ASSERT(IsHeapObject());
return Representation::HeapObject();
} else {
@ -1078,7 +1088,9 @@ class Object : public MaybeObject {
}
inline bool FitsRepresentation(Representation representation) {
if (FLAG_track_fields && representation.IsSmi()) {
if (FLAG_track_fields && representation.IsNone()) {
return false;
} else if (FLAG_track_fields && representation.IsSmi()) {
return IsSmi();
} else if (FLAG_track_double_fields && representation.IsDouble()) {
return IsNumber();
@ -1827,7 +1839,8 @@ class JSObject: public JSReceiver {
Handle<JSObject> object,
Handle<Name> key,
Handle<Object> value,
PropertyAttributes attributes);
PropertyAttributes attributes,
ValueType value_type = OPTIMAL_REPRESENTATION);
static inline Handle<String> ExpectedTransitionKey(Handle<Map> map);
static inline Handle<Map> ExpectedTransitionTarget(Handle<Map> map);
@ -1854,7 +1867,8 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT MaybeObject* SetLocalPropertyIgnoreAttributes(
Name* key,
Object* value,
PropertyAttributes attributes);
PropertyAttributes attributes,
ValueType value_type = OPTIMAL_REPRESENTATION);
// Retrieve a value in a normalized object given a lookup result.
// Handles the special representation of JS global objects.
@ -2216,7 +2230,8 @@ class JSObject: public JSReceiver {
Name* name,
Object* value,
PropertyAttributes attributes,
StoreFromKeyed store_mode = MAY_BE_STORE_FROM_KEYED);
StoreFromKeyed store_mode = MAY_BE_STORE_FROM_KEYED,
ValueType value_type = OPTIMAL_REPRESENTATION);
// Add a property to a slow-case object.
MUST_USE_RESULT MaybeObject* AddSlowProperty(Name* name,
@ -2230,7 +2245,8 @@ class JSObject: public JSReceiver {
PropertyAttributes attributes,
StrictModeFlag strict_mode,
StoreFromKeyed store_mode = MAY_BE_STORE_FROM_KEYED,
ExtensibilityCheck extensibility_check = PERFORM_EXTENSIBILITY_CHECK);
ExtensibilityCheck extensibility_check = PERFORM_EXTENSIBILITY_CHECK,
ValueType value_type = OPTIMAL_REPRESENTATION);
// Convert the object to use the canonical dictionary
// representation. If the object is expected to have additional properties
@ -8483,7 +8499,8 @@ class Oddball: public HeapObject {
static const byte kNull = 3;
static const byte kArgumentMarker = 4;
static const byte kUndefined = 5;
static const byte kOther = 6;
static const byte kUninitialized = 6;
static const byte kOther = 7;
typedef FixedBodyDescriptor<kToStringOffset,
kToNumberOffset + kPointerSize,

View File

@ -3593,7 +3593,7 @@ Expression* Parser::ParseArrayLiteral(bool* ok) {
Handle<Object> boilerplate_value = GetBoilerplateValue(values->at(i));
if (boilerplate_value->IsTheHole()) {
is_holey = true;
} else if (boilerplate_value->IsUndefined()) {
} else if (boilerplate_value->IsUninitialized()) {
is_simple = false;
JSObject::SetOwnElement(
array, i, handle(Smi::FromInt(0), isolate()), kNonStrictMode);
@ -3693,7 +3693,7 @@ Handle<Object> Parser::GetBoilerplateValue(Expression* expression) {
if (CompileTimeValue::IsCompileTimeValue(expression)) {
return CompileTimeValue::GetValue(expression);
}
return isolate()->factory()->undefined_value();
return isolate()->factory()->uninitialized_value();
}
// Validation per 11.1.5 Object Initialiser
@ -3804,13 +3804,17 @@ void Parser::BuildObjectLiteralConstantProperties(
Handle<Object> key = property->key()->handle();
Handle<Object> value = GetBoilerplateValue(property->value());
// Ensure objects with doubles are always treated as nested objects.
// Ensure objects that may, at any point in time, contain fields with double
// representation are always treated as nested objects. This is true for
// computed fields (value is undefined), and smi and double literals
// (value->IsNumber()).
// TODO(verwaest): Remove once we can store them inline.
if (FLAG_track_double_fields && value->IsNumber()) {
if (FLAG_track_double_fields &&
(value->IsNumber() || value->IsUninitialized())) {
*may_store_doubles = true;
}
is_simple_acc = is_simple_acc && !value->IsUndefined();
is_simple_acc = is_simple_acc && !value->IsUninitialized();
// Keep track of the number of elements in the object literal and
// the largest element index. If the largest element index is

View File

@ -113,7 +113,7 @@ class Representation {
bool is_more_general_than(const Representation& other) const {
ASSERT(kind_ != kExternal);
ASSERT(other.kind_ != kExternal);
if (IsHeapObject()) return other.IsDouble();
if (IsHeapObject()) return other.IsDouble() || other.IsNone();
return kind_ > other.kind_;
}
@ -213,6 +213,7 @@ class PropertyDetails BASE_EMBEDDED {
}
Representation representation() {
ASSERT(type() != NORMAL);
return DecodeRepresentation(RepresentationField::decode(value_));
}

View File

@ -203,6 +203,8 @@ class LookupResult BASE_EMBEDDED {
}
bool CanHoldValue(Handle<Object> value) {
if (IsNormal()) return true;
ASSERT(!IsTransition());
return value->FitsRepresentation(details_.representation());
}

View File

@ -325,3 +325,83 @@ df3.first_double = 1.7;
df3.second_function = some_function2;
df1.first_double = 10;
read_first_double(df1);
// Test boilerplates with computed values.
function none_boilerplate(a) {
return {"a_none":a};
}
%OptimizeFunctionOnNextCall(none_boilerplate);
var none_double1 = none_boilerplate(1.7);
var none_double2 = none_boilerplate(1.9);
assertTrue(%HaveSameMap(none_double1, none_double2));
assertEquals(1.7, none_double1.a_none);
assertEquals(1.9, none_double2.a_none);
none_double2.a_none = 3.5;
var none_double1 = none_boilerplate(1.7);
var none_double2 = none_boilerplate(3.5);
function none_to_smi(a) {
return {"a_smi":a};
}
var none_smi1 = none_to_smi(1);
var none_smi2 = none_to_smi(2);
%OptimizeFunctionOnNextCall(none_to_smi);
var none_smi3 = none_to_smi(3);
assertTrue(%HaveSameMap(none_smi1, none_smi2));
assertTrue(%HaveSameMap(none_smi1, none_smi3));
assertEquals(1, none_smi1.a_smi);
assertEquals(2, none_smi2.a_smi);
assertEquals(3, none_smi3.a_smi);
function none_to_double(a) {
return {"a_double":a};
}
var none_to_double1 = none_to_double(1.5);
var none_to_double2 = none_to_double(2.8);
%OptimizeFunctionOnNextCall(none_to_double);
var none_to_double3 = none_to_double(3.7);
assertTrue(%HaveSameMap(none_to_double1, none_to_double2));
assertTrue(%HaveSameMap(none_to_double1, none_to_double3));
assertEquals(1.5, none_to_double1.a_double);
assertEquals(2.8, none_to_double2.a_double);
assertEquals(3.7, none_to_double3.a_double);
function none_to_object(a) {
return {"an_object":a};
}
var none_to_object1 = none_to_object(true);
var none_to_object2 = none_to_object(false);
%OptimizeFunctionOnNextCall(none_to_object);
var none_to_object3 = none_to_object(3.7);
assertTrue(%HaveSameMap(none_to_object1, none_to_object2));
assertTrue(%HaveSameMap(none_to_object1, none_to_object3));
assertEquals(true, none_to_object1.an_object);
assertEquals(false, none_to_object2.an_object);
assertEquals(3.7, none_to_object3.an_object);
function double_to_object(a) {
var o = {"d_to_h":1.8};
o.d_to_h = a;
return o;
}
var dh1 = double_to_object(true);
var dh2 = double_to_object(false);
assertTrue(%HaveSameMap(dh1, dh2));
assertEquals(true, dh1.d_to_h);
assertEquals(false, dh2.d_to_h);
function smi_to_object(a) {
var o = {"s_to_t":18};
o.s_to_t = a;
return o;
}
var st1 = smi_to_object(true);
var st2 = smi_to_object(false);
assertTrue(%HaveSameMap(st1, st2));
assertEquals(true, st1.s_to_t);
assertEquals(false, st2.s_to_t);