i965: Add src/dst interference for certain instructions with hazards.
authorKenneth Graunke <kenneth@whitecape.org>
Fri, 20 Nov 2015 00:00:18 +0000 (16:00 -0800)
committerKenneth Graunke <kenneth@whitecape.org>
Mon, 30 Nov 2015 08:34:07 +0000 (00:34 -0800)
When working on tessellation shaders, I created some vec4 virtual
opcodes for creating message headers through a sequence like:

   mov(8) g7<1>UD      0x00000000UD    { align1 WE_all 1Q compacted };
   mov(1) g7.5<1>UD    0x00000100UD    { align1 WE_all };
   mov(1) g7<1>UD      g0<0,1,0>UD     { align1 WE_all compacted };
   mov(1) g7.3<1>UD    g8<0,1,0>UD     { align1 WE_all };

This is done in the generator since the vec4 backend can't handle align1
regioning.  From the visitor's point of view, this is a single opcode:

   hs_set_output_urb_offsets vgrf7.0:UD, 1U, vgrf8.xxxx:UD

Normally, there's no hazard between sources and destinations - an
instruction (naturally) reads its sources, then writes the result to the
destination.  However, when the virtual instruction generates multiple
hardware instructions, we can get into trouble.

In the above example, if the register allocator assigned vgrf7 and vgrf8
to the same hardware register, then we'd clobber the source with 0 in
the first instruction, and read back the wrong value in the last one.

It occured to me that this is exactly the same problem we have with
SIMD16 instructions that use W/UW or B/UB types with 0 stride.  The
hardware implicitly decodes them as two SIMD8 instructions, and with
the overlapping regions, the first would clobber the second.

Previously, we handled that by incrementing the live range end IP by 1,
which works, but is excessive: the next instruction doesn't actually
care about that.  It might also be the end of control flow.  This might
keep values alive too long.  What we really want is to say "my source
and destinations interfere".

This patch creates new infrastructure for doing just that, and teaches
the register allocator to add interference when there's a hazard.  For
my vec4 case, we can determine this by switching on opcodes.  For the
SIMD16 case, we just move the existing code there.

I audited our existing virtual opcodes that generate multiple
instructions; I believe FS_OPCODE_PACK_HALF_2x16_SPLIT needs this
treatment as well, but no others.

v2: Rebased by mattst88.

Signed-off-by: Kenneth Graunke <kenneth@whitecape.org>
Reviewed-by: Matt Turner <mattst88@gmail.com>
src/mesa/drivers/dri/i965/brw_fs.cpp
src/mesa/drivers/dri/i965/brw_fs_live_variables.cpp
src/mesa/drivers/dri/i965/brw_fs_reg_allocate.cpp
src/mesa/drivers/dri/i965/brw_ir_fs.h
src/mesa/drivers/dri/i965/brw_ir_vec4.h
src/mesa/drivers/dri/i965/brw_vec4.cpp
src/mesa/drivers/dri/i965/brw_vec4_reg_allocate.cpp

index 7904f4d286259f89aef41e258c5fc47b6c7b10ac..d2881b2d7a251bcab6716bb2a252435bd3c5536f 100644 (file)
@@ -288,6 +288,71 @@ fs_inst::is_send_from_grf() const
    }
 }
 
+/**
+ * Returns true if this instruction's sources and destinations cannot
+ * safely be the same register.
+ *
+ * In most cases, a register can be written over safely by the same
+ * instruction that is its last use.  For a single instruction, the
+ * sources are dereferenced before writing of the destination starts
+ * (naturally).
+ *
+ * However, there are a few cases where this can be problematic:
+ *
+ * - Virtual opcodes that translate to multiple instructions in the
+ *   code generator: if src == dst and one instruction writes the
+ *   destination before a later instruction reads the source, then
+ *   src will have been clobbered.
+ *
+ * - SIMD16 compressed instructions with certain regioning (see below).
+ *
+ * The register allocator uses this information to set up conflicts between
+ * GRF sources and the destination.
+ */
+bool
+fs_inst::has_source_and_destination_hazard() const
+{
+   switch (opcode) {
+   case FS_OPCODE_PACK_HALF_2x16_SPLIT:
+      /* Multiple partial writes to the destination */
+      return true;
+   default:
+      /* The SIMD16 compressed instruction
+       *
+       * add(16)      g4<1>F      g4<8,8,1>F   g6<8,8,1>F
+       *
+       * is actually decoded in hardware as:
+       *
+       * add(8)       g4<1>F      g4<8,8,1>F   g6<8,8,1>F
+       * add(8)       g5<1>F      g5<8,8,1>F   g7<8,8,1>F
+       *
+       * Which is safe.  However, if we have uniform accesses
+       * happening, we get into trouble:
+       *
+       * add(8)       g4<1>F      g4<0,1,0>F   g6<8,8,1>F
+       * add(8)       g5<1>F      g4<0,1,0>F   g7<8,8,1>F
+       *
+       * Now our destination for the first instruction overwrote the
+       * second instruction's src0, and we get garbage for those 8
+       * pixels.  There's a similar issue for the pre-gen6
+       * pixel_x/pixel_y, which are registers of 16-bit values and thus
+       * would get stomped by the first decode as well.
+       */
+      if (exec_size == 16) {
+         for (int i = 0; i < sources; i++) {
+            if (src[i].file == VGRF && (src[i].stride == 0 ||
+                                        src[i].type == BRW_REGISTER_TYPE_UW ||
+                                        src[i].type == BRW_REGISTER_TYPE_W ||
+                                        src[i].type == BRW_REGISTER_TYPE_UB ||
+                                        src[i].type == BRW_REGISTER_TYPE_B)) {
+               return true;
+            }
+         }
+      }
+      return false;
+   }
+}
+
 bool
 fs_inst::is_copy_payload(const brw::simple_allocator &grf_alloc) const
 {
index 80fb8c28f81bca31cda83083045df1c42240b650..66b70a9144b1a4ace27bd5f03311ff0aabacd297 100644 (file)
@@ -59,42 +59,8 @@ fs_live_variables::setup_one_read(struct block_data *bd, fs_inst *inst,
    int var = var_from_reg(reg);
    assert(var < num_vars);
 
-   /* In most cases, a register can be written over safely by the
-    * same instruction that is its last use.  For a single
-    * instruction, the sources are dereferenced before writing of the
-    * destination starts (naturally).  This gets more complicated for
-    * simd16, because the instruction:
-    *
-    * add(16)      g4<1>F      g4<8,8,1>F   g6<8,8,1>F
-    *
-    * is actually decoded in hardware as:
-    *
-    * add(8)       g4<1>F      g4<8,8,1>F   g6<8,8,1>F
-    * add(8)       g5<1>F      g5<8,8,1>F   g7<8,8,1>F
-    *
-    * Which is safe.  However, if we have uniform accesses
-    * happening, we get into trouble:
-    *
-    * add(8)       g4<1>F      g4<0,1,0>F   g6<8,8,1>F
-    * add(8)       g5<1>F      g4<0,1,0>F   g7<8,8,1>F
-    *
-    * Now our destination for the first instruction overwrote the
-    * second instruction's src0, and we get garbage for those 8
-    * pixels.  There's a similar issue for the pre-gen6
-    * pixel_x/pixel_y, which are registers of 16-bit values and thus
-    * would get stomped by the first decode as well.
-    */
-   int end_ip = ip;
-   if (inst->exec_size == 16 && (reg.stride == 0 ||
-                                 reg.type == BRW_REGISTER_TYPE_UW ||
-                                 reg.type == BRW_REGISTER_TYPE_W ||
-                                 reg.type == BRW_REGISTER_TYPE_UB ||
-                                 reg.type == BRW_REGISTER_TYPE_B)) {
-      end_ip++;
-   }
-
    start[var] = MIN2(start[var], ip);
-   end[var] = MAX2(end[var], end_ip);
+   end[var] = MAX2(end[var], ip);
 
    /* The use[] bitset marks when the block makes use of a variable (VGRF
     * channel) without having completely defined that variable within the
index 40129fd695ed0c4959aea89b2253ee28c4753f49..2347cd5d33f726233b1f04d29c337623584594c0 100644 (file)
@@ -597,6 +597,19 @@ fs_visitor::assign_regs(bool allow_spilling)
       }
    }
 
+   /* Certain instructions can't safely use the same register for their
+    * sources and destination.  Add interference.
+    */
+   foreach_block_and_inst(block, fs_inst, inst, cfg) {
+      if (inst->dst.file == VGRF && inst->has_source_and_destination_hazard()) {
+         for (unsigned i = 0; i < 3; i++) {
+            if (inst->src[i].file == VGRF) {
+               ra_add_node_interference(g, inst->dst.nr, inst->src[i].nr);
+            }
+         }
+      }
+   }
+
    setup_payload_interference(g, payload_node_count, first_payload_node);
    if (devinfo->gen >= 7) {
       int first_used_mrf = BRW_MAX_MRF(devinfo->gen);
index 84ee529290846a6562812bbee519d3a0ce690ca0..c3eec2efb42fa9b43f676cefb840ee18bfc3aac6 100644 (file)
@@ -205,6 +205,7 @@ public:
    bool can_do_source_mods(const struct brw_device_info *devinfo);
    bool can_change_types() const;
    bool has_side_effects() const;
+   bool has_source_and_destination_hazard() const;
 
    bool reads_flag() const;
    bool writes_flag() const;
index 861d7b83e108132d9038de406ca9c6543c1e1784..660becaafa74703cdc2cfabd11799aa594d93aed 100644 (file)
@@ -169,6 +169,7 @@ public:
    void reswizzle(int dst_writemask, int swizzle);
    bool can_do_source_mods(const struct brw_device_info *devinfo);
    bool can_change_types() const;
+   bool has_source_and_destination_hazard() const;
 
    bool reads_flag()
    {
index 9a79d67e12f2d55653e64c158d58ee5347f3b15e..a697bdf84a082533ec4065291ee5c399521c89b2 100644 (file)
@@ -161,6 +161,35 @@ vec4_instruction::is_send_from_grf()
    }
 }
 
+/**
+ * Returns true if this instruction's sources and destinations cannot
+ * safely be the same register.
+ *
+ * In most cases, a register can be written over safely by the same
+ * instruction that is its last use.  For a single instruction, the
+ * sources are dereferenced before writing of the destination starts
+ * (naturally).
+ *
+ * However, there are a few cases where this can be problematic:
+ *
+ * - Virtual opcodes that translate to multiple instructions in the
+ *   code generator: if src == dst and one instruction writes the
+ *   destination before a later instruction reads the source, then
+ *   src will have been clobbered.
+ *
+ * The register allocator uses this information to set up conflicts between
+ * GRF sources and the destination.
+ */
+bool
+vec4_instruction::has_source_and_destination_hazard() const
+{
+   switch (opcode) {
+   /* Most opcodes in the vec4 world use MRFs. */
+   default:
+      return false;
+   }
+}
+
 unsigned
 vec4_instruction::regs_read(unsigned arg) const
 {
index 01c9c96276e923b1ab0d8435c477acfb5f4b621b..afc326612a2812f3ff0b667a356788e6fcdab48a 100644 (file)
@@ -221,6 +221,19 @@ vec4_visitor::reg_allocate()
       }
    }
 
+   /* Certain instructions can't safely use the same register for their
+    * sources and destination.  Add interference.
+    */
+   foreach_block_and_inst(block, vec4_instruction, inst, cfg) {
+      if (inst->dst.file == VGRF && inst->has_source_and_destination_hazard()) {
+         for (unsigned i = 0; i < 3; i++) {
+            if (inst->src[i].file == VGRF) {
+               ra_add_node_interference(g, inst->dst.nr, inst->src[i].nr);
+            }
+         }
+      }
+   }
+
    setup_payload_interference(g, first_payload_node, node_count);
 
    if (!ra_allocate(g)) {