Skip to content

[RFC] Multi-Proc AST-gen + fuzzing #2027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
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
19 changes: 19 additions & 0 deletions xls/fuzzer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,25 @@ cc_library(
],
)

cc_binary(
name = "ast_generator_main",
srcs = ["ast_generator_main.cc"],
deps = [
":ast_generator",
"//xls/common:exit_status",
"//xls/common:init_xls",
"//xls/common/logging:log_lines",
"//xls/dslx:create_import_data",
"//xls/dslx:import_data",
"//xls/dslx:parse_and_typecheck",
"//xls/dslx/frontend:module",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/log",
"@com_google_absl//absl/random",
"@com_google_absl//absl/strings",
],
)

cc_test(
name = "ast_generator_test",
srcs = ["ast_generator_test.cc"],
Expand Down
603 changes: 476 additions & 127 deletions xls/fuzzer/ast_generator.cc

Large diffs are not rendered by default.

110 changes: 68 additions & 42 deletions xls/fuzzer/ast_generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ struct BitsAndSignedness {
bool signedness;
};

enum class AvailableChannelKind : uint8_t { kToChildProc, kToParentProc };
struct AvailableChannel {
ProcMember* member;
AvailableChannelKind kind;
};

// Options that are used to configure the AST generator.
//
// See ast_generator_options.proto for field descriptions.
Expand All @@ -116,6 +122,7 @@ struct AstGeneratorOptions {
bool emit_gate = true;
bool generate_proc = false;
bool emit_stateless_proc = false;
bool emit_proc_spawns = false;
// TODO(https://github.com/google/xls/issues/1138): Switch this to default
// true.
bool emit_zero_width_bits_types = false;
Expand All @@ -133,6 +140,35 @@ bool AbslParseFlag(std::string_view text,
std::string* error);
std::string AbslUnparseFlag(const AstGeneratorOptions& ast_generator_options);

// Note: we use a btree for a stable iteration order; i.e. so we can stably
// select a random value from the environment across something like different
// hash function seed states. That is, ideally different process invocation
// would all produce identical generated functions for the same seed.
using Env = absl::btree_map<std::string, TypedExpr>;

// The context contains information for an instance in the call stack.
struct Context {
// TODO(https://github.com/google/xls/issues/789).
// TODO(https://github.com/google/xls/issues/790).
Env env;

// Context of the proc currently being generated.
struct ProcContext {
// Proc state type.
TypeAnnotation* state_type;

int64_t spawn_depth;

// Channels available to the proc next functiont that have not yet been
// interacted with:
std::vector<AvailableChannel> available_channels;
};

// Only present if generating a proc.
std::optional<ProcContext*> proc_context;
bool IsGeneratingProc() const { return proc_context.has_value(); }
};

// Type that generates a random module for use in fuzz testing; i.e.
//
// std::mt19937_64 rng;
Expand Down Expand Up @@ -181,20 +217,6 @@ class AstGenerator {
XLS_FRIEND_TEST(AstGeneratorTest, GeneratesParametricBindings);
XLS_FRIEND_TEST(AstGeneratorTest, BitsTypeGetMetadata);

// Note: we use a btree for a stable iteration order; i.e. so we can stably
// select a random value from the environment across something like different
// hash function seed states. That is, ideally different process invocation
// would all produce identical generated functions for the same seed.
using Env = absl::btree_map<std::string, TypedExpr>;

// The context contains information for an instance in the call stack.
struct Context {
// TODO(https://github.com/google/xls/issues/789).
// TODO(https://github.com/google/xls/issues/790).
Env env;
bool is_generating_proc;
};

static bool IsTypeRef(const TypeAnnotation* t);
static bool IsBits(const TypeAnnotation* t);
static bool IsUBits(const TypeAnnotation* t);
Expand Down Expand Up @@ -253,21 +275,38 @@ class AstGenerator {
const std::string& name, int64_t call_depth,
absl::Span<const AnnotatedType> param_types);

// Generate the proc's config function with the given name and proc parameters
// (proc members).
absl::StatusOr<Function*> GenerateProcConfigFunction(
std::string name, absl::Span<Param* const> proc_params);
// TODO
absl::StatusOr<std::tuple<Statement*, NameDef*, NameDef*>>
GenerateProcConfigChannel(TypeAnnotation* element_type);

// Generate the proc's next function with the given name.
absl::StatusOr<AnnotatedFunction> GenerateProcNextFunction(std::string name);

// Generate a function to return a constant with the given TypeAnnotation to
// serve as a Proc's [required] init function.
absl::StatusOr<Function*> GenerateProcInitFunction(
std::string_view name, TypeAnnotation* return_type);
// Generate the proc's config function with the given name. proc_io, defines
// the number and types of the channels the proc's config function accepts.
// spawn_depth is the current depth of the parent procs (if any) that spawn
// this proc.
//
// Returns the config function and proc members.
absl::StatusOr<std::pair<Function*, std::vector<AvailableChannel>>>
GenerateProcConfigFunction(
std::string name,
absl::Span<const std::pair<TypeAnnotation*, ChannelDirection>> proc_io,
int64_t spawn_depth);

// Generate a DSLX proc with the given name.
absl::StatusOr<AnnotatedProc> GenerateProc(const std::string& name);
// Generate the proc's next function with the given name.
absl::StatusOr<AnnotatedFunction> GenerateProcNextFunction(
std::string name, Context::ProcContext* proc_ctx);

// Generate a function to return a constant initial state to serve as a Proc's
// [required] init function.
absl::StatusOr<Function*> GenerateProcInitFunction(std::string_view name,
TypeAnnotation* state_typ);

// Generate a DSLX proc with the given name. proc_io, defines the number and
// types of the channels the proc's config function accepts. spawn_depth is
// the current depth of the parent procs (if any) that spaw this proc.
absl::StatusOr<AnnotatedProc> GenerateProc(
const std::string& name,
absl::Span<const std::pair<TypeAnnotation*, ChannelDirection>> proc_io,
int64_t spawn_depth);

// Chooses a value from the environment that satisfies the predicate "take",
// or returns nullopt if none exists.
Expand Down Expand Up @@ -725,19 +764,6 @@ class AstGenerator {
return absl::OkStatus();
}

struct ProcProperties {
// A list of the state types in the proc's next function. Currently, at most
// a single state is supported. The order of types as they appear in the
// container mirrors the order present in the proc's next function.
std::vector<TypeAnnotation*> state_types;

// Parameters of the proc config function.
std::vector<Param*> config_params;

// Members of the proc.
std::vector<ProcMember*> members;
};

absl::BitGenRef bit_gen_;

const AstGeneratorOptions options_;
Expand Down Expand Up @@ -765,8 +791,8 @@ class AstGenerator {
// Set of constants defined during module generation.
absl::btree_map<std::string, ConstantDef*> constants_;

// Contains properties of the generated proc.
ProcProperties proc_properties_;
// Procs created during generation.
std::vector<AnnotatedProc> procs_;
};

} // namespace xls::dslx
Expand Down
131 changes: 131 additions & 0 deletions xls/fuzzer/ast_generator_main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2025 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.

#include <ctime>

#include "absl/flags/flag.h"
#include "absl/log/log.h"
#include "absl/random/random.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "xls/common/exit_status.h"
#include "xls/common/init_xls.h"
#include "xls/common/logging/log_lines.h"
#include "xls/dslx/create_import_data.h"
#include "xls/dslx/frontend/module.h"
#include "xls/dslx/import_data.h"
#include "xls/dslx/parse_and_typecheck.h"
#include "xls/fuzzer/ast_generator.h"

ABSL_FLAG(bool, emit_signed_types, true, "Emit signed types.");

ABSL_FLAG(int64_t, max_width_bits_types, 64,
"Maximum width of generated bits types.");

ABSL_FLAG(int64_t, max_width_aggregate_types, 1024,
"Maximum width of generated aggregate types (tuples, arrays, ..).");

ABSL_FLAG(bool, emit_loops, true, "Emit loops.");

ABSL_FLAG(bool, emit_gate, true, "Emit gates.");

ABSL_FLAG(bool, generate_proc, false, "Generate a proc.");

ABSL_FLAG(bool, emit_stateless_proc, false, "Emit only stateless procs.");

ABSL_FLAG(bool, emit_proc_spawns, true,
"Emit helper procs spawned by top proc.");

ABSL_FLAG(bool, emit_zero_width_bits_types, false,
"Emit zero-wdith bits types.");

ABSL_FLAG(std::optional<int64_t>, seed, std::nullopt,
"Seed value for generation. By default, a nondetermistic seed is "
"used; if a seed is provided, it is used for determinism");

ABSL_FLAG(std::string, top_entity_name, "test_top",
"Name of the generated top entity.");

ABSL_FLAG(std::string, module_name, "test_mod",
"Name of the generated module.");

namespace xls {
namespace {

absl::Status RealMain() {
std::optional<uint64_t> seed_flag = absl::GetFlag(FLAGS_seed);
uint64_t rng_seed = seed_flag.has_value()
? *seed_flag
: absl::Uniform<uint64_t>(absl::BitGen());
std::mt19937_64 rng(rng_seed);

xls::dslx::FileTable ft;
xls::dslx::AstGeneratorOptions opts = {
.emit_signed_types = absl::GetFlag(FLAGS_emit_signed_types),
.max_width_bits_types = absl::GetFlag(FLAGS_max_width_bits_types),
.max_width_aggregate_types =
absl::GetFlag(FLAGS_max_width_aggregate_types),
.emit_loops = absl::GetFlag(FLAGS_emit_loops),
.emit_gate = absl::GetFlag(FLAGS_emit_gate),
.generate_proc = absl::GetFlag(FLAGS_generate_proc),
.emit_stateless_proc = absl::GetFlag(FLAGS_emit_stateless_proc),
.emit_proc_spawns = absl::GetFlag(FLAGS_emit_proc_spawns),
.emit_zero_width_bits_types =
absl::GetFlag(FLAGS_emit_zero_width_bits_types),
};

std::string top_entity_name = absl::GetFlag(FLAGS_top_entity_name);
std::string module_name = absl::GetFlag(FLAGS_module_name);

xls::dslx::AstGenerator g(opts, rng, ft);
XLS_ASSIGN_OR_RETURN(auto result, g.Generate(top_entity_name, module_name));

auto dslx_text = result.module->ToString();

// Parse and type check the DSLX program.
dslx::ImportData import_data(
dslx::CreateImportData(/*stdlib_path=*/"",
/*additional_search_paths=*/{},
/*enabled_warnings=*/dslx::kAllWarningsSet,
std::make_unique<dslx::RealFilesystem>()));
absl::StatusOr<dslx::TypecheckedModule> tm =
ParseAndTypecheck(dslx_text, "sample.x", "sample", &import_data);
if (!tm.ok()) {
LOG(ERROR) << "Generated sample failed to parse-and-typecheck ("
<< dslx_text.size() << " bytes):";
XLS_LOG_LINES(ERROR, dslx_text);
return tm.status();
}

std::cout << "// min_stages: " << result.min_stages << std::endl;
std::cout << dslx_text << std::endl;

return absl::OkStatus();
}

} // namespace
} // namespace xls

int main(int argc, char** argv) {
std::vector<std::string_view> positional_arguments = xls::InitXls(
absl::StrCat("Generate a random DSLX module; sample usage:\n\n\t",
argv[0]),
argc, argv);

if (!positional_arguments.empty()) {
LOG(QFATAL) << "Unexpected positional arguments: "
<< absl::StrJoin(positional_arguments, ", ");
}
return xls::ExitStatus(xls::RealMain());
}
6 changes: 6 additions & 0 deletions xls/fuzzer/ast_generator_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ message AstGeneratorOptionsProto {
// `true`.
optional bool emit_stateless_proc = 7;

// Whether to emit a top proc that spawns other procs. When true, a small
// helper proc may randomly be generated and spawned from the top proc, and
// the top proc's next function will interact with the child proc's channels.
// Its value is only meaningful when generate_proc is `true`.
optional bool emit_proc_spawns = 9;

// Whether to emit zero-width bits types.
optional bool emit_zero_width_bits_types = 8;
}
4 changes: 4 additions & 0 deletions xls/fuzzer/run_fuzz_multiprocess_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ABSL_FLAG(
bool, force_failure, false,
"Forces the samples to fail. Can be used to test failure code paths.");
ABSL_FLAG(bool, generate_proc, false, "Generate a proc sample.");
ABSL_FLAG(bool, emit_proc_spawns, false, "Emit helper procs spawned by top proc.");
ABSL_FLAG(int64_t, max_width_aggregate_types, 1024,
"The maximum width of aggregate types (tuples and arrays) in the "
"generated samples.");
Expand Down Expand Up @@ -97,6 +98,7 @@ struct Options {
bool emit_loops;
bool force_failure;
bool generate_proc;
bool emit_proc_spawns;
int64_t max_width_aggregate_types;
int64_t max_width_bits_types;
int64_t proc_ticks;
Expand Down Expand Up @@ -149,6 +151,7 @@ absl::Status RealMain(const Options& options) {
ast_generator_options.max_width_aggregate_types =
options.max_width_aggregate_types;
ast_generator_options.generate_proc = options.generate_proc;
ast_generator_options.emit_proc_spawns = options.emit_proc_spawns;

SampleOptions sample_options;
sample_options.set_calls_per_sample(
Expand Down Expand Up @@ -206,6 +209,7 @@ int main(int argc, char** argv) {
.emit_loops = absl::GetFlag(FLAGS_emit_loops),
.force_failure = absl::GetFlag(FLAGS_force_failure),
.generate_proc = absl::GetFlag(FLAGS_generate_proc),
.emit_proc_spawns = absl::GetFlag(FLAGS_emit_proc_spawns),
.max_width_aggregate_types =
absl::GetFlag(FLAGS_max_width_aggregate_types),
.max_width_bits_types = absl::GetFlag(FLAGS_max_width_bits_types),
Expand Down
13 changes: 13 additions & 0 deletions xls/fuzzer/sample.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ bool SampleOptions::operator==(const SampleOptions& other) const {
throughput->set_stderr_regex(
".*Impossible to schedule proc .* as specified; .*: cannot achieve full "
"throughput.*");
// TODO: Remove when fixed.
auto* combinational_proc = proto.add_known_failure();
combinational_proc->set_tool(".*codegen_main");
combinational_proc->set_stderr_regex(
".*Proc combinational generator only supports streaming output channels "
"which can be determined to be mutually exclusive, got .* output "
"channels which were not proven to be mutually exclusive.*");
// TODO: Remove when fixed.
auto* proc_deadlock = proto.add_known_failure();
proc_deadlock->set_tool(".*eval_proc_main");
proc_deadlock->set_stderr_regex(
".*Error: INTERNAL: Proc network is deadlocked. Blocked channel "
"instances: .*");
return proto;
}

Expand Down
Loading