/* * Copyright (c) 2023 Intel Corporation * SPDX-License-Identifier: MIT */ #include #include "elk_fs.h" #include "elk_fs_builder.h" #include "elk_cfg.h" using namespace elk; class PredicatedBreakTest : public ::testing::Test { virtual void SetUp(); virtual void TearDown(); public: bool debug; void *mem_ctx; elk_compiler compiler; elk_compile_params params; intel_device_info devinfo; struct elk_wm_prog_data prog_data; struct gl_shader_program *shader_prog; elk_fs_visitor *shader_a; elk_fs_visitor *shader_b; bool elk_opt_predicated_break(elk_fs_visitor *s); }; void PredicatedBreakTest::SetUp() { debug = getenv("TEST_DEBUG"); mem_ctx = ralloc_context(NULL); devinfo = {}; devinfo.ver = 9; devinfo.verx10 = 90; compiler = {}; compiler.devinfo = &devinfo; elk_init_isa_info(&compiler.isa, &devinfo); params = {}; params.mem_ctx = mem_ctx; prog_data = {}; nir_shader *nir = nir_shader_create(mem_ctx, MESA_SHADER_FRAGMENT, NULL, NULL); shader_a = new elk_fs_visitor(&compiler, ¶ms, NULL, &prog_data.base, nir, 8, false, false); shader_b = new elk_fs_visitor(&compiler, ¶ms, NULL, &prog_data.base, nir, 8, false, false); } void PredicatedBreakTest::TearDown() { delete shader_a; delete shader_b; ralloc_free(mem_ctx); mem_ctx = NULL; } bool PredicatedBreakTest::elk_opt_predicated_break(elk_fs_visitor *s) { const bool print = getenv("TEST_DEBUG"); if (print) { fprintf(stderr, "= Before =\n"); s->cfg->dump(); } bool ret = ::elk_opt_predicated_break(s); if (print) { fprintf(stderr, "\n= After =\n"); s->cfg->dump(); } return ret; } static fs_builder make_builder(elk_fs_visitor *s) { return fs_builder(s, s->dispatch_width).at_end(); } static testing::AssertionResult shaders_match(const char *a_expr, const char *b_expr, elk_fs_visitor *a, elk_fs_visitor *b) { /* Using the CFG string dump for this. Not ideal but it is * convenient that covers some CFG information, helping to * check if the optimization is keeping the CFG valid. */ char *a_str = NULL; size_t a_size = 0; FILE *a_file = open_memstream(&a_str, &a_size); a->cfg->dump(a_file); fclose(a_file); char *b_str = NULL; size_t b_size = 0; FILE *b_file = open_memstream(&b_str, &b_size); b->cfg->dump(b_file); fclose(b_file); if (a_size != b_size || strcmp(a_str, b_str) != 0) { std::stringstream result; result << "Shaders don't match.\n\n" << a_expr << " is:\n\n" << a_str << "\n---\n" << b_expr << " is:\n\n" << b_str << "\n"; free(a_str); free(b_str); return testing::AssertionFailure() << result.str(); } free(a_str); free(b_str); return testing::AssertionSuccess(); } #define ASSERT_SHADERS_MATCH(a, b) ASSERT_PRED_FORMAT2(shaders_match, a, b) TEST_F(PredicatedBreakTest, TopBreakWithoutContinue) { fs_builder a = make_builder(shader_a); fs_builder b = make_builder(shader_b); elk_fs_reg r1 = elk_vec8_grf(1, 0); elk_fs_reg r2 = elk_vec8_grf(2, 0); elk_fs_reg r3 = elk_vec8_grf(3, 0); a.DO(); a.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.ADD(r1, r2, r3); a.WHILE(); a.NOP(); /* There's always going to be something after a WHILE. */ shader_a->calculate_cfg(); /* The IF/ENDIF around the BREAK is expected to be removed. */ bool progress = elk_opt_predicated_break(shader_a); EXPECT_TRUE(progress); b.DO(); b.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); b.BREAK()->predicate = ELK_PREDICATE_NORMAL; b.ADD(r1, r2, r3); b.WHILE(); b.NOP(); shader_b->calculate_cfg(); ASSERT_SHADERS_MATCH(shader_a, shader_b); } TEST_F(PredicatedBreakTest, TopBreakWithContinue) { fs_builder a = make_builder(shader_a); fs_builder b = make_builder(shader_b); elk_fs_reg r1 = elk_vec8_grf(1, 0); elk_fs_reg r2 = elk_vec8_grf(2, 0); elk_fs_reg r3 = elk_vec8_grf(3, 0); a.DO(); a.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.ADD(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); a.IF(ELK_PREDICATE_NORMAL); a.CONTINUE(); a.ENDIF(); a.MUL(r1, r2, r3); a.WHILE(); a.NOP(); /* There's always going to be something after a WHILE. */ shader_a->calculate_cfg(); /* The IF/ENDIF around the BREAK and the CONTINUE are expected to be * removed. */ bool progress = elk_opt_predicated_break(shader_a); EXPECT_TRUE(progress); b.DO(); b.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); b.BREAK()->predicate = ELK_PREDICATE_NORMAL; b.ADD(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); b.CONTINUE()->predicate = ELK_PREDICATE_NORMAL; b.MUL(r1, r2, r3); b.WHILE(); b.NOP(); shader_b->calculate_cfg(); ASSERT_SHADERS_MATCH(shader_a, shader_b); } TEST_F(PredicatedBreakTest, DISABLED_BottomBreakWithoutContinue) { fs_builder a = make_builder(shader_a); fs_builder b = make_builder(shader_b); elk_fs_reg r1 = elk_vec8_grf(1, 0); elk_fs_reg r2 = elk_vec8_grf(2, 0); elk_fs_reg r3 = elk_vec8_grf(3, 0); a.DO(); a.ADD(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.WHILE(); a.NOP(); /* There's always going to be something after a WHILE. */ shader_a->calculate_cfg(); /* BREAK is the only way to exit the loop, so expect to remove the * IF/BREAK/ENDIF and add a predicate to WHILE. */ bool progress = elk_opt_predicated_break(shader_a); EXPECT_TRUE(progress); b.DO(); b.ADD(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); auto w = b.WHILE(); w->predicate = ELK_PREDICATE_NORMAL; w->predicate_inverse = true; b.NOP(); shader_b->calculate_cfg(); ASSERT_SHADERS_MATCH(shader_a, shader_b); } TEST_F(PredicatedBreakTest, BottomBreakWithContinue) { fs_builder a = make_builder(shader_a); fs_builder b = make_builder(shader_b); elk_fs_reg r1 = elk_vec8_grf(1, 0); elk_fs_reg r2 = elk_vec8_grf(2, 0); elk_fs_reg r3 = elk_vec8_grf(3, 0); a.DO(); a.ADD(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); a.IF(ELK_PREDICATE_NORMAL); a.CONTINUE(); a.ENDIF(); a.MUL(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.WHILE(); a.NOP(); /* There's always going to be something after a WHILE. */ shader_a->calculate_cfg(); /* With a CONTINUE, the BREAK can't be removed, but still remove the * IF/ENDIF around both of them. */ bool progress = elk_opt_predicated_break(shader_a); EXPECT_TRUE(progress); b.DO(); b.ADD(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); b.CONTINUE()->predicate = ELK_PREDICATE_NORMAL; b.MUL(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); b.BREAK()->predicate = ELK_PREDICATE_NORMAL; b.WHILE(); b.NOP(); shader_b->calculate_cfg(); ASSERT_SHADERS_MATCH(shader_a, shader_b); } TEST_F(PredicatedBreakTest, TwoBreaks) { fs_builder a = make_builder(shader_a); fs_builder b = make_builder(shader_b); elk_fs_reg r1 = elk_vec8_grf(1, 0); elk_fs_reg r2 = elk_vec8_grf(2, 0); elk_fs_reg r3 = elk_vec8_grf(3, 0); a.DO(); a.ADD(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.MUL(r1, r2, r3); a.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); a.IF(ELK_PREDICATE_NORMAL); a.BREAK(); a.ENDIF(); a.AND(r1, r2, r3); a.WHILE(); a.NOP(); /* There's always going to be something after a WHILE. */ shader_a->calculate_cfg(); /* The IF/ENDIF around the breaks are expected to be removed. */ bool progress = elk_opt_predicated_break(shader_a); EXPECT_TRUE(progress); b.DO(); b.ADD(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_NZ); b.BREAK()->predicate = ELK_PREDICATE_NORMAL; b.MUL(r1, r2, r3); b.CMP(r1, r2, r3, ELK_CONDITIONAL_GE); b.BREAK()->predicate = ELK_PREDICATE_NORMAL; b.AND(r1, r2, r3); b.WHILE(); b.NOP(); /* There's always going to be something after a WHILE. */ shader_b->calculate_cfg(); ASSERT_SHADERS_MATCH(shader_a, shader_b); }