| /* |
| * Copyright (C) 2025 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/trace_processor/dataframe/impl/query_plan.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/status.h" |
| #include "perfetto/ext/base/small_vector.h" |
| #include "perfetto/ext/base/status_macros.h" |
| #include "perfetto/ext/base/variant.h" |
| #include "perfetto/public/compiler.h" |
| #include "src/trace_processor/dataframe/impl/bytecode_core.h" |
| #include "src/trace_processor/dataframe/impl/bytecode_instructions.h" |
| #include "src/trace_processor/dataframe/impl/bytecode_registers.h" |
| #include "src/trace_processor/dataframe/impl/slab.h" |
| #include "src/trace_processor/dataframe/impl/types.h" |
| #include "src/trace_processor/dataframe/specs.h" |
| #include "src/trace_processor/dataframe/type_set.h" |
| #include "src/trace_processor/dataframe/types.h" |
| #include "src/trace_processor/util/regex.h" |
| |
| namespace perfetto::trace_processor::dataframe::impl { |
| |
| namespace { |
| |
| // Calculates filter preference score for ordering filters. |
| // Lower scores are applied first for better efficiency. |
| uint32_t FilterPreference(const FilterSpec& fs, const impl::Column& col) { |
| enum AbsolutePreference : uint8_t { |
| kIdEq, // Most efficient: id equality check |
| kSetIdSortedEq, // Set id sorted equality check |
| kIdInequality, // Id inequality check |
| kNumericSortedEq, // Numeric sorted equality check |
| kNumericSortedInequality, // Numeric inequality check |
| kStringSortedEq, // String sorted equality check |
| kStringSortedInequality, // String inequality check |
| kLeastPreferred, // Least preferred |
| }; |
| const auto& op = fs.op; |
| const auto& ct = col.storage.type(); |
| const auto& n = col.null_storage.nullability(); |
| if (n.Is<NonNull>() && ct.Is<Id>() && op.Is<Eq>()) { |
| return kIdEq; |
| } |
| if (n.Is<NonNull>() && ct.Is<Uint32>() && col.sort_state.Is<SetIdSorted>() && |
| op.Is<Eq>()) { |
| return kSetIdSortedEq; |
| } |
| if (n.Is<NonNull>() && ct.Is<Id>() && op.IsAnyOf<InequalityOp>()) { |
| return kIdInequality; |
| } |
| if (n.Is<NonNull>() && col.sort_state.Is<Sorted>() && |
| ct.IsAnyOf<IntegerOrDoubleType>() && op.Is<Eq>()) { |
| return kNumericSortedEq; |
| } |
| if (n.Is<NonNull>() && col.sort_state.Is<Sorted>() && |
| ct.IsAnyOf<IntegerOrDoubleType>() && op.IsAnyOf<InequalityOp>()) { |
| return kNumericSortedInequality; |
| } |
| if (n.Is<NonNull>() && col.sort_state.Is<Sorted>() && ct.Is<String>() && |
| op.Is<Eq>()) { |
| return kStringSortedEq; |
| } |
| if (n.Is<NonNull>() && col.sort_state.Is<Sorted>() && ct.Is<String>() && |
| op.IsAnyOf<InequalityOp>()) { |
| return kStringSortedInequality; |
| } |
| return kLeastPreferred; |
| } |
| |
| // Gets the appropriate bound modifier and range operation type |
| // for a given range operation. |
| std::pair<BoundModifier, EqualRangeLowerBoundUpperBound> GetSortedFilterArgs( |
| const RangeOp& op) { |
| switch (op.index()) { |
| case RangeOp::GetTypeIndex<Eq>(): |
| return std::make_pair(BothBounds{}, EqualRange{}); |
| case RangeOp::GetTypeIndex<Lt>(): |
| return std::make_pair(EndBound{}, LowerBound{}); |
| case RangeOp::GetTypeIndex<Le>(): |
| return std::make_pair(EndBound{}, UpperBound{}); |
| case RangeOp::GetTypeIndex<Gt>(): |
| return std::make_pair(BeginBound{}, UpperBound{}); |
| case RangeOp::GetTypeIndex<Ge>(): |
| return std::make_pair(BeginBound{}, LowerBound{}); |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| |
| // Helper to get byte size of storage types for layout calculation. |
| // Returns 0 for Id type as it's handled specially. |
| uint8_t GetDataSize(StorageType type) { |
| switch (type.index()) { |
| case StorageType::GetTypeIndex<Id>(): |
| case StorageType::GetTypeIndex<Uint32>(): |
| case StorageType::GetTypeIndex<Int32>(): |
| case StorageType::GetTypeIndex<String>(): |
| return sizeof(uint32_t); |
| case StorageType::GetTypeIndex<Int64>(): |
| return sizeof(int64_t); |
| case StorageType::GetTypeIndex<Double>(): |
| return sizeof(double); |
| default: |
| PERFETTO_FATAL("Invalid storage type"); |
| } |
| } |
| |
| SparseNullCollapsedNullability NullabilityToSparseNullCollapsedNullability( |
| Nullability nullability) { |
| switch (nullability.index()) { |
| case Nullability::GetTypeIndex<NonNull>(): |
| return NonNull{}; |
| case Nullability::GetTypeIndex<DenseNull>(): |
| return DenseNull{}; |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountUntilFinalization>(): |
| return SparseNull{}; |
| default: |
| PERFETTO_FATAL("Invalid nullability type"); |
| } |
| } |
| |
| struct BestIndex { |
| uint32_t best_index_idx; |
| std::vector<uint32_t> best_index_specs; |
| }; |
| std::optional<BestIndex> GetBestIndexForFilterSpecs( |
| const QueryPlan::ExecutionParams& params, |
| const std::vector<FilterSpec>& all_specs, |
| const std::vector<uint8_t>& spec_already_handled, |
| const std::vector<Index>& indexes) { |
| // If we have very few rows, there's no point in using an index. |
| if (params.max_row_count <= 1) { |
| return std::nullopt; |
| } |
| uint32_t best_index_idx = std::numeric_limits<uint32_t>::max(); |
| std::vector<uint32_t> best_index_specs; |
| for (uint32_t i = 0; i < indexes.size(); ++i) { |
| const Index& index = indexes[i]; |
| std::vector<uint32_t> current_specs_for_this_index; |
| for (uint32_t column : index.columns()) { |
| bool found_spec_for_column = false; |
| for (uint32_t spec_idx = 0; spec_idx < all_specs.size(); ++spec_idx) { |
| if (spec_already_handled[spec_idx]) { |
| continue; |
| } |
| const FilterSpec& current_spec = all_specs[spec_idx]; |
| if (current_spec.col == column && current_spec.op.Is<Eq>()) { |
| current_specs_for_this_index.push_back(spec_idx); |
| found_spec_for_column = true; |
| break; |
| } |
| } |
| if (!found_spec_for_column) { |
| break; |
| } |
| } |
| if (current_specs_for_this_index.size() > best_index_specs.size()) { |
| best_index_idx = i; |
| best_index_specs = std::move(current_specs_for_this_index); |
| } |
| } |
| if (best_index_idx == std::numeric_limits<uint32_t>::max()) { |
| return std::nullopt; |
| } |
| return BestIndex{best_index_idx, std::move(best_index_specs)}; |
| } |
| |
| } // namespace |
| |
| QueryPlanBuilder::QueryPlanBuilder( |
| uint32_t row_count, |
| const std::vector<std::shared_ptr<Column>>& columns, |
| const std::vector<Index>& indexes) |
| : columns_(columns), indexes_(indexes) { |
| for (uint32_t i = 0; i < columns_.size(); ++i) { |
| column_states_.emplace_back(); |
| } |
| // Setup the maximum and estimated row counts. |
| plan_.params.max_row_count = row_count; |
| plan_.params.estimated_row_count = row_count; |
| |
| // Initialize with a range covering all rows |
| bytecode::reg::RwHandle<Range> range{plan_.params.register_count++}; |
| { |
| using B = bytecode::InitRange; |
| auto& ir = AddOpcode<B>(UnchangedRowCount{}); |
| ir.arg<B::size>() = row_count; |
| ir.arg<B::dest_register>() = range; |
| } |
| indices_reg_ = range; |
| } |
| |
| base::Status QueryPlanBuilder::Filter(std::vector<FilterSpec>& specs) { |
| // Sort filters by efficiency (most selective/cheapest first) |
| std::stable_sort(specs.begin(), specs.end(), |
| [this](const FilterSpec& a, const FilterSpec& b) { |
| const auto& a_col = GetColumn(a.col); |
| const auto& b_col = GetColumn(b.col); |
| return FilterPreference(a, a_col) < |
| FilterPreference(b, b_col); |
| }); |
| |
| std::vector<uint8_t> specs_handled(specs.size(), false); |
| |
| // Phase 1: Handle sorted constraints first |
| for (uint32_t i = 0; i < specs.size(); ++i) { |
| if (specs_handled[i]) { |
| continue; |
| } |
| FilterSpec& c = specs[i]; |
| auto non_null_op = c.op.TryDowncast<NonNullOp>(); |
| if (!non_null_op) { |
| continue; |
| } |
| const Column& col = GetColumn(c.col); |
| if (!TrySortedConstraint(c, col.storage.type(), *non_null_op)) { |
| continue; |
| } |
| specs_handled[i] = true; |
| } |
| |
| // Phase 2: Handle constraints which can use an index. |
| std::optional<BestIndex> best_index = |
| GetBestIndexForFilterSpecs(plan_.params, specs, specs_handled, indexes_); |
| if (best_index) { |
| IndexConstraints(specs, specs_handled, best_index->best_index_idx, |
| best_index->best_index_specs); |
| } |
| |
| // Phase 3: Handle all remaining constraints. |
| for (uint32_t i = 0; i < specs.size(); ++i) { |
| if (specs_handled[i]) { |
| continue; |
| } |
| FilterSpec& c = specs[i]; |
| const Column& col = GetColumn(c.col); |
| StorageType ct = col.storage.type(); |
| |
| if (c.op.Is<In>()) { |
| bytecode::reg::RwHandle<CastFilterValueListResult> value{ |
| plan_.params.register_count++}; |
| { |
| using B = bytecode::CastFilterValueListBase; |
| auto& bc = |
| AddOpcode<B>(bytecode::Index<bytecode::CastFilterValueList>(ct), |
| UnchangedRowCount{}); |
| bc.arg<B::fval_handle>() = {plan_.params.filter_value_count}; |
| bc.arg<B::write_register>() = value; |
| bc.arg<B::op>() = Eq{}; |
| c.value_index = plan_.params.filter_value_count++; |
| } |
| auto update = EnsureIndicesAreInSlab(); |
| PruneNullIndices(c.col, update); |
| auto source = TranslateNonNullIndices(c.col, update, false); |
| { |
| using B = bytecode::InBase; |
| B& bc = AddOpcode<B>(bytecode::Index<bytecode::In>(col.storage.type()), |
| RowCountModifier{NonEqualityFilterRowCount{}}); |
| bc.arg<B::col>() = c.col; |
| bc.arg<B::value_list_register>() = value; |
| bc.arg<B::source_register>() = source; |
| bc.arg<B::update_register>() = update; |
| } |
| MaybeReleaseScratchSpanRegister(); |
| continue; |
| } |
| |
| // Get the non-null operation (all our ops are non-null at this point) |
| auto non_null_op = c.op.TryDowncast<NonNullOp>(); |
| if (!non_null_op) { |
| NullConstraint(*c.op.TryDowncast<NullOp>(), c); |
| continue; |
| } |
| |
| // Handle non-string data types |
| if (const auto& n = ct.TryDowncast<NonStringType>(); n) { |
| if (auto op = c.op.TryDowncast<NonStringOp>(); op) { |
| NonStringConstraint(c, *n, *op, CastFilterValue(c, ct, *non_null_op)); |
| } else { |
| SetGuaranteedToBeEmpty(); |
| } |
| continue; |
| } |
| |
| PERFETTO_CHECK(ct.Is<String>()); |
| auto op = non_null_op->TryDowncast<StringOp>(); |
| PERFETTO_CHECK(op); |
| RETURN_IF_ERROR( |
| StringConstraint(c, *op, CastFilterValue(c, ct, *non_null_op))); |
| } |
| return base::OkStatus(); |
| } |
| |
| void QueryPlanBuilder::Distinct( |
| const std::vector<DistinctSpec>& distinct_specs) { |
| if (distinct_specs.empty()) { |
| return; |
| } |
| std::vector<RowLayoutParams> row_layout_params; |
| row_layout_params.reserve(distinct_specs.size()); |
| for (const auto& spec : distinct_specs) { |
| row_layout_params.push_back({spec.col, false}); |
| } |
| uint16_t total_row_stride = CalculateRowLayoutStride(row_layout_params); |
| bytecode::reg::RwHandle<Span<uint32_t>> indices = EnsureIndicesAreInSlab(); |
| auto buffer_reg = |
| CopyToRowLayout(total_row_stride, indices, {}, row_layout_params); |
| { |
| using B = bytecode::Distinct; |
| auto& bc = AddOpcode<B>(NonEqualityFilterRowCount{}); |
| bc.arg<B::buffer_register>() = buffer_reg; |
| bc.arg<B::total_row_stride>() = total_row_stride; |
| bc.arg<B::indices_register>() = indices; |
| } |
| } |
| |
| void QueryPlanBuilder::Sort(const std::vector<SortSpec>& sort_specs) { |
| if (sort_specs.empty()) { |
| return; |
| } |
| |
| // Optimization: If there's a single sort constraint on a NonNull |
| // column that is already sorted accordingly, skip the sort operation. |
| if (sort_specs.size() == 1) { |
| const auto& single_spec = sort_specs[0]; |
| const Column& col = GetColumn(single_spec.col); |
| if (col.null_storage.nullability().Is<NonNull>() && |
| (col.sort_state.Is<Sorted>() || col.sort_state.Is<IdSorted>() || |
| col.sort_state.Is<SetIdSorted>())) { |
| switch (single_spec.direction) { |
| case SortDirection::kAscending: |
| // The column is NonNull and already sorted as required. |
| return; |
| case SortDirection::kDescending: |
| // The column is NonNull and sorted in the reverse order. Just |
| // reverse the indices to get the correct order. |
| { |
| auto indices = EnsureIndicesAreInSlab(); |
| using B = bytecode::Reverse; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::update_register>() = indices; |
| return; |
| } |
| } |
| } |
| } |
| |
| // main_indices_span will be modified by the final sort operation. |
| // EnsureIndicesAreInSlab makes it an RwHandle. |
| bytecode::reg::RwHandle<Span<uint32_t>> indices = EnsureIndicesAreInSlab(); |
| |
| bool has_string_sort_keys = false; |
| for (const auto& spec : sort_specs) { |
| if (GetColumn(spec.col).storage.type().Is<String>()) { |
| has_string_sort_keys = true; |
| break; |
| } |
| } |
| |
| using Map = bytecode::reg::StringIdToRankMap; |
| bytecode::reg::RwHandle<Map> string_rank_map; |
| if (has_string_sort_keys) { |
| string_rank_map = |
| bytecode::reg::RwHandle<Map>{plan_.params.register_count++}; |
| { |
| using B = bytecode::InitRankMap; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::dest_register>() = string_rank_map; |
| } |
| |
| // For each string column in the sort specification, collect its unique IDs. |
| // This involves preparing a temporary set of indices for that column which |
| // are non-null and translated to storage indices if originally sparse. |
| for (const auto& spec : sort_specs) { |
| const Column& col = GetColumn(spec.col); |
| if (!col.storage.type().Is<String>()) { |
| continue; |
| } |
| |
| bytecode::reg::RwHandle<Span<uint32_t>> translated; |
| if (col.null_storage.nullability().Is<NonNull>()) { |
| // If the column is non-null, we can use the main indices directly. |
| translated = indices; |
| } else { |
| // Get a scratch register to prepare indices for this specific column. |
| // This ensures that the main_indices_span is not modified, allowing |
| // each string column to be processed independently from the original |
| // set of rows. |
| bytecode::reg::RwHandle<Span<uint32_t>> scratch = |
| GetOrCreateScratchSpanRegister(plan_.params.max_row_count); |
| |
| // 1. Copy the current indices to our temporary scratch span. |
| { |
| auto& op = AddOpcode<bytecode::StrideCopy>(UnchangedRowCount{}); |
| op.arg<bytecode::StrideCopy::source_register>() = indices; |
| op.arg<bytecode::StrideCopy::update_register>() = scratch; |
| op.arg<bytecode::StrideCopy::stride>() = 1; |
| } |
| |
| // 2. Prune nulls from this temporary span in-place. |
| PruneNullIndices(spec.col, scratch); |
| |
| // 3. Translate these non-null table indices to storage indices if |
| // necessary. |
| translated = TranslateNonNullIndices(spec.col, scratch, true); |
| PERFETTO_CHECK(translated.index == scratch.index); |
| } |
| |
| // Collect IDs using the prepared (non-null, translated) indices. |
| { |
| using B = bytecode::CollectIdIntoRankMap; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::col>() = spec.col; |
| op.arg<B::source_register>() = translated; |
| op.arg<B::rank_map_register>() = string_rank_map; |
| } |
| |
| // Maybe release the scratch register if we used one. |
| MaybeReleaseScratchSpanRegister(); |
| } |
| |
| // Finalize ranks in the map (sorts keys, updates map values to ranks). |
| // The argument name in the patch for FinalizeRanksInMap was |
| // 'update_register_register'. |
| { |
| using B = bytecode::FinalizeRanksInMap; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::update_register>() = string_rank_map; |
| } |
| } |
| |
| std::vector<RowLayoutParams> row_layout_params; |
| row_layout_params.reserve(sort_specs.size()); |
| for (const auto& spec : sort_specs) { |
| row_layout_params.push_back( |
| {spec.col, columns_[spec.col]->storage.type().Is<String>(), |
| spec.direction == SortDirection::kDescending}); |
| } |
| uint16_t total_row_stride = CalculateRowLayoutStride(row_layout_params); |
| auto buffer_reg = CopyToRowLayout(total_row_stride, indices, string_rank_map, |
| row_layout_params); |
| { |
| using B = bytecode::SortRowLayout; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::buffer_register>() = buffer_reg; |
| op.arg<B::total_row_stride>() = total_row_stride; |
| op.arg<B::indices_register>() = indices; |
| } |
| } |
| |
| void QueryPlanBuilder::MinMax(const SortSpec& sort_spec) { |
| uint32_t col_idx = sort_spec.col; |
| const auto& col = GetColumn(col_idx); |
| StorageType storage_type = col.storage.type(); |
| |
| MinMaxOp mmop = sort_spec.direction == SortDirection::kAscending |
| ? MinMaxOp(MinOp{}) |
| : MinMaxOp(MaxOp{}); |
| |
| auto indices = EnsureIndicesAreInSlab(); |
| using B = bytecode::FindMinMaxIndexBase; |
| auto& op = AddOpcode<B>( |
| bytecode::Index<bytecode::FindMinMaxIndex>(storage_type, mmop), |
| OneRowCount{}); |
| op.arg<B::update_register>() = indices; |
| op.arg<B::col>() = col_idx; |
| } |
| |
| void QueryPlanBuilder::Output(const LimitSpec& limit, uint64_t cols_used) { |
| // Structure to track column and offset pairs |
| struct ColAndOffset { |
| uint32_t col; |
| uint32_t offset; |
| }; |
| |
| base::SmallVector<ColAndOffset, 24> null_cols; |
| plan_.params.output_per_row = 1; |
| for (uint32_t i = 0; i < columns_.size(); ++i) { |
| plan_.col_to_output_offset.emplace_back(); |
| } |
| |
| // Process each column that will be used in the output |
| for (uint32_t i = 0; i < columns_.size(); ++i) { |
| // Any column with index >= 64 uses the 64th bit in cols_used. |
| uint64_t mask = 1ULL << std::min(i, 63u); |
| if ((cols_used & mask) == 0) { |
| continue; |
| } |
| const auto& col = GetColumn(i); |
| switch (col.null_storage.nullability().index()) { |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountUntilFinalization>(): |
| case Nullability::GetTypeIndex<DenseNull>(): { |
| uint32_t offset = plan_.params.output_per_row++; |
| null_cols.emplace_back(ColAndOffset{i, offset}); |
| plan_.col_to_output_offset[i] = offset; |
| break; |
| } |
| case Nullability::GetTypeIndex<NonNull>(): |
| // For non-null columns, we can directly use the indices |
| plan_.col_to_output_offset[i] = 0; |
| break; |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| |
| auto in_memory_indices = EnsureIndicesAreInSlab(); |
| if (limit.limit || limit.offset) { |
| auto o = limit.offset.value_or(0); |
| auto l = limit.limit.value_or(std::numeric_limits<uint32_t>::max()); |
| using B = bytecode::LimitOffsetIndices; |
| auto& bc = AddOpcode<B>(LimitOffsetRowCount{l, o}); |
| bc.arg<B::offset_value>() = o; |
| bc.arg<B::limit_value>() = l; |
| bc.arg<B::update_register>() = in_memory_indices; |
| } |
| |
| bytecode::reg::RwHandle<Span<uint32_t>> storage_update_register; |
| if (plan_.params.output_per_row > 1) { |
| bytecode::reg::RwHandle<Slab<uint32_t>> slab_register{ |
| plan_.params.register_count++}; |
| storage_update_register = |
| bytecode::reg::RwHandle<Span<uint32_t>>{plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::size>() = |
| plan_.params.max_row_count * plan_.params.output_per_row; |
| bc.arg<B::dest_slab_register>() = slab_register; |
| bc.arg<B::dest_span_register>() = storage_update_register; |
| } |
| { |
| using B = bytecode::StrideCopy; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::source_register>() = in_memory_indices; |
| bc.arg<B::update_register>() = storage_update_register; |
| bc.arg<B::stride>() = plan_.params.output_per_row; |
| } |
| for (auto [col, offset] : null_cols) { |
| const auto& c = GetColumn(col); |
| switch (c.null_storage.nullability().index()) { |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex< |
| SparseNullWithPopcountUntilFinalization>(): { |
| using B = bytecode::StrideTranslateAndCopySparseNullIndices; |
| auto reg = PrefixPopcountRegisterFor(col); |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::update_register>() = storage_update_register; |
| bc.arg<B::popcount_register>() = {reg}; |
| bc.arg<B::col>() = col; |
| bc.arg<B::offset>() = offset; |
| bc.arg<B::stride>() = plan_.params.output_per_row; |
| break; |
| } |
| case Nullability::GetTypeIndex<DenseNull>(): { |
| using B = bytecode::StrideCopyDenseNullIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::update_register>() = storage_update_register; |
| bc.arg<B::col>() = col; |
| bc.arg<B::offset>() = offset; |
| bc.arg<B::stride>() = plan_.params.output_per_row; |
| break; |
| } |
| case Nullability::GetTypeIndex<NonNull>(): |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| } else { |
| PERFETTO_CHECK(null_cols.empty()); |
| storage_update_register = in_memory_indices; |
| } |
| plan_.params.output_register = storage_update_register; |
| } |
| |
| QueryPlan QueryPlanBuilder::Build() && { |
| return std::move(plan_); |
| } |
| |
| void QueryPlanBuilder::NonStringConstraint( |
| const FilterSpec& c, |
| const NonStringType& type, |
| const NonStringOp& op, |
| const bytecode::reg::ReadHandle<CastFilterValueResult>& result) { |
| const auto& col = GetColumn(c.col); |
| if (std::holds_alternative<bytecode::reg::RwHandle<Range>>(indices_reg_) && |
| op.Is<Eq>() && col.null_storage.nullability().Is<NonNull>()) { |
| // Non null equality on an id column should have been handled earlier. |
| PERFETTO_CHECK(!type.Is<Id>()); |
| auto non_id_type = type.TryDowncast<NonIdStorageType>(); |
| PERFETTO_CHECK(non_id_type); |
| AddLinearFilterEqBytecode(c, result, *non_id_type); |
| return; |
| } |
| auto update = EnsureIndicesAreInSlab(); |
| PruneNullIndices(c.col, update); |
| auto source = TranslateNonNullIndices(c.col, update, false); |
| { |
| using B = bytecode::NonStringFilterBase; |
| B& bc = AddOpcode<B>( |
| bytecode::Index<bytecode::NonStringFilter>(type, op), |
| op.Is<Eq>() |
| ? RowCountModifier{EqualityFilterRowCount{col.duplicate_state}} |
| : RowCountModifier{NonEqualityFilterRowCount{}}); |
| bc.arg<B::col>() = c.col; |
| bc.arg<B::val_register>() = result; |
| bc.arg<B::source_register>() = source; |
| bc.arg<B::update_register>() = update; |
| } |
| MaybeReleaseScratchSpanRegister(); |
| } |
| |
| base::Status QueryPlanBuilder::StringConstraint( |
| const FilterSpec& c, |
| const StringOp& op, |
| const bytecode::reg::ReadHandle<CastFilterValueResult>& result) { |
| const auto& col = GetColumn(c.col); |
| if (op.Is<Eq>() && |
| std::holds_alternative<bytecode::reg::RwHandle<Range>>(indices_reg_) && |
| col.null_storage.nullability().Is<NonNull>()) { |
| AddLinearFilterEqBytecode(c, result, NonIdStorageType{String{}}); |
| return base::OkStatus(); |
| } |
| if constexpr (!regex::IsRegexSupported()) { |
| if (op.Is<Regex>()) { |
| return base::ErrStatus( |
| "Regex is not supported on non-Unix platforms (e.g. Windows)."); |
| } |
| } |
| auto update = EnsureIndicesAreInSlab(); |
| PruneNullIndices(c.col, update); |
| auto source = TranslateNonNullIndices(c.col, update, false); |
| { |
| using B = bytecode::StringFilterBase; |
| B& bc = AddOpcode<B>( |
| bytecode::Index<bytecode::StringFilter>(op), |
| op.Is<Eq>() |
| ? RowCountModifier{EqualityFilterRowCount{col.duplicate_state}} |
| : RowCountModifier{NonEqualityFilterRowCount{}}); |
| bc.arg<B::col>() = c.col; |
| bc.arg<B::val_register>() = result; |
| bc.arg<B::source_register>() = source; |
| bc.arg<B::update_register>() = update; |
| } |
| MaybeReleaseScratchSpanRegister(); |
| return base::OkStatus(); |
| } |
| |
| void QueryPlanBuilder::NullConstraint(const NullOp& op, FilterSpec& c) { |
| // Even if we don't need this to filter null/non-null, we add it so that |
| // the caller (i.e. SQLite) knows that we are able to handle the constraint. |
| c.value_index = plan_.params.filter_value_count++; |
| |
| const auto& col = GetColumn(c.col); |
| uint32_t nullability_type_index = col.null_storage.nullability().index(); |
| switch (nullability_type_index) { |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountUntilFinalization>(): |
| case Nullability::GetTypeIndex<DenseNull>(): { |
| auto indices = EnsureIndicesAreInSlab(); |
| { |
| using B = bytecode::NullFilterBase; |
| B& bc = AddOpcode<B>(bytecode::Index<bytecode::NullFilter>(op), |
| NonEqualityFilterRowCount{}); |
| bc.arg<B::col>() = c.col; |
| bc.arg<B::update_register>() = indices; |
| } |
| break; |
| } |
| case Nullability::GetTypeIndex<NonNull>(): |
| if (op.Is<IsNull>()) { |
| SetGuaranteedToBeEmpty(); |
| return; |
| } |
| // Nothing to do as the column is non-null. |
| return; |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| |
| void QueryPlanBuilder::IndexConstraints( |
| std::vector<FilterSpec>& specs, |
| std::vector<uint8_t>& specs_handled, |
| uint32_t index_idx, |
| const std::vector<uint32_t>& filter_specs) { |
| bytecode::reg::RwHandle<Span<uint32_t>> reg{plan_.params.register_count++}; |
| { |
| using B = bytecode::IndexPermutationVectorToSpan; |
| auto& bc_alloc = AddOpcode<B>(UnchangedRowCount{}); |
| bc_alloc.arg<B::index>() = index_idx; |
| bc_alloc.arg<B::write_register>() = reg; |
| } |
| |
| for (uint32_t spec_idx : filter_specs) { |
| FilterSpec& fs = specs[spec_idx]; |
| const Column& column = GetColumn(fs.col); |
| auto value_reg = CastFilterValue(fs, column.storage.type(), |
| *fs.op.TryDowncast<NonNullOp>()); |
| auto non_id = column.storage.type().TryDowncast<NonIdStorageType>(); |
| PERFETTO_CHECK(non_id); |
| { |
| using B = bytecode::IndexedFilterEqBase; |
| using PopcountHandle = bytecode::reg::ReadHandle<Slab<uint32_t>>; |
| PopcountHandle popcount_register; |
| if (column.null_storage.nullability().IsAnyOf<SparseNullTypes>()) { |
| popcount_register = PrefixPopcountRegisterFor(fs.col); |
| } else { |
| // Dummy register for non-sparse null columns. IndexedFilterEq knows |
| // how to handle this. |
| popcount_register = bytecode::reg::ReadHandle<Slab<uint32_t>>{ |
| plan_.params.register_count++}; |
| } |
| auto& bc = AddOpcode<B>( |
| bytecode::Index<bytecode::IndexedFilterEq>( |
| *non_id, NullabilityToSparseNullCollapsedNullability( |
| column.null_storage.nullability())), |
| RowCountModifier{EqualityFilterRowCount{column.duplicate_state}}); |
| bc.arg<B::col>() = fs.col; |
| bc.arg<B::filter_value_reg>() = value_reg; |
| bc.arg<B::popcount_register>() = popcount_register; |
| bc.arg<B::update_register>() = reg; |
| } |
| specs_handled[spec_idx] = true; |
| } |
| |
| PERFETTO_CHECK( |
| std::holds_alternative<bytecode::reg::RwHandle<Range>>(indices_reg_)); |
| const auto& indices_reg = |
| base::unchecked_get<bytecode::reg::RwHandle<Range>>(indices_reg_); |
| |
| bytecode::reg::RwHandle<Slab<uint32_t>> output_slab_reg{ |
| plan_.params.register_count++}; |
| bytecode::reg::RwHandle<Span<uint32_t>> output_span_reg{ |
| plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::size>() = plan_.params.max_row_count; |
| bc.arg<B::dest_slab_register>() = output_slab_reg; |
| bc.arg<B::dest_span_register>() = output_span_reg; |
| } |
| { |
| using B = bytecode::CopySpanIntersectingRange; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::source_register>() = reg; |
| bc.arg<B::source_range_register>() = indices_reg; |
| bc.arg<B::update_register>() = output_span_reg; |
| } |
| indices_reg_ = output_span_reg; |
| } |
| |
| bool QueryPlanBuilder::TrySortedConstraint(FilterSpec& fs, |
| const StorageType& ct, |
| const NonNullOp& op) { |
| const auto& col = GetColumn(fs.col); |
| const auto& nullability = col.null_storage.nullability(); |
| if (!nullability.Is<NonNull>() || col.sort_state.Is<Unsorted>()) { |
| return false; |
| } |
| auto range_op = op.TryDowncast<RangeOp>(); |
| if (!range_op) { |
| return false; |
| } |
| |
| // We should have ordered the constraints such that we only reach this |
| // point with range indices. |
| PERFETTO_CHECK( |
| std::holds_alternative<bytecode::reg::RwHandle<Range>>(indices_reg_)); |
| const auto& reg = |
| base::unchecked_get<bytecode::reg::RwHandle<Range>>(indices_reg_); |
| |
| auto value_reg = CastFilterValue(fs, ct, op); |
| |
| // Handle set id equality with a specialized opcode. |
| if (ct.Is<Uint32>() && col.sort_state.Is<SetIdSorted>() && op.Is<Eq>()) { |
| using B = bytecode::Uint32SetIdSortedEq; |
| auto& bc = AddOpcode<B>( |
| RowCountModifier{EqualityFilterRowCount{col.duplicate_state}}); |
| bc.arg<B::col>() = fs.col; |
| bc.arg<B::val_register>() = value_reg; |
| bc.arg<B::update_register>() = reg; |
| return true; |
| } |
| |
| if (col.specialized_storage.Is<SpecializedStorage::SmallValueEq>() && |
| op.Is<Eq>()) { |
| using B = bytecode::SpecializedStorageSmallValueEq; |
| auto& bc = AddOpcode<B>( |
| RowCountModifier{EqualityFilterRowCount{col.duplicate_state}}); |
| bc.arg<B::col>() = fs.col; |
| bc.arg<B::val_register>() = value_reg; |
| bc.arg<B::update_register>() = reg; |
| return true; |
| } |
| |
| const auto& [bound, erlbub] = GetSortedFilterArgs(*range_op); |
| RowCountModifier modifier; |
| if (op.Is<Eq>()) { |
| modifier = EqualityFilterRowCount{col.duplicate_state}; |
| } else { |
| modifier = NonEqualityFilterRowCount{}; |
| } |
| { |
| using B = bytecode::SortedFilterBase; |
| auto& bc = |
| AddOpcode<B>(bytecode::Index<bytecode::SortedFilter>(ct, erlbub), |
| modifier, bytecode::SortedFilterBase::EstimateCost(ct)); |
| bc.arg<B::col>() = fs.col; |
| bc.arg<B::val_register>() = value_reg; |
| bc.arg<B::update_register>() = reg; |
| bc.arg<B::write_result_to>() = bound; |
| } |
| return true; |
| } |
| |
| void QueryPlanBuilder::PruneNullIndices( |
| uint32_t col, |
| bytecode::reg::RwHandle<Span<uint32_t>> indices) { |
| switch (GetColumn(col).null_storage.nullability().index()) { |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountUntilFinalization>(): |
| case Nullability::GetTypeIndex<DenseNull>(): { |
| using B = bytecode::NullFilter<IsNotNull>; |
| bytecode::NullFilterBase& bc = AddOpcode<B>(NonEqualityFilterRowCount{}); |
| bc.arg<B::col>() = col; |
| bc.arg<B::update_register>() = indices; |
| break; |
| } |
| case Nullability::GetTypeIndex<NonNull>(): |
| break; |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| |
| bytecode::reg::RwHandle<Span<uint32_t>> |
| QueryPlanBuilder::TranslateNonNullIndices( |
| uint32_t col, |
| bytecode::reg::RwHandle<Span<uint32_t>> table_indices_register, |
| bool in_place) { |
| switch (GetColumn(col).null_storage.nullability().index()) { |
| case Nullability::GetTypeIndex<SparseNull>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountAlways>(): |
| case Nullability::GetTypeIndex<SparseNullWithPopcountUntilFinalization>(): { |
| auto update = |
| in_place ? table_indices_register |
| : GetOrCreateScratchSpanRegister(plan_.params.max_row_count); |
| auto popcount_reg = PrefixPopcountRegisterFor(col); |
| { |
| using B = bytecode::TranslateSparseNullIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::col>() = col; |
| bc.arg<B::popcount_register>() = popcount_reg; |
| bc.arg<B::source_register>() = table_indices_register; |
| bc.arg<B::update_register>() = update; |
| } |
| return update; |
| } |
| case Nullability::GetTypeIndex<DenseNull>(): |
| case Nullability::GetTypeIndex<NonNull>(): |
| return table_indices_register; |
| default: |
| PERFETTO_FATAL("Unreachable"); |
| } |
| } |
| |
| PERFETTO_NO_INLINE bytecode::reg::RwHandle<Span<uint32_t>> |
| QueryPlanBuilder::EnsureIndicesAreInSlab() { |
| using SpanReg = bytecode::reg::RwHandle<Span<uint32_t>>; |
| using SlabReg = bytecode::reg::RwHandle<Slab<uint32_t>>; |
| |
| if (PERFETTO_LIKELY(std::holds_alternative<SpanReg>(indices_reg_))) { |
| return base::unchecked_get<SpanReg>(indices_reg_); |
| } |
| |
| using RegRange = bytecode::reg::RwHandle<Range>; |
| PERFETTO_DCHECK(std::holds_alternative<RegRange>(indices_reg_)); |
| auto range_reg = base::unchecked_get<RegRange>(indices_reg_); |
| |
| SlabReg slab_reg{plan_.params.register_count++}; |
| SpanReg span_reg{plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::size>() = plan_.params.max_row_count; |
| bc.arg<B::dest_slab_register>() = slab_reg; |
| bc.arg<B::dest_span_register>() = span_reg; |
| } |
| { |
| using B = bytecode::Iota; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::source_register>() = range_reg; |
| bc.arg<B::update_register>() = span_reg; |
| } |
| indices_reg_ = span_reg; |
| return span_reg; |
| } |
| |
| PERFETTO_NO_INLINE bytecode::Bytecode& QueryPlanBuilder::AddRawOpcode( |
| uint32_t option, |
| RowCountModifier rc, |
| bytecode::Cost cost) { |
| static constexpr uint32_t kFixedBytecodeCost = 5; |
| switch (cost.index()) { |
| case base::variant_index<bytecode::Cost, bytecode::FixedCost>(): { |
| const auto& c = base::unchecked_get<bytecode::FixedCost>(cost); |
| plan_.params.estimated_cost += c.cost; |
| break; |
| } |
| case base::variant_index<bytecode::Cost, bytecode::LogPerRowCost>(): { |
| const auto& c = base::unchecked_get<bytecode::LogPerRowCost>(cost); |
| plan_.params.estimated_cost += |
| plan_.params.estimated_row_count == 0 |
| ? kFixedBytecodeCost |
| : c.cost * log2(plan_.params.estimated_row_count); |
| break; |
| } |
| case base::variant_index<bytecode::Cost, bytecode::LinearPerRowCost>(): { |
| const auto& c = base::unchecked_get<bytecode::LinearPerRowCost>(cost); |
| plan_.params.estimated_cost += |
| plan_.params.estimated_row_count == 0 |
| ? kFixedBytecodeCost |
| : c.cost * plan_.params.estimated_row_count; |
| break; |
| } |
| case base::variant_index<bytecode::Cost, bytecode::LogLinearPerRowCost>(): { |
| const auto& c = base::unchecked_get<bytecode::LogLinearPerRowCost>(cost); |
| plan_.params.estimated_cost += |
| plan_.params.estimated_row_count == 0 |
| ? kFixedBytecodeCost |
| : c.cost * plan_.params.estimated_row_count * |
| log2(plan_.params.estimated_row_count); |
| break; |
| } |
| case base::variant_index<bytecode::Cost, |
| bytecode::PostOperationLinearPerRowCost>(): |
| break; |
| default: |
| PERFETTO_FATAL("Unknown cost type"); |
| } |
| switch (rc.index()) { |
| case base::variant_index<RowCountModifier, UnchangedRowCount>(): |
| break; |
| case base::variant_index<RowCountModifier, NonEqualityFilterRowCount>(): |
| if (plan_.params.estimated_row_count > 1) { |
| plan_.params.estimated_row_count = plan_.params.estimated_row_count / 2; |
| } else { |
| // Leave the estimated row count as is if it is already 1 or less. |
| } |
| break; |
| case base::variant_index<RowCountModifier, EqualityFilterRowCount>(): { |
| const auto& eq = base::unchecked_get<EqualityFilterRowCount>(rc); |
| if (eq.duplicate_state.Is<HasDuplicates>()) { |
| if (plan_.params.estimated_row_count > 1) { |
| double new_count = plan_.params.estimated_row_count / |
| (2 * log2(plan_.params.estimated_row_count)); |
| plan_.params.estimated_row_count = |
| std::max(1u, static_cast<uint32_t>(new_count)); |
| } else { |
| // Leave the estimated row count as is if it is already 1 or less. |
| } |
| } else { |
| PERFETTO_CHECK(eq.duplicate_state.Is<NoDuplicates>()); |
| plan_.params.estimated_row_count = |
| std::min(1u, plan_.params.estimated_row_count); |
| plan_.params.max_row_count = std::min(1u, plan_.params.max_row_count); |
| } |
| break; |
| } |
| case base::variant_index<RowCountModifier, OneRowCount>(): |
| plan_.params.estimated_row_count = |
| std::min(1u, plan_.params.estimated_row_count); |
| plan_.params.max_row_count = std::min(1u, plan_.params.max_row_count); |
| break; |
| case base::variant_index<RowCountModifier, ZeroRowCount>(): |
| plan_.params.estimated_row_count = 0; |
| plan_.params.max_row_count = 0; |
| break; |
| case base::variant_index<RowCountModifier, LimitOffsetRowCount>(): { |
| const auto& lc = base::unchecked_get<LimitOffsetRowCount>(rc); |
| |
| // Offset will cut out `offset` rows from the start of indices. |
| uint32_t remove_from_start = |
| std::min(plan_.params.max_row_count, lc.offset); |
| plan_.params.max_row_count -= remove_from_start; |
| |
| // Limit will only preserve at most `limit` rows. |
| plan_.params.max_row_count = |
| std::min(lc.limit, plan_.params.max_row_count); |
| |
| // The max row count is also the best possible estimate we can make for |
| // the row count. |
| plan_.params.estimated_row_count = plan_.params.max_row_count; |
| break; |
| } |
| default: |
| PERFETTO_FATAL("Unknown row count modifier type"); |
| } |
| // Handle all the cost types which need to be calculated *post* the row |
| // estimate update. |
| if (cost.index() == |
| base::variant_index<bytecode::Cost, |
| bytecode::PostOperationLinearPerRowCost>()) { |
| const auto& c = |
| base::unchecked_get<bytecode::PostOperationLinearPerRowCost>(cost); |
| plan_.params.estimated_cost += c.cost * plan_.params.estimated_cost; |
| } |
| plan_.bytecode.emplace_back(); |
| plan_.bytecode.back().option = option; |
| return plan_.bytecode.back(); |
| } |
| |
| void QueryPlanBuilder::SetGuaranteedToBeEmpty() { |
| bytecode::reg::RwHandle<Slab<uint32_t>> slab_reg{ |
| plan_.params.register_count++}; |
| bytecode::reg::RwHandle<Span<uint32_t>> span_reg{ |
| plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(ZeroRowCount{}); |
| bc.arg<B::size>() = 0; |
| bc.arg<B::dest_slab_register>() = slab_reg; |
| bc.arg<B::dest_span_register>() = span_reg; |
| } |
| indices_reg_ = span_reg; |
| } |
| |
| bytecode::reg::ReadHandle<Slab<uint32_t>> |
| QueryPlanBuilder::PrefixPopcountRegisterFor(uint32_t col) { |
| auto& reg = column_states_[col].prefix_popcount; |
| if (!reg) { |
| reg = |
| bytecode::reg::RwHandle<Slab<uint32_t>>{plan_.params.register_count++}; |
| { |
| using B = bytecode::PrefixPopcount; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::col>() = col; |
| bc.arg<B::dest_register>() = *reg; |
| } |
| } |
| return *reg; |
| } |
| |
| bool QueryPlanBuilder::CanUseMinMaxOptimization( |
| const std::vector<SortSpec>& sort_specs, |
| const LimitSpec& limit_spec) { |
| return sort_specs.size() == 1 && |
| GetColumn(sort_specs[0].col) |
| .null_storage.nullability() |
| .Is<NonNull>() && |
| limit_spec.limit == 1 && limit_spec.offset.value_or(0) == 0; |
| } |
| |
| bytecode::reg::ReadHandle<CastFilterValueResult> |
| QueryPlanBuilder::CastFilterValue(FilterSpec& c, |
| const StorageType& ct, |
| NonNullOp op) { |
| bytecode::reg::RwHandle<CastFilterValueResult> value_reg{ |
| plan_.params.register_count++}; |
| { |
| using B = bytecode::CastFilterValueBase; |
| auto& bc = AddOpcode<B>(bytecode::Index<bytecode::CastFilterValue>(ct), |
| UnchangedRowCount{}); |
| bc.arg<B::fval_handle>() = {plan_.params.filter_value_count}; |
| bc.arg<B::write_register>() = value_reg; |
| bc.arg<B::op>() = op; |
| c.value_index = plan_.params.filter_value_count++; |
| } |
| return value_reg; |
| } |
| |
| bytecode::reg::RwHandle<Span<uint32_t>> |
| QueryPlanBuilder::GetOrCreateScratchSpanRegister(uint32_t size) { |
| bytecode::reg::RwHandle<Slab<uint32_t>> scratch_slab; |
| bytecode::reg::RwHandle<Span<uint32_t>> scratch_span; |
| if (scratch_indices_) { |
| PERFETTO_CHECK(size <= scratch_indices_->size); |
| PERFETTO_CHECK(!scratch_indices_->in_use); |
| scratch_slab = scratch_indices_->slab; |
| scratch_span = scratch_indices_->span; |
| } else { |
| scratch_slab = |
| bytecode::reg::RwHandle<Slab<uint32_t>>{plan_.params.register_count++}; |
| scratch_span = |
| bytecode::reg::RwHandle<Span<uint32_t>>{plan_.params.register_count++}; |
| } |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::size>() = size; |
| bc.arg<B::dest_slab_register>() = scratch_slab; |
| bc.arg<B::dest_span_register>() = scratch_span; |
| } |
| scratch_indices_ = ScratchIndices{size, scratch_slab, scratch_span, true}; |
| return scratch_span; |
| } |
| |
| void QueryPlanBuilder::MaybeReleaseScratchSpanRegister() { |
| if (scratch_indices_) { |
| scratch_indices_->in_use = false; |
| } |
| } |
| |
| uint16_t QueryPlanBuilder::CalculateRowLayoutStride( |
| const std::vector<RowLayoutParams>& row_layout_params) { |
| PERFETTO_CHECK(!row_layout_params.empty()); |
| uint16_t calculated_total_row_stride = 0; |
| for (const auto& param : row_layout_params) { |
| const Column& col = GetColumn(param.column); |
| bool is_non_null = col.null_storage.nullability().Is<NonNull>(); |
| calculated_total_row_stride += |
| (is_non_null ? 0u : 1u) + GetDataSize(col.storage.type()); |
| } |
| return calculated_total_row_stride; |
| } |
| |
| bytecode::reg::RwHandle<Slab<uint8_t>> QueryPlanBuilder::CopyToRowLayout( |
| uint16_t row_stride, |
| bytecode::reg::RwHandle<Span<uint32_t>> indices, |
| bytecode::reg::ReadHandle<bytecode::reg::StringIdToRankMap> rank_map, |
| const std::vector<RowLayoutParams>& row_layout_params) { |
| uint32_t buffer_size = plan_.params.max_row_count * row_stride; |
| bytecode::reg::RwHandle<Slab<uint8_t>> new_buffer_reg{ |
| plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateRowLayoutBuffer; |
| auto& op = AddOpcode<B>(UnchangedRowCount{}); |
| op.arg<B::buffer_size>() = buffer_size; |
| op.arg<B::dest_buffer_register>() = new_buffer_reg; |
| } |
| uint16_t current_offset = 0; |
| for (const auto& param : row_layout_params) { |
| const Column& col = GetColumn(param.column); |
| const auto& nullability = col.null_storage.nullability(); |
| auto popcount = nullability.IsAnyOf<SparseNullTypes>() |
| ? PrefixPopcountRegisterFor(param.column) |
| : bytecode::reg::ReadHandle<Slab<uint32_t>>{ |
| std::numeric_limits<uint32_t>::max()}; |
| { |
| using B = bytecode::CopyToRowLayoutBase; |
| auto index = bytecode::Index<bytecode::CopyToRowLayout>( |
| col.storage.type(), |
| NullabilityToSparseNullCollapsedNullability(nullability)); |
| auto& op = AddOpcode<B>(index, UnchangedRowCount{}); |
| op.arg<B::col>() = param.column; |
| op.arg<B::source_indices_register>() = indices; |
| op.arg<B::dest_buffer_register>() = new_buffer_reg; |
| op.arg<B::rank_map_register>() = rank_map; |
| op.arg<B::row_layout_offset>() = current_offset; |
| op.arg<B::row_layout_stride>() = row_stride; |
| op.arg<B::invert_copied_bits>() = param.invert_copied_bits; |
| op.arg<B::popcount_register>() = popcount; |
| } |
| current_offset += |
| (nullability.Is<NonNull>() ? 0u : 1u) + GetDataSize(col.storage.type()); |
| } |
| PERFETTO_CHECK(current_offset == row_stride); |
| return new_buffer_reg; |
| } |
| |
| void QueryPlanBuilder::AddLinearFilterEqBytecode( |
| const FilterSpec& c, |
| const bytecode::reg::ReadHandle<CastFilterValueResult>& |
| filter_value_result_reg, |
| const NonIdStorageType& non_id_storage_type) { |
| const auto& col = GetColumn(c.col); |
| PERFETTO_DCHECK( |
| std::holds_alternative<bytecode::reg::RwHandle<Range>>(indices_reg_)); |
| PERFETTO_DCHECK(col.null_storage.nullability().Is<NonNull>()); |
| PERFETTO_DCHECK(c.op.Is<Eq>()); |
| |
| using SpanReg = bytecode::reg::RwHandle<Span<uint32_t>>; |
| using SlabReg = bytecode::reg::RwHandle<Slab<uint32_t>>; |
| using RegRange = bytecode::reg::RwHandle<Range>; |
| |
| auto range_reg = base::unchecked_get<RegRange>(indices_reg_); |
| SlabReg slab_reg{plan_.params.register_count++}; |
| SpanReg span_reg{plan_.params.register_count++}; |
| { |
| using B = bytecode::AllocateIndices; |
| auto& bc = AddOpcode<B>(UnchangedRowCount{}); |
| bc.arg<B::size>() = plan_.params.max_row_count; |
| bc.arg<B::dest_slab_register>() = slab_reg; |
| bc.arg<B::dest_span_register>() = span_reg; |
| } |
| |
| { |
| using B = bytecode::LinearFilterEqBase; |
| B& bc = AddOpcode<B>( |
| bytecode::Index<bytecode::LinearFilterEq>(non_id_storage_type), |
| RowCountModifier{EqualityFilterRowCount{col.duplicate_state}}); |
| bc.arg<B::col>() = c.col; |
| bc.arg<B::filter_value_reg>() = filter_value_result_reg; |
| // For NonNull columns, popcount_register is not used by LinearFilterEq |
| // logic. Pass a default-constructed handle. |
| bc.arg<B::popcount_register>() = |
| bytecode::reg::ReadHandle<Slab<uint32_t>>{}; |
| bc.arg<B::source_register>() = range_reg; |
| bc.arg<B::update_register>() = span_reg; |
| } |
| indices_reg_ = span_reg; |
| } |
| |
| template <typename T> |
| T& QueryPlanBuilder::AddOpcode(RowCountModifier rc) { |
| return AddOpcode<T>(bytecode::Index<T>(), rc, T::kCost); |
| } |
| |
| } // namespace perfetto::trace_processor::dataframe::impl |