sketch out structure for ops with immediates

Lots of x86 instructions can take their right hand side argument from
memory directly rather than a register.  We can use this to avoid the
need to allocate a register for many constants.

The strategy in this CL is one of several I've been stewing over, the
simplest of those strategies I think.  There are some trade offs
particularly on ARM; this naive ARM implementation means we'll load&op
every time, even though the load part of the operation can logically be
hoisted.  From here on I'm going to just briefly enumerate a few other
approaches that allow the optimization on x86 and still allow the
immediate splats to hoist on ARM.

1) don't do it on ARM
A very simple approach is to simply not perform this optimization on
ARM.  ARM has more vector registers than x86, and so register pressure
is lower there.  We're going to end up with splatted constants in
registers anyway, so maybe just let that happen the normal way instead
of some roundabout complicated hack like I'll talk about in 2).  The
only downside in my mind is that this approach would make high-level
program descriptions platform dependent, which isn't so bad, but it's
been nice to be able to compare and diff debug dumps.

2) split Op::splat up
The next less-simple approach to this problem could fix this by
splitting splats into two Ops internally, one inner Op::immediate that
guantees at least the constant is in memory and is compatible with
immediate-aware Ops like mul_f32_imm, and an outer Op::constant that
depends on that Op::immediate and further guarantees that constant has
been broadcast into a register to be compatible with non-immediate-aware
ops like div_f32.  When building a program, immediate-aware ops would
peek for Op::constants as they do today for Op::splats, but instead of
embedding the immediate themselves, they'd replace their dependency with
the inner Op::immediate.

On x86 these new Ops would work just as advertised, with Op::immediate a
runtime no-op, Op::constant the usual vbroadcastss.  On ARM
Op::immediate needs to go all the way and splat out a register to make
the constant compatible with immediate-aware ops, and the Op::constant
becomes a noop now instead.  All this comes together to let the
Op::immediate splat hoist up out of the loop while still feeding
Op::mul_f32_imm and co.  It's a rather complicated approach to solving
this issue, but I might want to explore it just to see how bad it is.

3) do it inside the x86 JIT
The conceptually best approach is to find a way to do this peepholing
only inside the JIT only on x86, avoiding the need for new
Op::mul_f32_imm and co.  ARM and the interpreter don't benefit from this
peephole, so the x86 JIT is the logical owner of this optimization.
Finding a clean way to do this without too much disruption is the least
baked idea I've got here, though I think the most desirable long-term.

Cq-Include-Trybots: skia.primary:Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_SKVM_BLITTER,Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_USE_SKVM_BLITTER
Change-Id: Ie9c6336ed08b6fbeb89acf920a48a319f74f3643
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/254217
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Herb Derby <herb@google.com>
diff --git a/include/private/SkTHash.h b/include/private/SkTHash.h
index bc563d1..d04a2cc 100644
--- a/include/private/SkTHash.h
+++ b/include/private/SkTHash.h
@@ -274,6 +274,13 @@
         return nullptr;
     }
 
+    V& operator[](const K& key) {
+        if (V* val = this->find(key)) {
+            return *val;
+        }
+        return *this->set(key, V{});
+    }
+
     // Remove the key/value entry in the table with this key.
     void remove(const K& key) {
         SkASSERT(this->find(key));
diff --git a/resources/SkVMTest.expected b/resources/SkVMTest.expected
index 994aa59..c674540 100644
--- a/resources/SkVMTest.expected
+++ b/resources/SkVMTest.expected
@@ -1,526 +1,484 @@
 A8 over A8
-14 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load8 arg(0)
-  v2 = to_f32 v1
-  v3 = mul_f32 v0 v2
-  v4 = load8 arg(1)
-  v5 = to_f32 v4
-  v6 = mul_f32 v0 v5
-↑ v7 = splat 3F800000 (1)
-  v8 = sub_f32 v7 v3
-  v9 = mad_f32 v6 v8 v3
-↑ v10 = splat 437F0000 (255)
-  v11 = mul_f32 v9 v10
-  v12 = round v11
-  store8 arg(1) v12
+12 values:
+  v0 = load8 arg(0)
+  v1 = to_f32 v0
+  v2 = mul_f32 v1 3B808081 (0.0039215689)
+  v3 = load8 arg(1)
+  v4 = to_f32 v3
+  v5 = mul_f32 v4 3B808081 (0.0039215689)
+↑ v6 = splat 3F800000 (1)
+  v7 = sub_f32 v6 v2
+  v8 = mad_f32 v5 v7 v2
+  v9 = mul_f32 v8 437F0000 (255)
+  v10 = round v9
+  store8 arg(1) v10
 
-6 registers, 14 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat 3F800000 (1)
-r2 = splat 437F0000 (255)
+4 registers, 12 instructions:
+r0 = splat 3F800000 (1)
 loop:
-    r3 = load8 arg(0)
+    r1 = load8 arg(0)
+    r1 = to_f32 r1
+    r1 = mul_f32 r1 3B808081 (0.0039215689)
+    r2 = load8 arg(1)
+    r2 = to_f32 r2
+    r2 = mul_f32 r2 3B808081 (0.0039215689)
+    r3 = sub_f32 r0 r1
+    r1 = mad_f32 r2 r3 r1
+    r1 = mul_f32 r1 437F0000 (255)
+    r1 = round r1
+    store8 arg(1) r1
+
+A8 over G8
+17 values:
+  v0 = load8 arg(1)
+  v1 = to_f32 v0
+  v2 = mul_f32 v1 3B808081 (0.0039215689)
+  v3 = load8 arg(0)
+  v4 = to_f32 v3
+  v5 = mul_f32 v4 3B808081 (0.0039215689)
+↑ v6 = splat 3F800000 (1)
+  v7 = sub_f32 v6 v5
+  v8 = mul_f32 v2 v7
+↑ v9 = splat 3E59B3D0 (0.21259999)
+↑ v10 = splat 3F371759 (0.71520001)
+  v11 = mul_f32 v8 3D93DD98 (0.0722)
+  v12 = mad_f32 v8 v10 v11
+  v13 = mad_f32 v8 v9 v12
+  v14 = mul_f32 v13 437F0000 (255)
+  v15 = round v14
+  store8 arg(1) v15
+
+5 registers, 17 instructions:
+r0 = splat 3F800000 (1)
+r1 = splat 3E59B3D0 (0.21259999)
+r2 = splat 3F371759 (0.71520001)
+loop:
+    r3 = load8 arg(1)
     r3 = to_f32 r3
-    r3 = mul_f32 r0 r3
-    r4 = load8 arg(1)
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = load8 arg(0)
     r4 = to_f32 r4
-    r4 = mul_f32 r0 r4
-    r5 = sub_f32 r1 r3
-    r3 = mad_f32 r4 r5 r3
-    r3 = mul_f32 r3 r2
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r4 = sub_f32 r0 r4
+    r4 = mul_f32 r3 r4
+    r3 = mul_f32 r4 3D93DD98 (0.0722)
+    r3 = mad_f32 r4 r2 r3
+    r3 = mad_f32 r4 r1 r3
+    r3 = mul_f32 r3 437F0000 (255)
     r3 = round r3
     store8 arg(1) r3
 
-A8 over G8
-20 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load8 arg(1)
-  v2 = to_f32 v1
-  v3 = mul_f32 v0 v2
-  v4 = load8 arg(0)
-  v5 = to_f32 v4
-  v6 = mul_f32 v0 v5
-↑ v7 = splat 3F800000 (1)
-  v8 = sub_f32 v7 v6
-  v9 = mul_f32 v3 v8
-↑ v10 = splat 3E59B3D0 (0.21259999)
-↑ v11 = splat 3F371759 (0.71520001)
-↑ v12 = splat 3D93DD98 (0.0722)
-  v13 = mul_f32 v9 v12
-  v14 = mad_f32 v9 v11 v13
-  v15 = mad_f32 v9 v10 v14
-↑ v16 = splat 437F0000 (255)
-  v17 = mul_f32 v15 v16
-  v18 = round v17
-  store8 arg(1) v18
-
-8 registers, 20 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat 3F800000 (1)
-r2 = splat 3E59B3D0 (0.21259999)
-r3 = splat 3F371759 (0.71520001)
-r4 = splat 3D93DD98 (0.0722)
-r5 = splat 437F0000 (255)
-loop:
-    r6 = load8 arg(1)
-    r6 = to_f32 r6
-    r6 = mul_f32 r0 r6
-    r7 = load8 arg(0)
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r7 = sub_f32 r1 r7
-    r7 = mul_f32 r6 r7
-    r6 = mul_f32 r7 r4
-    r6 = mad_f32 r7 r3 r6
-    r6 = mad_f32 r7 r2 r6
-    r6 = mul_f32 r6 r5
-    r6 = round r6
-    store8 arg(1) r6
-
 A8 over RGBA_8888
-37 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load32 arg(1)
-↑ v2 = splat FF (3.5733111e-43)
-  v3 = extract v1 0 v2
-  v4 = to_f32 v3
-  v5 = mul_f32 v0 v4
-  v6 = load8 arg(0)
-  v7 = to_f32 v6
-  v8 = mul_f32 v0 v7
-↑ v9 = splat 3F800000 (1)
-  v10 = sub_f32 v9 v8
-  v11 = mul_f32 v5 v10
-↑ v12 = splat 437F0000 (255)
-  v13 = mul_f32 v11 v12
-  v14 = round v13
-  v15 = extract v1 8 v2
-  v16 = to_f32 v15
-  v17 = mul_f32 v0 v16
-  v18 = mul_f32 v17 v10
-  v19 = mul_f32 v18 v12
-  v20 = round v19
-  v21 = pack v14 v20 8
-  v22 = extract v1 16 v2
-  v23 = to_f32 v22
-  v24 = mul_f32 v0 v23
-  v25 = mul_f32 v24 v10
-  v26 = mul_f32 v25 v12
-  v27 = round v26
-  v28 = extract v1 24 v2
-  v29 = to_f32 v28
-  v30 = mul_f32 v0 v29
-  v31 = mad_f32 v30 v10 v8
-  v32 = mul_f32 v31 v12
-  v33 = round v32
-  v34 = pack v27 v33 8
-  v35 = pack v21 v34 16
-  store32 arg(1) v35
+35 values:
+  v0 = load32 arg(1)
+↑ v1 = splat FF (3.5733111e-43)
+  v2 = extract v0 0 v1
+  v3 = to_f32 v2
+  v4 = mul_f32 v3 3B808081 (0.0039215689)
+  v5 = load8 arg(0)
+  v6 = to_f32 v5
+  v7 = mul_f32 v6 3B808081 (0.0039215689)
+↑ v8 = splat 3F800000 (1)
+  v9 = sub_f32 v8 v7
+  v10 = mul_f32 v4 v9
+  v11 = mul_f32 v10 437F0000 (255)
+  v12 = round v11
+  v13 = extract v0 8 v1
+  v14 = to_f32 v13
+  v15 = mul_f32 v14 3B808081 (0.0039215689)
+  v16 = mul_f32 v15 v9
+  v17 = mul_f32 v16 437F0000 (255)
+  v18 = round v17
+  v19 = pack v12 v18 8
+  v20 = extract v0 16 v1
+  v21 = to_f32 v20
+  v22 = mul_f32 v21 3B808081 (0.0039215689)
+  v23 = mul_f32 v22 v9
+  v24 = mul_f32 v23 437F0000 (255)
+  v25 = round v24
+  v26 = extract v0 24 v1
+  v27 = to_f32 v26
+  v28 = mul_f32 v27 3B808081 (0.0039215689)
+  v29 = mad_f32 v28 v9 v7
+  v30 = mul_f32 v29 437F0000 (255)
+  v31 = round v30
+  v32 = pack v25 v31 8
+  v33 = pack v19 v32 16
+  store32 arg(1) v33
 
-9 registers, 37 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat FF (3.5733111e-43)
-r2 = splat 3F800000 (1)
-r3 = splat 437F0000 (255)
+7 registers, 35 instructions:
+r0 = splat FF (3.5733111e-43)
+r1 = splat 3F800000 (1)
 loop:
-    r4 = load32 arg(1)
-    r5 = extract r4 0 r1
-    r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r6 = load8 arg(0)
-    r6 = to_f32 r6
-    r6 = mul_f32 r0 r6
-    r7 = sub_f32 r2 r6
-    r5 = mul_f32 r5 r7
-    r5 = mul_f32 r5 r3
-    r5 = round r5
-    r8 = extract r4 8 r1
-    r8 = to_f32 r8
-    r8 = mul_f32 r0 r8
-    r8 = mul_f32 r8 r7
-    r8 = mul_f32 r8 r3
-    r8 = round r8
-    r8 = pack r5 r8 8
-    r5 = extract r4 16 r1
-    r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r5 = mul_f32 r5 r7
-    r5 = mul_f32 r5 r3
-    r5 = round r5
-    r4 = extract r4 24 r1
+    r2 = load32 arg(1)
+    r3 = extract r2 0 r0
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = load8 arg(0)
     r4 = to_f32 r4
-    r4 = mul_f32 r0 r4
-    r6 = mad_f32 r4 r7 r6
-    r6 = mul_f32 r6 r3
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r5 = sub_f32 r1 r4
+    r3 = mul_f32 r3 r5
+    r3 = mul_f32 r3 437F0000 (255)
+    r3 = round r3
+    r6 = extract r2 8 r0
+    r6 = to_f32 r6
+    r6 = mul_f32 r6 3B808081 (0.0039215689)
+    r6 = mul_f32 r6 r5
+    r6 = mul_f32 r6 437F0000 (255)
     r6 = round r6
-    r6 = pack r5 r6 8
-    r6 = pack r8 r6 16
-    store32 arg(1) r6
+    r6 = pack r3 r6 8
+    r3 = extract r2 16 r0
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r3 = mul_f32 r3 r5
+    r3 = mul_f32 r3 437F0000 (255)
+    r3 = round r3
+    r2 = extract r2 24 r0
+    r2 = to_f32 r2
+    r2 = mul_f32 r2 3B808081 (0.0039215689)
+    r4 = mad_f32 r2 r5 r4
+    r4 = mul_f32 r4 437F0000 (255)
+    r4 = round r4
+    r4 = pack r3 r4 8
+    r4 = pack r6 r4 16
+    store32 arg(1) r4
 
 G8 over A8
-11 values:
+9 values:
 ↑ v0 = splat 3F800000 (1)
-↑ v1 = splat 3B808081 (0.0039215689)
-  v2 = load8 arg(1)
-  v3 = to_f32 v2
-  v4 = mul_f32 v1 v3
-↑ v5 = sub_f32 v0 v0
-  v6 = mad_f32 v4 v5 v0
-↑ v7 = splat 437F0000 (255)
-  v8 = mul_f32 v6 v7
-  v9 = round v8
-  store8 arg(1) v9
+  v1 = load8 arg(1)
+  v2 = to_f32 v1
+  v3 = mul_f32 v2 3B808081 (0.0039215689)
+↑ v4 = sub_f32 v0 v0
+  v5 = mad_f32 v3 v4 v0
+  v6 = mul_f32 v5 437F0000 (255)
+  v7 = round v6
+  store8 arg(1) v7
 
-5 registers, 11 instructions:
+3 registers, 9 instructions:
 r0 = splat 3F800000 (1)
-r1 = splat 3B808081 (0.0039215689)
-r2 = sub_f32 r0 r0
-r3 = splat 437F0000 (255)
+r1 = sub_f32 r0 r0
 loop:
-    r4 = load8 arg(1)
-    r4 = to_f32 r4
-    r4 = mul_f32 r1 r4
-    r4 = mad_f32 r4 r2 r0
-    r4 = mul_f32 r4 r3
-    r4 = round r4
-    store8 arg(1) r4
+    r2 = load8 arg(1)
+    r2 = to_f32 r2
+    r2 = mul_f32 r2 3B808081 (0.0039215689)
+    r2 = mad_f32 r2 r1 r0
+    r2 = mul_f32 r2 437F0000 (255)
+    r2 = round r2
+    store8 arg(1) r2
 
 G8 over G8
-20 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load8 arg(0)
-  v2 = to_f32 v1
-  v3 = mul_f32 v0 v2
-  v4 = load8 arg(1)
-  v5 = to_f32 v4
-  v6 = mul_f32 v0 v5
-↟ v7 = splat 3F800000 (1)
-↑ v8 = sub_f32 v7 v7
-  v9 = mad_f32 v6 v8 v3
-↑ v10 = splat 3E59B3D0 (0.21259999)
-↑ v11 = splat 3F371759 (0.71520001)
-↑ v12 = splat 3D93DD98 (0.0722)
-  v13 = mul_f32 v9 v12
-  v14 = mad_f32 v9 v11 v13
-  v15 = mad_f32 v9 v10 v14
-↑ v16 = splat 437F0000 (255)
-  v17 = mul_f32 v15 v16
-  v18 = round v17
-  store8 arg(1) v18
-
-8 registers, 20 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat 3F800000 (1)
-r1 = sub_f32 r1 r1
-r2 = splat 3E59B3D0 (0.21259999)
-r3 = splat 3F371759 (0.71520001)
-r4 = splat 3D93DD98 (0.0722)
-r5 = splat 437F0000 (255)
-loop:
-    r6 = load8 arg(0)
-    r6 = to_f32 r6
-    r6 = mul_f32 r0 r6
-    r7 = load8 arg(1)
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r6 = mad_f32 r7 r1 r6
-    r7 = mul_f32 r6 r4
-    r7 = mad_f32 r6 r3 r7
-    r7 = mad_f32 r6 r2 r7
-    r7 = mul_f32 r7 r5
-    r7 = round r7
-    store8 arg(1) r7
-
-G8 over RGBA_8888
-37 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load8 arg(0)
-  v2 = to_f32 v1
-  v3 = mul_f32 v0 v2
-  v4 = load32 arg(1)
-↑ v5 = splat FF (3.5733111e-43)
-  v6 = extract v4 0 v5
-  v7 = to_f32 v6
-  v8 = mul_f32 v0 v7
-↑ v9 = splat 3F800000 (1)
-↑ v10 = sub_f32 v9 v9
-  v11 = mad_f32 v8 v10 v3
-↑ v12 = splat 437F0000 (255)
-  v13 = mul_f32 v11 v12
-  v14 = round v13
-  v15 = extract v4 8 v5
-  v16 = to_f32 v15
-  v17 = mul_f32 v0 v16
-  v18 = mad_f32 v17 v10 v3
-  v19 = mul_f32 v18 v12
-  v20 = round v19
-  v21 = pack v14 v20 8
-  v22 = extract v4 16 v5
-  v23 = to_f32 v22
-  v24 = mul_f32 v0 v23
-  v25 = mad_f32 v24 v10 v3
-  v26 = mul_f32 v25 v12
-  v27 = round v26
-  v28 = extract v4 24 v5
-  v29 = to_f32 v28
-  v30 = mul_f32 v0 v29
-  v31 = mad_f32 v30 v10 v9
-  v32 = mul_f32 v31 v12
-  v33 = round v32
-  v34 = pack v27 v33 8
-  v35 = pack v21 v34 16
-  store32 arg(1) v35
-
-9 registers, 37 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat FF (3.5733111e-43)
-r2 = splat 3F800000 (1)
-r3 = sub_f32 r2 r2
-r4 = splat 437F0000 (255)
-loop:
-    r5 = load8 arg(0)
-    r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r6 = load32 arg(1)
-    r7 = extract r6 0 r1
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r7 = mad_f32 r7 r3 r5
-    r7 = mul_f32 r7 r4
-    r7 = round r7
-    r8 = extract r6 8 r1
-    r8 = to_f32 r8
-    r8 = mul_f32 r0 r8
-    r8 = mad_f32 r8 r3 r5
-    r8 = mul_f32 r8 r4
-    r8 = round r8
-    r8 = pack r7 r8 8
-    r7 = extract r6 16 r1
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r5 = mad_f32 r7 r3 r5
-    r5 = mul_f32 r5 r4
-    r5 = round r5
-    r6 = extract r6 24 r1
-    r6 = to_f32 r6
-    r6 = mul_f32 r0 r6
-    r6 = mad_f32 r6 r3 r2
-    r6 = mul_f32 r6 r4
-    r6 = round r6
-    r6 = pack r5 r6 8
-    r6 = pack r8 r6 16
-    store32 arg(1) r6
-
-RGBA_8888 over A8
-16 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load32 arg(0)
-↑ v2 = splat FF (3.5733111e-43)
-  v3 = extract v1 24 v2
+17 values:
+  v0 = load8 arg(0)
+  v1 = to_f32 v0
+  v2 = mul_f32 v1 3B808081 (0.0039215689)
+  v3 = load8 arg(1)
   v4 = to_f32 v3
-  v5 = mul_f32 v0 v4
-  v6 = load8 arg(1)
-  v7 = to_f32 v6
-  v8 = mul_f32 v0 v7
-↑ v9 = splat 3F800000 (1)
-  v10 = sub_f32 v9 v5
-  v11 = mad_f32 v8 v10 v5
-↑ v12 = splat 437F0000 (255)
-  v13 = mul_f32 v11 v12
-  v14 = round v13
-  store8 arg(1) v14
+  v5 = mul_f32 v4 3B808081 (0.0039215689)
+↟ v6 = splat 3F800000 (1)
+↑ v7 = sub_f32 v6 v6
+  v8 = mad_f32 v5 v7 v2
+↑ v9 = splat 3E59B3D0 (0.21259999)
+↑ v10 = splat 3F371759 (0.71520001)
+  v11 = mul_f32 v8 3D93DD98 (0.0722)
+  v12 = mad_f32 v8 v10 v11
+  v13 = mad_f32 v8 v9 v12
+  v14 = mul_f32 v13 437F0000 (255)
+  v15 = round v14
+  store8 arg(1) v15
 
-7 registers, 16 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat FF (3.5733111e-43)
-r2 = splat 3F800000 (1)
-r3 = splat 437F0000 (255)
+5 registers, 17 instructions:
+r0 = splat 3F800000 (1)
+r0 = sub_f32 r0 r0
+r1 = splat 3E59B3D0 (0.21259999)
+r2 = splat 3F371759 (0.71520001)
 loop:
-    r4 = load32 arg(0)
-    r4 = extract r4 24 r1
+    r3 = load8 arg(0)
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = load8 arg(1)
     r4 = to_f32 r4
-    r4 = mul_f32 r0 r4
-    r5 = load8 arg(1)
-    r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r6 = sub_f32 r2 r4
-    r4 = mad_f32 r5 r6 r4
-    r4 = mul_f32 r4 r3
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r3 = mad_f32 r4 r0 r3
+    r4 = mul_f32 r3 3D93DD98 (0.0722)
+    r4 = mad_f32 r3 r2 r4
+    r4 = mad_f32 r3 r1 r4
+    r4 = mul_f32 r4 437F0000 (255)
     r4 = round r4
     store8 arg(1) r4
 
-RGBA_8888 over G8
-33 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load32 arg(0)
-↑ v2 = splat FF (3.5733111e-43)
-  v3 = extract v1 0 v2
-  v4 = to_f32 v3
-  v5 = mul_f32 v0 v4
-  v6 = load8 arg(1)
-  v7 = to_f32 v6
-  v8 = mul_f32 v0 v7
-  v9 = extract v1 24 v2
-  v10 = to_f32 v9
-  v11 = mul_f32 v0 v10
-↑ v12 = splat 3F800000 (1)
-  v13 = sub_f32 v12 v11
-  v14 = mad_f32 v8 v13 v5
-↑ v15 = splat 3E59B3D0 (0.21259999)
-  v16 = extract v1 8 v2
-  v17 = to_f32 v16
-  v18 = mul_f32 v0 v17
-  v19 = mad_f32 v8 v13 v18
-↑ v20 = splat 3F371759 (0.71520001)
-  v21 = extract v1 16 v2
-  v22 = to_f32 v21
-  v23 = mul_f32 v0 v22
-  v24 = mad_f32 v8 v13 v23
-↑ v25 = splat 3D93DD98 (0.0722)
-  v26 = mul_f32 v24 v25
-  v27 = mad_f32 v19 v20 v26
-  v28 = mad_f32 v14 v15 v27
-↑ v29 = splat 437F0000 (255)
-  v30 = mul_f32 v28 v29
-  v31 = round v30
-  store8 arg(1) v31
-
-12 registers, 33 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat FF (3.5733111e-43)
-r2 = splat 3F800000 (1)
-r3 = splat 3E59B3D0 (0.21259999)
-r4 = splat 3F371759 (0.71520001)
-r5 = splat 3D93DD98 (0.0722)
-r6 = splat 437F0000 (255)
-loop:
-    r7 = load32 arg(0)
-    r8 = extract r7 0 r1
-    r8 = to_f32 r8
-    r8 = mul_f32 r0 r8
-    r9 = load8 arg(1)
-    r9 = to_f32 r9
-    r9 = mul_f32 r0 r9
-    r10 = extract r7 24 r1
-    r10 = to_f32 r10
-    r10 = mul_f32 r0 r10
-    r10 = sub_f32 r2 r10
-    r8 = mad_f32 r9 r10 r8
-    r11 = extract r7 8 r1
-    r11 = to_f32 r11
-    r11 = mul_f32 r0 r11
-    r11 = mad_f32 r9 r10 r11
-    r7 = extract r7 16 r1
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r7 = mad_f32 r9 r10 r7
-    r7 = mul_f32 r7 r5
-    r7 = mad_f32 r11 r4 r7
-    r7 = mad_f32 r8 r3 r7
-    r7 = mul_f32 r7 r6
-    r7 = round r7
-    store8 arg(1) r7
-
-RGBA_8888 over RGBA_8888
-47 values:
-↑ v0 = splat 3B808081 (0.0039215689)
-  v1 = load32 arg(0)
-↑ v2 = splat FF (3.5733111e-43)
-  v3 = extract v1 0 v2
-  v4 = to_f32 v3
-  v5 = mul_f32 v0 v4
-  v6 = load32 arg(1)
-  v7 = extract v6 0 v2
-  v8 = to_f32 v7
-  v9 = mul_f32 v0 v8
-  v10 = extract v1 24 v2
-  v11 = to_f32 v10
-  v12 = mul_f32 v0 v11
-↑ v13 = splat 3F800000 (1)
-  v14 = sub_f32 v13 v12
-  v15 = mad_f32 v9 v14 v5
-↑ v16 = splat 437F0000 (255)
-  v17 = mul_f32 v15 v16
+G8 over RGBA_8888
+35 values:
+  v0 = load8 arg(0)
+  v1 = to_f32 v0
+  v2 = mul_f32 v1 3B808081 (0.0039215689)
+  v3 = load32 arg(1)
+↑ v4 = splat FF (3.5733111e-43)
+  v5 = extract v3 0 v4
+  v6 = to_f32 v5
+  v7 = mul_f32 v6 3B808081 (0.0039215689)
+↑ v8 = splat 3F800000 (1)
+↑ v9 = sub_f32 v8 v8
+  v10 = mad_f32 v7 v9 v2
+  v11 = mul_f32 v10 437F0000 (255)
+  v12 = round v11
+  v13 = extract v3 8 v4
+  v14 = to_f32 v13
+  v15 = mul_f32 v14 3B808081 (0.0039215689)
+  v16 = mad_f32 v15 v9 v2
+  v17 = mul_f32 v16 437F0000 (255)
   v18 = round v17
-  v19 = extract v1 8 v2
-  v20 = to_f32 v19
-  v21 = mul_f32 v0 v20
-  v22 = extract v6 8 v2
-  v23 = to_f32 v22
-  v24 = mul_f32 v0 v23
-  v25 = mad_f32 v24 v14 v21
-  v26 = mul_f32 v25 v16
-  v27 = round v26
-  v28 = pack v18 v27 8
-  v29 = extract v1 16 v2
-  v30 = to_f32 v29
-  v31 = mul_f32 v0 v30
-  v32 = extract v6 16 v2
-  v33 = to_f32 v32
-  v34 = mul_f32 v0 v33
-  v35 = mad_f32 v34 v14 v31
-  v36 = mul_f32 v35 v16
-  v37 = round v36
-  v38 = extract v6 24 v2
-  v39 = to_f32 v38
-  v40 = mul_f32 v0 v39
-  v41 = mad_f32 v40 v14 v12
-  v42 = mul_f32 v41 v16
-  v43 = round v42
-  v44 = pack v37 v43 8
-  v45 = pack v28 v44 16
-  store32 arg(1) v45
+  v19 = pack v12 v18 8
+  v20 = extract v3 16 v4
+  v21 = to_f32 v20
+  v22 = mul_f32 v21 3B808081 (0.0039215689)
+  v23 = mad_f32 v22 v9 v2
+  v24 = mul_f32 v23 437F0000 (255)
+  v25 = round v24
+  v26 = extract v3 24 v4
+  v27 = to_f32 v26
+  v28 = mul_f32 v27 3B808081 (0.0039215689)
+  v29 = mad_f32 v28 v9 v8
+  v30 = mul_f32 v29 437F0000 (255)
+  v31 = round v30
+  v32 = pack v25 v31 8
+  v33 = pack v19 v32 16
+  store32 arg(1) v33
 
-11 registers, 47 instructions:
-r0 = splat 3B808081 (0.0039215689)
-r1 = splat FF (3.5733111e-43)
-r2 = splat 3F800000 (1)
-r3 = splat 437F0000 (255)
+7 registers, 35 instructions:
+r0 = splat FF (3.5733111e-43)
+r1 = splat 3F800000 (1)
+r2 = sub_f32 r1 r1
+loop:
+    r3 = load8 arg(0)
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = load32 arg(1)
+    r5 = extract r4 0 r0
+    r5 = to_f32 r5
+    r5 = mul_f32 r5 3B808081 (0.0039215689)
+    r5 = mad_f32 r5 r2 r3
+    r5 = mul_f32 r5 437F0000 (255)
+    r5 = round r5
+    r6 = extract r4 8 r0
+    r6 = to_f32 r6
+    r6 = mul_f32 r6 3B808081 (0.0039215689)
+    r6 = mad_f32 r6 r2 r3
+    r6 = mul_f32 r6 437F0000 (255)
+    r6 = round r6
+    r6 = pack r5 r6 8
+    r5 = extract r4 16 r0
+    r5 = to_f32 r5
+    r5 = mul_f32 r5 3B808081 (0.0039215689)
+    r3 = mad_f32 r5 r2 r3
+    r3 = mul_f32 r3 437F0000 (255)
+    r3 = round r3
+    r4 = extract r4 24 r0
+    r4 = to_f32 r4
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r4 = mad_f32 r4 r2 r1
+    r4 = mul_f32 r4 437F0000 (255)
+    r4 = round r4
+    r4 = pack r3 r4 8
+    r4 = pack r6 r4 16
+    store32 arg(1) r4
+
+RGBA_8888 over A8
+14 values:
+  v0 = load32 arg(0)
+↑ v1 = splat FF (3.5733111e-43)
+  v2 = extract v0 24 v1
+  v3 = to_f32 v2
+  v4 = mul_f32 v3 3B808081 (0.0039215689)
+  v5 = load8 arg(1)
+  v6 = to_f32 v5
+  v7 = mul_f32 v6 3B808081 (0.0039215689)
+↑ v8 = splat 3F800000 (1)
+  v9 = sub_f32 v8 v4
+  v10 = mad_f32 v7 v9 v4
+  v11 = mul_f32 v10 437F0000 (255)
+  v12 = round v11
+  store8 arg(1) v12
+
+5 registers, 14 instructions:
+r0 = splat FF (3.5733111e-43)
+r1 = splat 3F800000 (1)
+loop:
+    r2 = load32 arg(0)
+    r2 = extract r2 24 r0
+    r2 = to_f32 r2
+    r2 = mul_f32 r2 3B808081 (0.0039215689)
+    r3 = load8 arg(1)
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = sub_f32 r1 r2
+    r2 = mad_f32 r3 r4 r2
+    r2 = mul_f32 r2 437F0000 (255)
+    r2 = round r2
+    store8 arg(1) r2
+
+RGBA_8888 over G8
+30 values:
+  v0 = load32 arg(0)
+↑ v1 = splat FF (3.5733111e-43)
+  v2 = extract v0 0 v1
+  v3 = to_f32 v2
+  v4 = mul_f32 v3 3B808081 (0.0039215689)
+  v5 = load8 arg(1)
+  v6 = to_f32 v5
+  v7 = mul_f32 v6 3B808081 (0.0039215689)
+  v8 = extract v0 24 v1
+  v9 = to_f32 v8
+  v10 = mul_f32 v9 3B808081 (0.0039215689)
+↑ v11 = splat 3F800000 (1)
+  v12 = sub_f32 v11 v10
+  v13 = mad_f32 v7 v12 v4
+↑ v14 = splat 3E59B3D0 (0.21259999)
+  v15 = extract v0 8 v1
+  v16 = to_f32 v15
+  v17 = mul_f32 v16 3B808081 (0.0039215689)
+  v18 = mad_f32 v7 v12 v17
+↑ v19 = splat 3F371759 (0.71520001)
+  v20 = extract v0 16 v1
+  v21 = to_f32 v20
+  v22 = mul_f32 v21 3B808081 (0.0039215689)
+  v23 = mad_f32 v7 v12 v22
+  v24 = mul_f32 v23 3D93DD98 (0.0722)
+  v25 = mad_f32 v18 v19 v24
+  v26 = mad_f32 v13 v14 v25
+  v27 = mul_f32 v26 437F0000 (255)
+  v28 = round v27
+  store8 arg(1) v28
+
+9 registers, 30 instructions:
+r0 = splat FF (3.5733111e-43)
+r1 = splat 3F800000 (1)
+r2 = splat 3E59B3D0 (0.21259999)
+r3 = splat 3F371759 (0.71520001)
 loop:
     r4 = load32 arg(0)
-    r5 = extract r4 0 r1
+    r5 = extract r4 0 r0
     r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r6 = load32 arg(1)
-    r7 = extract r6 0 r1
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r8 = extract r4 24 r1
-    r8 = to_f32 r8
-    r8 = mul_f32 r0 r8
-    r9 = sub_f32 r2 r8
-    r5 = mad_f32 r7 r9 r5
-    r5 = mul_f32 r5 r3
-    r5 = round r5
-    r7 = extract r4 8 r1
-    r7 = to_f32 r7
-    r7 = mul_f32 r0 r7
-    r10 = extract r6 8 r1
-    r10 = to_f32 r10
-    r10 = mul_f32 r0 r10
-    r7 = mad_f32 r10 r9 r7
-    r7 = mul_f32 r7 r3
-    r7 = round r7
-    r7 = pack r5 r7 8
-    r4 = extract r4 16 r1
-    r4 = to_f32 r4
-    r4 = mul_f32 r0 r4
-    r5 = extract r6 16 r1
-    r5 = to_f32 r5
-    r5 = mul_f32 r0 r5
-    r4 = mad_f32 r5 r9 r4
-    r4 = mul_f32 r4 r3
-    r4 = round r4
-    r6 = extract r6 24 r1
+    r5 = mul_f32 r5 3B808081 (0.0039215689)
+    r6 = load8 arg(1)
     r6 = to_f32 r6
-    r6 = mul_f32 r0 r6
-    r8 = mad_f32 r6 r9 r8
-    r8 = mul_f32 r8 r3
-    r8 = round r8
-    r8 = pack r4 r8 8
-    r8 = pack r7 r8 16
-    store32 arg(1) r8
+    r6 = mul_f32 r6 3B808081 (0.0039215689)
+    r7 = extract r4 24 r0
+    r7 = to_f32 r7
+    r7 = mul_f32 r7 3B808081 (0.0039215689)
+    r7 = sub_f32 r1 r7
+    r5 = mad_f32 r6 r7 r5
+    r8 = extract r4 8 r0
+    r8 = to_f32 r8
+    r8 = mul_f32 r8 3B808081 (0.0039215689)
+    r8 = mad_f32 r6 r7 r8
+    r4 = extract r4 16 r0
+    r4 = to_f32 r4
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r4 = mad_f32 r6 r7 r4
+    r4 = mul_f32 r4 3D93DD98 (0.0722)
+    r4 = mad_f32 r8 r3 r4
+    r4 = mad_f32 r5 r2 r4
+    r4 = mul_f32 r4 437F0000 (255)
+    r4 = round r4
+    store8 arg(1) r4
+
+RGBA_8888 over RGBA_8888
+45 values:
+  v0 = load32 arg(0)
+↑ v1 = splat FF (3.5733111e-43)
+  v2 = extract v0 0 v1
+  v3 = to_f32 v2
+  v4 = mul_f32 v3 3B808081 (0.0039215689)
+  v5 = load32 arg(1)
+  v6 = extract v5 0 v1
+  v7 = to_f32 v6
+  v8 = mul_f32 v7 3B808081 (0.0039215689)
+  v9 = extract v0 24 v1
+  v10 = to_f32 v9
+  v11 = mul_f32 v10 3B808081 (0.0039215689)
+↑ v12 = splat 3F800000 (1)
+  v13 = sub_f32 v12 v11
+  v14 = mad_f32 v8 v13 v4
+  v15 = mul_f32 v14 437F0000 (255)
+  v16 = round v15
+  v17 = extract v0 8 v1
+  v18 = to_f32 v17
+  v19 = mul_f32 v18 3B808081 (0.0039215689)
+  v20 = extract v5 8 v1
+  v21 = to_f32 v20
+  v22 = mul_f32 v21 3B808081 (0.0039215689)
+  v23 = mad_f32 v22 v13 v19
+  v24 = mul_f32 v23 437F0000 (255)
+  v25 = round v24
+  v26 = pack v16 v25 8
+  v27 = extract v0 16 v1
+  v28 = to_f32 v27
+  v29 = mul_f32 v28 3B808081 (0.0039215689)
+  v30 = extract v5 16 v1
+  v31 = to_f32 v30
+  v32 = mul_f32 v31 3B808081 (0.0039215689)
+  v33 = mad_f32 v32 v13 v29
+  v34 = mul_f32 v33 437F0000 (255)
+  v35 = round v34
+  v36 = extract v5 24 v1
+  v37 = to_f32 v36
+  v38 = mul_f32 v37 3B808081 (0.0039215689)
+  v39 = mad_f32 v38 v13 v11
+  v40 = mul_f32 v39 437F0000 (255)
+  v41 = round v40
+  v42 = pack v35 v41 8
+  v43 = pack v26 v42 16
+  store32 arg(1) v43
+
+9 registers, 45 instructions:
+r0 = splat FF (3.5733111e-43)
+r1 = splat 3F800000 (1)
+loop:
+    r2 = load32 arg(0)
+    r3 = extract r2 0 r0
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r4 = load32 arg(1)
+    r5 = extract r4 0 r0
+    r5 = to_f32 r5
+    r5 = mul_f32 r5 3B808081 (0.0039215689)
+    r6 = extract r2 24 r0
+    r6 = to_f32 r6
+    r6 = mul_f32 r6 3B808081 (0.0039215689)
+    r7 = sub_f32 r1 r6
+    r3 = mad_f32 r5 r7 r3
+    r3 = mul_f32 r3 437F0000 (255)
+    r3 = round r3
+    r5 = extract r2 8 r0
+    r5 = to_f32 r5
+    r5 = mul_f32 r5 3B808081 (0.0039215689)
+    r8 = extract r4 8 r0
+    r8 = to_f32 r8
+    r8 = mul_f32 r8 3B808081 (0.0039215689)
+    r5 = mad_f32 r8 r7 r5
+    r5 = mul_f32 r5 437F0000 (255)
+    r5 = round r5
+    r5 = pack r3 r5 8
+    r2 = extract r2 16 r0
+    r2 = to_f32 r2
+    r2 = mul_f32 r2 3B808081 (0.0039215689)
+    r3 = extract r4 16 r0
+    r3 = to_f32 r3
+    r3 = mul_f32 r3 3B808081 (0.0039215689)
+    r2 = mad_f32 r3 r7 r2
+    r2 = mul_f32 r2 437F0000 (255)
+    r2 = round r2
+    r4 = extract r4 24 r0
+    r4 = to_f32 r4
+    r4 = mul_f32 r4 3B808081 (0.0039215689)
+    r6 = mad_f32 r4 r7 r6
+    r6 = mul_f32 r6 437F0000 (255)
+    r6 = round r6
+    r6 = pack r2 r6 8
+    r6 = pack r5 r6 16
+    store32 arg(1) r6
 
 I32 (Naive) 8888 over 8888
 29 values:
diff --git a/src/core/SkVM.cpp b/src/core/SkVM.cpp
index 6dc36c7..f76bccb 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -144,6 +144,8 @@
                 case Op::max_f32: write(o, V{id}, "= max_f32", V{x}, V{y}      ); break;
                 case Op::mad_f32: write(o, V{id}, "= mad_f32", V{x}, V{y}, V{z}); break;
 
+                case Op::mul_f32_imm: write(o, V{id}, "= mul_f32", V{x}, Splat{imm}); break;
+
                 case Op:: eq_f32: write(o, V{id}, "= eq_f32", V{x}, V{y}); break;
                 case Op::neq_f32: write(o, V{id}, "= neq_f32", V{x}, V{y}); break;
                 case Op:: gt_f32: write(o, V{id}, "= gt_f32", V{x}, V{y}); break;
@@ -254,6 +256,8 @@
                 case Op::max_f32: write(o, R{d}, "= max_f32", R{x}, R{y}      ); break;
                 case Op::mad_f32: write(o, R{d}, "= mad_f32", R{x}, R{y}, R{z}); break;
 
+                case Op::mul_f32_imm: write(o, R{d}, "= mul_f32", R{x}, Splat{imm}); break;
+
                 case Op:: eq_f32: write(o, R{d}, "= eq_f32", R{x}, R{y}); break;
                 case Op::neq_f32: write(o, R{d}, "= neq_f32", R{x}, R{y}); break;
                 case Op:: gt_f32: write(o, R{d}, "= gt_f32", R{x}, R{y}); break;
@@ -454,9 +458,12 @@
         return id;
     }
 
-    bool Builder::isZero(Val id) const {
-        return fProgram[id].op  == Op::splat
-            && fProgram[id].imm == 0;
+    bool Builder::isImm(Val id, int* imm) const {
+        if (fProgram[id].op == Op::splat) {
+            *imm = fProgram[id].imm;
+            return true;
+        }
+        return false;
     }
 
     Arg Builder::arg(int stride) {
@@ -511,17 +518,24 @@
 
     F32 Builder::add(F32 x, F32 y       ) { return {this->push(Op::add_f32, x.id, y.id)}; }
     F32 Builder::sub(F32 x, F32 y       ) { return {this->push(Op::sub_f32, x.id, y.id)}; }
-    F32 Builder::mul(F32 x, F32 y       ) { return {this->push(Op::mul_f32, x.id, y.id)}; }
     F32 Builder::div(F32 x, F32 y       ) { return {this->push(Op::div_f32, x.id, y.id)}; }
     F32 Builder::min(F32 x, F32 y       ) { return {this->push(Op::min_f32, x.id, y.id)}; }
     F32 Builder::max(F32 x, F32 y       ) { return {this->push(Op::max_f32, x.id, y.id)}; }
     F32 Builder::mad(F32 x, F32 y, F32 z) {
-        if (this->isZero(z.id)) {
+        int imm;
+        if (this->isImm(z.id, &imm) && imm == 0) {
             return this->mul(x,y);
         }
         return {this->push(Op::mad_f32, x.id, y.id, z.id)};
     }
 
+    F32 Builder::mul(F32 x, F32 y) {
+        int imm;
+        if (this->isImm(y.id, &imm)) { return {this->push(Op::mul_f32_imm, x.id,NA,NA, imm)}; }
+        if (this->isImm(x.id, &imm)) { return {this->push(Op::mul_f32_imm, y.id,NA,NA, imm)}; }
+        return {this->push(Op::mul_f32, x.id, y.id)};
+    }
+
     I32 Builder::add(I32 x, I32 y) { return {this->push(Op::add_i32, x.id, y.id)}; }
     I32 Builder::sub(I32 x, I32 y) { return {this->push(Op::sub_i32, x.id, y.id)}; }
     I32 Builder::mul(I32 x, I32 y) { return {this->push(Op::mul_i32, x.id, y.id)}; }
@@ -873,6 +887,7 @@
     void Assembler::vpshufb(Ymm dst, Ymm x, Label* l) { this->op(0x66,0x380f,0x00, dst,x,l); }
     void Assembler::vpaddd (Ymm dst, Ymm x, Label* l) { this->op(0x66,  0x0f,0xfe, dst,x,l); }
     void Assembler::vpsubd (Ymm dst, Ymm x, Label* l) { this->op(0x66,  0x0f,0xfa, dst,x,l); }
+    void Assembler::vmulps (Ymm dst, Ymm x, Label* l) { this->op(   0,  0x0f,0x59, dst,x,l); }
 
     void Assembler::vptest(Ymm dst, Label* l) { this->op(0x66, 0x380f, 0x17, dst, (Ymm)0, l); }
 
@@ -1401,6 +1416,12 @@
                     CASE(Op::min_f32): r(d).f32 = min(r(x).f32, r(y).f32); break;
                     CASE(Op::max_f32): r(d).f32 = max(r(x).f32, r(y).f32); break;
 
+                    CASE(Op::mul_f32_imm): {
+                        Slot tmp;
+                        tmp.i32 = imm;
+                        r(d).f32 = r(x).f32 * tmp.f32;
+                    } break;
+
                     CASE(Op::mad_f32): r(d).f32 = r(x).f32 * r(y).f32 + r(z).f32; break;
 
                     CASE(Op::add_i32): r(d).i32 = r(x).i32 + r(y).i32; break;
@@ -1706,10 +1727,9 @@
             A::Label label;
             Reg      reg;
         };
-        SkTHashMap<int, LabelAndReg> splats,
-                                     bytes_masks;
-        LabelAndReg                  iota,
-                                     all_mask;
+        SkTHashMap<int, LabelAndReg> constants,    // All constants share the same pool.
+                                     bytes_masks;  // These vary per-lane.
+        LabelAndReg                  iota;         // Exists _only_ to vary per-lane.
 
         auto warmup = [&](Val id) {
             const Builder::Instruction& inst = instructions[id];
@@ -1720,9 +1740,6 @@
             switch (op) {
                 default: break;
 
-                case Op::splat: if (imm/*0 -> xor*/ && !splats.find(imm)) { splats.set(imm, {}); }
-                                break;
-
                 case Op::bytes: if (!bytes_masks.find(imm)) {
                                     bytes_masks.set(imm, {});
                                     if (try_hoisting) {
@@ -1852,7 +1869,7 @@
 
             #if defined(__x86_64__)
                 case Op::assert_true: {
-                    a->vptest (r[x], &all_mask.label);
+                    a->vptest (r[x], &constants[0xffffffff].label);
                     A::Label all_true;
                     a->jc(&all_true);
                     a->int3();
@@ -1907,15 +1924,9 @@
                                 a->vpsubd(dst(), tmp(), &iota.label);
                                 break;
 
-                case Op::splat: if (imm) { a->vbroadcastss(dst(), &splats.find(imm)->label); }
+                case Op::splat: if (imm) { a->vbroadcastss(dst(), &constants[imm].label); }
                                 else     { a->vpxor(dst(), dst(), dst()); }
                                 break;
-                                // TODO: many of these instructions have variants that
-                                // can read one of their arugments from 32-byte memory
-                                // instead of a register.  Find a way to avoid needing
-                                // to splat most* constants out at all?
-                                // (*Might work for x - 255 but not 255 - x, so will
-                                // always need to be able to splat to a register.)
 
                 case Op::add_f32: a->vaddps(dst(), r[x], r[y]); break;
                 case Op::sub_f32: a->vsubps(dst(), r[x], r[y]); break;
@@ -1933,6 +1944,8 @@
                                                                  a->vfmadd132ps(dst(),r[z], r[y]); }
                                                                  break;
 
+                case Op::mul_f32_imm: a->vmulps(dst(), r[x], &constants[imm].label); break;
+
                 case Op::add_i32: a->vpaddd (dst(), r[x], r[y]); break;
                 case Op::sub_i32: a->vpsubd (dst(), r[x], r[y]); break;
                 case Op::mul_i32: a->vpmulld(dst(), r[x], r[y]); break;
@@ -1999,7 +2012,7 @@
                                  else        { a->ldrq(dst(), arg[imm]); }
                                                break;
 
-                case Op::splat: if (imm) { a->ldrq(dst(), &splats.find(imm)->label); }
+                case Op::splat: if (imm) { a->ldrq(dst(), &constants[imm].label); }
                                 else     { a->eor16b(dst(), dst(), dst()); }
                                 break;
                                 // TODO: If we hoist these, pack 4 values in each register
@@ -2020,6 +2033,11 @@
                                        if(dst() != tmp()) { a->orr16b(dst(), tmp(), tmp()); } }
                                                             break;
 
+                // TODO: handle these immediate op constants better on ARM?
+                case Op::mul_f32_imm: a->ldrq(tmp(), &constants[imm].label);
+                                      a->fmul4s(dst(), r[x], tmp());
+                                      break;
+
                 case Op:: gt_f32: a->fcmgt4s (dst(), r[x], r[y]); break;
                 case Op::gte_f32: a->fcmge4s (dst(), r[x], r[y]); break;
                 case Op:: eq_f32: a->fcmeq4s (dst(), r[x], r[y]); break;
@@ -2157,20 +2175,16 @@
         }
 
         // Except for explicit aligned load and store instructions, AVX allows
-        // memory operands to be unaligned.  So even though bytes_masks and
-        // iota use 32-byte patterns on x86, we need only align them to 4
-        // bytes, the element size and required alignment on ARM.
+        // memory operands to be unaligned.  So even though we're creating 16
+        // byte patterns on ARM or 32-byte patterns on x86, we only need to
+        // align to 4 bytes, the element size and alignment requirement.
 
-        splats.foreach([&](int imm, LabelAndReg* entry) {
-            // vbroadcastss 4 bytes on x86-64, or simply load 16-bytes on aarch64.
+        constants.foreach([&](int imm, LabelAndReg* entry) {
             a->align(4);
             a->label(&entry->label);
-            a->word(imm);
-        #if defined(__aarch64__)
-            a->word(imm);
-            a->word(imm);
-            a->word(imm);
-        #endif
+            for (int i = 0; i < K; i++) {
+                a->word(imm);
+            }
         });
 
         bytes_masks.foreach([&](int imm, LabelAndReg* entry) {
@@ -2192,13 +2206,6 @@
                 a->word(i);
             }
         }
-        if (!all_mask.label.references.empty()) {
-            a->align(4);
-            a->label(&all_mask.label);
-            for (int i = 0; i < K; i++) {
-                a->word(0xffffffff);
-            }
-        }
 
         return true;
     }
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index 4954920..9306cbc 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -117,6 +117,7 @@
         void vpshufb(Ymm dst, Ymm x, Label*);
         void vpaddd (Ymm dst, Ymm x, Label*);
         void vpsubd (Ymm dst, Ymm x, Label*);
+        void vmulps (Ymm dst, Ymm x, Label*);
 
         void vmovups  (Ymm dst, GP64 ptr);   // dst = *ptr, 256-bit
         void vpmovzxwd(Ymm dst, GP64 ptr);   // dst = *ptr, 128-bit, each uint16_t expanded to int
@@ -272,6 +273,7 @@
                  shl_i32, shl_i16x2,
                  shr_i32, shr_i16x2,
                  sra_i32, sra_i16x2,
+        mul_f32_imm,
 
          trunc, round,  to_f32,
 
@@ -474,7 +476,7 @@
         };
 
         Val push(Op, Val x, Val y=NA, Val z=NA, int imm=0);
-        bool isZero(Val) const;
+        bool isImm(Val, int* imm) const;
 
         SkTHashMap<Instruction, Val, InstructionHash> fIndex;
         std::vector<Instruction>                      fProgram;
diff --git a/tests/SkVMTest.cpp b/tests/SkVMTest.cpp
index 44f056b..7c31f46 100644
--- a/tests/SkVMTest.cpp
+++ b/tests/SkVMTest.cpp
@@ -886,6 +886,8 @@
         a.vpsubd (A::ymm4, A::ymm3, &l);
 
         a.vptest(A::ymm4, &l);
+
+        a.vmulps (A::ymm4, A::ymm3, &l);
     },{
         0x01, 0x02, 0x03, 0x4,
 
@@ -900,7 +902,9 @@
         0xc5, 0xe5,        0xfe,   0b00'100'101,   0xc7,0xff,0xff,0xff,   // 0xffffffc7 == -57
         0xc5, 0xe5,        0xfa,   0b00'100'101,   0xbf,0xff,0xff,0xff,   // 0xffffffbf == -65
 
-        0xc4, 0xe2, 0x7d,  0x17,   0b00'100'101,   0xb6,0xff,0xff,0xff,   // 0xffffffb6 == -72
+        0xc4, 0xe2, 0x7d,  0x17,   0b00'100'101,   0xb6,0xff,0xff,0xff,   // 0xffffffb6 == -74
+
+        0xc5, 0xe4,        0x59,   0b00'100'101,   0xae,0xff,0xff,0xff,   // 0xffffffaf == -82
     });
 
     test_asm(r, [&](A& a) {