blob: 48fedddb965a9cff81e4193f70f367dcf7dd763b [file]
// Copyright 2026 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_
#define V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_
#include "src/objects/js-data-object-builder.h"
// Include the non-inl header before the rest of the headers.
#include "include/v8config.h"
#include "src/common/assert-scope.h"
#include "src/heap/factory.h"
#include "src/objects/descriptor-array-inl.h"
#include "src/objects/field-index.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/heap-object-set-map-inl.h"
#include "src/objects/js-objects-inl.h"
#include "src/objects/map-updater.h"
#include "src/objects/object-conversions-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/transitions-inl.h"
namespace v8 {
namespace internal {
class FoldedMutableHeapNumberAllocation {
public:
// TODO(leszeks): If allocation alignment is ever enabled, we'll need to add
// padding fillers between heap numbers.
static_assert(!USE_ALLOCATION_ALIGNMENT_HEAP_NUMBER_BOOL);
FoldedMutableHeapNumberAllocation(Isolate* isolate, uint32_t count) {
if (count == 0) return;
uint32_t size = count * sizeof(HeapNumber);
raw_bytes_ = isolate->factory()->NewByteArray(size);
}
Handle<ByteArray> raw_bytes() const { return raw_bytes_; }
private:
Handle<ByteArray> raw_bytes_ = {};
};
class FoldedMutableHeapNumberAllocator {
public:
FoldedMutableHeapNumberAllocator(
Isolate* isolate, FoldedMutableHeapNumberAllocation* allocation,
DisallowGarbageCollection& no_gc V8_LIFETIME_BOUND)
: isolate_(isolate), roots_(isolate) {
if (allocation->raw_bytes().is_null()) return;
raw_bytes_ = allocation->raw_bytes();
mutable_double_address_ =
reinterpret_cast<Address>(allocation->raw_bytes()->begin());
}
~FoldedMutableHeapNumberAllocator() {
// Make all mutable HeapNumbers alive.
if (mutable_double_address_ == 0) {
DCHECK(raw_bytes_.is_null());
return;
}
// We should have calculated the exact correct number of mutable heap
// numbers for the double fields we've seen, however nested value
// allocations could have transitioned those double field to tagged.
Address raw_bytes_end = reinterpret_cast<Address>(raw_bytes_->end());
if (mutable_double_address_ < raw_bytes_end) {
isolate_->heap()->CreateFillerObjectAt(
mutable_double_address_,
base::checked_cast<int>(raw_bytes_end - mutable_double_address_));
} else {
DCHECK_EQ(mutable_double_address_, raw_bytes_end);
}
// Before setting the length of mutable_double_buffer back to zero, we
// must ensure that the sweeper is not running or has already swept the
// object's page. Otherwise the GC can add the contents of
// mutable_double_buffer to the free list.
isolate_->heap()->EnsureSweepingCompletedForObject(*raw_bytes_);
raw_bytes_->set_length(0);
}
Tagged<HeapNumber> AllocateNext(ReadOnlyRoots roots, Float64 value) {
DCHECK_GE(mutable_double_address_,
reinterpret_cast<Address>(raw_bytes_->begin()));
Tagged<HeapObject> hn = HeapObject::FromAddress(mutable_double_address_);
hn->set_map_after_allocation(isolate_, roots.heap_number_map());
Cast<HeapNumber>(hn)->set_value_as_bits(value.get_bits());
mutable_double_address_ +=
ALIGN_TO_ALLOCATION_ALIGNMENT(sizeof(HeapNumber));
DCHECK_LE(mutable_double_address_,
reinterpret_cast<Address>(raw_bytes_->end()));
return Cast<HeapNumber>(hn);
}
private:
Isolate* isolate_;
ReadOnlyRoots roots_;
Handle<ByteArray> raw_bytes_ = {};
Address mutable_double_address_ = 0;
};
template <JSDataPropertyIteratorWithRawKeys PropertyIterator>
Handle<JSObject> JSDataObjectBuilder::BuildFromIterator(
PropertyIterator&& it, MaybeDirectHandle<FixedArrayBase> maybe_elements) {
may_have_duplicate_keys_ =
std::remove_reference_t<PropertyIterator>::kMayHaveDuplicateKeys;
DirectHandle<InternalizedString> failed_property_add_key;
for (; !it.Done(); it.Advance()) {
DirectHandle<InternalizedString> property_key;
if (!TryAddFastPropertyForValue(
it.GetKeyChars(),
[&](Handle<InternalizedString> expected_key) {
return it.GetKey(expected_key);
},
[&]() { return it.GetValue(true); }, &property_key)) {
failed_property_add_key = property_key;
break;
}
}
DirectHandle<FixedArrayBase> elements;
if (!maybe_elements.ToHandle(&elements)) {
elements = isolate_->factory()->empty_fixed_array();
}
{
auto value_it = it.RevisitValues();
CreateAndInitialiseObject(value_it, elements);
if (!it.Done()) {
// If we're not yet done, we must have failed to add a property.
// This property's value will be in the value iterator, so get it from
// there rather than it.GetValue().
DirectHandle<InternalizedString> key =
std::exchange(failed_property_add_key, {});
DCHECK(!key.is_null());
Handle<Object> value(value_it.GetNext(), isolate_);
AddSlowProperty(key, value);
it.Advance();
}
}
// Slow path: define remaining named properties.
for (; !it.Done(); it.Advance()) {
DCHECK(failed_property_add_key.is_null());
DirectHandle<InternalizedString> key = it.GetKey({});
#ifdef DEBUG
uint32_t index;
DCHECK(!key->AsArrayIndex(&index));
#endif
Handle<Object> value = it.GetValue(false);
AddSlowProperty(key, value);
}
DCHECK(!object_.is_null());
return object_;
}
template <JSDataPropertyIteratorWithEagerKeys PropertyIterator>
Handle<JSObject> JSDataObjectBuilder::BuildFromIterator(
PropertyIterator&& it, MaybeDirectHandle<FixedArrayBase> maybe_elements) {
may_have_duplicate_keys_ =
std::remove_reference_t<PropertyIterator>::kMayHaveDuplicateKeys;
for (; !it.Done(); it.Advance()) {
if (!TryAddFastPropertyForValue(it.GetKey(), it.GetValue(true))) {
break;
}
}
DirectHandle<FixedArrayBase> elements;
if (!maybe_elements.ToHandle(&elements)) {
elements = isolate_->factory()->empty_fixed_array();
}
{
auto value_it = it.RevisitValues();
CreateAndInitialiseObject(value_it, elements);
if (!it.Done()) {
// If we're not yet done, we must have failed to add a property.
// This property's value will be in the value iterator, so get it from
// there rather than it.GetValue().
DirectHandle<InternalizedString> key = it.GetKey();
Handle<Object> value(value_it.GetNext(), isolate_);
AddSlowProperty(key, value);
it.Advance();
}
}
// Slow path: define remaining named properties.
for (; !it.Done(); it.Advance()) {
DirectHandle<InternalizedString> key = it.GetKey();
#ifdef DEBUG
uint32_t index;
DCHECK(!key->AsArrayIndex(&index));
#endif
Handle<Object> value = it.GetValue(false);
AddSlowProperty(key, value);
}
DCHECK(!object_.is_null());
return object_;
}
template <typename Char, typename GetKeyFunction, typename GetValueFunction>
bool JSDataObjectBuilder::TryAddFastPropertyForValue(
base::Vector<const Char> key_chars, GetKeyFunction&& get_key,
GetValueFunction&& get_value, DirectHandle<InternalizedString>* out_key) {
// The fast path is only valid as long as we haven't allocated an object
// yet.
DCHECK(object_.is_null());
DirectHandle<Map> previous_map = map_;
bool existing_map_found =
TryFastTransitionToPropertyKey(key_chars, get_key, out_key);
// Unconditionally get the value after getting the transition result.
DirectHandle<Object> value = get_value();
if (existing_map_found) {
// We found a map with a field for our value -- now make sure that field
// is compatible with our value.
if (map_->is_deprecated() || !TryGeneralizeFieldToValue(value)) {
// TODO(leszeks): Try to stay on the fast path if we just deprecate
// here.
map_ = previous_map;
return false;
}
AdvanceToNextProperty();
return true;
}
return TryAddFastPropertyTransitionForValue(*out_key, value);
}
bool JSDataObjectBuilder::TryAddFastPropertyForValue(
DirectHandle<InternalizedString> key, DirectHandle<Object> value) {
if (map_->is_deprecated()) {
// Normally previous TryAddFastPropertyForValue wouldn't allow a deprecated
// map to reach here. However, we could get a deprecated map here if the
// property iterator's GetKey/GetValue calls deprecated the map (e.g.
// through recursive object literal building).
DirectHandle<Map> new_map = Map::Update(isolate_, map_);
// The migration target could be a dictionary map -- we can't rewind to a
// previous map here like we do in the GetValueFunction version of this
// function, or like in TryAddFastPropertyTransitionForValue, so we just
// have to bail out and force CreateAndInitialiseObject deal with it.
if (new_map->is_dictionary_map()) return false;
map_ = new_map;
RecalculateExtraHeapNumbersNeeded();
}
DCHECK(object_.is_null());
DirectHandle<Map> previous_map = map_;
bool existing_map_found = TryFastTransitionToPropertyKey(key);
if (existing_map_found) {
// We found a map with a field for our value -- now make sure that field
// is compatible with our value.
if (map_->is_deprecated() || !TryGeneralizeFieldToValue(value)) {
// TODO(leszeks): Try to stay on the fast path if we just deprecate
// here.
map_ = previous_map;
return false;
}
AdvanceToNextProperty();
return true;
}
return TryAddFastPropertyTransitionForValue(key, value);
}
template <typename ValueIterator>
inline void JSDataObjectBuilder::CreateAndInitialiseObject(
ValueIterator&& value_it, DirectHandle<FixedArrayBase> elements) {
bool needs_migration = false;
if (map_->is_deprecated()) {
// We could get a deprecated map here if a GetKey/GetValue deprecated the
// existing map_. We ideally don't want to create deprecated objects and
// then migrate them, so update the map first.
DirectHandle<Map> new_map = Map::Update(isolate_, map_);
if (current_property_index_ > 0 && new_map->is_dictionary_map()) {
// In edge cases, the migration target might be a dictionary map. We can't
// switch to a dictionary map when current_property_index_ > 0, because
// we'll have already iterated over some keys and we don't persist them
// anywhere but the map's descriptor array, so we have to use the
// deprecated map after all and migrate the object once we're done
// initializing it.
//
// TODO(leszeks): We _could_ immediately allocate a slow JSObject and
// populate it by walking the deprecated map's descriptor array to recover
// the keys -- this is probably overkill for this edge case though.
needs_migration = true;
} else {
map_ = new_map;
RecalculateExtraHeapNumbersNeeded();
}
}
// We've created a map for the first `i` property stack values (which might
// be all of them). We need to write these properties to a newly allocated
// object.
DCHECK(object_.is_null());
if (current_property_index_ < property_count_in_expected_final_map_) {
// If we were on the expected map fast path all the way, but never reached
// the expected final map itself, then finalize the map by rewinding to
// the one whose property is the actual current property index.
//
// TODO(leszeks): Do we actually want to use the final map fast path when
// we know that the current map _can't_ reach the final map? Will we even
// hit this case given that we check for matching instance size?
RewindExpectedFinalMapFastPathToBeforeCurrent();
}
if (map_->is_dictionary_map()) {
// It's only safe to emit a dictionary map when we've not set up any
// properties, as the caller assumes it can set up the first N properties
// as fast data properties.
DCHECK_EQ(current_property_index_, 0);
Handle<JSObject> object = isolate_->factory()->NewSlowJSObjectFromMap(
map_, expected_property_count_);
object->set_elements(*elements);
object_ = object;
return;
}
// The map should have as many own descriptors as the number of properties
// we've created so far...
DCHECK_EQ(current_property_index_, map_->NumberOfOwnDescriptors());
// ... and all of those properties should be in-object data properties.
DCHECK_EQ(current_property_index_,
map_->GetInObjectProperties() - map_->UnusedInObjectProperties());
// Create a folded mutable HeapNumber allocation area before allocating the
// object -- this ensures that there is no allocation between the object
// allocation and its initial fields being initialised, where the verifier
// would see invalid double field state.
FoldedMutableHeapNumberAllocation hn_allocation(isolate_,
extra_heap_numbers_needed_);
// Allocate the object then immediately start a no_gc scope -- again, this
// is so the verifier doesn't see invalid double field state.
Handle<JSObject> object = isolate_->factory()->NewJSObjectFromMap(
map_, AllocationType::kYoung, DirectHandle<AllocationSite>::null(),
NewJSObjectType::kNoEmbedderFieldsAndNoApiWrapper);
{
DisallowGarbageCollection no_gc;
Tagged<JSObject> raw_object = *object;
raw_object->set_elements(*elements);
Tagged<DescriptorArray> descriptors =
raw_object->map()->instance_descriptors();
FoldedMutableHeapNumberAllocator hn_allocator(isolate_, &hn_allocation,
no_gc);
ReadOnlyRoots roots(isolate_);
// Initialize the in-object properties up to the last added property.
int current_property_offset = raw_object->GetInObjectPropertyOffset(0);
for (int i = 0; i < current_property_index_; ++i) {
InternalIndex descriptor_index(i);
Tagged<Object> value = value_it.GetNext();
// See comment in RegisterFieldNeedsFreshHeapNumber, we need to allocate
// HeapNumbers for double representation fields when we can't make
// existing HeapNumbers mutable, or when we only have a Smi value.
if (heap_number_mode_ != kHeapNumbersGuaranteedUniquelyOwned ||
IsSmi(value)) {
PropertyDetails details = descriptors->GetDetails(descriptor_index);
if (details.representation().IsDouble()) {
Float64 d = Float64::hole_nan();
if (IsNumber(value)) {
d = Float64::FromMaybeNaN(Object::NumberValue(value));
}
value = hn_allocator.AllocateNext(roots, d);
}
}
DCHECK(FieldIndex::ForPropertyIndex(object->map(), i).is_inobject());
DCHECK_EQ(current_property_offset,
FieldIndex::ForPropertyIndex(object->map(), i).offset());
DCHECK_EQ(current_property_offset,
object->map()->GetInObjectPropertyOffset(i));
FieldIndex index = FieldIndex::ForInObjectOffset(current_property_offset,
FieldIndex::kTagged);
// Object is the most recent young allocation, so no write barrier
// required.
raw_object->RawFastInobjectPropertyAtPut(index, value,
SKIP_WRITE_BARRIER);
current_property_offset += kTaggedSize;
}
DCHECK_EQ(current_property_offset, object->map()->GetInObjectPropertyOffset(
current_property_index_));
}
if (needs_migration) {
JSObject::MigrateToMap(isolate_, object, Map::Update(isolate_, map_));
}
object_ = object;
}
template <typename Char, typename GetKeyFunction>
bool JSDataObjectBuilder::TryFastTransitionToPropertyKey(
base::Vector<const Char> key_chars, GetKeyFunction&& get_key,
DirectHandle<InternalizedString>* out_key) {
Handle<InternalizedString> expected_key;
DirectHandle<Map> target_map;
InternalIndex descriptor_index(current_property_index_);
if (IsOnExpectedFinalMapFastPath()) {
expected_key =
handle(Cast<InternalizedString>(
expected_final_map_->instance_descriptors()->GetKey(
descriptor_index)),
isolate_);
target_map = expected_final_map_;
} else if (key_chars.data() != nullptr) {
// The byte-compare transition fast path is only valid when `key_chars`
// faithfully represent the decoded key. Callers that cannot provide valid
// chars (the JSON parser passes an empty vector for escaped keys, whose raw
// source bytes differ from the decoded key) fall through to the
// decode-then-FindTransitionToField path below.
TransitionsAccessor transitions(isolate_, *map_);
auto expected_transition = transitions.ExpectedTransition(key_chars);
if (!expected_transition.first.is_null()) {
// Directly read out the target while reading out the key, otherwise it
// might die if `get_key` can allocate.
target_map = expected_transition.second;
*out_key = Cast<InternalizedString>(expected_transition.first);
// We were successful and we are done.
DCHECK_EQ(target_map->instance_descriptors()
->GetDetails(descriptor_index)
.location(),
PropertyLocation::kField);
map_ = target_map;
return true;
}
}
DirectHandle<String> key = *out_key = get_key(expected_key);
if (key.is_identical_to(expected_key)) {
// We were successful and we are done.
DCHECK_EQ(target_map->instance_descriptors()
->GetDetails(descriptor_index)
.location(),
PropertyLocation::kField);
map_ = target_map;
return true;
}
if (IsOnExpectedFinalMapFastPath()) {
// We were on the expected map fast path, but this missed that fast
// path, so rewind the optimistic setting of the current map and disable
// this fast path.
RewindExpectedFinalMapFastPathToBeforeCurrent();
property_count_in_expected_final_map_ = 0;
}
MaybeHandle<Map> maybe_target =
TransitionsAccessor(isolate_, *map_).FindTransitionToField(key);
if (!maybe_target.ToHandle(&target_map)) return false;
map_ = target_map;
return true;
}
bool JSDataObjectBuilder::TryFastTransitionToPropertyKey(
DirectHandle<InternalizedString> key) {
Tagged<InternalizedString> expected_key;
DirectHandle<Map> target_map;
InternalIndex descriptor_index(current_property_index_);
if (IsOnExpectedFinalMapFastPath()) {
expected_key = Cast<InternalizedString>(
expected_final_map_->instance_descriptors()->GetKey(descriptor_index));
target_map = expected_final_map_;
}
if (!expected_key.is_null() && key->Equals(expected_key)) {
DCHECK_EQ(target_map->instance_descriptors()
->GetDetails(descriptor_index)
.location(),
PropertyLocation::kField);
map_ = target_map;
return true;
}
if (IsOnExpectedFinalMapFastPath()) {
RewindExpectedFinalMapFastPathToBeforeCurrent();
property_count_in_expected_final_map_ = 0;
}
MaybeHandle<Map> maybe_target =
TransitionsAccessor(isolate_, *map_).FindTransitionToField(key);
if (!maybe_target.ToHandle(&target_map)) return false;
map_ = target_map;
return true;
}
bool JSDataObjectBuilder::TryAddFastPropertyTransitionForValue(
DirectHandle<InternalizedString> key, DirectHandle<Object> value) {
if (may_have_duplicate_keys_) {
Tagged<DescriptorArray> descriptors = map_->instance_descriptors();
InternalIndex descriptor_number =
descriptors->SearchWithCache(isolate_, *key, *map_);
if (descriptor_number.is_found()) {
return false;
}
} else {
DCHECK(map_->instance_descriptors()
->SearchWithCache(isolate_, *key, *map_)
.is_not_found());
}
if (!TransitionsAccessor::CanHaveMoreTransitions(isolate_, map_)) {
return false;
}
auto [representation, constness] =
Object::OptimalRepresentation(*value, PropertyConstness::kConst);
DirectHandle<FieldType> type =
Object::OptimalType(*value, isolate_, representation);
MaybeHandle<Map> maybe_map =
Map::CopyWithField(isolate_, map_, key, type, NONE, constness,
representation, INSERT_TRANSITION);
DirectHandle<Map> next_map;
if (!maybe_map.ToHandle(&next_map)) return false;
if (next_map->is_dictionary_map()) return false;
if (next_map->is_deprecated()) {
next_map = Map::Update(isolate_, next_map);
// If the migration target is a dictionary map, bail out so that we still
// have a fast map (otherwise we'd lose its keys, since we don't persist
// them anywhere else).
if (next_map->is_dictionary_map()) return false;
map_ = next_map;
RecalculateExtraHeapNumbersNeeded();
} else {
map_ = next_map;
}
if (representation.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber(value);
}
AdvanceToNextProperty();
return true;
}
bool JSDataObjectBuilder::TryGeneralizeFieldToValue(
DirectHandle<Object> value) {
DCHECK_LT(current_property_index_, map_->NumberOfOwnDescriptors());
InternalIndex descriptor_index(current_property_index_);
PropertyDetails current_details =
map_->instance_descriptors()->GetDetails(descriptor_index);
Representation expected_representation = current_details.representation();
DCHECK_EQ(current_details.kind(), PropertyKind::kData);
DCHECK_EQ(current_details.location(), PropertyLocation::kField);
if (!Object::FitsRepresentation(*value, expected_representation)) {
if (IsUninitializedHole(*value)) {
if (expected_representation.IsDouble()) {
// Value is uninitialized, so we'll unconditionally need a new box for
// double fields.
RegisterFieldNeedsFreshHeapNumber();
}
return true;
}
auto [representation, constness] =
Object::OptimalRepresentation(*value, current_details.constness());
representation = representation.generalize(expected_representation);
if (!expected_representation.CanBeInPlaceChangedTo(representation)) {
// Reconfigure the map for the value, deprecating if necessary. This
// will only happen for double representation fields.
if (IsOnExpectedFinalMapFastPath()) {
// If we're on the fast path, we will have advanced the current map
// all the way to the final expected map. Make sure to rewind to the
// "real" current map if this happened.
//
// An alternative would be to deprecate the expected final map,
// migrate it to the new representation, and stay on the fast path.
// However, this would mean allocating all-new maps (with the new
// representation) all the way between the current map and the new
// expected final map; if we later fall off the fast path anyway, then
// all those newly allocated maps will end up unused.
RewindExpectedFinalMapFastPathToIncludeCurrent();
property_count_in_expected_final_map_ = 0;
}
MapUpdater mu(isolate_, map_);
Handle<Map> new_map = mu.ReconfigureToDataField(
descriptor_index, current_details.attributes(), constness,
representation, FieldType::Any(isolate_));
// We only want to stay on the fast path if we got a fast map.
if (new_map->is_dictionary_map()) return false;
map_ = new_map;
if (representation.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber(value);
}
} else {
// Do the in-place reconfiguration.
DCHECK(!representation.IsDouble() || expected_representation.IsDouble());
DirectHandle<FieldType> value_type =
Object::OptimalType(*value, isolate_, representation);
MapUpdater::GeneralizeField(isolate_, map_, descriptor_index, constness,
representation, value_type);
}
} else if (expected_representation.IsHeapObject() &&
!FieldType::NowContains(
map_->instance_descriptors()->GetFieldType(descriptor_index),
value)) {
DirectHandle<FieldType> value_type =
Object::OptimalType(*value, isolate_, expected_representation);
MapUpdater::GeneralizeField(isolate_, map_, descriptor_index,
current_details.constness(),
expected_representation, value_type);
} else if (expected_representation.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber(value);
}
DCHECK(FieldType::NowContains(
map_->instance_descriptors()->GetFieldType(descriptor_index), value));
return true;
}
bool JSDataObjectBuilder::IsOnExpectedFinalMapFastPath() const {
DCHECK_IMPLIES(property_count_in_expected_final_map_ > 0,
!expected_final_map_.is_null());
return current_property_index_ < property_count_in_expected_final_map_;
}
void JSDataObjectBuilder::RegisterFieldNeedsFreshHeapNumber() {
extra_heap_numbers_needed_++;
}
void JSDataObjectBuilder::RegisterFieldNeedsFreshHeapNumber(
DirectHandle<Object> value) {
// We need to allocate a new HeapNumber for double representation fields if
// the HeapNumber values is not guaranteed to be uniquely owned by this
// object (and therefore can't be made mutable), or if the value is a Smi
// and there is no HeapNumber box for this value yet at all.
if (heap_number_mode_ == kHeapNumbersGuaranteedUniquelyOwned &&
!IsSmi(*value)) {
DCHECK(IsHeapNumber(*value));
return;
}
extra_heap_numbers_needed_++;
}
void JSDataObjectBuilder::RecalculateExtraHeapNumbersNeeded() {
extra_heap_numbers_needed_ = 0;
Tagged<DescriptorArray> map_descriptors = map_->instance_descriptors();
for (int i = 0; i < current_property_index_; ++i) {
if (map_descriptors->GetDetails(InternalIndex(i))
.representation()
.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber();
}
}
}
void JSDataObjectBuilder::AdvanceToNextProperty() {
DCHECK(!map_->is_deprecated());
current_property_index_++;
}
} // namespace internal
} // namespace v8
#endif // V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_