No public description

PiperOrigin-RevId: 933237643
diff --git a/centipede/BUILD b/centipede/BUILD
index f16df04..37d2a1d 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -1074,6 +1074,7 @@
     ":knobs",
     ":mutation_data",
     ":rolling_hash",
+    ":engine_abi",
     ":runner_cmp_trace",
     ":runner_fork_server",
     ":runner_request",
@@ -1231,6 +1232,7 @@
     deps = [
         ":callstack",
         ":dispatcher_flag_helper",
+        ":engine_abi",
         ":execution_metadata",
         ":feature",
         ":foreach_nonzero",
diff --git a/centipede/engine_abi.h b/centipede/engine_abi.h
index 793e707..6361665 100644
--- a/centipede/engine_abi.h
+++ b/centipede/engine_abi.h
@@ -87,21 +87,32 @@
   size_t size;
 } FuzzTestUint64sView;
 
+// Constants for the layout of the coverage feature as a 64-bit unsigned
+// integer:
+//
+//   - Bits 63..59: 5-bit domain ID of the feature. Each domain is a
+//     logically independent feature namespace registered in
+//     `FuzzTestAdapter::SetUpCoverageDomains`.
+//   - Bits 58..32: 27-bit feature ID within the domain.
+//   - Bits 31..0:  32-bit counter value of the feature.
+//
+typedef enum {
+  kFuzzTestCoverageCounterStartBit = 0,
+  kFuzzTestCoverageCounterBitSize = 32,
+  kFuzzTestCoverageFeatureIdStartBit = 32,
+  kFuzzTestCoverageFeatureIdBitSize = 27,
+  kFuzzTestCoverageDomainIdStartBit = 59,
+  kFuzzTestCoverageDomainIdBitSize = 5,
+} FuzzTestCoverageFeatureLayout;
+
 // Sink for execution feedback.
 typedef struct FuzzTestFeedbackSinkCtx FuzzTestFeedbackSinkCtx;
 typedef struct {
   FuzzTestFeedbackSinkCtx* ctx;
 
   // Emits an array of coverage features captured from the execution
-  // inside `Execute` call.
-  //
-  // Each feature is a 64-bit unsigned integer with the following layout:
-  //
-  //   - Bits 63..59: 5-bit domain ID of the feature. Each domain is a
-  //     logically independent feature namespace registered in
-  //     `FuzzTestAdapter::SetUpCoverageDomains`.
-  //   - Bits 58..32: 27-bit feature ID within the domain.
-  //   - Bits 31..0:  32-bit counter value of the feature.
+  // inside `Execute` call. See `FuzzTestCoverageFeatureLayout` for the feature
+  // layout.
   //
   // Multiple emissions are concatenated.
   void (*EmitCoverageFeatures)(FuzzTestFeedbackSinkCtx* ctx,
diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc
index c647eda..19886f8 100644
--- a/centipede/engine_worker.cc
+++ b/centipede/engine_worker.cc
@@ -542,13 +542,6 @@
   }
 }
 
-constexpr size_t kCounterStartBit = 0;
-constexpr size_t kCounterBitSize = 32;
-constexpr size_t kFeatureIdStartBit = 32;
-constexpr size_t kFeatureIdBitSize = 27;
-constexpr size_t kDomainIdStartBit = 59;
-constexpr size_t kDomainIdBitSize = 5;
-
 template <typename T>
 constexpr T Bits(T v, size_t begin, size_t size) {
   return (v >> begin) & ((T{1} << size) - 1);
@@ -560,7 +553,8 @@
   uint8_t feature_id_bit_size;
   uint8_t counter_bit_size;
 };
-std::array<CoverageDomainConfiguration, 1 << kDomainIdBitSize> coverage_domains;
+std::array<CoverageDomainConfiguration, 1 << kFuzzTestCoverageDomainIdBitSize>
+    coverage_domains;
 
 void WorkerDoExecute(const FuzzTestAdapter& adapter) {
   auto* inputs_blobseq = GetInputsBlobSequence();
@@ -579,12 +573,15 @@
         /*ctx=*/nullptr,
         /*Register=*/[](FuzzTestCoverageDomainRegistryCtx* ctx,
                         const FuzzTestCoverageDomain* domain) {
-          WorkerCheck(domain->domain_id < (1 << kDomainIdBitSize),
-                      "domain ID is too large");
-          WorkerCheck(domain->feature_id_bit_size <= kFeatureIdBitSize,
-                      "domain feature id bit size is too large");
-          WorkerCheck(domain->counter_bit_size <= kCounterBitSize,
-                      "domain counter bit size is too large");
+          WorkerCheck(
+              domain->domain_id < (1 << kFuzzTestCoverageDomainIdBitSize),
+              "domain ID is too large");
+          WorkerCheck(
+              domain->feature_id_bit_size <= kFuzzTestCoverageFeatureIdBitSize,
+              "domain feature id bit size is too large");
+          WorkerCheck(
+              domain->counter_bit_size <= kFuzzTestCoverageCounterBitSize,
+              "domain counter bit size is too large");
           WorkerCheck(!coverage_domains[domain->domain_id].registered,
                       "domain ID is already registered");
           coverage_domains[domain->domain_id].registered = true;
@@ -651,14 +648,15 @@
     // Convert to the Centipede feature layout with possible loss.
     for (auto& feature : features) {
       const uint64_t domain_id =
-          Bits(feature, kDomainIdStartBit, kDomainIdBitSize);
+          Bits(feature, kFuzzTestCoverageDomainIdStartBit,
+               kFuzzTestCoverageDomainIdBitSize);
       WorkerCheck(coverage_domains[domain_id].registered,
                   "Emitted features in unregistered domain");
       const auto& domain = coverage_domains[domain_id];
-      uint64_t feature_id =
-          Bits(feature, kFeatureIdStartBit, domain.feature_id_bit_size);
-      uint64_t counter =
-          Bits(feature, kCounterStartBit, domain.counter_bit_size);
+      uint64_t feature_id = Bits(feature, kFuzzTestCoverageFeatureIdStartBit,
+                                 domain.feature_id_bit_size);
+      uint64_t counter = Bits(feature, kFuzzTestCoverageCounterStartBit,
+                              domain.counter_bit_size);
       if (coverage_domains[domain_id].counter_bit_size > 0) {
         // Assume that `domain_id` is one of the scoring domains in Centipede,
         // which uses the lower 6 bits for the counter value. The conversion is
diff --git a/centipede/sancov_runtime.h b/centipede/sancov_runtime.h
index ce3dbab..7368e8b 100644
--- a/centipede/sancov_runtime.h
+++ b/centipede/sancov_runtime.h
@@ -23,6 +23,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "./centipede/engine_abi.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -48,8 +50,28 @@
 // the process.
 //
 // If `reject_input==true`, then it will simply empty the feature array.
+//
+// DEPRECATED: the features here is in the raw Centipede layout instead of
+// the standard engine layout. Use `SanCovRuntimePostProcessCoverage` +
+// `SanCovRuntimeEmitFeatures` to work with the engine.
 struct SanCovRuntimeRawFeatureParts SanCovRuntimeGetCoverage(bool reject_input);
 
+// Registers the SanCov coverage domains. Returns next available domain ID.
+size_t SanCovRuntimeSetUpCoverageDomains(
+    const FuzzTestCoverageDomainRegistry* registry);
+
+// Post-processes all coverage data, and saves it internally for later emission
+// and retrival.
+//
+// If `reject_input==true`, no coverage would be saved.
+void SanCovRuntimePostProcessCoverage(bool reject_input);
+
+// Emits post-processed SanCov coverage features into the engine `sink`.
+//
+// Should be called at most once after `SanCovPostProcessCoverage` - later calls
+// will emit empty coverage.
+void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/centipede/sancov_state.cc b/centipede/sancov_state.cc
index 0718616..1bda588 100644
--- a/centipede/sancov_state.cc
+++ b/centipede/sancov_state.cc
@@ -25,6 +25,7 @@
 
 #include "absl/base/nullability.h"
 #include "./centipede/dispatcher_flag_helper.h"
+#include "./centipede/engine_abi.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/feature.h"
 #include "./centipede/int_utils.h"
@@ -512,6 +513,41 @@
   }
 }
 
+// Left-shifts the first `size` bits of `value` by `shift_by`.
+template <typename T>
+constexpr T GetShiftedBits(T value, size_t size, size_t shift_by) {
+  return (value & ((T{1} << size) - 1)) << shift_by;
+}
+
+void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size) {
+  using ::fuzztest::internal::feature_domains::CMPScoreFeatureIndex;
+  using ::fuzztest::internal::feature_domains::Domain;
+  using ::fuzztest::internal::feature_domains::IsComparisonScoreFeature;
+  using ::fuzztest::internal::feature_domains::kCMPScoreBitmask;
+  using ::fuzztest::internal::feature_domains::kCMPScoreBits;
+
+  for (size_t i = 0; i < size; ++i) {
+    auto& feature = start[i];
+    const auto domain_id = Domain::FeatureToDomainId(feature);
+    auto feature_id = Domain::FeatureToIndexInDomain(feature);
+    if (IsComparisonScoreFeature(feature)) {
+      const auto counter = feature_id & kCMPScoreBitmask;
+      feature_id >>= kCMPScoreBits;
+      feature = GetShiftedBits(domain_id, kFuzzTestCoverageDomainIdBitSize,
+                               kFuzzTestCoverageDomainIdStartBit) |
+                GetShiftedBits(feature_id, kFuzzTestCoverageFeatureIdBitSize,
+                               kFuzzTestCoverageFeatureIdStartBit) |
+                GetShiftedBits(counter, kFuzzTestCoverageCounterBitSize,
+                               kFuzzTestCoverageCounterStartBit);
+      continue;
+    }
+    feature = GetShiftedBits(domain_id, kFuzzTestCoverageDomainIdBitSize,
+                             kFuzzTestCoverageDomainIdStartBit) |
+              GetShiftedBits(feature_id, kFuzzTestCoverageFeatureIdBitSize,
+                             kFuzzTestCoverageFeatureIdStartBit);
+  }
+}
+
 SanCovRuntimeRawFeatureParts SanCovRuntimeGetFeatures() {
   return {fuzztest::internal::sancov_state->g_features.data(),
           fuzztest::internal::sancov_state->g_features.size()};
@@ -541,3 +577,56 @@
 
   return fuzztest::internal::SanCovRuntimeGetFeatures();
 }
+
+size_t SanCovRuntimeSetUpCoverageDomains(
+    const FuzzTestCoverageDomainRegistry* registry) {
+  using ::fuzztest::internal::feature_domains::Domain;
+  using ::fuzztest::internal::feature_domains::kCMPScoreBits;
+  using ::fuzztest::internal::feature_domains::kCMPScoreDomains;
+  using ::fuzztest::internal::feature_domains::kNumDomains;
+
+  static_assert(Domain::kDomainSize == 1 << 27);
+  static_assert(kNumDomains <= 100);
+  static_assert(kCMPScoreBits <= 27);
+  static_assert(kCMPScoreBits <= kFuzzTestCoverageCounterBitSize);
+
+  for (size_t domain_id = 0; domain_id < kNumDomains; ++domain_id) {
+    const bool is_scoring_domain =
+        domain_id >= kCMPScoreDomains.front().domain_id() &&
+        domain_id <= kCMPScoreDomains.back().domain_id();
+    std::string domain_name = "dom";
+    domain_name += '0' + domain_id / 10;
+    domain_name += '0' + domain_id % 10;
+    const FuzzTestCoverageDomain domain = {
+        static_cast<uint8_t>(domain_id),
+        FuzzTestBytesView{
+            reinterpret_cast<const uint8_t*>(domain_name.data()),
+            domain_name.size(),
+        },
+        /*feature_id_bit_size=*/
+        static_cast<uint8_t>(is_scoring_domain ? 27 - kCMPScoreBits : 27),
+        /*counter_bit_size=*/
+        static_cast<uint8_t>(is_scoring_domain ? kCMPScoreBits : 0),
+    };
+    registry->Register(registry->ctx, &domain);
+  }
+
+  return kNumDomains;
+}
+
+void SanCovRuntimePostProcessCoverage(bool reject_input) {
+  fuzztest::internal::PostProcessSancov(reject_input);
+}
+
+void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink) {
+  using ::fuzztest::internal::sancov_state;
+
+  fuzztest::internal::SanCovRuntimeConvertToEngineFeatures(
+      sancov_state->g_features.data(), sancov_state->g_features.size());
+  const FuzzTestUint64sView features = {
+      sancov_state->g_features.data(),
+      sancov_state->g_features.size(),
+  };
+  sink->EmitCoverageFeatures(sink->ctx, &features);
+  sancov_state->g_features.clear();
+}
diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h
index 5ff8d27..bde30e1 100644
--- a/centipede/sancov_state.h
+++ b/centipede/sancov_state.h
@@ -313,6 +313,10 @@
 // Gets the execution metadata gathered in `PostProcessSancov`.
 const ExecutionMetadata& SanCovRuntimeGetExecutionMetadata();
 
+// Convert Centipede features in `{start, start + size}` to the engine feature
+// layout as in `engine_abi.h`.
+void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size);
+
 // Check for stack limit for `stack_usage`, with `is_current_stack` set if it
 // is for the current calling stack.
 __attribute__((weak)) void CheckStackLimit(size_t stack_usage,