more bits for shader program hash

gm/lumafilter is drawing wrong for me with SkVMBlitter,
but only when I enable its cache.  I thought it must be
a collision between program fingerprints, but on further
inspection that doesn't seem to be the case, even 32-bit.

But now that I've done this, might as well keep it.
Even if p(collision) is small, p^2 is smaller.

Other small cleanups to what we hash and how:
   - don't include derived fields in Instruction equality and hashing
   - use SkOpts::hash() instead of manual std::hash/xor hack

Small struct layout changes to keep everything dense.

Change-Id: I5ba817296f0bfefa0e18f62d103094d0c63bd50d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/266282
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
diff --git a/src/core/SkVM.cpp b/src/core/SkVM.cpp
index 87c7a1b..912d4cd 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -13,8 +13,8 @@
 #include "include/private/SkThreadID.h"
 #include "include/private/SkVx.h"
 #include "src/core/SkCpu.h"
+#include "src/core/SkOpts.h"
 #include "src/core/SkVM.h"
-#include <functional>  // std::hash
 
 bool gSkVMJITViaDylib{false};
 
@@ -446,39 +446,22 @@
         return {fProgram, fStrides, debug_name};
     }
 
-    // TODO: it's probably not important that we include post-Builder::done() fields like
-    // death, can_hoist, and used_in_loop in operator==() and InstructionHash::operator().
-    // They'll always have the same, initial values as set in Builder::push().
-
+    // We skip fields only written after Builder::done() (death, can_hoist, used_in_loop) here
+    // for equality and hashing.  They'll always have the same default values in Builder::push().
     static bool operator==(const Builder::Instruction& a, const Builder::Instruction& b) {
-        return a.op           == b.op
-            && a.x            == b.x
-            && a.y            == b.y
-            && a.z            == b.z
-            && a.immy         == b.immy
-            && a.immz         == b.immz
-            && a.death        == b.death
-            && a.can_hoist    == b.can_hoist
-            && a.used_in_loop == b.used_in_loop;
+        return a.op   == b.op
+            && a.x    == b.x
+            && a.y    == b.y
+            && a.z    == b.z
+            && a.immy == b.immy
+            && a.immz == b.immz;
     }
 
-    // TODO: replace with SkOpts::hash()?
-    size_t Builder::InstructionHash::operator()(const Instruction& inst) const {
-        auto hash = [](auto v) {
-            return std::hash<decltype(v)>{}(v);
-        };
-        return hash((uint8_t)inst.op)
-             ^ hash(inst.x)
-             ^ hash(inst.y)
-             ^ hash(inst.z)
-             ^ hash(inst.immy)
-             ^ hash(inst.immz)
-             ^ hash(inst.death)
-             ^ hash(inst.can_hoist)
-             ^ hash(inst.used_in_loop);
+    uint32_t Builder::InstructionHash::operator()(const Instruction& inst, uint32_t seed) const {
+        return SkOpts::hash(&inst, offsetof(Instruction, death), seed);
     }
 
-    uint32_t Builder::hash() const { return fHash; }
+    uint64_t Builder::hash() const { return (uint64_t)fHashLo | (uint64_t)fHashHi << 32; }
 
     // Most instructions produce a value and return it by ID,
     // the value-producing instruction's own index in the program vector.
@@ -486,9 +469,11 @@
         Instruction inst{op, x, y, z, immy, immz,
                          /*death=*/0, /*can_hoist=*/true, /*used_in_loop=*/false};
 
-        // This InstructionHash{}() call should be free given we're about to use fIndex below.
-        fHash ^= InstructionHash{}(inst);
-        fHash = SkChecksum::CheapMix(fHash);  // Make sure instruction order matters.
+        // This first InstructionHash{}() call should be free given we're about to use fIndex below.
+        fHashLo ^= InstructionHash{}(inst, 0);    // Two hash streams with different seeds.
+        fHashHi ^= InstructionHash{}(inst, 1);
+        fHashLo = SkChecksum::CheapMix(fHashLo);  // Mix to make sure instruction order matters.
+        fHashHi = SkChecksum::CheapMix(fHashHi);
 
         // Basic common subexpression elimination:
         // if we've already seen this exact Instruction, use it instead of creating a new one.
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index 2ddd335..84780b5 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -319,7 +319,7 @@
         M(select) M(bytes) M(pack)            \
     // End of SKVM_OPS
 
-    enum class Op : uint8_t {
+    enum class Op : int {
     #define M(op) op,
         SKVM_OPS(M)
     #undef M
@@ -338,9 +338,11 @@
     class Builder {
     public:
         struct Instruction {
+            // Tightly packed for hashing:
             Op  op;         // v* = op(x,y,z,imm), where * == index of this Instruction.
             Val x,y,z;      // Enough arguments for mad().
             int immy,immz;  // Immediate bit pattern, shift count, argument index, etc.
+            // End tightly packed for hashing.
 
             // Not populated until done() has been called.
             int  death;         // Index of last live instruction taking this input; live if != 0.
@@ -539,11 +541,11 @@
 
         void dump(SkWStream* = nullptr) const;
 
-        uint32_t hash() const;
+        uint64_t hash() const;
 
     private:
         struct InstructionHash {
-            size_t operator()(const Instruction& inst) const;
+            uint32_t operator()(const Instruction& inst, uint32_t seed=0) const;
         };
 
         Val push(Op, Val x, Val y=NA, Val z=NA, int immy=0, int immz=0);
@@ -562,7 +564,8 @@
         SkTHashMap<Instruction, Val, InstructionHash> fIndex;
         std::vector<Instruction>                      fProgram;
         std::vector<int>                              fStrides;
-        uint32_t                                      fHash{0};
+        uint32_t                                      fHashLo{0},
+                                                      fHashHi{0};
     };
 
     // Helper to streamline allocating and working with uniforms.
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index 5248307..5f86e21 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -50,11 +50,12 @@
     SK_BEGIN_REQUIRE_DENSE;
     struct Key {
         uint64_t colorSpace;
-        uint32_t shader;
+        uint64_t shader;
         uint8_t  colorType,
                  alphaType,
                  blendMode,
                  coverage;
+        uint32_t padding{0};
         // Params::quality and Params::ctm are only passed to shader->program(),
         // not used here by the blitter itself.  No need to include them in the key;
         // they'll be folded into the shader key if used.
@@ -77,7 +78,7 @@
     SK_END_REQUIRE_DENSE;
 
     static SkString debug_name(const Key& key) {
-        return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-CS%llx-Shader%x",
+        return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-CS%llx-Shader%llx",
                               key.colorType,
                               key.alphaType,
                               key.coverage,
@@ -106,7 +107,7 @@
         // If Builder can't build this program, CacheKey() sets *ok to false.
         static Key CacheKey(const Params& params, skvm::Uniforms* uniforms, bool* ok) {
             SkASSERT(params.shader);
-            uint32_t shaderHash = 0;
+            uint64_t shaderHash = 0;
             {
                 const SkShaderBase* shader = as_SB(params.shader);
                 skvm::Builder p;