| /* |
| * 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_ |