diff --git a/src/Deinterleave.cpp b/src/Deinterleave.cpp index f7a5b5f49aa8..a1c881ea9744 100644 --- a/src/Deinterleave.cpp +++ b/src/Deinterleave.cpp @@ -256,7 +256,7 @@ class Deinterleaver : public IRGraphMutator { if (base_lanes > 1) { if (new_lanes == 1) { int index = starting_lane / base_lanes; - Expr expr = op->base + cast(op->base.type(), index) * op->stride; + Expr expr = simplify(op->base + cast(op->base.type(), index) * op->stride); ScopedValue old_starting_lane(starting_lane, starting_lane % base_lanes); ScopedValue old_lane_stride(lane_stride, base_lanes); expr = mutate(expr); @@ -274,10 +274,10 @@ class Deinterleaver : public IRGraphMutator { return mutate(flatten_nested_ramps(op)); } } - Expr expr = op->base + cast(op->base.type(), starting_lane) * op->stride; + Expr expr = simplify(op->base + cast(op->base.type(), starting_lane) * op->stride); internal_assert(expr.type() == op->base.type()); if (new_lanes > 1) { - expr = Ramp::make(expr, op->stride * lane_stride, new_lanes); + expr = Ramp::make(expr, simplify(op->stride * lane_stride), new_lanes); } return expr; } @@ -410,7 +410,7 @@ Expr deinterleave(Expr e, int starting_lane, int lane_stride, int new_lanes, con Deinterleaver d(starting_lane, lane_stride, new_lanes, lets); e = d.mutate(e); e = common_subexpression_elimination(e); - return simplify(e); + return e; } Expr extract_odd_lanes(const Expr &e, const Scope<> &lets) { diff --git a/src/Simplify_Div.cpp b/src/Simplify_Div.cpp index be78d10a34f8..cf3d796e2b67 100644 --- a/src/Simplify_Div.cpp +++ b/src/Simplify_Div.cpp @@ -31,7 +31,7 @@ Expr Simplify::visit(const Div *op, ExprInfo *info) { // we're better off returning an overflow condition than // a known-wrong value. (Note that no_overflow_int() should // only be true for signed integers.) - internal_assert(no_overflow_int(op->type)); + internal_assert(no_overflow_int(op->type)) << op->type << " " << info->bounds; clear_expr_info(info); return make_signed_integer_overflow(op->type); } diff --git a/test/correctness/fuzz_simplify.cpp b/test/correctness/fuzz_simplify.cpp index 4154773dd1ec..52f9354d0f35 100644 --- a/test/correctness/fuzz_simplify.cpp +++ b/test/correctness/fuzz_simplify.cpp @@ -3,6 +3,8 @@ #include #include +#include "random_expr_generator.h" + // Test the simplifier in Halide by testing for equivalence of randomly generated expressions. namespace { @@ -11,241 +13,7 @@ using std::string; using namespace Halide; using namespace Halide::Internal; -using make_bin_op_fn = Expr (*)(Expr, Expr); -using RandomEngine = std::mt19937_64; - -constexpr int fuzz_var_count = 5; - -Type fuzz_types[] = {UInt(1), UInt(8), UInt(16), UInt(32), Int(8), Int(16), Int(32)}; - -std::string fuzz_var(int i) { - return std::string(1, 'a' + i); -} - -Expr random_var(RandomEngine &rng, Type t) { - std::uniform_int_distribution dist(0, fuzz_var_count - 1); - int fuzz_count = dist(rng); - return cast(t, Variable::make(Int(32), fuzz_var(fuzz_count))); -} - -template -decltype(auto) random_choice(RandomEngine &rng, T &&choices) { - std::uniform_int_distribution dist(0, std::size(choices) - 1); - return choices[dist(rng)]; -} - -Type random_type(RandomEngine &rng, int width) { - Type t = random_choice(rng, fuzz_types); - if (width > 1) { - t = t.with_lanes(width); - } - return t; -} - -int get_random_divisor(RandomEngine &rng, Type t) { - std::vector divisors = {t.lanes()}; - for (int dd = 2; dd < t.lanes(); dd++) { - if (t.lanes() % dd == 0) { - divisors.push_back(dd); - } - } - - return random_choice(rng, divisors); -} - -Expr random_leaf(RandomEngine &rng, Type t, bool overflow_undef = false, bool imm_only = false) { - if (t.is_int() && t.bits() == 32) { - overflow_undef = true; - } - if (t.is_scalar()) { - if (!imm_only && (rng() & 1)) { - return random_var(rng, t); - } else { - if (overflow_undef) { - // For Int(32), we don't care about correctness during - // overflow, so just use numbers that are unlikely to - // overflow. - return cast(t, (int32_t)((int8_t)(rng() & 255))); - } else { - return cast(t, (int32_t)(rng())); - } - } - } else { - int lanes = get_random_divisor(rng, t); - if (rng() & 1) { - auto e1 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); - auto e2 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); - return Ramp::make(e1, e2, lanes); - } else { - auto e1 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); - return Broadcast::make(e1, lanes); - } - } -} - -Expr random_expr(RandomEngine &rng, Type t, int depth, bool overflow_undef = false); - -Expr random_condition(RandomEngine &rng, Type t, int depth, bool maybe_scalar) { - static make_bin_op_fn make_bin_op[] = { - EQ::make, - NE::make, - LT::make, - LE::make, - GT::make, - GE::make, - }; - - if (maybe_scalar && (rng() & 1)) { - t = t.element_of(); - } - - Expr a = random_expr(rng, t, depth); - Expr b = random_expr(rng, t, depth); - return random_choice(rng, make_bin_op)(a, b); -} - -Expr make_absd(Expr a, Expr b) { - // random_expr() assumes that the result t is the same as the input t, - // which isn't true for all absd variants, so force the issue. - return cast(a.type(), absd(a, b)); -} - -Expr make_bitwise_or(Expr a, Expr b) { - return a | b; -} - -Expr make_bitwise_and(Expr a, Expr b) { - return a & b; -} - -Expr make_bitwise_xor(Expr a, Expr b) { - return a ^ b; -} - -Expr make_abs(Expr a, Expr) { - if (!a.type().is_uint()) { - return cast(a.type(), abs(a)); - } else { - return a; - } -} - -Expr make_bitwise_not(Expr a, Expr) { - return ~a; -} - -Expr make_shift_right(Expr a, Expr b) { - return a >> (b % a.type().bits()); -} - -Expr random_expr(RandomEngine &rng, Type t, int depth, bool overflow_undef) { - if (t.is_int() && t.bits() == 32) { - overflow_undef = true; - } - - if (depth-- <= 0) { - return random_leaf(rng, t, overflow_undef); - } - - std::function operations[] = { - [&]() { - return random_leaf(rng, t); - }, - [&]() { - auto c = random_condition(rng, t, depth, true); - auto e1 = random_expr(rng, t, depth, overflow_undef); - auto e2 = random_expr(rng, t, depth, overflow_undef); - return select(c, e1, e2); - }, - [&]() { - if (t.lanes() != 1) { - int lanes = get_random_divisor(rng, t); - auto e1 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); - return Broadcast::make(e1, lanes); - } - return random_expr(rng, t, depth, overflow_undef); - }, - [&]() { - if (t.lanes() != 1) { - int lanes = get_random_divisor(rng, t); - auto e1 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); - auto e2 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); - return Ramp::make(e1, e2, lanes); - } - return random_expr(rng, t, depth, overflow_undef); - }, - [&]() { - if (t.is_bool()) { - auto e1 = random_expr(rng, t, depth); - return Not::make(e1); - } - return random_expr(rng, t, depth, overflow_undef); - }, - [&]() { - // When generating boolean expressions, maybe throw in a condition on non-bool types. - if (t.is_bool()) { - return random_condition(rng, random_type(rng, t.lanes()), depth, false); - } - return random_expr(rng, t, depth, overflow_undef); - }, - [&]() { - // Get a random type that isn't `t` or int32 (int32 can overflow, and we don't care about that). - std::vector subtypes; - for (const Type &subtype : fuzz_types) { - if (subtype != t && subtype != Int(32)) { - subtypes.push_back(subtype); - } - } - Type subtype = random_choice(rng, subtypes).with_lanes(t.lanes()); - return Cast::make(t, random_expr(rng, subtype, depth, overflow_undef)); - }, - [&]() { - static make_bin_op_fn make_bin_op[] = { - // Arithmetic operations. - Add::make, - Sub::make, - Mul::make, - Min::make, - Max::make, - Div::make, - Mod::make, - }; - - static make_bin_op_fn make_rare_bin_op[] = { - make_absd, - make_bitwise_or, - make_bitwise_and, - make_bitwise_xor, - make_bitwise_not, - make_abs, - make_shift_right, // No shift left or we just keep testing integer overflow - }; - - Expr a = random_expr(rng, t, depth, overflow_undef); - Expr b = random_expr(rng, t, depth, overflow_undef); - if ((rng() & 7) == 0) { - return random_choice(rng, make_rare_bin_op)(a, b); - } else { - return random_choice(rng, make_bin_op)(a, b); - } - }, - [&]() { - static make_bin_op_fn make_bin_op[] = { - And::make, - Or::make, - }; - - // Boolean operations -- both sides must be cast to booleans, - // and then we must cast the result back to 't'. - Expr a = random_expr(rng, t, depth, overflow_undef); - Expr b = random_expr(rng, t, depth, overflow_undef); - Type bool_with_lanes = Bool(t.lanes()); - a = cast(bool_with_lanes, a); - b = cast(bool_with_lanes, b); - return cast(t, random_choice(rng, make_bin_op)(a, b)); - }}; - return random_choice(rng, operations)(); -} +using RandomEngine = RandomExpressionGenerator::RandomEngine; bool test_simplification(Expr a, Expr b, Type t, const map &vars) { if (equal(a, b) && !a.same_as(b)) { @@ -292,12 +60,12 @@ bool test_simplification(Expr a, Expr b, Type t, const map &vars) return true; } -bool test_expression(RandomEngine &rng, Expr test, int samples) { +bool test_expression(RandomExpressionGenerator ®, RandomEngine &rng, Expr test, int samples) { Expr simplified = simplify(test); map vars; - for (int i = 0; i < fuzz_var_count; i++) { - vars[fuzz_var(i)] = Expr(); + for (int i = 0; i < (int)reg.fuzz_vars.size(); i++) { + vars[reg.fuzz_var(i)] = Expr(); } for (int i = 0; i < samples; i++) { @@ -306,7 +74,7 @@ bool test_expression(RandomEngine &rng, Expr test, int samples) { // Don't let the random leaf depend on v itself. size_t iterations = 0; do { - val = random_leaf(rng, Int(32), true); + val = reg.random_leaf(rng, Int(32), true); iterations++; } while (expr_uses_var(val, var) && iterations < kMaxLeafIterations); } @@ -337,6 +105,16 @@ int main(int argc, char **argv) { auto seed_generator = initialize_rng(); + RandomExpressionGenerator reg; + reg.fuzz_types = {UInt(1), UInt(8), UInt(16), UInt(32), Int(8), Int(16), Int(32)}; + // FIXME: UInt64 fails! + // FIXME: These need to be disabled (otherwise crashes and/or failures): + // reg.gen_ramp_of_vector = false; + // reg.gen_broadcast_of_vector = false; + reg.gen_vector_reduce = false; + reg.gen_reinterpret = false; + reg.gen_shuffles = false; + for (int i = 0; i < ((argc == 1) ? 10000 : 1); i++) { auto seed = seed_generator(); if (argc > 1) { @@ -347,11 +125,12 @@ int main(int argc, char **argv) { std::cout << "Seed: " << seed << "\n"; RandomEngine rng{seed}; std::array vector_widths = {1, 2, 3, 4, 6, 8}; - int width = random_choice(rng, vector_widths); - Type VT = random_type(rng, width); + int width = reg.random_choice(rng, vector_widths); + Type VT = reg.random_type(rng, width); // Generate a random expr... - Expr test = random_expr(rng, VT, depth); - if (!test_expression(rng, test, samples)) { + Expr test = reg.random_expr(rng, VT, depth); + std::cout << test << "\n"; + if (!test_expression(reg, rng, test, samples)) { class LimitDepth : public IRMutator { int limit; @@ -378,6 +157,7 @@ int main(int argc, char **argv) { // Failure. Find the minimal subexpression that failed. std::cout << "Testing subexpressions...\n"; class TestSubexpressions : public IRMutator { + RandomExpressionGenerator reg; RandomEngine &rng; bool found_failure = false; @@ -392,19 +172,19 @@ int main(int argc, char **argv) { Expr limited; for (int i = 1; i < 4 && !found_failure; i++) { limited = LimitDepth(i).mutate(e); - found_failure = !test_expression(rng, limited, samples); + found_failure = !test_expression(reg, rng, limited, samples); } if (!found_failure) { - found_failure = !test_expression(rng, e, samples); + found_failure = !test_expression(reg, rng, e, samples); } } return e; } - TestSubexpressions(RandomEngine &rng) - : rng(rng) { + TestSubexpressions(RandomExpressionGenerator ®, RandomEngine &rng) + : reg(reg), rng(rng) { } - } tester(rng); + } tester(reg, rng); tester.mutate(test); std::cout << "Failed with seed " << seed << "\n"; diff --git a/test/correctness/random_expr_generator.h b/test/correctness/random_expr_generator.h new file mode 100644 index 000000000000..afde4ab4bbcc --- /dev/null +++ b/test/correctness/random_expr_generator.h @@ -0,0 +1,389 @@ +#include "Halide.h" + +#include +#include +#include +#include + +namespace Halide { +namespace Internal { + +using namespace std; +using namespace Halide; +using namespace Halide::Internal; + +class RandomExpressionGenerator { +public: + using RandomEngine = std::mt19937_64; + using make_bin_op_fn = Expr (*)(Expr, Expr); + + bool gen_cast = true; + bool gen_select = true; + bool gen_arithmetic = true; + bool gen_bitwise = true; + bool gen_bool_ops = true; + bool gen_reinterpret = true; + bool gen_broadcast_of_vector = true; + bool gen_ramp_of_vector = true; + bool gen_shuffles = true; + bool gen_vector_reduce = true; + + std::vector fuzz_types = {UInt(1), UInt(8), UInt(16), UInt(32), UInt(64), Int(8), Int(16), Int(32), Int(64)}; + std::vector> fuzz_vars{5}; + + template + decltype(auto) random_choice(RandomEngine &rng, T &&choices) { + std::uniform_int_distribution dist(0, std::size(choices) - 1); + return choices[dist(rng)]; + } + + Type random_scalar_type(RandomEngine &rng) { + return random_choice(rng, fuzz_types); + } + + int get_random_divisor(RandomEngine &rng, int x) { + vector divisors; + divisors.reserve(x); + for (int i = 2; i <= x; i++) { + if (x % i == 0) { + divisors.push_back(i); + } + } + return random_choice(rng, divisors); + } + + std::string fuzz_var(int i) { + return std::string(1, 'a' + i); + } + + Expr random_var(RandomEngine &rng, Type t) { + std::uniform_int_distribution dist(0, fuzz_vars.size() - 1); + int fuzz_count = dist(rng); + return cast(t, Variable::make(Int(32), fuzz_var(fuzz_count))); + } + + Type random_type(RandomEngine &rng, int width) { + Type t = random_choice(rng, fuzz_types); + if (width > 1) { + t = t.with_lanes(width); + } + return t; + } + + Expr random_const(RandomEngine &rng, Type t) { + int val = (int)((int8_t)(rng() & 0x0f)); + if (t.is_vector()) { + return Broadcast::make(cast(t.element_of(), val), t.lanes()); + } else { + return cast(t, val); + } + } + + static Expr make_absd(Expr a, Expr b) { + // random_expr() assumes that the result t is the same as the input t, + // which isn't true for all absd variants, so force the issue. + return cast(a.type(), absd(a, b)); + } + + static Expr make_bitwise_or(Expr a, Expr b) { + return a | b; + } + + static Expr make_bitwise_and(Expr a, Expr b) { + return a & b; + } + + static Expr make_bitwise_xor(Expr a, Expr b) { + return a ^ b; + } + + static Expr make_abs(Expr a, Expr) { + if (!a.type().is_uint()) { + return cast(a.type(), abs(a)); + } else { + return a; + } + } + + static Expr make_bitwise_not(Expr a, Expr) { + return ~a; + } + + static Expr make_shift_right(Expr a, Expr b) { + return a >> (b % a.type().bits()); + } + + Expr random_leaf(RandomEngine &rng, Type t, bool overflow_undef = false, bool imm_only = false) { + if (t.is_int() && t.bits() == 32) { + overflow_undef = true; + } + if (t.is_scalar()) { + if (!imm_only && (rng() & 1)) { + return random_var(rng, t); + } else { + if (overflow_undef) { + // For Int(32), we don't care about correctness during + // overflow, so just use numbers that are unlikely to + // overflow. + return cast(t, (int32_t)((int8_t)(rng() & 255))); + } else { + return cast(t, (int32_t)(rng())); + } + } + } else { + int lanes = get_random_divisor(rng, t.lanes()); + + if (rng() & 1) { + auto e1 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); + auto e2 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); + return Ramp::make(e1, e2, lanes); + } else { + auto e1 = random_leaf(rng, t.with_lanes(t.lanes() / lanes), overflow_undef); + return Broadcast::make(e1, lanes); + } + } + } + + // Expr random_expr(RandomEngine &rng, Type t, int depth, bool overflow_undef = false); + + Expr random_condition(RandomEngine &rng, Type t, int depth, bool maybe_scalar) { + static make_bin_op_fn make_bin_op[] = { + EQ::make, + NE::make, + LT::make, + LE::make, + GT::make, + GE::make, + }; + + if (maybe_scalar && (rng() & 1)) { + t = t.element_of(); + } + + Expr a = random_expr(rng, t, depth); + Expr b = random_expr(rng, t, depth); + return random_choice(rng, make_bin_op)(a, b); + } + + Expr random_expr(RandomEngine &rng, Type t, int depth, bool overflow_undef = false) { + if (t.is_int() && t.bits() == 32) { + overflow_undef = true; + } + + if (depth-- <= 0) { + return random_leaf(rng, t, overflow_undef); + } + + // Weight the choices to cover all Deinterleaver visit methods: + // Broadcast, Ramp, Cast, Reinterpret, Call (via abs), Shuffle, + // VectorReduce, Add/Sub/Min/Max (handled by default IRMutator) + std::vector> ops; + + // Leaf + ops.push_back([&]() -> Expr { + return random_leaf(rng, t); + }); + + if (gen_arithmetic) { + // Arithmetic + ops.push_back([&]() { + static make_bin_op_fn make_bin_op[] = { + // Arithmetic operations. + Add::make, + Sub::make, + Mul::make, + Min::make, + Max::make, + Div::make, + Mod::make, + make_absd, + make_abs}; + Expr a = random_expr(rng, t, depth, overflow_undef); + Expr b = random_expr(rng, t, depth, overflow_undef); + return random_choice(rng, make_bin_op)(a, b); + }); + } + if (gen_bitwise) { + // Bitwise + ops.push_back([&]() { + static make_bin_op_fn make_bin_op[] = { + make_bitwise_or, + make_bitwise_and, + make_bitwise_xor, + make_bitwise_not, + make_shift_right, // No shift left or we just keep testing integer overflow + }; + + Expr a = random_expr(rng, t, depth, overflow_undef); + Expr b = random_expr(rng, t, depth, overflow_undef); + return random_choice(rng, make_bin_op)(a, b); + }); + } + if (gen_bool_ops) { + // Boolean ops + ops.push_back([&]() { + static make_bin_op_fn make_bin_op[] = { + And::make, + Or::make, + }; + + // Boolean operations -- both sides must be cast to booleans, + // and then we must cast the result back to 't'. + Expr a = random_expr(rng, t, depth, overflow_undef); + Expr b = random_expr(rng, t, depth, overflow_undef); + Type bool_with_lanes = Bool(t.lanes()); + a = cast(bool_with_lanes, a); + b = cast(bool_with_lanes, b); + return cast(t, random_choice(rng, make_bin_op)(a, b)); + }); + } + if (gen_select) { + // Select + ops.push_back( + [&]() -> Expr { + auto c = random_condition(rng, t, depth, true); + auto e1 = random_expr(rng, t, depth, overflow_undef); + auto e2 = random_expr(rng, t, depth, overflow_undef); + return select(c, e1, e2); + }); + } + // Cast + if (gen_cast) { + ops.push_back([&]() { + // Get a random type that isn't `t` or int32 (int32 can overflow, and we don't care about that). + std::vector subtypes; + for (const Type &subtype : fuzz_types) { + if (subtype != t && subtype != Int(32)) { + subtypes.push_back(subtype); + } + } + Type subtype = random_choice(rng, subtypes).with_lanes(t.lanes()); + return Cast::make(t, random_expr(rng, subtype, depth, overflow_undef)); + }); + } + if (gen_reinterpret) { + // Reinterpret (different bit width, changes lane count) + ops.push_back([&]() -> Expr { + int total_bits = t.bits() * t.lanes(); + // Pick a different bit width that divides the total bits evenly + int bit_widths[] = {8, 16, 32, 64}; + vector valid_widths; + for (int bw : bit_widths) { + if (total_bits % bw == 0) { + valid_widths.push_back(bw); + } + } + // Should at least be able to preserve the existing bit width and change signedness. + internal_assert(!valid_widths.empty()); + int other_bits = random_choice(rng, valid_widths); + int other_lanes = total_bits / other_bits; + Type other = ((rng() & 1) ? Int(other_bits) : UInt(other_bits)).with_lanes(other_lanes); + Expr e = random_expr(rng, other, depth); + return Reinterpret::make(t, e); + }); + } + + if (gen_broadcast_of_vector) { + // Broadcast of vector + ops.push_back([&]() -> Expr { + if (t.lanes() != 1) { + int lanes = get_random_divisor(rng, t.lanes()); + auto e1 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); + return Broadcast::make(e1, lanes); + } + return random_expr(rng, t, depth, overflow_undef); + }); + } + + if (gen_ramp_of_vector) { + // Ramp + ops.push_back([&]() { + if (t.lanes() != 1) { + int lanes = get_random_divisor(rng, t.lanes()); + auto e1 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); + auto e2 = random_expr(rng, t.with_lanes(t.lanes() / lanes), depth, overflow_undef); + return Ramp::make(e1, e2, lanes); + } + return random_expr(rng, t, depth, overflow_undef); + }); + } + if (gen_bool_ops) { + ops.push_back([&]() { + if (t.is_bool()) { + auto e1 = random_expr(rng, t, depth); + return Not::make(e1); + } + return random_expr(rng, t, depth, overflow_undef); + }); + ops.push_back([&]() { + // When generating boolean expressions, maybe throw in a condition on non-bool types. + if (t.is_bool()) { + return random_condition(rng, random_type(rng, t.lanes()), depth, false); + } + return random_expr(rng, t, depth, overflow_undef); + }); + } + if (gen_shuffles) { + // Shuffle (interleave) + ops.push_back([&]() -> Expr { + if (t.lanes() >= 4 && t.lanes() % 2 == 0) { + int half = t.lanes() / 2; + Expr a = random_expr(rng, t.with_lanes(half), depth); + Expr b = random_expr(rng, t.with_lanes(half), depth); + return Shuffle::make_interleave({a, b}); + } + // Fall back to a simple expression + return random_expr(rng, t, depth); + }); + // Shuffle (concat) + ops.push_back([&]() -> Expr { + if (t.lanes() >= 4 && t.lanes() % 2 == 0) { + int half = t.lanes() / 2; + Expr a = random_expr(rng, t.with_lanes(half), depth); + Expr b = random_expr(rng, t.with_lanes(half), depth); + return Shuffle::make_concat({a, b}); + } + return random_expr(rng, t, depth); + }); + // Shuffle (slice) + ops.push_back([&]() -> Expr { + // Make a wider vector and slice it + if (t.lanes() <= 8) { + int wider = t.lanes() * 2; + Expr e = random_expr(rng, t.with_lanes(wider), depth); + // Slice: take every other element starting at 0 or 1 + int start = rng() & 1; + return Shuffle::make_slice(e, start, 2, t.lanes()); + } + return random_expr(rng, t, depth); + }); + } + if (gen_vector_reduce) { + // VectorReduce (only when we can make it work with lane counts) + ops.push_back([&]() -> Expr { + // Input has more lanes, output has t.lanes() lanes + // factor must divide input lanes, and input lanes = t.lanes() * factor + int factor = (rng() % 3) + 2; + int input_lanes = t.lanes() * factor; + if (input_lanes <= 32) { + VectorReduce::Operator ops[] = { + VectorReduce::Add, + VectorReduce::Min, + VectorReduce::Max, + }; + auto op = random_choice(rng, ops); + Expr val = random_expr(rng, t.with_lanes(input_lanes), depth); + internal_assert(val.type().lanes() == input_lanes) << val; + return VectorReduce::make(op, val, t.lanes()); + } + return random_expr(rng, t, depth); + }); + } + + Expr e = random_choice(rng, ops)(); + internal_assert(e.type() == t) << e.type() << " " << t << " " << e; + return e; + } +}; +} // namespace Internal +} // namespace Halide