| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #if defined(DART_PRECOMPILER) |
| |
| #include "vm/compiler/aot/dispatch_table_generator.h" |
| |
| #include <memory> |
| |
| #include "vm/compiler/frontend/kernel_translation_helper.h" |
| #include "vm/dispatch_table.h" |
| #include "vm/stub_code.h" |
| #include "vm/thread.h" |
| |
| #define Z zone_ |
| |
| namespace dart { |
| namespace compiler { |
| |
| class Interval { |
| public: |
| Interval() : begin_(-1), end_(-1) {} |
| Interval(int32_t begin, int32_t end) : begin_(begin), end_(end) { |
| ASSERT(end > begin); |
| } |
| |
| int32_t begin() const { return begin_; } |
| void set_begin(int32_t value) { begin_ = value; } |
| |
| int32_t end() const { return end_; } |
| void set_end(int32_t value) { end_ = value; } |
| |
| int32_t length() const { return end_ - begin_; } |
| |
| Interval WithOffset(int32_t offset) const { |
| return Interval(begin_ + offset, end_ + offset); |
| } |
| |
| bool IsSame(const Interval other) const { |
| return end() == other.end() && begin() == other.begin(); |
| } |
| |
| bool IsBefore(const Interval other) const { return end() <= other.begin(); } |
| |
| bool IsAfter(const Interval other) const { return begin() >= other.end(); } |
| |
| bool Overlap(const Interval other) const { |
| return !IsBefore(other) && !IsAfter(other); |
| } |
| |
| bool ContainsBeginOf(const Interval other) const { |
| return begin() <= other.begin() && other.begin() <= end(); |
| } |
| |
| bool ContainsEndOf(const Interval other) const { |
| return begin() <= other.end() && other.end() <= end(); |
| } |
| |
| bool Contains(const Interval other) const { |
| return ContainsBeginOf(other) && ContainsEndOf(other); |
| } |
| |
| void ExtendToIncludeInterval(const Interval& other) { |
| if (other.begin() < begin_) begin_ = other.begin(); |
| if (other.end() > end_) end_ = other.end(); |
| } |
| |
| private: |
| int32_t begin_; |
| int32_t end_; |
| }; |
| |
| class CidInterval { |
| public: |
| CidInterval(classid_t cid, |
| int16_t depth, |
| Interval range, |
| const Function* function) |
| : cid_(cid), depth_(depth), range_(range), function_(function) {} |
| |
| classid_t cid() const { return cid_; } |
| int16_t depth() const { return depth_; } |
| const Interval& range() const { return range_; } |
| Interval& range() { return range_; } |
| const Function* function() const { return function_; } |
| |
| private: |
| classid_t cid_; |
| int16_t depth_; |
| Interval range_; |
| const Function* function_; |
| }; |
| |
| class SelectorRow { |
| public: |
| SelectorRow(Zone* zone, TableSelector* selector) |
| : selector_(selector), |
| class_ranges_(zone, 0), |
| ranges_(zone, 0), |
| code_(Code::ZoneHandle(zone)) {} |
| |
| TableSelector* selector() const { return selector_; } |
| |
| int32_t total_size() const { return total_size_; } |
| |
| const GrowableArray<Interval>& ranges() const { return ranges_; } |
| |
| const GrowableArray<CidInterval>& class_ranges() const { |
| return class_ranges_; |
| } |
| |
| void DefineSelectorImplementationForInterval(classid_t cid, |
| int16_t depth, |
| const Interval& range, |
| const Function* function); |
| bool Finalize(); |
| |
| int32_t CallCount() const { return selector_->call_count; } |
| |
| bool IsAllocated() const { |
| return selector_->offset != SelectorMap::kInvalidSelectorOffset; |
| } |
| |
| void AllocateAt(int32_t offset) { |
| ASSERT(!IsAllocated()); |
| selector_->offset = offset; |
| } |
| |
| void FillTable(ClassTable* class_table, const Array& entries); |
| |
| private: |
| TableSelector* selector_; |
| int32_t total_size_ = 0; |
| |
| GrowableArray<CidInterval> class_ranges_; |
| GrowableArray<Interval> ranges_; |
| Code& code_; |
| }; |
| |
| class RowFitter { |
| public: |
| RowFitter() : first_slot_index_(0) { free_slots_.Add(Interval(0, INT_MAX)); } |
| |
| // Try to fit a row at the specified offset and return whether it was |
| // successful. If successful, the entries taken up by the row are marked |
| // internally as occupied. If unsuccessful, next_offset is set to the next |
| // potential offset where the row might fit. |
| bool TryFit(SelectorRow* row, int32_t offset, int32_t* next_offset); |
| |
| // If the row is not already allocated, try to fit it within the given range |
| // of offsets and allocate it if successful. |
| void FitAndAllocate(SelectorRow* row, |
| int32_t min_offset, |
| int32_t max_offset = INT32_MAX); |
| |
| int32_t TableSize() const { return free_slots_.Last().begin(); } |
| |
| private: |
| intptr_t MoveForwardToCover(const Interval range, intptr_t slot_index); |
| |
| void UpdateFreeSlots(int32_t offset, |
| const GrowableArray<Interval>& ranges, |
| intptr_t slot_index); |
| |
| intptr_t FitInFreeSlot(const Interval range, intptr_t slot_index); |
| |
| GrowableArray<Interval> free_slots_; |
| intptr_t first_slot_index_; |
| }; |
| |
| void SelectorRow::DefineSelectorImplementationForInterval( |
| classid_t cid, |
| int16_t depth, |
| const Interval& range, |
| const Function* function) { |
| CidInterval cid_range(cid, depth, range, function); |
| class_ranges_.Add(cid_range); |
| } |
| |
| bool SelectorRow::Finalize() { |
| if (class_ranges_.length() == 0) { |
| return false; |
| } |
| |
| // Make a list of [begin, end) ranges which are disjunct and cover all |
| // areas that [class_ranges_] cover (i.e. there can be holes, but no overlap). |
| for (intptr_t i = 0; i < class_ranges_.length(); i++) { |
| ranges_.Add(class_ranges_[i].range()); |
| } |
| |
| struct IntervalSorter { |
| static int Compare(const Interval* a, const Interval* b) { |
| if (a->begin() != b->begin()) { |
| return a->begin() - b->begin(); |
| } |
| return b->length() - a->length(); |
| } |
| }; |
| |
| ranges_.Sort(IntervalSorter::Compare); |
| |
| intptr_t current_index = 0; |
| intptr_t write_index = 1; |
| intptr_t read_index = 1; |
| for (; read_index < ranges_.length(); read_index++) { |
| Interval& current_range = ranges_[current_index]; |
| Interval& next_range = ranges_[read_index]; |
| if (current_range.Contains(next_range)) { |
| // We drop the entry. |
| } else if (current_range.end() == next_range.begin()) { |
| // We extend the current entry and drop the entry. |
| current_range.ExtendToIncludeInterval(next_range); |
| } else { |
| // We keep the entry. |
| if (read_index != write_index) { |
| ranges_[write_index] = ranges_[read_index]; |
| } |
| current_index = write_index; |
| write_index++; |
| } |
| } |
| ranges_.TruncateTo(write_index); |
| |
| for (intptr_t i = 0; i < ranges_.length() - 1; i++) { |
| const Interval& a = ranges_[i]; |
| const Interval& b = ranges_[i + 1]; |
| ASSERT(a.begin() < b.begin()); |
| ASSERT(a.end() < b.begin()); |
| } |
| |
| for (intptr_t i = 0; i < ranges_.length(); i++) { |
| total_size_ += ranges_[i].length(); |
| } |
| |
| return true; |
| } |
| |
| void SelectorRow::FillTable(ClassTable* class_table, const Array& entries) { |
| // Define the entries in the table by going top-down, which means more |
| // specific ones will override more general ones. |
| |
| // Sort by depth. |
| struct IntervalSorter { |
| static int Compare(const CidInterval* a, const CidInterval* b) { |
| ASSERT(a == b || a->depth() != b->depth() || |
| !a->range().Overlap(b->range())); |
| return a->depth() - b->depth(); |
| } |
| }; |
| class_ranges_.Sort(IntervalSorter::Compare); |
| |
| for (intptr_t i = 0; i < class_ranges_.length(); i++) { |
| const CidInterval& cid_range = class_ranges_[i]; |
| const Interval& range = cid_range.range(); |
| const Function* function = cid_range.function(); |
| if (function != nullptr && function->HasCode()) { |
| code_ = function->CurrentCode(); |
| for (classid_t cid = range.begin(); cid < range.end(); cid++) { |
| entries.SetAt(selector()->offset + cid, code_); |
| } |
| } |
| } |
| } |
| |
| void RowFitter::FitAndAllocate(SelectorRow* row, |
| int32_t min_offset, |
| int32_t max_offset) { |
| if (row->IsAllocated()) { |
| return; |
| } |
| |
| int32_t next_offset; |
| |
| int32_t offset = min_offset; |
| while (offset <= max_offset && !TryFit(row, offset, &next_offset)) { |
| offset = next_offset; |
| } |
| if (offset <= max_offset) { |
| row->AllocateAt(offset); |
| } |
| } |
| |
| bool RowFitter::TryFit(SelectorRow* row, int32_t offset, int32_t* next_offset) { |
| const GrowableArray<Interval>& ranges = row->ranges(); |
| |
| Interval first_range = ranges[0].WithOffset(offset); |
| if (first_slot_index_ > 0 && |
| free_slots_[first_slot_index_ - 1].end() >= first_range.end()) { |
| // Trying lower offset than last time. Start over in free slots. |
| first_slot_index_ = 0; |
| } |
| first_slot_index_ = MoveForwardToCover(first_range, first_slot_index_); |
| intptr_t slot_index = first_slot_index_; |
| |
| for (intptr_t index = 0; index < ranges.length(); index++) { |
| Interval range = ranges[index].WithOffset(offset); |
| slot_index = MoveForwardToCover(range, slot_index); |
| ASSERT(slot_index < free_slots_.length()); |
| const Interval slot = free_slots_[slot_index]; |
| ASSERT(slot.end() >= range.end()); |
| if (slot.begin() > range.begin()) { |
| *next_offset = offset + slot.begin() - range.begin(); |
| return false; |
| } |
| } |
| |
| UpdateFreeSlots(offset, ranges, first_slot_index_); |
| return true; |
| } |
| |
| intptr_t RowFitter::MoveForwardToCover(const Interval range, |
| intptr_t slot_index) { |
| while (free_slots_[slot_index].end() < range.end()) { |
| slot_index++; |
| } |
| return slot_index; |
| } |
| |
| void RowFitter::UpdateFreeSlots(int32_t offset, |
| const GrowableArray<Interval>& ranges, |
| intptr_t slot_index) { |
| for (intptr_t i = 0; i < ranges.length(); i++) { |
| ASSERT(slot_index < free_slots_.length()); |
| const Interval range = ranges[i].WithOffset(offset); |
| |
| ASSERT(!free_slots_[slot_index].IsAfter(range)); |
| slot_index = MoveForwardToCover(range, slot_index); |
| |
| // Assert that we have a valid slot. |
| ASSERT(slot_index < free_slots_.length()); |
| ASSERT(free_slots_[slot_index].Contains(range)); |
| |
| slot_index = FitInFreeSlot(range, slot_index); |
| } |
| |
| for (intptr_t i = 0; i < free_slots_.length(); i++) { |
| ASSERT(free_slots_[i].begin() < free_slots_[i].end()); |
| } |
| } |
| |
| intptr_t RowFitter::FitInFreeSlot(const Interval range, intptr_t slot_index) { |
| const Interval& slot = free_slots_[slot_index]; |
| ASSERT(slot.Contains(range)); |
| if (slot.begin() < range.begin()) { |
| Interval free_before = Interval(slot.begin(), range.begin()); |
| if (slot.end() > range.end()) { |
| Interval free_after(range.end(), slot.end()); |
| free_slots_[slot_index] = free_before; |
| free_slots_.InsertAt(slot_index + 1, free_after); |
| } else { |
| free_slots_[slot_index] = free_before; |
| slot_index++; |
| } |
| } else if (slot.end() <= range.end()) { |
| ASSERT(slot.IsSame(range)); |
| free_slots_.EraseAt(slot_index); |
| } else { |
| Interval free_after(range.end(), slot.end()); |
| free_slots_[slot_index] = free_after; |
| } |
| return slot_index; |
| } |
| |
| int32_t SelectorMap::SelectorId(const Function& interface_target) const { |
| kernel::ProcedureAttributesMetadata metadata; |
| metadata = kernel::ProcedureAttributesOf(interface_target, Z); |
| return interface_target.IsGetterFunction() || |
| interface_target.IsImplicitGetterFunction() || |
| interface_target.IsMethodExtractor() |
| ? metadata.getter_selector_id |
| : metadata.method_or_setter_selector_id; |
| } |
| |
| const TableSelector* SelectorMap::GetSelector( |
| const Function& interface_target) const { |
| const int32_t sid = SelectorId(interface_target); |
| return GetSelector(sid); |
| } |
| |
| const TableSelector* SelectorMap::GetSelector(int32_t sid) const { |
| if (sid == kInvalidSelectorId) return nullptr; |
| const TableSelector* selector = &selectors_[sid]; |
| if (!selector->IsUsed()) return nullptr; |
| if (selector->offset == kInvalidSelectorOffset) return nullptr; |
| return selector; |
| } |
| |
| void SelectorMap::AddSelector(int32_t call_count, |
| bool called_on_null, |
| bool torn_off) { |
| const int32_t added_sid = selectors_.length(); |
| selectors_.Add(TableSelector(added_sid, call_count, kInvalidSelectorOffset, |
| called_on_null, torn_off)); |
| } |
| |
| void SelectorMap::SetSelectorProperties(int32_t sid, |
| bool on_null_interface, |
| bool requires_args_descriptor) { |
| ASSERT(sid < selectors_.length()); |
| selectors_[sid].on_null_interface |= on_null_interface; |
| selectors_[sid].requires_args_descriptor |= requires_args_descriptor; |
| } |
| |
| DispatchTableGenerator::DispatchTableGenerator(Zone* zone) |
| : zone_(zone), |
| classes_(nullptr), |
| num_selectors_(-1), |
| num_classes_(-1), |
| selector_map_(zone) {} |
| |
| void DispatchTableGenerator::Initialize(ClassTable* table) { |
| classes_ = table; |
| |
| HANDLESCOPE(Thread::Current()); |
| ReadTableSelectorInfo(); |
| NumberSelectors(); |
| SetupSelectorRows(); |
| ComputeSelectorOffsets(); |
| } |
| |
| void DispatchTableGenerator::ReadTableSelectorInfo() { |
| const auto& object_class = Class::Handle(Z, classes_->At(kInstanceCid)); |
| const auto& script = Script::Handle(Z, object_class.script()); |
| const auto& info = KernelProgramInfo::Handle(Z, script.kernel_program_info()); |
| kernel::TableSelectorMetadata* metadata = |
| kernel::TableSelectorMetadataForProgram(info, Z); |
| // Errors out if gen_kernel was run in non-AOT mode or without TFA. |
| if (metadata == nullptr) { |
| FATAL( |
| "Missing table selector metadata!\n" |
| "Probably gen_kernel was run in non-AOT mode or without TFA.\n"); |
| } |
| for (intptr_t i = 0; i < metadata->selectors.length(); i++) { |
| const kernel::TableSelectorInfo* info = &metadata->selectors[i]; |
| selector_map_.AddSelector(info->call_count, info->called_on_null, |
| info->torn_off); |
| } |
| } |
| |
| void DispatchTableGenerator::NumberSelectors() { |
| num_classes_ = classes_->NumCids(); |
| |
| Object& obj = Object::Handle(Z); |
| Class& klass = Class::Handle(Z); |
| Array& functions = Array::Handle(Z); |
| Function& function = Function::Handle(Z); |
| |
| for (classid_t cid = kIllegalCid + 1; cid < num_classes_; cid++) { |
| obj = classes_->At(cid); |
| if (obj.IsClass()) { |
| klass = Class::RawCast(obj.ptr()); |
| functions = klass.current_functions(); |
| if (!functions.IsNull()) { |
| for (intptr_t j = 0; j < functions.Length(); j++) { |
| function ^= functions.At(j); |
| if (function.IsDynamicFunction(/*allow_abstract=*/false)) { |
| const bool on_null_interface = klass.IsObjectClass(); |
| const bool requires_args_descriptor = |
| function.PrologueNeedsArgumentsDescriptor(); |
| // Get assigned selector ID for this function. |
| const int32_t sid = selector_map_.SelectorId(function); |
| if (sid == SelectorMap::kInvalidSelectorId) { |
| // Probably gen_kernel was run in non-AOT mode or without TFA. |
| FATAL("Function has no assigned selector ID.\n"); |
| } |
| selector_map_.SetSelectorProperties(sid, on_null_interface, |
| requires_args_descriptor); |
| } |
| } |
| } |
| } |
| } |
| |
| num_selectors_ = selector_map_.NumIds(); |
| } |
| |
| void DispatchTableGenerator::SetupSelectorRows() { |
| Object& obj = Object::Handle(Z); |
| Class& klass = Class::Handle(Z); |
| Array& functions = Array::Handle(Z); |
| Function& function = Function::Handle(Z); |
| |
| // For each class, we first need to figure out the ranges of cids that will |
| // inherit methods from it (this is due to the fact that cids don't have the |
| // property that they are assigned preorder and don't have holes). |
| |
| // Make a condensed array which stores parent cids. |
| std::unique_ptr<classid_t[]> parent_cids(new classid_t[num_classes_]); |
| std::unique_ptr<bool[]> is_concrete_class(new bool[num_classes_]); |
| for (classid_t cid = kIllegalCid + 1; cid < num_classes_; cid++) { |
| classid_t parent_cid = kIllegalCid; |
| bool concrete = false; |
| if (cid > kIllegalCid) { |
| obj = classes_->At(cid); |
| if (obj.IsClass()) { |
| klass = Class::RawCast(obj.ptr()); |
| concrete = !klass.is_abstract(); |
| klass = klass.SuperClass(); |
| if (!klass.IsNull()) { |
| parent_cid = klass.id(); |
| } |
| } |
| } |
| parent_cids[cid] = parent_cid; |
| is_concrete_class[cid] = concrete; |
| } |
| |
| // Precompute depth level. |
| std::unique_ptr<int16_t[]> cid_depth(new int16_t[num_classes_]); |
| for (classid_t cid = kIllegalCid + 1; cid < num_classes_; cid++) { |
| int16_t depth = 0; |
| classid_t pcid = cid; |
| while (pcid != kIllegalCid) { |
| pcid = parent_cids[pcid]; |
| depth++; |
| } |
| cid_depth[cid] = depth; |
| } |
| |
| // Find all regions that have [cid] as parent (which should include [cid])! |
| std::unique_ptr<GrowableArray<Interval>[]> cid_subclass_ranges( |
| new GrowableArray<Interval>[num_classes_]()); |
| for (classid_t cid = kIllegalCid + 1; cid < num_classes_; cid++) { |
| classid_t start = kIllegalCid; |
| for (classid_t sub_cid = kIllegalCid + 1; sub_cid < num_classes_; |
| sub_cid++) { |
| // Is [sub_cid] a subclass of [cid]? |
| classid_t pcid = sub_cid; |
| while (pcid != kIllegalCid && pcid != cid) { |
| pcid = parent_cids[pcid]; |
| } |
| const bool is_subclass = cid == pcid; |
| const bool in_range = is_subclass && is_concrete_class[sub_cid]; |
| |
| if (start == kIllegalCid && in_range) { |
| start = sub_cid; |
| } else if (start != kIllegalCid && !in_range) { |
| Interval range(start, sub_cid); |
| cid_subclass_ranges[cid].Add(range); |
| start = kIllegalCid; |
| } |
| } |
| if (start != kIllegalCid) { |
| Interval range(start, num_classes_); |
| cid_subclass_ranges[cid].Add(range); |
| } |
| } |
| |
| // Initialize selector rows. |
| SelectorRow* selector_rows = Z->Alloc<SelectorRow>(num_selectors_); |
| for (intptr_t i = 0; i < num_selectors_; i++) { |
| TableSelector* selector = &selector_map_.selectors_[i]; |
| new (&selector_rows[i]) SelectorRow(Z, selector); |
| if (selector->called_on_null && !selector->on_null_interface) { |
| selector_rows[i].DefineSelectorImplementationForInterval( |
| kNullCid, 0, Interval(kNullCid, kNullCid + 1), nullptr); |
| } |
| } |
| |
| // Add implementation intervals to the selector rows for all classes that |
| // have concrete implementations of the selector. |
| for (classid_t cid = kIllegalCid + 1; cid < num_classes_; cid++) { |
| obj = classes_->At(cid); |
| if (obj.IsClass()) { |
| klass = Class::RawCast(obj.ptr()); |
| GrowableArray<Interval>& subclasss_cid_ranges = cid_subclass_ranges[cid]; |
| |
| functions = klass.current_functions(); |
| if (!functions.IsNull()) { |
| const int16_t depth = cid_depth[cid]; |
| for (intptr_t j = 0; j < functions.Length(); j++) { |
| function ^= functions.At(j); |
| if (function.IsDynamicFunction(/*allow_abstract=*/false)) { |
| const int32_t sid = selector_map_.SelectorId(function); |
| |
| if (sid != SelectorMap::kInvalidSelectorId) { |
| auto MakeIntervals = [&](const Function& function, int32_t sid) { |
| // A function handle that survives until the table is built. |
| auto& function_handle = Function::ZoneHandle(Z, function.ptr()); |
| |
| for (intptr_t i = 0; i < subclasss_cid_ranges.length(); i++) { |
| Interval& subclass_cid_range = subclasss_cid_ranges[i]; |
| selector_rows[sid].DefineSelectorImplementationForInterval( |
| cid, depth, subclass_cid_range, &function_handle); |
| } |
| }; |
| MakeIntervals(function, sid); |
| |
| if (selector_map_.selectors_[sid].torn_off) { |
| const String& method_name = String::Handle(Z, function.name()); |
| const String& getter_name = |
| String::Handle(Z, Field::GetterName(method_name)); |
| const Function& tearoff = Function::Handle( |
| Z, function.GetMethodExtractor(getter_name)); |
| const int32_t tearoff_sid = selector_map_.SelectorId(tearoff); |
| |
| if (tearoff_sid != SelectorMap::kInvalidSelectorId) { |
| MakeIntervals(tearoff, tearoff_sid); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Retain all selectors that contain implementation intervals. |
| for (intptr_t i = 0; i < num_selectors_; i++) { |
| const TableSelector& selector = selector_map_.selectors_[i]; |
| if (selector.IsUsed() && selector_rows[i].Finalize()) { |
| table_rows_.Add(&selector_rows[i]); |
| } |
| } |
| } |
| |
| void DispatchTableGenerator::ComputeSelectorOffsets() { |
| ASSERT(table_rows_.length() > 0); |
| |
| RowFitter fitter; |
| |
| // Sort the table rows according to popularity, descending. |
| struct PopularitySorter { |
| static int Compare(SelectorRow* const* a, SelectorRow* const* b) { |
| return (*b)->CallCount() - (*a)->CallCount(); |
| } |
| }; |
| table_rows_.Sort(PopularitySorter::Compare); |
| |
| // Try to allocate at optimal offset. |
| const int32_t optimal_offset = DispatchTable::OriginElement(); |
| for (intptr_t i = 0; i < table_rows_.length(); i++) { |
| fitter.FitAndAllocate(table_rows_[i], optimal_offset, optimal_offset); |
| } |
| |
| // Sort the table rows according to popularity / size, descending. |
| struct PopularitySizeRatioSorter { |
| static int Compare(SelectorRow* const* a, SelectorRow* const* b) { |
| return (*b)->CallCount() * (*a)->total_size() - |
| (*a)->CallCount() * (*b)->total_size(); |
| } |
| }; |
| table_rows_.Sort(PopularitySizeRatioSorter::Compare); |
| |
| // Try to allocate at small offsets. |
| const int32_t max_offset = DispatchTable::LargestSmallOffset(); |
| for (intptr_t i = 0; i < table_rows_.length(); i++) { |
| fitter.FitAndAllocate(table_rows_[i], 0, max_offset); |
| } |
| |
| // Sort the table rows according to size, descending. |
| struct SizeSorter { |
| static int Compare(SelectorRow* const* a, SelectorRow* const* b) { |
| return (*b)->total_size() - (*a)->total_size(); |
| } |
| }; |
| table_rows_.Sort(SizeSorter::Compare); |
| |
| // Allocate remaining rows at large offsets. |
| const int32_t min_large_offset = DispatchTable::LargestSmallOffset() + 1; |
| for (intptr_t i = 0; i < table_rows_.length(); i++) { |
| fitter.FitAndAllocate(table_rows_[i], min_large_offset); |
| } |
| |
| table_size_ = fitter.TableSize(); |
| } |
| |
| ArrayPtr DispatchTableGenerator::BuildCodeArray() { |
| auto& entries = Array::Handle(zone_, Array::New(table_size_, Heap::kOld)); |
| for (intptr_t i = 0; i < table_rows_.length(); i++) { |
| table_rows_[i]->FillTable(classes_, entries); |
| } |
| entries.MakeImmutable(); |
| return entries.ptr(); |
| } |
| |
| } // namespace compiler |
| } // namespace dart |
| |
| #endif // defined(DART_PRECOMPILER) |