Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions xls/build_rules/tests/fuzz_test_example.x
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#[fuzz_test(domains=`u32:0..100, u32:0..100`)]
fn my_fuzz_property(x: u32, y: u32) -> bool { x + y == y + x }
3 changes: 3 additions & 0 deletions xls/dev_tools/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ cc_library(
srcs = ["extract_interface.cc"],
hdrs = ["extract_interface.h"],
deps = [
"//xls/common:attribute_data",
"//xls/ir",
"//xls/ir:channel",
"//xls/ir:register",
Expand All @@ -127,6 +128,7 @@ cc_library(
"//xls/ir:xls_ir_interface_cc_proto",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status:statusor",
"@com_google_protobuf//:protobuf",
],
)

Expand Down Expand Up @@ -154,6 +156,7 @@ cc_test(
srcs = ["extract_interface_test.cc"],
deps = [
":extract_interface",
"//xls/common:attribute_data",
"//xls/common:proto_test_utils",
"//xls/common:xls_gunit_main",
"//xls/common/status:matchers",
Expand Down
32 changes: 32 additions & 0 deletions xls/dev_tools/extract_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@

#include "xls/dev_tools/extract_interface.h"

#include <string>
#include <variant>

#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "google/protobuf/text_format.h"
#include "xls/common/attribute_data.h"
#include "xls/ir/block.h"
#include "xls/ir/channel.h"
#include "xls/ir/function.h"
Expand Down Expand Up @@ -61,6 +66,33 @@ PackageInterfaceProto::Function ExtractFunctionInterface(Function* func) {
AddNamed(proto.add_parameters(), param);
}
*proto.mutable_result_type() = func->GetType()->return_type()->ToProto();

if (func->HasAttribute(AttributeKind::kFuzzTest)) {
for (const auto& attr : func->attributes()) {
if (attr.kind() == AttributeKind::kFuzzTest) {
for (const auto& arg : attr.args()) {
CHECK(std::holds_alternative<AttributeData::StringKeyValueArgument>(
arg))
<< "kFuzzTest argument must be StringKeyValueArgument";
const auto& skv =
std::get<AttributeData::StringKeyValueArgument>(arg);
CHECK_EQ(skv.first, "domains")
<< "kFuzzTest only supports 'domains' argument";
CHECK(skv.is_backticked)
<< "kFuzzTest domains argument must be backticked";

std::string proto_str = skv.second;
PackageInterfaceProto::Function temp_func;
CHECK(google::protobuf::TextFormat::ParseFromString(proto_str, &temp_func))
<< "Failed to parse fuzz test domains proto from attribute";
proto.mutable_parameter_domains()->CopyFrom(
temp_func.parameter_domains());
}
break;
}
}
}

return proto;
}

Expand Down
100 changes: 100 additions & 0 deletions xls/dev_tools/extract_interface_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
#include "xls/dev_tools/extract_interface.h"

#include <string_view>
#include <utility>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "xls/common/attribute_data.h"
#include "xls/common/proto_test_utils.h"
#include "xls/common/status/matchers.h"
#include "xls/ir/bits.h"
Expand Down Expand Up @@ -164,5 +167,102 @@ TEST_F(ExtractInterfaceTest, BasicBlock) {
)pb"));
}

TEST_F(ExtractInterfaceTest, FuzzTestFunction) {
constexpr std::string_view kIr = R"(
package test

#[fuzz_test(domains = `parameter_domains { range { min { bits { bit_count: 32 data: "\000" } } max { bits { bit_count: 32 data: "\012" } } } } parameter_domains { arbitrary: true }`)]
fn f(x: bits[32], y: bits[32]) -> bits[32] {
ret x: bits[32] = param(name=x)
}
)";
XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(kIr));

PackageInterfaceProto proto = ExtractPackageInterface(p.get());

ASSERT_EQ(proto.functions().size(), 1);
const auto& func_proto = proto.functions(0);

ASSERT_EQ(func_proto.parameter_domains().size(), 2);
EXPECT_TRUE(func_proto.parameter_domains(1).arbitrary());
EXPECT_TRUE(func_proto.parameter_domains(0).has_range());
}

TEST_F(ExtractInterfaceTest, FuzzTestFunctionNoDomains) {
constexpr std::string_view kIr = R"(
package test

#[fuzz_test]
fn f(x: bits[32]) -> bits[32] {
ret x: bits[32] = param(name=x)
}
)";
XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(kIr));

PackageInterfaceProto proto = ExtractPackageInterface(p.get());

ASSERT_EQ(proto.functions().size(), 1);
const auto& func_proto = proto.functions(0);

EXPECT_THAT(func_proto.parameter_domains(), testing::IsEmpty());
}

TEST_F(ExtractInterfaceTest, FuzzTestFunctionInvalidArgKeyManual) {
VerifiedPackage p("test_package");
Function* f;
{
FunctionBuilder fb("f", &p);
fb.Param("x", p.GetBitsType(32));
XLS_ASSERT_OK_AND_ASSIGN(f, fb.Build());
}

std::vector<AttributeData::Argument> args;
args.push_back(AttributeData::StringKeyValueArgument{
.first = "invalid", .second = "value", .is_backticked = true});
f->AddAttribute(AttributeData(AttributeKind::kFuzzTest, std::move(args)));

EXPECT_DEATH(ExtractPackageInterface(&p),
"kFuzzTest only supports 'domains' argument");
}

TEST_F(ExtractInterfaceTest, PackageWithMixOfFuzzAndNonFuzzFunctions) {
constexpr std::string_view kIr = R"(
package test

#[fuzz_test(domains = `parameter_domains { arbitrary: true }`)]
fn fuzz_me(x: bits[32]) -> bits[32] {
ret x: bits[32] = param(name=x)
}

fn dont_fuzz_me(x: bits[32]) -> bits[32] {
ret x: bits[32] = param(name=x)
}
)";
XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(kIr));

PackageInterfaceProto proto = ExtractPackageInterface(p.get());

ASSERT_EQ(proto.functions().size(), 2);

const PackageInterfaceProto::Function* fuzz_proto = nullptr;
const PackageInterfaceProto::Function* non_fuzz_proto = nullptr;

for (const auto& f : proto.functions()) {
if (f.base().name() == "fuzz_me") {
fuzz_proto = &f;
} else if (f.base().name() == "dont_fuzz_me") {
non_fuzz_proto = &f;
}
}

ASSERT_NE(fuzz_proto, nullptr);
ASSERT_NE(non_fuzz_proto, nullptr);

EXPECT_EQ(fuzz_proto->parameter_domains().size(), 1);
EXPECT_TRUE(fuzz_proto->parameter_domains(0).arbitrary());

EXPECT_TRUE(non_fuzz_proto->parameter_domains().empty());
}

} // namespace
} // namespace xls
5 changes: 5 additions & 0 deletions xls/dslx/ir_convert/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ cc_library(
":ir_conversion_utils",
":proc_config_ir_converter",
":proc_scoped_channel_scope",
"//xls/common:attribute_data",
"//xls/common:visitor",
"//xls/common/status:ret_check",
"//xls/common/status:status_macros",
Expand Down Expand Up @@ -383,6 +384,7 @@ cc_library(
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
"@com_google_absl//absl/types:variant",
"@com_google_protobuf//:protobuf",
],
)

Expand All @@ -395,6 +397,7 @@ cc_test(
":convert_options",
":function_converter",
":test_utils",
"//xls/common:attribute_data",
"//xls/common:proto_test_utils",
"//xls/common:xls_gunit_main",
"//xls/common/status:matchers",
Expand All @@ -404,6 +407,8 @@ cc_test(
"//xls/dslx/frontend:ast",
"//xls/ir",
"//xls/ir:xls_ir_interface_cc_proto",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:span",
"@googletest//:gtest",
],
)
Expand Down
98 changes: 98 additions & 0 deletions xls/dslx/ir_convert/function_converter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
#include "absl/strings/substitute.h"
#include "absl/types/span.h"
#include "absl/types/variant.h"
#include "google/protobuf/text_format.h"
#include "xls/common/attribute_data.h"
#include "xls/common/status/ret_check.h"
#include "xls/common/status/status_macros.h"
#include "xls/common/visitor.h"
Expand Down Expand Up @@ -3580,6 +3582,12 @@ absl::Status FunctionConverter::HandleFunction(
VLOG(5) << "Built function: " << ir_fn->name();
XLS_RETURN_IF_ERROR(VerifyFunction(ir_fn));

XLS_ASSIGN_OR_RETURN(std::optional<AttributeData> fuzz_test_attr,
LowerFuzzTestDomains(node));
if (fuzz_test_attr.has_value()) {
ir_fn->AddAttribute(std::move(*fuzz_test_attr));
}

// If it's a public fallible function, or it's the entry function for the
// package, we make a wrapper so that the external world (e.g. JIT, verilog
// module) doesn't need to take implicit token arguments.
Expand All @@ -3605,6 +3613,96 @@ absl::Status FunctionConverter::HandleFunction(
return absl::OkStatus();
}

absl::Status FunctionConverter::LowerDomainExpr(
Expr* expr, PackageInterfaceProto::FuzzTestDomain* proto) {
if (expr->kind() == AstNodeKind::kXlsTuple &&
dynamic_cast<XlsTuple*>(expr)->empty()) {
proto->set_arbitrary(true);
return absl::OkStatus();
}
if (expr->kind() == AstNodeKind::kRange) {
Range* range_node = static_cast<Range*>(expr);

XLS_ASSIGN_OR_RETURN(InterpValue min_val,
current_type_info_->GetConstExpr(range_node->start()));
XLS_ASSIGN_OR_RETURN(InterpValue max_val,
current_type_info_->GetConstExpr(range_node->end()));

XLS_ASSIGN_OR_RETURN(Value ir_min, InterpValueToValue(min_val));
XLS_ASSIGN_OR_RETURN(Value ir_max, InterpValueToValue(max_val));

XLS_ASSIGN_OR_RETURN(ValueProto min_proto, ir_min.AsProto());
XLS_ASSIGN_OR_RETURN(ValueProto max_proto, ir_max.AsProto());

auto* range_proto = proto->mutable_range();
*range_proto->mutable_min() = std::move(min_proto);
*range_proto->mutable_max() = std::move(max_proto);
return absl::OkStatus();
}
if (expr->kind() == AstNodeKind::kArray) {
Array* array_node = static_cast<Array*>(expr);

auto* element_of_proto = proto->mutable_element_of();
for (Expr* member : array_node->members()) {
XLS_ASSIGN_OR_RETURN(InterpValue val,
current_type_info_->GetConstExpr(member));
XLS_ASSIGN_OR_RETURN(Value ir_val, InterpValueToValue(val));
XLS_ASSIGN_OR_RETURN(ValueProto val_proto, ir_val.AsProto());
*element_of_proto->add_values() = std::move(val_proto);
}
return absl::OkStatus();
}
if (expr->kind() == AstNodeKind::kXlsTuple) {
XlsTuple* tuple_node = static_cast<XlsTuple*>(expr);

auto* tuple_proto = proto->mutable_tuple();
for (Expr* member : tuple_node->members()) {
XLS_RETURN_IF_ERROR(LowerDomainExpr(member, tuple_proto->add_elements()));
}
return absl::OkStatus();
}
return absl::UnimplementedError(
absl::StrCat("Unsupported fuzztest domain type: ", expr->ToString()));
}

absl::StatusOr<std::optional<AttributeData>>
FunctionConverter::LowerFuzzTestDomains(Function* node) {
if (node->parent() != nullptr &&
node->parent()->kind() == AstNodeKind::kFuzzTestFunction) {
FuzzTestFunction* ft = static_cast<FuzzTestFunction*>(node->parent());

if (ft->domains().has_value()) {
XlsTuple* domains_tuple = *ft->domains();
// We use a dummy Function proto here solely to get the
// `parameter_domains` field name wrapper in the serialized text proto.
// This will allow clients to easily parse the string back into a Function
// proto and recover the domains therein.
PackageInterfaceProto::Function temp_func;

for (Expr* domain_expr : domains_tuple->members()) {
PackageInterfaceProto::FuzzTestDomain* domain_proto =
temp_func.add_parameter_domains();

XLS_RETURN_IF_ERROR(LowerDomainExpr(domain_expr, domain_proto));
}

std::string proto_str;
google::protobuf::TextFormat::Printer printer;
printer.SetSingleLineMode(true);
XLS_RET_CHECK(printer.PrintToString(temp_func, &proto_str));

std::vector<AttributeData::Argument> args;
args.push_back(
AttributeData::StringKeyValueArgument{.first = "domains",
.second = std::move(proto_str),
.is_backticked = true});

return AttributeData(AttributeKind::kFuzzTest, std::move(args));
}
}
return std::nullopt;
}

absl::Status FunctionConverter::HandleSpawn(const Spawn* node) {
VLOG(5) << "HandleSpawn: " << node->ToString();
if (!options_.lower_to_proc_scoped_channels) {
Expand Down
10 changes: 10 additions & 0 deletions xls/dslx/ir_convert/function_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "xls/common/attribute_data.h"
#include "xls/dslx/channel_direction.h"
#include "xls/dslx/frontend/ast.h"
#include "xls/dslx/frontend/pos.h"
Expand Down Expand Up @@ -693,6 +694,15 @@ class FunctionConverter {
absl::flat_hash_map<std::string, BValue> state_write_called_by_state_name_;

std::vector<std::unique_ptr<ProcDefInstance>> proc_def_instances_;

// If the function has a kFuzzTest attribute, this method will convert the
// fuzz test domains to proto and insert it into the AttributeData for
// storage in the IR.
absl::StatusOr<std::optional<AttributeData>> LowerFuzzTestDomains(
Function* node);

absl::Status LowerDomainExpr(Expr* expr,
PackageInterfaceProto::FuzzTestDomain* proto);
};

} // namespace xls::dslx
Expand Down
Loading
Loading