From 86b95e30377715377724abe5725589fcbb74eecd Mon Sep 17 00:00:00 2001
From: Paul Rigge
Date: Wed, 25 Mar 2026 13:59:05 -0700
Subject: [PATCH] Implement a handler for XLS->LLVM conversion for the cover
op.
We conditionally invoke no-op inline asm that is marked to be side effecting in an effort to keep an edge in the CFG around for coverage-guided fuzzing.
PiperOrigin-RevId: 889423101
---
xls/jit/BUILD | 24 ++++++++++++++++++++++++
xls/jit/coverage_test.x | 18 ++++++++++++++++++
xls/jit/ir_builder_visitor.cc | 23 +++++++++++++++++++++--
3 files changed, 63 insertions(+), 2 deletions(-)
create mode 100644 xls/jit/coverage_test.x
diff --git a/xls/jit/BUILD b/xls/jit/BUILD
index 83693ad2ee..b5902a51a8 100644
--- a/xls/jit/BUILD
+++ b/xls/jit/BUILD
@@ -1381,12 +1381,36 @@ xls_aot_generate(
with_msan = XLS_IS_MSAN_BUILD,
)
+xls_dslx_library(
+ name = "coverage_test_dslx",
+ srcs = ["coverage_test.x"],
+)
+
+xls_dslx_ir(
+ name = "coverage_test_ir",
+ dslx_top = "run_coverage",
+ ir_conv_args = {"lower_to_proc_scoped_channels": "true"},
+ library = ":coverage_test_dslx",
+)
+
+xls_aot_generate(
+ name = "coverage_test_aot",
+ testonly = True,
+ src = ":coverage_test_ir",
+ enable_llvm_coverage = True,
+ salt_symbols = False,
+ top = "run_coverage",
+ top_type = "FUNCTION",
+ with_msan = XLS_IS_MSAN_BUILD,
+)
+
# Tests that the coverage instrumentation can be enabled.
build_test(
name = "jit_build_test",
targets = [
":multi_function_aot_coverage",
":multi_function_aot_coverage_jobs",
+ ":coverage_test_aot",
],
)
diff --git a/xls/jit/coverage_test.x b/xls/jit/coverage_test.x
new file mode 100644
index 0000000000..b51c11d05a
--- /dev/null
+++ b/xls/jit/coverage_test.x
@@ -0,0 +1,18 @@
+// Copyright 2026 The XLS Authors
+//
+// 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.
+
+pub fn run_coverage(a: u32, b: u32, c: u32) -> u32 {
+ cover!("a_plus_b_gt_c", a + b > c);
+ a + b + c
+}
diff --git a/xls/jit/ir_builder_visitor.cc b/xls/jit/ir_builder_visitor.cc
index 41692e8739..aaee27d8b6 100644
--- a/xls/jit/ir_builder_visitor.cc
+++ b/xls/jit/ir_builder_visitor.cc
@@ -43,6 +43,7 @@
#include "llvm/include/llvm/IR/DerivedTypes.h"
#include "llvm/include/llvm/IR/GEPNoWrapFlags.h"
#include "llvm/include/llvm/IR/IRBuilder.h"
+#include "llvm/include/llvm/IR/InlineAsm.h"
#include "llvm/include/llvm/IR/Instructions.h"
#include "llvm/include/llvm/IR/Intrinsics.h"
#include "llvm/include/llvm/IR/LLVMContext.h"
@@ -323,7 +324,7 @@ std::vector NumberedStrings(std::string_view s, int64_t count) {
return result;
}
-// Returns the concatentation of two vectors.
+// Returns the concatenation of two vectors.
std::vector ConcatVectors(absl::Span a,
absl::Span b) {
std::vector result(a.begin(), a.end());
@@ -2295,8 +2296,26 @@ absl::Status IrBuilderVisitor::HandleCover(Cover* cover) {
// support to the JIT.
XLS_ASSIGN_OR_RETURN(NodeIrContext node_context,
NewNodeIrContext(cover, {"condition"}));
+ std::optional*> exit_builder;
+ if (jit_context_.llvm_compiler().include_llvm_coverage()) {
+ llvm::IRBuilder<>& b = node_context.entry_builder();
+ llvm::Value* cond = Truthiness(node_context.LoadOperand(0), b);
+ LlvmIfThen if_then = CreateIfThen(cond, b, cover->GetName());
+ llvm::FunctionType* ft =
+ llvm::FunctionType::get(llvm::Type::getVoidTy(ctx()), {}, false);
+ // We invoke inline asm (with hasSideEffects=true) to ensure that the
+ // coverage point is not optimized away.
+ llvm::InlineAsm* ia =
+ llvm::InlineAsm::get(ft, absl::StrCat("# coverage: ", cover->label()),
+ "", /*hasSideEffects=*/true);
+ if_then.then_builder->CreateCall(ia, {});
+
+ exit_builder = if_then.Finalize().get();
+ }
+
llvm::Value* token = type_converter()->GetToken();
- return FinalizeNodeIrContextWithValue(std::move(node_context), token);
+ return FinalizeNodeIrContextWithValue(std::move(node_context), token,
+ exit_builder);
}
absl::Status IrBuilderVisitor::HandleDecode(Decode* decode) {