add Q14x2 type to SkVM

This is a 32-bit pair of Q14 (signed 1.14 fixed point) values.

Q14 * Q14 is (x*y + 0x2000)>>14 but we'll do it as
((x*y + 0x4000)>>15)<<1 to allow use of vpmulhrsw.

Somewhat awkwardly this approximate math does not preserve x*1.0 == x,
e.g.  0x0001 * 0x4000 = 0x0002, not 0x0001.  I'm unsure if we'll care.
x*0.0 == 0.0 does always hold, and 1.0*1.0 == 1.0.

Most other operations are normal 16-bit operations.

TODO:
    - implmement in interpreter
    - unit test
    - constant propgation and strength reduction
    - implement in JIT
    - effect/blitter hooks enough to demo
    - thorough effect/blitter support

Change-Id: Ic714fc16b6530259b36300bd042bbfd5a57a663b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/317149
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 eaad15d..7d469fa 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -315,8 +315,26 @@
             case Op::shr_i32: write(o, V{id}, "=", op, V{x}, Shift{immy}, fs(id)...); break;
             case Op::sra_i32: write(o, V{id}, "=", op, V{x}, Shift{immy}, fs(id)...); break;
 
-            case Op:: eq_i32: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
-            case Op:: gt_i32: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::eq_i32: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::gt_i32: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+
+
+            case Op::add_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::sub_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::mul_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+
+            case Op::shl_q14x2: write(o, V{id}, "=", op, V{x}, Shift{immy}, fs(id)...); break;
+            case Op::shr_q14x2: write(o, V{id}, "=", op, V{x}, Shift{immy}, fs(id)...); break;
+            case Op::sra_q14x2: write(o, V{id}, "=", op, V{x}, Shift{immy}, fs(id)...); break;
+
+            case Op:: min_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op:: max_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::umin_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::uavg_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+
+            case Op::eq_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+            case Op::gt_q14x2: write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...); break;
+
 
             case Op::bit_and  : write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...      ); break;
             case Op::bit_or   : write(o, V{id}, "=", op, V{x}, V{y}, fs(id)...      ); break;
@@ -441,8 +459,26 @@
                 case Op::shr_i32: write(o, R{d}, "=", op, R{x}, Shift{immy}); break;
                 case Op::sra_i32: write(o, R{d}, "=", op, R{x}, Shift{immy}); break;
 
-                case Op:: eq_i32: write(o, R{d}, "=", op, R{x}, R{y}); break;
-                case Op:: gt_i32: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::eq_i32: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::gt_i32: write(o, R{d}, "=", op, R{x}, R{y}); break;
+
+
+                case Op::add_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::sub_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::mul_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+
+                case Op::shl_q14x2: write(o, R{d}, "=", op, R{x}, Shift{immy}); break;
+                case Op::shr_q14x2: write(o, R{d}, "=", op, R{x}, Shift{immy}); break;
+                case Op::sra_q14x2: write(o, R{d}, "=", op, R{x}, Shift{immy}); break;
+
+                case Op:: min_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op:: max_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::umin_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::uavg_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+
+                case Op::eq_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+                case Op::gt_q14x2: write(o, R{d}, "=", op, R{x}, R{y}); break;
+
 
                 case Op::bit_and  : write(o, R{d}, "=", op, R{x}, R{y}      ); break;
                 case Op::bit_or   : write(o, R{d}, "=", op, R{x}, R{y}      ); break;
@@ -978,6 +1014,32 @@
         return {this, this->push(Op::max_f32, x.id, y.id)};
     }
 
+    // TODO: constant propagation and strength reduction for all these Q14x2 ops
+    Q14x2 Builder::add(Q14x2 x, Q14x2 y) { return {this, this->push(Op::add_q14x2, x.id, y.id)}; }
+    Q14x2 Builder::sub(Q14x2 x, Q14x2 y) { return {this, this->push(Op::sub_q14x2, x.id, y.id)}; }
+    Q14x2 Builder::mul(Q14x2 x, Q14x2 y) { return {this, this->push(Op::mul_q14x2, x.id, y.id)}; }
+
+    Q14x2 Builder::shl(Q14x2 x, int k) { return {this, this->push(Op::shl_q14x2, x.id,NA,NA,k)}; }
+    Q14x2 Builder::shr(Q14x2 x, int k) { return {this, this->push(Op::shr_q14x2, x.id,NA,NA,k)}; }
+    Q14x2 Builder::sra(Q14x2 x, int k) { return {this, this->push(Op::sra_q14x2, x.id,NA,NA,k)}; }
+
+    I32 Builder:: eq(Q14x2 x, Q14x2 y) { return {this, this->push(Op::eq_q14x2, x.id, y.id)}; }
+    I32 Builder::gt (Q14x2 x, Q14x2 y) { return {this, this->push(Op::gt_q14x2, x.id, y.id)}; }
+    I32 Builder::lt (Q14x2 x, Q14x2 y) { return  gt(y,x); }
+    I32 Builder::neq(Q14x2 x, Q14x2 y) { return ~eq(x,y); }
+    I32 Builder::gte(Q14x2 x, Q14x2 y) { return ~lt(x,y); }
+    I32 Builder::lte(Q14x2 x, Q14x2 y) { return ~gt(x,y); }
+
+    Q14x2 Builder::min(Q14x2 x, Q14x2 y) { return {this, this->push(Op::min_q14x2, x.id, y.id)}; }
+    Q14x2 Builder::max(Q14x2 x, Q14x2 y) { return {this, this->push(Op::max_q14x2, x.id, y.id)}; }
+
+    Q14x2 Builder::unsigned_avg(Q14x2 x, Q14x2 y) {
+        return {this, this->push(Op::uavg_q14x2, x.id, y.id)};
+    }
+    Q14x2 Builder::unsigned_min(Q14x2 x, Q14x2 y) {
+        return {this, this->push(Op::umin_q14x2, x.id, y.id)};
+    }
+
     I32 Builder::add(I32 x, I32 y) {
         if (int X,Y; this->allImm(x.id,&X, y.id,&Y)) { return splat(X+Y); }
         if (this->isImm(x.id, 0)) { return y; }
@@ -3459,6 +3521,20 @@
                     (void)constants[immy];
                     break;
 
+                case Op:: add_q14x2:
+                case Op:: sub_q14x2:
+                case Op:: mul_q14x2:
+                case Op:: shl_q14x2:
+                case Op:: shr_q14x2:
+                case Op:: sra_q14x2:
+                case Op::  eq_q14x2:
+                case Op::  gt_q14x2:
+                case Op:: min_q14x2:
+                case Op:: max_q14x2:
+                case Op::uavg_q14x2:
+                case Op::umin_q14x2:
+                    return false; // TODO
+
             #if defined(__x86_64__) || defined(_M_X64)
                 case Op::assert_true: {
                     a->vptest (r(x), &constants[0xffffffff]);
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index 04c5630..07e6c83 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -409,22 +409,20 @@
         M(gather8)  M(gather16)  M(gather32)                       \
         M(uniform8) M(uniform16) M(uniform32)                      \
         M(splat)                                                   \
-        M(add_f32) M(add_i32)                                      \
-        M(sub_f32) M(sub_i32)                                      \
-        M(mul_f32) M(mul_i32)                                      \
+        M(add_f32) M(add_i32) M(add_q14x2)                         \
+        M(sub_f32) M(sub_i32) M(sub_q14x2)                         \
+        M(mul_f32) M(mul_i32) M(mul_q14x2)                         \
         M(div_f32)                                                 \
-        M(min_f32)                                                 \
-        M(max_f32)                                                 \
+        M(min_f32)   M(max_f32)                                    \
+        M(min_q14x2) M(max_q14x2) M(uavg_q14x2) M(umin_q14x2)      \
         M(fma_f32) M(fms_f32) M(fnma_f32)                          \
         M(sqrt_f32)                                                \
-        M(shl_i32) M(shr_i32) M(sra_i32)                           \
-        M(ceil) M(floor)                                           \
-        M(trunc) M(round) M(to_half) M(from_half)                  \
+        M(shl_i32)   M(shr_i32)   M(sra_i32)                       \
+        M(shl_q14x2) M(shr_q14x2) M(sra_q14x2)                     \
+        M(ceil) M(floor) M(trunc) M(round) M(to_half) M(from_half) \
         M(to_f32)                                                  \
-        M( eq_f32) M( eq_i32)                                      \
-        M(neq_f32)                                                 \
-        M( gt_f32) M( gt_i32)                                      \
-        M(gte_f32)                                                 \
+        M(neq_f32) M(eq_f32) M(eq_i32) M(eq_q14x2)                 \
+        M(gte_f32) M(gt_f32) M(gt_i32) M(gt_q14x2)                 \
         M(bit_and)                                                 \
         M(bit_or)                                                  \
         M(bit_xor)                                                 \
@@ -466,6 +464,13 @@
         Builder* operator->()    const { return builder; }
     };
 
+    struct Q14x2 {
+        Builder* builder = nullptr;
+        Val      id      = NA;
+        explicit operator bool() const { return id != NA; }
+        Builder* operator->()    const { return builder; }
+    };
+
     // Some operations make sense with immediate arguments,
     // so we use I32a and F32a to receive them transparently.
     //
@@ -496,18 +501,38 @@
         float imm = 0;
     };
 
+    struct Q14x2a {
+        Q14x2a(Q14x2 v) : SkDEBUGCODE(builder(v.builder),) id(v.id) {}
+        Q14x2a(float f) {
+            SkASSERT(-1.0f <= f && f <= 1.0f);  // TODO: allow full [-2,+2)?
+            int q14 = (int)(f * 16384.0f)
+                    & 0xffff;
+            imm = q14 | (q14<<16);
+        }
+
+        SkDEBUGCODE(Builder* builder = nullptr;)
+        Val   id  = NA;
+        int   imm = 0;
+    };
+
     struct Color {
-        skvm::F32 r,g,b,a;
+        F32 r,g,b,a;
         explicit operator bool() const { return r && g && b && a; }
         Builder* operator->()    const { return a.operator->(); }
     };
 
     struct HSLA {
-        skvm::F32 h,s,l,a;
+        F32 h,s,l,a;
         explicit operator bool() const { return h && s && l && a; }
         Builder* operator->()    const { return a.operator->(); }
     };
 
+    struct ColorQ14 {
+        Q14x2 rb, ga;  // TODO: simpler to start with r,g,b,a?
+        explicit operator bool() const { return rb && ga; }
+        Builder* operator->()    const { return ga.operator->(); }
+    };
+
     struct Coord {
         F32 x,y;
         explicit operator bool() const { return x && y; }
@@ -699,7 +724,7 @@
 
         I32 trunc(F32 x);
         I32 round(F32 x);  // Round to int using current rounding mode (as if lrintf()).
-        I32 bit_cast(F32 x) { return {x.builder, x.id}; }
+        I32 bit_cast(F32 x) { return {x.builder, x.id}; }  // TODO: rename to as_I32()?
 
         I32   to_half(F32 x);
         F32 from_half(I32 x);
@@ -734,7 +759,7 @@
         I32 gte(I32 x, I32 y);  I32 gte(I32a x, I32a y) { return gte(_(x), _(y)); }
 
         F32 to_f32(I32 x);
-        F32 bit_cast(I32 x) { return {x.builder, x.id}; }
+        F32 bit_cast(I32 x) { return {x.builder, x.id}; }  // TODO: rename to as_F32()?
 
         // Bitwise operations.
         I32 bit_and  (I32, I32);  I32 bit_and  (I32a x, I32a y) { return bit_and  (_(x), _(y)); }
@@ -753,9 +778,14 @@
             return bit_cast(select(cond, bit_cast(t)
                                        , bit_cast(f)));
         }
+        Q14x2 select(I32 cond, Q14x2 t, Q14x2 f) {
+            return as_Q14x2(select(cond, as_I32(t)
+                                       , as_I32(f)));
+        }
 
-        I32 select(I32a cond, I32a t, I32a f) { return select(_(cond), _(t), _(f)); }
-        F32 select(I32a cond, F32a t, F32a f) { return select(_(cond), _(t), _(f)); }
+        I32   select(I32a cond, I32a   t, I32a   f) { return select(_(cond), _(t), _(f)); }
+        F32   select(I32a cond, F32a   t, F32a   f) { return select(_(cond), _(t), _(f)); }
+        Q14x2 select(I32a cond, Q14x2a t, Q14x2a f) { return select(_(cond), _(t), _(f)); }
 
         I32 extract(I32 x, int bits, I32 z);   // (x>>bits) & z
         I32 pack   (I32 x, I32 y, int bits);   // x | (y << bits), assuming (x & (y << bits)) == 0
@@ -763,6 +793,32 @@
         I32 extract(I32a x, int bits, I32a z) { return extract(_(x), bits, _(z)); }
         I32 pack   (I32a x, I32a y, int bits) { return pack   (_(x), _(y), bits); }
 
+        I32   as_I32  (Q14x2 x) { return {x.builder, x.id}; }
+        Q14x2 as_Q14x2(I32   x) { return {x.builder, x.id}; }
+
+        Q14x2 add(Q14x2, Q14x2);  Q14x2 add(Q14x2a x, Q14x2a y) { return add(_(x), _(y)); }
+        Q14x2 sub(Q14x2, Q14x2);  Q14x2 sub(Q14x2a x, Q14x2a y) { return sub(_(x), _(y)); }
+        Q14x2 mul(Q14x2, Q14x2);  Q14x2 mul(Q14x2a x, Q14x2a y) { return mul(_(x), _(y)); }
+
+        Q14x2 min(Q14x2, Q14x2);  Q14x2 min(Q14x2a x, Q14x2a y) { return min(_(x), _(y)); }
+        Q14x2 max(Q14x2, Q14x2);  Q14x2 max(Q14x2a x, Q14x2a y) { return max(_(x), _(y)); }
+
+        Q14x2 shl(Q14x2, int bits);
+        Q14x2 shr(Q14x2, int bits);
+        Q14x2 sra(Q14x2, int bits);
+
+        I32 eq (Q14x2, Q14x2);  I32  eq(Q14x2a x, Q14x2a y) { return  eq(_(x), _(y)); }
+        I32 neq(Q14x2, Q14x2);  I32 neq(Q14x2a x, Q14x2a y) { return neq(_(x), _(y)); }
+        I32 lt (Q14x2, Q14x2);  I32 lt (Q14x2a x, Q14x2a y) { return lt (_(x), _(y)); }
+        I32 lte(Q14x2, Q14x2);  I32 lte(Q14x2a x, Q14x2a y) { return lte(_(x), _(y)); }
+        I32 gt (Q14x2, Q14x2);  I32 gt (Q14x2a x, Q14x2a y) { return gt (_(x), _(y)); }
+        I32 gte(Q14x2, Q14x2);  I32 gte(Q14x2a x, Q14x2a y) { return gte(_(x), _(y)); }
+
+        Q14x2 unsigned_avg(Q14x2  x, Q14x2  y);  // (x+y+1)>>1
+        Q14x2 unsigned_avg(Q14x2a x, Q14x2a y) { return unsigned_avg(_(x), _(y)); }
+
+        Q14x2 unsigned_min(Q14x2  x, Q14x2  y);
+        Q14x2 unsigned_min(Q14x2a x, Q14x2a y) { return unsigned_min(_(x), _(y)); }
 
         // Common idioms used in several places, worth centralizing for consistency.
         F32 from_unorm(int bits, I32);   // E.g. from_unorm(8, x) -> x * (1/255.0f)
@@ -818,6 +874,14 @@
             return splat(x.imm);
         }
 
+        Q14x2 _(Q14x2a x) {
+            if (x.id != NA) {
+                SkASSERT(x.builder == this);
+                return {this, x.id};
+            }
+            return as_Q14x2(splat(x.imm));
+        }
+
         bool allImm() const;
 
         template <typename T, typename... Rest>
@@ -921,6 +985,48 @@
     // TODO: control flow
     // TODO: 64-bit values?
 
+    static inline Q14x2 operator+(Q14x2 x, Q14x2a y) { return x->add(x,y); }
+    static inline Q14x2 operator+(float x, Q14x2  y) { return y->add(x,y); }
+
+    static inline Q14x2 operator-(Q14x2 x, Q14x2a y) { return x->sub(x,y); }
+    static inline Q14x2 operator-(float x, Q14x2  y) { return y->sub(x,y); }
+
+    static inline Q14x2 operator*(Q14x2 x, Q14x2a y) { return x->mul(x,y); }
+    static inline Q14x2 operator*(float x, Q14x2  y) { return y->mul(x,y); }
+
+    static inline Q14x2 min(Q14x2 x, Q14x2a y) { return x->min(x,y); }
+    static inline Q14x2 min(float x, Q14x2  y) { return y->min(x,y); }
+
+    static inline Q14x2 max(Q14x2 x, Q14x2a y) { return x->max(x,y); }
+    static inline Q14x2 max(float x, Q14x2  y) { return y->max(x,y); }
+
+    static inline Q14x2 unsigned_min(Q14x2 x, Q14x2a y) { return x->unsigned_min(x,y); }
+    static inline Q14x2 unsigned_min(float x, Q14x2  y) { return y->unsigned_min(x,y); }
+
+    static inline Q14x2 unsigned_avg(Q14x2 x, Q14x2a y) { return x->unsigned_avg(x,y); }
+    static inline Q14x2 unsigned_avg(float x, Q14x2  y) { return y->unsigned_avg(x,y); }
+
+    static inline I32 operator==(Q14x2 x, Q14x2 y) { return x->eq(x,y); }
+    static inline I32 operator==(Q14x2 x, float y) { return x->eq(x,y); }
+    static inline I32 operator==(float x, Q14x2 y) { return y->eq(x,y); }
+
+    static inline I32 operator!=(Q14x2 x, Q14x2 y) { return x->neq(x,y); }
+    static inline I32 operator!=(Q14x2 x, float y) { return x->neq(x,y); }
+    static inline I32 operator!=(float x, Q14x2 y) { return y->neq(x,y); }
+
+    static inline I32 operator< (Q14x2 x, Q14x2a y) { return x->lt(x,y); }
+    static inline I32 operator< (float x, Q14x2  y) { return y->lt(x,y); }
+
+    static inline I32 operator<=(Q14x2 x, Q14x2a y) { return x->lte(x,y); }
+    static inline I32 operator<=(float x, Q14x2  y) { return y->lte(x,y); }
+
+    static inline I32 operator> (Q14x2 x, Q14x2a y) { return x->gt(x,y); }
+    static inline I32 operator> (float x, Q14x2  y) { return y->gt(x,y); }
+
+    static inline I32 operator>=(Q14x2 x, Q14x2a y) { return x->gte(x,y); }
+    static inline I32 operator>=(float x, Q14x2  y) { return y->gte(x,y); }
+
+
     static inline I32 operator+(I32 x, I32a y) { return x->add(x,y); }
     static inline I32 operator+(int x, I32  y) { return y->add(x,y); }
 
@@ -995,6 +1101,9 @@
     static inline I32 operator>=(F32   x, F32a y) { return x->gte(x,y); }
     static inline I32 operator>=(float x, F32  y) { return y->gte(x,y); }
 
+    static inline Q14x2& operator+=(Q14x2& x, Q14x2a y) { return (x = x + y); }
+    static inline Q14x2& operator-=(Q14x2& x, Q14x2a y) { return (x = x - y); }
+    static inline Q14x2& operator*=(Q14x2& x, Q14x2a y) { return (x = x * y); }
 
     static inline I32& operator+=(I32& x, I32a y) { return (x = x + y); }
     static inline I32& operator-=(I32& x, I32a y) { return (x = x - y); }
@@ -1075,6 +1184,11 @@
     static inline I32        shr(I32 x, int bits) { return x->shr(x, bits); }
     static inline I32        sra(I32 x, int bits) { return x->sra(x, bits); }
 
+    static inline Q14x2 operator<<(Q14x2 x, int bits) { return x->shl(x, bits); }
+    static inline Q14x2        shl(Q14x2 x, int bits) { return x->shl(x, bits); }
+    static inline Q14x2        shr(Q14x2 x, int bits) { return x->shr(x, bits); }
+    static inline Q14x2        sra(Q14x2 x, int bits) { return x->sra(x, bits); }
+
     static inline I32 operator&(I32 x, I32a y) { return x->bit_and(x,y); }
     static inline I32 operator&(int x, I32  y) { return y->bit_and(x,y); }
 
@@ -1088,17 +1202,19 @@
     static inline I32& operator|=(I32& x, I32a y) { return (x = x | y); }
     static inline I32& operator^=(I32& x, I32a y) { return (x = x ^ y); }
 
-    static inline I32 select(I32 cond, I32a t, I32a f) { return cond->select(cond,t,f); }
-    static inline F32 select(I32 cond, F32a t, F32a f) { return cond->select(cond,t,f); }
+    static inline I32   select(I32 cond, I32a   t, I32a   f) { return cond->select(cond,t,f); }
+    static inline F32   select(I32 cond, F32a   t, F32a   f) { return cond->select(cond,t,f); }
+    static inline Q14x2 select(I32 cond, Q14x2a t, Q14x2a f) { return cond->select(cond,t,f); }
 
     static inline I32 extract(I32 x, int bits, I32a z) { return x->extract(x,bits,z); }
     static inline I32 extract(int x, int bits, I32  z) { return z->extract(x,bits,z); }
     static inline I32 pack   (I32 x, I32a y, int bits) { return x->pack   (x,y,bits); }
     static inline I32 pack   (int x, I32  y, int bits) { return y->pack   (x,y,bits); }
 
-    static inline I32 operator~(I32 x) { return ~0^x; }
-    static inline I32 operator-(I32 x) { return  0-x; }
-    static inline F32 operator-(F32 x) { return  0-x; }
+    static inline I32   operator~(I32   x) { return ~0^x; }
+    static inline I32   operator-(I32   x) { return  0-x; }
+    static inline F32   operator-(F32   x) { return 0.0f-x; }
+    static inline Q14x2 operator-(Q14x2 x) { return 0.0f-x; }
 
     static inline F32 from_unorm(int bits, I32 x) { return x->from_unorm(bits,x); }
     static inline I32   to_unorm(int bits, F32 x) { return x->  to_unorm(bits,x); }
diff --git a/src/opts/SkVM_opts.h b/src/opts/SkVM_opts.h
index 401c516..13a022b 100644
--- a/src/opts/SkVM_opts.h
+++ b/src/opts/SkVM_opts.h
@@ -260,6 +260,20 @@
                     CASE(Op::from_half):
                         r[d].f32 = skvx::from_half(skvx::cast<uint16_t>(r[x].i32));
                         break;
+
+                    CASE(Op:: add_q14x2):
+                    CASE(Op:: sub_q14x2):
+                    CASE(Op:: mul_q14x2):
+                    CASE(Op:: shl_q14x2):
+                    CASE(Op:: shr_q14x2):
+                    CASE(Op:: sra_q14x2):
+                    CASE(Op::  eq_q14x2):
+                    CASE(Op::  gt_q14x2):
+                    CASE(Op:: min_q14x2):
+                    CASE(Op:: max_q14x2):
+                    CASE(Op::uavg_q14x2):
+                    CASE(Op::umin_q14x2):
+                        SkUNREACHABLE;
                 #undef CASE
                 }
             }