nir: Add tests for dead write elimination
authorCaio Marcelo de Oliveira Filho <caio.oliveira@intel.com>
Wed, 29 Aug 2018 23:30:09 +0000 (16:30 -0700)
committerCaio Marcelo de Oliveira Filho <caio.oliveira@intel.com>
Tue, 16 Oct 2018 00:29:46 +0000 (17:29 -0700)
Note at the moment the pass called is nir_opt_copy_prop_vars, because
dead write elimination is implemented there.

Also added tests that involve identifying dead writes in multiple
blocks (e.g. the overwrite happens in another block).  Those currently
fail as expected, so are marked to be skipped.

Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
src/compiler/nir/tests/vars_tests.cpp

index 9b2877fcc922ce45466b3e606675dac9ab9fad34..0a2ff3f3d3291df72b80caf896d7ec5733c1dbd2 100644 (file)
@@ -26,6 +26,9 @@
 #include "nir.h"
 #include "nir_builder.h"
 
+/* This optimization is done together with copy propagation. */
+#define nir_opt_dead_write_vars nir_opt_copy_prop_vars
+
 namespace {
 
 class nir_vars_test : public ::testing::Test {
@@ -132,6 +135,7 @@ nir_vars_test::find_next_intrinsic(nir_intrinsic_op intrinsic,
 
 /* Allow grouping the tests while still sharing the helpers. */
 class nir_copy_prop_vars_test : public nir_vars_test {};
+class nir_dead_write_vars_test : public nir_vars_test {};
 
 } // namespace
 
@@ -188,3 +192,240 @@ TEST_F(nir_copy_prop_vars_test, simple_store_load)
       EXPECT_EQ(store->src[1].ssa, stored_value);
    }
 }
+
+TEST_F(nir_dead_write_vars_test, no_dead_writes_in_block)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 2);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_FALSE(progress);
+}
+
+TEST_F(nir_dead_write_vars_test, no_dead_writes_different_components_in_block)
+{
+   nir_variable **v = create_many_ivec2(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1 << 0);
+   nir_store_var(b, v[0], nir_load_var(b, v[2]), 1 << 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_FALSE(progress);
+}
+
+TEST_F(nir_dead_write_vars_test, no_dead_writes_in_if_statement)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 6);
+
+   nir_store_var(b, v[2], nir_load_var(b, v[0]), 1);
+   nir_store_var(b, v[3], nir_load_var(b, v[1]), 1);
+
+   /* Each arm of the if statement will overwrite one store. */
+   nir_if *if_stmt = nir_push_if(b, nir_imm_int(b, 0));
+   nir_store_var(b, v[2], nir_load_var(b, v[4]), 1);
+
+   nir_push_else(b, if_stmt);
+   nir_store_var(b, v[3], nir_load_var(b, v[5]), 1);
+
+   nir_pop_if(b, if_stmt);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_FALSE(progress);
+}
+
+TEST_F(nir_dead_write_vars_test, no_dead_writes_in_loop_statement)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+
+   /* Loop will write other value.  Since it might not be executed, it doesn't
+    * kill the first write.
+    */
+   nir_loop *loop = nir_push_loop(b);
+
+   nir_if *if_stmt = nir_push_if(b, nir_imm_int(b, 0));
+   nir_jump(b, nir_jump_break);
+   nir_pop_if(b, if_stmt);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[2]), 1);
+   nir_pop_loop(b, loop);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_FALSE(progress);
+}
+
+TEST_F(nir_dead_write_vars_test, dead_write_in_block)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+   nir_ssa_def *load_v2 = nir_load_var(b, v[2]);
+   nir_store_var(b, v[0], load_v2, 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   EXPECT_EQ(1, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = find_next_intrinsic(nir_intrinsic_store_deref, NULL);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v2);
+}
+
+TEST_F(nir_dead_write_vars_test, dead_write_components_in_block)
+{
+   nir_variable **v = create_many_ivec2(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1 << 0);
+   nir_ssa_def *load_v2 = nir_load_var(b, v[2]);
+   nir_store_var(b, v[0], load_v2, 1 << 0);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   EXPECT_EQ(1, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = find_next_intrinsic(nir_intrinsic_store_deref, NULL);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v2);
+}
+
+
+/* TODO: The DISABLED tests below depend on the dead write removal be able to
+ * identify dead writes between multiple blocks.  This is still not
+ * implemented.
+ */
+
+TEST_F(nir_dead_write_vars_test, DISABLED_dead_write_in_two_blocks)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+   nir_ssa_def *load_v2 = nir_load_var(b, v[2]);
+
+   /* Causes the stores to be in different blocks. */
+   nir_pop_if(b, nir_push_if(b, nir_imm_int(b, 0)));
+
+   nir_store_var(b, v[0], load_v2, 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   EXPECT_EQ(1, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = find_next_intrinsic(nir_intrinsic_store_deref, NULL);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v2);
+}
+
+TEST_F(nir_dead_write_vars_test, DISABLED_dead_write_components_in_two_blocks)
+{
+   nir_variable **v = create_many_ivec2(nir_var_shader_storage, "v", 3);
+
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1 << 0);
+
+   /* Causes the stores to be in different blocks. */
+   nir_pop_if(b, nir_push_if(b, nir_imm_int(b, 0)));
+
+   nir_ssa_def *load_v2 = nir_load_var(b, v[2]);
+   nir_store_var(b, v[0], load_v2, 1 << 0);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   EXPECT_EQ(1, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = find_next_intrinsic(nir_intrinsic_store_deref, NULL);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v2);
+}
+
+TEST_F(nir_dead_write_vars_test, DISABLED_dead_writes_in_if_statement)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 4);
+
+   /* Both branches will overwrite, making the previous store dead. */
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+
+   nir_if *if_stmt = nir_push_if(b, nir_imm_int(b, 0));
+   nir_ssa_def *load_v2 = nir_load_var(b, v[2]);
+   nir_store_var(b, v[0], load_v2, 1);
+
+   nir_push_else(b, if_stmt);
+   nir_ssa_def *load_v3 = nir_load_var(b, v[3]);
+   nir_store_var(b, v[0], load_v3, 1);
+
+   nir_pop_if(b, if_stmt);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+   EXPECT_EQ(2, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = NULL;
+   store = find_next_intrinsic(nir_intrinsic_store_deref, store);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v2);
+
+   store = find_next_intrinsic(nir_intrinsic_store_deref, store);
+   ASSERT_TRUE(store->src[1].is_ssa);
+   EXPECT_EQ(store->src[1].ssa, load_v3);
+}
+
+TEST_F(nir_dead_write_vars_test, DISABLED_memory_barrier_in_two_blocks)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 2);
+
+   nir_store_var(b, v[0], nir_imm_int(b, 1), 1);
+   nir_store_var(b, v[1], nir_imm_int(b, 2), 1);
+
+   /* Split into many blocks. */
+   nir_pop_if(b, nir_push_if(b, nir_imm_int(b, 0)));
+
+   /* Because it is before the barrier, this will kill the previous store to that target. */
+   nir_store_var(b, v[0], nir_imm_int(b, 3), 1);
+
+   nir_builder_instr_insert(b, &nir_intrinsic_instr_create(b->shader, nir_intrinsic_memory_barrier)->instr);
+
+   nir_store_var(b, v[1], nir_imm_int(b, 4), 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   EXPECT_EQ(3, count_intrinsics(nir_intrinsic_store_deref));
+}
+
+TEST_F(nir_dead_write_vars_test, DISABLED_unrelated_barrier_in_two_blocks)
+{
+   nir_variable **v = create_many_int(nir_var_shader_storage, "v", 3);
+   nir_variable *out = create_int(nir_var_shader_out, "out");
+
+   nir_store_var(b, out, nir_load_var(b, v[1]), 1);
+   nir_store_var(b, v[0], nir_load_var(b, v[1]), 1);
+
+   /* Split into many blocks. */
+   nir_pop_if(b, nir_push_if(b, nir_imm_int(b, 0)));
+
+   /* Emit vertex will ensure writes to output variables are considered used,
+    * but should not affect other types of variables. */
+
+   nir_builder_instr_insert(b, &nir_intrinsic_instr_create(b->shader, nir_intrinsic_emit_vertex)->instr);
+
+   nir_store_var(b, out, nir_load_var(b, v[2]), 1);
+   nir_store_var(b, v[0], nir_load_var(b, v[2]), 1);
+
+   bool progress = nir_opt_dead_write_vars(b->shader);
+   ASSERT_TRUE(progress);
+
+   /* Verify the first write to v[0] was removed. */
+   EXPECT_EQ(3, count_intrinsics(nir_intrinsic_store_deref));
+
+   nir_intrinsic_instr *store = NULL;
+   store = find_next_intrinsic(nir_intrinsic_store_deref, store);
+   EXPECT_EQ(nir_intrinsic_get_var(store, 0), out);
+   store = find_next_intrinsic(nir_intrinsic_store_deref, store);
+   EXPECT_EQ(nir_intrinsic_get_var(store, 0), out);
+   store = find_next_intrinsic(nir_intrinsic_store_deref, store);
+   EXPECT_EQ(nir_intrinsic_get_var(store, 0), v[0]);
+}