| /* | |
| * 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. | |
| */ | |
| #ifndef SRC_TRACE_PROCESSOR_DATAFRAME_IMPL_BYTECODE_INSTRUCTIONS_H_ | |
| #define SRC_TRACE_PROCESSOR_DATAFRAME_IMPL_BYTECODE_INSTRUCTIONS_H_ | |
| #include <cstdint> | |
| #include <string> | |
| #include <variant> | |
| #include "perfetto/base/logging.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_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" | |
| namespace perfetto::trace_processor::dataframe::impl::bytecode { | |
| // Bytecode instructions - each represents a specific operation for query | |
| // execution. | |
| // Initializes a range register with a given size. | |
| struct InitRange : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| size, | |
| reg::WriteHandle<Range>, | |
| dest_register); | |
| }; | |
| // Allocates a slab of indices. | |
| struct AllocateIndices : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{30}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(uint32_t, | |
| size, | |
| reg::WriteHandle<Slab<uint32_t>>, | |
| dest_slab_register, | |
| reg::WriteHandle<Span<uint32_t>>, | |
| dest_span_register); | |
| }; | |
| // Fills a memory region with sequential integers (0...n-1). | |
| struct Iota : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(reg::ReadHandle<Range>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // Base class for casting filter value operations. | |
| struct CastFilterValueBase : TemplatedBytecode1<StorageType> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(FilterValueHandle, | |
| fval_handle, | |
| reg::WriteHandle<CastFilterValueResult>, | |
| write_register, | |
| NonNullOp, | |
| op); | |
| }; | |
| template <typename T> | |
| struct CastFilterValue : CastFilterValueBase { | |
| static_assert(TS1::Contains<T>()); | |
| }; | |
| // Casts a list of filter values. | |
| struct CastFilterValueListBase : TemplatedBytecode1<StorageType> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{1000}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3( | |
| FilterValueHandle, | |
| fval_handle, | |
| reg::WriteHandle<CastFilterValueListResult>, | |
| write_register, | |
| NonNullOp, | |
| op); | |
| }; | |
| template <typename T> | |
| struct CastFilterValueList : CastFilterValueListBase { | |
| static_assert(TS1::Contains<T>()); | |
| }; | |
| // Base for operations on sorted data. | |
| struct SortedFilterBase | |
| : TemplatedBytecode2<StorageType, EqualRangeLowerBoundUpperBound> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost EstimateCost(StorageType type) { | |
| if (type.Is<Id>()) { | |
| return bytecode::FixedCost{20}; | |
| } | |
| return bytecode::LogPerRowCost{10}; | |
| } | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| val_register, | |
| reg::RwHandle<Range>, | |
| update_register, | |
| BoundModifier, | |
| write_result_to); | |
| }; | |
| // Specialized filter for sorted data with specific value type and range | |
| // operation. | |
| template <typename T, typename RangeOp> | |
| struct SortedFilter : SortedFilterBase { | |
| static_assert(TS1::Contains<T>()); | |
| static_assert(TS2::Contains<RangeOp>()); | |
| }; | |
| // Specialized filter for Uint32 columns with SetIdSorted state and equality | |
| // operation. | |
| struct Uint32SetIdSortedEq : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{100}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| val_register, | |
| reg::RwHandle<Range>, | |
| update_register); | |
| }; | |
| // Equality filter for columns with a specialized storage containing | |
| // SmallValueEq. | |
| struct SpecializedStorageSmallValueEq : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| val_register, | |
| reg::RwHandle<Range>, | |
| update_register); | |
| }; | |
| // Filter operations on non-string columns. | |
| struct NonStringFilterBase : TemplatedBytecode2<NonStringType, NonStringOp> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| val_register, | |
| reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename T, typename NonStringOp> | |
| struct NonStringFilter : NonStringFilterBase { | |
| static_assert(TS1::Contains<T>()); | |
| static_assert(TS2::Contains<NonStringOp>()); | |
| }; | |
| // Filter operations on string columns. | |
| struct StringFilterBase : TemplatedBytecode1<StringOp> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{15}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| val_register, | |
| reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename Op> | |
| struct StringFilter : StringFilterBase { | |
| static_assert(TS1::Contains<Op>()); | |
| }; | |
| // Copies data with a given stride. | |
| struct StrideCopy : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{15}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register, | |
| uint32_t, | |
| stride); | |
| }; | |
| // Computes the prefix popcount for the null overlay for a given column. | |
| // | |
| // Popcount means to compute the number of set bits in a word of a BitVector. So | |
| // prefix popcount is a along with a prefix sum over the counts vector. | |
| // | |
| // Note: if `dest_register` already has a value, we'll assume that this bytecode | |
| // has already been executed and skip the computation. This allows for caching | |
| // the result of this bytecode across executions of the interpreter. | |
| struct PrefixPopcount : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{20}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| col, | |
| reg::WriteHandle<Slab<uint32_t>>, | |
| dest_register); | |
| }; | |
| // Translates a set of indices into a sparse null overlay into indices into | |
| // the underlying storage. | |
| // | |
| // Note that every index in the `source_register` is assumed to be a non-null | |
| // index (i.e. the position of a set bit in the null overlay). To accomplish | |
| // this, make sure to first apply a NullFilter with the IsNotNull operator. | |
| // | |
| // `popcount_register` should point to a register containing the result of the | |
| // PrefixPopcount instruction. This is used to significantly accelerate the | |
| // translation. | |
| struct TranslateSparseNullIndices : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<Slab<uint32_t>>, | |
| popcount_register, | |
| reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // Base class for null filter operations. | |
| struct NullFilterBase : TemplatedBytecode1<NullOp> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| col, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // Template specialization for a given null operator. | |
| template <typename NullOp> | |
| struct NullFilter : NullFilterBase { | |
| static_assert(TS1::Contains<NullOp>()); | |
| }; | |
| // A complex opcode which does the following: | |
| // 1. Iterates over indices in `update_register` starting at offset 0 each | |
| // incrementing by `stride` each iteration. | |
| // 2. For each such index, if it's non-null, translates it using the sparse null | |
| // translation logic (see TranslateSparseNullIndices) for the sparse null | |
| // overlay of `col` | |
| // 3. If the index is null, replace it with UINT32_MAX (representing NULL). | |
| // 4. Copies the result of step 2/3 into position `offset` of the current "row" | |
| // of indices in `update_register`. | |
| // | |
| // Necessary for the case where we are trying to build the output indices span | |
| // with all the indices into the storage for each relevant column. | |
| struct StrideTranslateAndCopySparseNullIndices : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_5(uint32_t, | |
| col, | |
| reg::ReadHandle<Slab<uint32_t>>, | |
| popcount_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register, | |
| uint32_t, | |
| offset, | |
| uint32_t, | |
| stride); | |
| }; | |
| // A complex opcode which does the following: | |
| // 1. Iterates over indices in `read_register` starting at offset 0 each | |
| // incrementing by `stride` each iteration. | |
| // 2. For each such index, if it's non-null, just use it as is in step 4. | |
| // 3. If the index is null, replace it with UINT32_MAX (representing NULL). | |
| // 4. Copies the result of step 2/3 into position `offset` of the current "row" | |
| // of indices in `update_register`. | |
| // | |
| // Necessary for the case where we are trying to build the output indices span | |
| // with all the indices into the storage for each relevant column. | |
| struct StrideCopyDenseNullIndices : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register, | |
| uint32_t, | |
| offset, | |
| uint32_t, | |
| stride); | |
| }; | |
| // Allocates a buffer for row layout storage. | |
| struct AllocateRowLayoutBuffer : Bytecode { | |
| static constexpr Cost kCost = FixedCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| buffer_size, | |
| reg::WriteHandle<Slab<uint8_t>>, | |
| dest_buffer_register); | |
| }; | |
| // Copies data for a non-null column into the row layout buffer. | |
| struct CopyToRowLayoutBase | |
| : TemplatedBytecode2<StorageType, SparseNullCollapsedNullability> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_8(uint32_t, | |
| col, | |
| reg::ReadHandle<Span<uint32_t>>, | |
| source_indices_register, | |
| reg::RwHandle<Slab<uint8_t>>, | |
| dest_buffer_register, | |
| uint16_t, | |
| row_layout_offset, | |
| uint16_t, | |
| row_layout_stride, | |
| uint32_t, | |
| invert_copied_bits, | |
| reg::ReadHandle<Slab<uint32_t>>, | |
| popcount_register, | |
| reg::ReadHandle<reg::StringIdToRankMap>, | |
| rank_map_register); | |
| }; | |
| template <typename T, typename Nullability> | |
| struct CopyToRowLayout : CopyToRowLayoutBase { | |
| static_assert(TS1::Contains<T>()); | |
| static_assert(TS2::Contains<Nullability>()); | |
| }; | |
| // Performs distinct operation on row layout buffer using opaque byte | |
| // comparison. | |
| struct Distinct : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{7}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(reg::ReadHandle<Slab<uint8_t>>, | |
| buffer_register, | |
| uint32_t, | |
| total_row_stride, | |
| reg::RwHandle<Span<uint32_t>>, | |
| indices_register); | |
| }; | |
| // Applies an offset to the indices span and limits the rows. | |
| // Modifies the span referenced by `update_register` in place. | |
| // | |
| // Note: `limit_value` = UINT32_MAX means no limit. | |
| struct LimitOffsetIndices : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = PostOperationLinearPerRowCost{2}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(uint32_t, | |
| offset_value, | |
| uint32_t, | |
| limit_value, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // Finds the min/max for a single column. | |
| struct FindMinMaxIndexBase : TemplatedBytecode2<StorageType, MinMaxOp> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{2}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| col, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename T, typename Op> | |
| struct FindMinMaxIndex : FindMinMaxIndexBase { | |
| static_assert(TS1::Contains<T>()); | |
| static_assert(TS2::Contains<Op>()); | |
| }; | |
| // Given an index, creates a span of indices that point to the permutation | |
| // vector of the index. | |
| struct IndexPermutationVectorToSpan : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_2(uint32_t, | |
| index, | |
| reg::WriteHandle<Span<uint32_t>>, | |
| write_register); | |
| }; | |
| // Filters a column which is sorted by the given index with `update_register` | |
| // containing the span of permutation vector to consider. The span is updated | |
| // to only contain the indices which match the filter. | |
| struct IndexedFilterEqBase | |
| : TemplatedBytecode2<NonIdStorageType, SparseNullCollapsedNullability> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LogPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| filter_value_reg, | |
| reg::ReadHandle<Slab<uint32_t>>, | |
| popcount_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename T, typename N> | |
| struct IndexedFilterEq : IndexedFilterEqBase { | |
| static_assert(TS1::Contains<T>()); | |
| static_assert(TS2::Contains<N>()); | |
| }; | |
| // Given a source span and a source range, copies all indices in the span which | |
| // are in bounds in then range to the destiation span. The destination span must | |
| // be large enough to hold all the indices in the source span. | |
| struct CopySpanIntersectingRange : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{5}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::ReadHandle<Range>, | |
| source_range_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // Initializes a new StringIdToRankMap in a destination register. | |
| struct InitRankMap : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = FixedCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_1(reg::WriteHandle<reg::StringIdToRankMap>, | |
| dest_register); | |
| }; | |
| // Collects unique StringPool::Ids from a string column into a | |
| // StringIdToRankMap. Ranks are all initialized to 0. | |
| struct CollectIdIntoRankMap : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(uint32_t, | |
| col, | |
| reg::ReadHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<reg::StringIdToRankMap>, | |
| rank_map_register); | |
| }; | |
| // Takes a RankMap (populated with unique StringPool::Ids and placeholder | |
| // ranks), sorts the IDs lexicographically, and updates the map in-place with | |
| // the final ranks. | |
| struct FinalizeRanksInMap : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LogLinearPerRowCost{20}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_1(reg::RwHandle<reg::StringIdToRankMap>, | |
| update_register); | |
| }; | |
| // Performs a stable sort on indices based on a pre-built row layout buffer. | |
| struct SortRowLayout : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LogLinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_3(reg::ReadHandle<Slab<uint8_t>>, | |
| buffer_register, | |
| uint32_t, | |
| total_row_stride, | |
| reg::RwHandle<Span<uint32_t>>, | |
| indices_register); | |
| }; | |
| // Filters a column with a scan over a linear range of indices. Useful for the | |
| // first equality check of a query where we can scan a column without | |
| // materializing a large set of indices and then using | |
| // NonStringFilter/StringFilter to cut it down. | |
| struct LinearFilterEqBase : TemplatedBytecode1<NonIdStorageType> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{7}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_5(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueResult>, | |
| filter_value_reg, | |
| reg::ReadHandle<Slab<uint32_t>>, | |
| popcount_register, | |
| reg::ReadHandle<Range>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename T> | |
| struct LinearFilterEq : LinearFilterEqBase { | |
| static_assert(TS1::Contains<T>()); | |
| }; | |
| // Filters rows based on a list of values (IN operator). | |
| struct InBase : TemplatedBytecode1<StorageType> { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{10}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_4(uint32_t, | |
| col, | |
| reg::ReadHandle<CastFilterValueListResult>, | |
| value_list_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| source_register, | |
| reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| template <typename T> | |
| struct In : InBase { | |
| static_assert(TS1::Contains<T>()); | |
| }; | |
| // Reverses the order of indices in the given register. | |
| struct Reverse : Bytecode { | |
| // TODO(lalitm): while the cost type is legitimate, the cost estimate inside | |
| // is plucked from thin air and has no real foundation. Fix this by creating | |
| // benchmarks and backing it up with actual data. | |
| static constexpr Cost kCost = LinearPerRowCost{2}; | |
| PERFETTO_DATAFRAME_BYTECODE_IMPL_1(reg::RwHandle<Span<uint32_t>>, | |
| update_register); | |
| }; | |
| // List of all bytecode instruction types for variant definition. | |
| #define PERFETTO_DATAFRAME_BYTECODE_LIST(X) \ | |
| X(InitRange) \ | |
| X(AllocateIndices) \ | |
| X(Iota) \ | |
| X(CastFilterValue<Id>) \ | |
| X(CastFilterValue<Uint32>) \ | |
| X(CastFilterValue<Int32>) \ | |
| X(CastFilterValue<Int64>) \ | |
| X(CastFilterValue<Double>) \ | |
| X(CastFilterValue<String>) \ | |
| X(CastFilterValueList<Id>) \ | |
| X(CastFilterValueList<Uint32>) \ | |
| X(CastFilterValueList<Int32>) \ | |
| X(CastFilterValueList<Int64>) \ | |
| X(CastFilterValueList<Double>) \ | |
| X(CastFilterValueList<String>) \ | |
| X(SortedFilter<Id, EqualRange>) \ | |
| X(SortedFilter<Id, LowerBound>) \ | |
| X(SortedFilter<Id, UpperBound>) \ | |
| X(SortedFilter<Uint32, EqualRange>) \ | |
| X(SortedFilter<Uint32, LowerBound>) \ | |
| X(SortedFilter<Uint32, UpperBound>) \ | |
| X(SortedFilter<Int32, EqualRange>) \ | |
| X(SortedFilter<Int32, LowerBound>) \ | |
| X(SortedFilter<Int32, UpperBound>) \ | |
| X(SortedFilter<Int64, EqualRange>) \ | |
| X(SortedFilter<Int64, LowerBound>) \ | |
| X(SortedFilter<Int64, UpperBound>) \ | |
| X(SortedFilter<Double, EqualRange>) \ | |
| X(SortedFilter<Double, LowerBound>) \ | |
| X(SortedFilter<Double, UpperBound>) \ | |
| X(SortedFilter<String, EqualRange>) \ | |
| X(SortedFilter<String, LowerBound>) \ | |
| X(SortedFilter<String, UpperBound>) \ | |
| X(Uint32SetIdSortedEq) \ | |
| X(SpecializedStorageSmallValueEq) \ | |
| X(LinearFilterEq<Uint32>) \ | |
| X(LinearFilterEq<Int32>) \ | |
| X(LinearFilterEq<Int64>) \ | |
| X(LinearFilterEq<Double>) \ | |
| X(LinearFilterEq<String>) \ | |
| X(NonStringFilter<Id, Eq>) \ | |
| X(NonStringFilter<Id, Ne>) \ | |
| X(NonStringFilter<Id, Lt>) \ | |
| X(NonStringFilter<Id, Le>) \ | |
| X(NonStringFilter<Id, Gt>) \ | |
| X(NonStringFilter<Id, Ge>) \ | |
| X(NonStringFilter<Uint32, Eq>) \ | |
| X(NonStringFilter<Uint32, Ne>) \ | |
| X(NonStringFilter<Uint32, Lt>) \ | |
| X(NonStringFilter<Uint32, Le>) \ | |
| X(NonStringFilter<Uint32, Gt>) \ | |
| X(NonStringFilter<Uint32, Ge>) \ | |
| X(NonStringFilter<Int32, Eq>) \ | |
| X(NonStringFilter<Int32, Ne>) \ | |
| X(NonStringFilter<Int32, Lt>) \ | |
| X(NonStringFilter<Int32, Le>) \ | |
| X(NonStringFilter<Int32, Gt>) \ | |
| X(NonStringFilter<Int32, Ge>) \ | |
| X(NonStringFilter<Int64, Eq>) \ | |
| X(NonStringFilter<Int64, Ne>) \ | |
| X(NonStringFilter<Int64, Lt>) \ | |
| X(NonStringFilter<Int64, Le>) \ | |
| X(NonStringFilter<Int64, Gt>) \ | |
| X(NonStringFilter<Int64, Ge>) \ | |
| X(NonStringFilter<Double, Eq>) \ | |
| X(NonStringFilter<Double, Ne>) \ | |
| X(NonStringFilter<Double, Lt>) \ | |
| X(NonStringFilter<Double, Le>) \ | |
| X(NonStringFilter<Double, Gt>) \ | |
| X(NonStringFilter<Double, Ge>) \ | |
| X(StringFilter<Eq>) \ | |
| X(StringFilter<Ne>) \ | |
| X(StringFilter<Lt>) \ | |
| X(StringFilter<Le>) \ | |
| X(StringFilter<Gt>) \ | |
| X(StringFilter<Ge>) \ | |
| X(StringFilter<Glob>) \ | |
| X(StringFilter<Regex>) \ | |
| X(NullFilter<IsNotNull>) \ | |
| X(NullFilter<IsNull>) \ | |
| X(StrideCopy) \ | |
| X(StrideTranslateAndCopySparseNullIndices) \ | |
| X(StrideCopyDenseNullIndices) \ | |
| X(PrefixPopcount) \ | |
| X(TranslateSparseNullIndices) \ | |
| X(AllocateRowLayoutBuffer) \ | |
| X(CopyToRowLayout<Id, NonNull>) \ | |
| X(CopyToRowLayout<Id, SparseNull>) \ | |
| X(CopyToRowLayout<Id, DenseNull>) \ | |
| X(CopyToRowLayout<Uint32, NonNull>) \ | |
| X(CopyToRowLayout<Uint32, SparseNull>) \ | |
| X(CopyToRowLayout<Uint32, DenseNull>) \ | |
| X(CopyToRowLayout<Int32, NonNull>) \ | |
| X(CopyToRowLayout<Int32, SparseNull>) \ | |
| X(CopyToRowLayout<Int32, DenseNull>) \ | |
| X(CopyToRowLayout<Int64, NonNull>) \ | |
| X(CopyToRowLayout<Int64, SparseNull>) \ | |
| X(CopyToRowLayout<Int64, DenseNull>) \ | |
| X(CopyToRowLayout<Double, NonNull>) \ | |
| X(CopyToRowLayout<Double, SparseNull>) \ | |
| X(CopyToRowLayout<Double, DenseNull>) \ | |
| X(CopyToRowLayout<String, NonNull>) \ | |
| X(CopyToRowLayout<String, SparseNull>) \ | |
| X(CopyToRowLayout<String, DenseNull>) \ | |
| X(Distinct) \ | |
| X(LimitOffsetIndices) \ | |
| X(FindMinMaxIndex<Id, MinOp>) \ | |
| X(FindMinMaxIndex<Id, MaxOp>) \ | |
| X(FindMinMaxIndex<Uint32, MinOp>) \ | |
| X(FindMinMaxIndex<Uint32, MaxOp>) \ | |
| X(FindMinMaxIndex<Int32, MinOp>) \ | |
| X(FindMinMaxIndex<Int32, MaxOp>) \ | |
| X(FindMinMaxIndex<Int64, MinOp>) \ | |
| X(FindMinMaxIndex<Int64, MaxOp>) \ | |
| X(FindMinMaxIndex<Double, MinOp>) \ | |
| X(FindMinMaxIndex<Double, MaxOp>) \ | |
| X(FindMinMaxIndex<String, MinOp>) \ | |
| X(FindMinMaxIndex<String, MaxOp>) \ | |
| X(IndexPermutationVectorToSpan) \ | |
| X(IndexedFilterEq<Uint32, NonNull>) \ | |
| X(IndexedFilterEq<Uint32, SparseNull>) \ | |
| X(IndexedFilterEq<Uint32, DenseNull>) \ | |
| X(IndexedFilterEq<Int32, NonNull>) \ | |
| X(IndexedFilterEq<Int32, SparseNull>) \ | |
| X(IndexedFilterEq<Int32, DenseNull>) \ | |
| X(IndexedFilterEq<Int64, NonNull>) \ | |
| X(IndexedFilterEq<Int64, SparseNull>) \ | |
| X(IndexedFilterEq<Int64, DenseNull>) \ | |
| X(IndexedFilterEq<Double, NonNull>) \ | |
| X(IndexedFilterEq<Double, SparseNull>) \ | |
| X(IndexedFilterEq<Double, DenseNull>) \ | |
| X(IndexedFilterEq<String, NonNull>) \ | |
| X(IndexedFilterEq<String, SparseNull>) \ | |
| X(IndexedFilterEq<String, DenseNull>) \ | |
| X(CopySpanIntersectingRange) \ | |
| X(InitRankMap) \ | |
| X(CollectIdIntoRankMap) \ | |
| X(FinalizeRanksInMap) \ | |
| X(SortRowLayout) \ | |
| X(In<Id>) \ | |
| X(In<Uint32>) \ | |
| X(In<Int32>) \ | |
| X(In<Int64>) \ | |
| X(In<Double>) \ | |
| X(In<String>) \ | |
| X(Reverse) | |
| #define PERFETTO_DATAFRAME_BYTECODE_VARIANT(...) __VA_ARGS__, | |
| // Variant type containing all possible bytecode instructions. | |
| using BytecodeVariant = std::variant<PERFETTO_DATAFRAME_BYTECODE_LIST( | |
| PERFETTO_DATAFRAME_BYTECODE_VARIANT) std::monostate>; | |
| // Gets the variant index for a specific bytecode type. | |
| template <typename T> | |
| constexpr uint32_t Index() { | |
| return base::variant_index<BytecodeVariant, T>(); | |
| } | |
| // Gets bytecode index for a templated type with one type parameter. | |
| template <template <typename> typename T, typename V1> | |
| PERFETTO_ALWAYS_INLINE constexpr uint32_t Index(const V1& f) { | |
| using Start = T<typename V1::template GetTypeAtIndex<0>>; | |
| using End = T<typename V1::template GetTypeAtIndex<V1::kSize - 1>>; | |
| uint32_t offset = Start::OpcodeOffset(f); | |
| if (offset > Index<End>() - Index<Start>()) { | |
| PERFETTO_FATAL("Invalid opcode offset (t1: %u) %u (start: %u, end: %u)", | |
| f.index(), offset, Index<Start>(), Index<End>()); | |
| } | |
| return Index<Start>() + offset; | |
| } | |
| // Gets bytecode index for a templated type with two type parameters. | |
| template <template <typename, typename> typename T, typename V1, typename V2> | |
| PERFETTO_ALWAYS_INLINE constexpr uint32_t Index(const V1& f, const V2& s) { | |
| using Start = T<typename V1::template GetTypeAtIndex<0>, | |
| typename V2::template GetTypeAtIndex<0>>; | |
| using End = T<typename V1::template GetTypeAtIndex<V1::kSize - 1>, | |
| typename V2::template GetTypeAtIndex<V2::kSize - 1>>; | |
| uint32_t offset = Start::OpcodeOffset(f, s); | |
| if (offset > Index<End>() - Index<Start>()) { | |
| PERFETTO_FATAL( | |
| "Invalid opcode offset (t1: %u t2: %u) %u (start: %u, end: %u)", | |
| f.index(), s.index(), offset, Index<Start>(), Index<End>()); | |
| } | |
| return Index<Start>() + offset; | |
| } | |
| // Converts a bytecode instruction to string representation. | |
| inline std::string ToString(const Bytecode& op) { | |
| #define PERFETTO_DATAFRAME_BYTECODE_CASE_TO_STRING(...) \ | |
| case base::variant_index<bytecode::BytecodeVariant, \ | |
| bytecode::__VA_ARGS__>(): { \ | |
| bytecode::__VA_ARGS__ typed_op; \ | |
| typed_op.option = op.option; \ | |
| typed_op.args_buffer = op.args_buffer; \ | |
| return std::string(#__VA_ARGS__) + ": " + typed_op.ToString(); \ | |
| } | |
| switch (op.option) { | |
| PERFETTO_DATAFRAME_BYTECODE_LIST(PERFETTO_DATAFRAME_BYTECODE_CASE_TO_STRING) | |
| default: | |
| PERFETTO_FATAL("Unknown opcode %u", op.option); | |
| } | |
| } | |
| } // namespace perfetto::trace_processor::dataframe::impl::bytecode | |
| #endif // SRC_TRACE_PROCESSOR_DATAFRAME_IMPL_BYTECODE_INSTRUCTIONS_H_ |