tp: add dense storage to CEngine am: 60314bbfe3

Original change: https://android-review.googlesource.com/c/platform/external/perfetto/+/2863852

Change-Id: Iddeee82ebf79a0e909bb02d9ec83019a5655a626
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index fe9bd0a..ef5737b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10974,6 +10974,7 @@
     name: "perfetto_src_trace_processor_db_storage_storage",
     srcs: [
         "src/trace_processor/db/storage/arrangement_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
         "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/id_storage.cc",
         "src/trace_processor/db/storage/null_storage.cc",
@@ -10990,6 +10991,7 @@
     name: "perfetto_src_trace_processor_db_storage_unittests",
     srcs: [
         "src/trace_processor/db/storage/arrangement_storage_unittest.cc",
+        "src/trace_processor/db/storage/dense_null_storage_unittest.cc",
         "src/trace_processor/db/storage/id_storage_unittest.cc",
         "src/trace_processor/db/storage/null_storage_unittest.cc",
         "src/trace_processor/db/storage/numeric_storage_unittest.cc",
diff --git a/BUILD b/BUILD
index cddab6b..5c8fbe6 100644
--- a/BUILD
+++ b/BUILD
@@ -1302,6 +1302,8 @@
     srcs = [
         "src/trace_processor/db/storage/arrangement_storage.cc",
         "src/trace_processor/db/storage/arrangement_storage.h",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.h",
         "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/dummy_storage.h",
         "src/trace_processor/db/storage/id_storage.cc",
diff --git a/protos/perfetto/trace_processor/serialization.proto b/protos/perfetto/trace_processor/serialization.proto
index e79b7c9..f559bd9 100644
--- a/protos/perfetto/trace_processor/serialization.proto
+++ b/protos/perfetto/trace_processor/serialization.proto
@@ -92,6 +92,12 @@
       optional Storage storage = 2;
     }
 
+    // A schema for serialization of |storage::DenseNullStorage|.
+    message DenseNullStorage {
+      optional BitVector bit_vector = 1;
+      optional Storage storage = 2;
+    }
+
     oneof data {
       DummyStorage dummy_storage = 1;
       IdStorage id_storage = 2;
@@ -101,6 +107,7 @@
       NullStorage null_storage = 6;
       ArrangementStorage arrangement_storage = 7;
       SelectorStorage selector_storage = 8;
+      DenseNullStorage dense_null_storage = 9;
     }
   }
 
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index c77cb38..046aee4 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -28,6 +28,7 @@
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/query_executor.h"
 #include "src/trace_processor/db/storage/arrangement_storage.h"
+#include "src/trace_processor/db/storage/dense_null_storage.h"
 #include "src/trace_processor/db/storage/dummy_storage.h"
 #include "src/trace_processor/db/storage/id_storage.h"
 #include "src/trace_processor/db/storage/null_storage.h"
@@ -170,9 +171,6 @@
                  (c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
                   (int_with_double || double_with_int));
 
-    // Dense columns.
-    use_legacy = use_legacy || col.IsDense();
-
     // Extrinsically sorted columns.
     use_legacy = use_legacy ||
                  (col.IsSorted() && col.overlay().row_map().IsIndexVector());
@@ -255,8 +253,13 @@
       // String columns are inherently nullable: null values are signified
       // with Id::Null().
       PERFETTO_CHECK(col.col_type() != ColumnType::kString);
-      storage = std::make_unique<storage::NullStorage>(std::move(storage),
-                                                       col.storage_base().bv());
+      if (col.IsDense()) {
+        storage = std::make_unique<storage::DenseNullStorage>(
+            std::move(storage), col.storage_base().bv());
+      } else {
+        storage = std::make_unique<storage::NullStorage>(
+            std::move(storage), col.storage_base().bv());
+      }
     }
     if (col.overlay().row_map().IsIndexVector()) {
       storage = std::make_unique<storage::ArrangementStorage>(
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index 55b7eb8..bd80ec7 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -18,6 +18,8 @@
   sources = [
     "arrangement_storage.cc",
     "arrangement_storage.h",
+    "dense_null_storage.cc",
+    "dense_null_storage.h",
     "dummy_storage.cc",
     "dummy_storage.h",
     "id_storage.cc",
@@ -67,6 +69,7 @@
   testonly = true
   sources = [
     "arrangement_storage_unittest.cc",
+    "dense_null_storage_unittest.cc",
     "id_storage_unittest.cc",
     "null_storage_unittest.cc",
     "numeric_storage_unittest.cc",
diff --git a/src/trace_processor/db/storage/dense_null_storage.cc b/src/trace_processor/db/storage/dense_null_storage.cc
new file mode 100644
index 0000000..9f764a4
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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/db/storage/dense_null_storage.h"
+
+#include <cstdint>
+#include <variant>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+DenseNullStorage::DenseNullStorage(std::unique_ptr<Storage> inner,
+                                   const BitVector* non_null)
+    : inner_(std::move(inner)), non_null_(non_null) {}
+
+Storage::SearchValidationResult DenseNullStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector DenseNullStorage::Search(FilterOp op,
+                                          SqlValue sql_val,
+                                          RowMap::Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::Search");
+
+  RangeOrBitVector inner_res = inner_->Search(op, sql_val, in);
+  BitVector res;
+  if (inner_res.IsRange()) {
+    // If the inner storage returns a range, mask out the appropriate values in
+    // |non_null_| which matches the range. Then, resize to |in.end| as this
+    // is mandated by the API contract of |Storage::Search|.
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    PERFETTO_DCHECK(inner_range.end <= in.end);
+    PERFETTO_DCHECK(inner_range.start >= in.start);
+    res = non_null_->IntersectRange(inner_range.start, inner_range.end);
+    res.Resize(in.end, false);
+  } else {
+    res = std::move(inner_res).TakeIfBitVector();
+  }
+  PERFETTO_DCHECK(res.size() == in.end);
+
+  if (op == FilterOp::kIsNull) {
+    // For IS NULL, we need to add any rows in |non_null_| which are zeros: we
+    // do this by taking the appropriate number of rows, inverting it and then
+    // bitwise or-ing the result with it.
+    BitVector non_null_copy = non_null_->Copy();
+    non_null_copy.Resize(in.end);
+    non_null_copy.Not();
+    res.Or(non_null_copy);
+  } else {
+    // For anything else, we just need to ensure that any rows which are null
+    // are removed as they would not match.
+    res.And(*non_null_);
+  }
+  return RangeOrBitVector(std::move(res));
+}
+
+RangeOrBitVector DenseNullStorage::IndexSearch(FilterOp op,
+                                               SqlValue sql_val,
+                                               uint32_t* indices,
+                                               uint32_t indices_size,
+                                               bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::IndexSearch");
+
+  RangeOrBitVector inner_res =
+      inner_->IndexSearch(op, sql_val, indices, indices_size, sorted);
+  if (inner_res.IsRange()) {
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    BitVector::Builder builder(indices_size, inner_range.start);
+    for (uint32_t i = inner_range.start; i < inner_range.end; ++i) {
+      builder.Append(non_null_->IsSet(indices[i]));
+    }
+    return RangeOrBitVector(std::move(builder).Build());
+  }
+
+  BitVector::Builder builder(indices_size);
+  for (uint32_t i = 0; i < indices_size; ++i) {
+    builder.Append(non_null_->IsSet(indices[i]));
+  }
+  BitVector non_null = std::move(builder).Build();
+  PERFETTO_DCHECK(non_null.size() == indices_size);
+
+  BitVector res = std::move(inner_res).TakeIfBitVector();
+  PERFETTO_DCHECK(res.size() == indices_size);
+
+  if (op == FilterOp::kIsNull) {
+    BitVector null = std::move(non_null);
+    null.Not();
+    res.Or(null);
+  } else {
+    res.And(non_null);
+  }
+  return RangeOrBitVector(std::move(res));
+}
+
+void DenseNullStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Serialize(StorageProto* storage) const {
+  auto* null_storage = storage->set_dense_null_storage();
+  non_null_->Serialize(null_storage->set_bit_vector());
+  inner_->Serialize(null_storage->set_storage());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dense_null_storage.h b/src/trace_processor/db/storage/dense_null_storage.h
new file mode 100644
index 0000000..ec7b6e9
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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_DB_STORAGE_DENSE_NULL_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
+
+#include <memory>
+#include <variant>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which introduces the layer of nullability but without changing the
+// "spacing" of the underlying storage i.e. this storage simply "masks" out
+// rows in the underlying storage with nulls.
+class DenseNullStorage : public Storage {
+ public:
+  DenseNullStorage(std::unique_ptr<Storage> inner, const BitVector* non_null);
+
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return non_null_->size(); }
+
+ private:
+  std::unique_ptr<Storage> inner_;
+  const BitVector* non_null_ = nullptr;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
diff --git a/src/trace_processor/db/storage/dense_null_storage_unittest.cc b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
new file mode 100644
index 0000000..d8ec93c
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 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/db/storage/dense_null_storage.h"
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(DenseNullStorage, NoFilteringSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+}
+
+TEST(DenseNullStorage, RestrictInputSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(1, 3));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, RangeFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, Range(1, 3));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, BitvectorFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, IsNullSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
+
+  BitVector bv{1, 0, 0, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kIsNull, SqlValue(), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 4));
+}
+
+TEST(DenseNullStorage, IndexSearch) {
+  std::vector<uint32_t> data{1, 0, 0, 1, 1, 1};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{1, 0, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2, 3));
+}
+
+TEST(DenseNullStorage, IsNullIndexSearch) {
+  auto fake = FakeStorage::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
+
+  BitVector bv{0, 1, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kIsNull, SqlValue(), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 3));
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto