Revert "trim another instruction off SkRasterPipeline overhead"
authorMike Klein <mtklein@chromium.org>
Tue, 3 Jan 2017 23:42:04 +0000 (23:42 +0000)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Wed, 4 Jan 2017 00:32:29 +0000 (00:32 +0000)
This reverts commit e61c40707e70a2be9e32227a929173864f7895e1.

Reason for revert: this and the ODR caused operations on ContiguousContainerBase::elements_, another std::vector<void*> in Chrome, to start using AVX2 instructions.  Boy this is annoying...

Change-Id: I2c4837ad70fdef8096db904022b0703b88c6fd6c
Reviewed-on: https://skia-review.googlesource.com/6549
Commit-Queue: Mike Klein <mtklein@chromium.org>
Reviewed-by: Mike Klein <mtklein@chromium.org>
src/opts/SkRasterPipeline_opts.h

index 1c4d13a..e011541 100644 (file)
@@ -35,95 +35,58 @@ namespace {
     using SkNh = SkNx<N, uint16_t>;
     using SkNb = SkNx<N, uint8_t>;
 
-    using Fn = void(SK_VECTORCALL *)(size_t x_tail, void** p, SkNf,SkNf,SkNf,SkNf,
-                                                              SkNf,SkNf,SkNf,SkNf);
+    struct Stage;
+    using Fn = void(SK_VECTORCALL *)(Stage*, size_t x_tail, SkNf,SkNf,SkNf,SkNf,
+                                                            SkNf,SkNf,SkNf,SkNf);
+    struct Stage { Fn next; void* ctx; };
+
     // x_tail encodes two values x and tail as x*N+tail, where 0 <= tail < N.
     // x is the induction variable we're walking along, incrementing by N each step.
     // tail == 0 means work with a full N pixels; otherwise use only the low tail pixels.
-    //
-    // p is our program, a sequence of Fn to call interlaced with any void* context pointers.  E.g.
-    //    &load_8888
-    //    (src ptr)
-    //    &from_srgb
-    //    &load_f16_d
-    //    (dst ptr)
-    //    &srcover
-    //    &store_f16
-    //    (dst ptr)
-    //    &just_return
 
 }  // namespace
 
 #define SI static inline
 
-// Basically, return *(*ptr)++, maybe faster than the compiler can do it.
-SI void* load_and_increment(void*** ptr) {
-    // We do this often enough that it's worth hyper-optimizing.
-    // x86 can do this in one instruction if ptr is in rsi.
-    // (This is why p is the second argument to Fn: it's passed in rsi.)
-#if defined(__GNUC__) && defined(__x86_64__)
-    void* rax;
-    __asm__("lodsq" : "=a"(rax), "+S"(*ptr));
-    return rax;
-#else
-    return *(*ptr)++;
-#endif
-}
-
 // Stages are logically a pipeline, and physically are contiguous in an array.
 // To get to the next stage, we just increment our pointer to the next array element.
-SI void SK_VECTORCALL next(size_t x_tail, void** p, SkNf  r, SkNf  g, SkNf  b, SkNf  a,
-                                                    SkNf dr, SkNf dg, SkNf db, SkNf da) {
-    auto next = (Fn)load_and_increment(&p);
-    next(x_tail,p, r,g,b,a, dr,dg,db,da);
+SI void SK_VECTORCALL next(Stage* st, size_t x_tail, SkNf  r, SkNf  g, SkNf  b, SkNf  a,
+                                                     SkNf dr, SkNf dg, SkNf db, SkNf da) {
+    st->next(st+1, x_tail, r,g,b,a, dr,dg,db,da);
 }
 
 // Stages defined below always call next.
 // This is always the last stage, a backstop that actually returns to the caller when done.
-SI void SK_VECTORCALL just_return(size_t, void**, SkNf, SkNf, SkNf, SkNf,
+SI void SK_VECTORCALL just_return(Stage*, size_t, SkNf, SkNf, SkNf, SkNf,
                                                   SkNf, SkNf, SkNf, SkNf) {}
 
 #define STAGE(name)                                                                      \
-    static SK_ALWAYS_INLINE void name##_kernel(size_t x, size_t tail,                    \
+    static SK_ALWAYS_INLINE void name##_kernel(void* ctx, size_t x, size_t tail,         \
                                                SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
                                                SkNf& dr, SkNf& dg, SkNf& db, SkNf& da);  \
-    SI void SK_VECTORCALL name(size_t x_tail, void** p,                                  \
+    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                                 \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,                       \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {                     \
-        name##_kernel(x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                         \
-        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                            \
+        name##_kernel(st->ctx, x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                \
+        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                          \
     }                                                                                    \
-    static SK_ALWAYS_INLINE void name##_kernel(size_t x, size_t tail,                    \
+    static SK_ALWAYS_INLINE void name##_kernel(void* ctx, size_t x, size_t tail,         \
                                                SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
                                                SkNf& dr, SkNf& dg, SkNf& db, SkNf& da)
 
-#define STAGE_CTX(name, Ctx)                                                             \
-    static SK_ALWAYS_INLINE void name##_kernel(Ctx ctx, size_t x, size_t tail,           \
-                                               SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
-                                               SkNf& dr, SkNf& dg, SkNf& db, SkNf& da);  \
-    SI void SK_VECTORCALL name(size_t x_tail, void** p,                                  \
-                               SkNf  r, SkNf  g, SkNf  b, SkNf  a,                       \
-                               SkNf dr, SkNf dg, SkNf db, SkNf da) {                     \
-        auto ctx = (Ctx)load_and_increment(&p);                                          \
-        name##_kernel(ctx, x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                    \
-        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                            \
-    }                                                                                    \
-    static SK_ALWAYS_INLINE void name##_kernel(Ctx ctx, size_t x, size_t tail,           \
-                                               SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
-                                               SkNf& dr, SkNf& dg, SkNf& db, SkNf& da)
 
 // Many xfermodes apply the same logic to each channel.
 #define RGBA_XFERMODE(name)                                                     \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da);  \
-    SI void SK_VECTORCALL name(size_t x_tail, void** p,                         \
+    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                        \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,              \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {            \
         r = name##_kernel(r,a,dr,da);                                           \
         g = name##_kernel(g,a,dg,da);                                           \
         b = name##_kernel(b,a,db,da);                                           \
         a = name##_kernel(a,a,da,da);                                           \
-        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                   \
+        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                 \
     }                                                                           \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da)
@@ -132,14 +95,14 @@ SI void SK_VECTORCALL just_return(size_t, void**, SkNf, SkNf, SkNf, SkNf,
 #define RGB_XFERMODE(name)                                                      \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da);  \
-    SI void SK_VECTORCALL name(size_t x_tail, void** p,                         \
+    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                         \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,              \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {            \
         r = name##_kernel(r,a,dr,da);                                           \
         g = name##_kernel(g,a,dg,da);                                           \
         b = name##_kernel(b,a,db,da);                                           \
         a = a + (da * (1.0f-a));                                                \
-        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                   \
+        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                 \
     }                                                                           \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da)
@@ -311,8 +274,8 @@ SI void from_f16(const void* px, SkNf* r, SkNf* g, SkNf* b, SkNf* a) {
     *a = SkHalfToFloat_finite_ftz(ah);
 }
 
-STAGE_CTX(trace, const char*) {
-    SkDebugf("%s\n", ctx);
+STAGE(trace) {
+    SkDebugf("%s\n", (const char*)ctx);
 }
 STAGE(registers) {
     auto print = [](const char* name, const SkNf& v) {
@@ -369,10 +332,11 @@ STAGE(premul) {
     b *= a;
 }
 
-STAGE_CTX(set_rgb, const float*) {
-    r = ctx[0];
-    g = ctx[1];
-    b = ctx[2];
+STAGE(set_rgb) {
+    auto rgb = (const float*)ctx;
+    r = rgb[0];
+    g = rgb[1];
+    b = rgb[2];
 }
 
 STAGE(move_src_dst) {
@@ -417,7 +381,7 @@ STAGE(from_2dot2) {
         // x^(141/64) = x^(128/64) * x^(12/64) * x^(1/64)
         return SkNf::Max((x*x) * (x16*x16*x16) * (x64), 0.0f);
     };
-
+    
     r = from_2dot2(r);
     g = from_2dot2(g);
     b = from_2dot2(b);
@@ -439,16 +403,17 @@ STAGE(to_2dot2) {
 }
 
 // The default shader produces a constant color (from the SkPaint).
-STAGE_CTX(constant_color, const SkPM4f*) {
-    r = ctx->r();
-    g = ctx->g();
-    b = ctx->b();
-    a = ctx->a();
+STAGE(constant_color) {
+    auto color = (const SkPM4f*)ctx;
+    r = color->r();
+    g = color->g();
+    b = color->b();
+    a = color->a();
 }
 
 // s' = sc for a scalar c.
-STAGE_CTX(scale_1_float, const float*) {
-    SkNf c = *ctx;
+STAGE(scale_1_float) {
+    SkNf c = *(const float*)ctx;
 
     r *= c;
     g *= c;
@@ -456,10 +421,10 @@ STAGE_CTX(scale_1_float, const float*) {
     a *= c;
 }
 // s' = sc for 8-bit c.
-STAGE_CTX(scale_u8, const uint8_t**) {
-    auto ptr = *ctx + x;
-    SkNf c = SkNf_from_byte(load(tail, ptr));
+STAGE(scale_u8) {
+    auto ptr = *(const uint8_t**)ctx + x;
 
+    SkNf c = SkNf_from_byte(load(tail, ptr));
     r = r*c;
     g = g*c;
     b = b*c;
@@ -471,8 +436,8 @@ SI SkNf lerp(const SkNf& from, const SkNf& to, const SkNf& cov) {
 }
 
 // s' = d(1-c) + sc, for a scalar c.
-STAGE_CTX(lerp_1_float, const float*) {
-    SkNf c = *ctx;
+STAGE(lerp_1_float) {
+    SkNf c = *(const float*)ctx;
 
     r = lerp(dr, r, c);
     g = lerp(dg, g, c);
@@ -481,10 +446,10 @@ STAGE_CTX(lerp_1_float, const float*) {
 }
 
 // s' = d(1-c) + sc for 8-bit c.
-STAGE_CTX(lerp_u8, const uint8_t**) {
-    auto ptr = *ctx + x;
-    SkNf c = SkNf_from_byte(load(tail, ptr));
+STAGE(lerp_u8) {
+    auto ptr = *(const uint8_t**)ctx + x;
 
+    SkNf c = SkNf_from_byte(load(tail, ptr));
     r = lerp(dr, r, c);
     g = lerp(dg, g, c);
     b = lerp(db, b, c);
@@ -492,8 +457,8 @@ STAGE_CTX(lerp_u8, const uint8_t**) {
 }
 
 // s' = d(1-c) + sc for 565 c.
-STAGE_CTX(lerp_565, const uint16_t**) {
-    auto ptr = *ctx + x;
+STAGE(lerp_565) {
+    auto ptr = *(const uint16_t**)ctx + x;
     SkNf cr, cg, cb;
     from_565(load(tail, ptr), &cr, &cg, &cb);
 
@@ -503,26 +468,26 @@ STAGE_CTX(lerp_565, const uint16_t**) {
     a = 1.0f;
 }
 
-STAGE_CTX(load_565, const uint16_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_565) {
+    auto ptr = *(const uint16_t**)ctx + x;
     from_565(load(tail, ptr), &r,&g,&b);
     a = 1.0f;
 }
-STAGE_CTX(load_565_d, const uint16_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_565_d) {
+    auto ptr = *(const uint16_t**)ctx + x;
     from_565(load(tail, ptr), &dr,&dg,&db);
     da = 1.0f;
 }
-STAGE_CTX(store_565, uint16_t**) {
-    auto ptr = *ctx + x;
+STAGE(store_565) {
+    auto ptr = *(uint16_t**)ctx + x;
     store(tail, SkNx_cast<uint16_t>( SkNf_round(r, SK_R16_MASK) << SK_R16_SHIFT
                                    | SkNf_round(g, SK_G16_MASK) << SK_G16_SHIFT
                                    | SkNf_round(b, SK_B16_MASK) << SK_B16_SHIFT), ptr);
 }
 
 
-STAGE_CTX(load_f16, const uint64_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_f16) {
+    auto ptr = *(const uint64_t**)ctx + x;
 
     const void* src = ptr;
     SkNx<N, uint64_t> px;
@@ -532,8 +497,8 @@ STAGE_CTX(load_f16, const uint64_t**) {
     }
     from_f16(src, &r, &g, &b, &a);
 }
-STAGE_CTX(load_f16_d, const uint64_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_f16_d) {
+    auto ptr = *(const uint64_t**)ctx + x;
 
     const void* src = ptr;
     SkNx<N, uint64_t> px;
@@ -543,8 +508,8 @@ STAGE_CTX(load_f16_d, const uint64_t**) {
     }
     from_f16(src, &dr, &dg, &db, &da);
 }
-STAGE_CTX(store_f16, uint64_t**) {
-    auto ptr = *ctx + x;
+STAGE(store_f16) {
+    auto ptr = *(uint64_t**)ctx + x;
 
     SkNx<N, uint64_t> px;
     SkNh::Store4(tail ? (void*)&px : (void*)ptr, SkFloatToHalf_finite_ftz(r),
@@ -556,8 +521,8 @@ STAGE_CTX(store_f16, uint64_t**) {
     }
 }
 
-STAGE_CTX(store_f32, SkPM4f**) {
-    auto ptr = *ctx + x;
+STAGE(store_f32) {
+    auto ptr = *(SkPM4f**)ctx + x;
 
     SkNx<N, SkPM4f> px;
     SkNf::Store4(tail ? (void*)&px : (void*)ptr, r,g,b,a);
@@ -567,15 +532,15 @@ STAGE_CTX(store_f32, SkPM4f**) {
 }
 
 
-STAGE_CTX(load_8888, const uint32_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_8888) {
+    auto ptr = *(const uint32_t**)ctx + x;
     from_8888(load(tail, ptr), &r, &g, &b, &a);
 }
-STAGE_CTX(load_8888_d, const uint32_t**) {
-    auto ptr = *ctx + x;
+STAGE(load_8888_d) {
+    auto ptr = *(const uint32_t**)ctx + x;
     from_8888(load(tail, ptr), &dr, &dg, &db, &da);
 }
-STAGE_CTX(store_8888, uint32_t**) {
+STAGE(store_8888) {
     auto byte = [](const SkNf& x, int ix) {
         // Here's a neat trick: 0x47000000 == 32768.0f, and 0x470000ff == 32768.0f + (255/256.0f).
         auto v = SkNf_fma(255/256.0f, x, 32768.0f);
@@ -586,33 +551,35 @@ STAGE_CTX(store_8888, uint32_t**) {
         return (SkNi::Load(&v) & 0xff) << (8*ix);  // B or G
     };
 
-    auto ptr = *ctx + x;
+    auto ptr = *(uint32_t**)ctx + x;
     store(tail, byte(r,0)|byte(g,1)|byte(b,2)|byte(a,3), (int*)ptr);
 }
 
-STAGE_CTX(load_tables, const LoadTablesContext*) {
-    auto ptr = ctx->fSrc + x;
+STAGE(load_tables) {
+    auto loadCtx = (const LoadTablesContext*)ctx;
+    auto ptr = loadCtx->fSrc + x;
 
     SkNu rgba = load(tail, ptr);
     auto to_int = [](const SkNu& v) { return SkNi::Load(&v); };
-    r = gather(tail, ctx->fR, to_int((rgba >>  0) & 0xff));
-    g = gather(tail, ctx->fG, to_int((rgba >>  8) & 0xff));
-    b = gather(tail, ctx->fB, to_int((rgba >> 16) & 0xff));
+    r = gather(tail, loadCtx->fR, to_int((rgba >>  0) & 0xff));
+    g = gather(tail, loadCtx->fG, to_int((rgba >>  8) & 0xff));
+    b = gather(tail, loadCtx->fB, to_int((rgba >> 16) & 0xff));
     a = SkNf_from_byte(rgba >> 24);
 }
 
-STAGE_CTX(store_tables, const StoreTablesContext*) {
-    auto ptr = ctx->fDst + x;
+STAGE(store_tables) {
+    auto storeCtx = (const StoreTablesContext*)ctx;
+    auto ptr = storeCtx->fDst + x;
 
-    float scale = ctx->fCount - 1;
+    float scale = storeCtx->fCount - 1;
     SkNi ri = SkNf_round(scale, r);
     SkNi gi = SkNf_round(scale, g);
     SkNi bi = SkNf_round(scale, b);
 
-    store(tail, ( SkNx_cast<int>(gather(tail, ctx->fR, ri)) << 0
-                | SkNx_cast<int>(gather(tail, ctx->fG, gi)) << 8
-                | SkNx_cast<int>(gather(tail, ctx->fB, bi)) << 16
-                | SkNf_round(255.0f, a)                     << 24), (int*)ptr);
+    store(tail, ( SkNx_cast<int>(gather(tail, storeCtx->fR, ri)) << 0
+                | SkNx_cast<int>(gather(tail, storeCtx->fG, gi)) << 8
+                | SkNx_cast<int>(gather(tail, storeCtx->fB, bi)) << 16
+                | SkNf_round(255.0f, a)                          << 24), (int*)ptr);
 }
 
 SI SkNf inv(const SkNf& x) { return 1.0f - x; }
@@ -673,16 +640,16 @@ STAGE(luminance_to_alpha) {
     r = g = b = 0;
 }
 
-STAGE_CTX(matrix_2x3, const float*) {
-    auto m = ctx;
+STAGE(matrix_2x3) {
+    auto m = (const float*)ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[2], m[4])),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[3], m[5]));
     r = R;
     g = G;
 }
-STAGE_CTX(matrix_3x4, const float*) {
-    auto m = ctx;
+STAGE(matrix_3x4) {
+    auto m = (const float*)ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[3], SkNf_fma(b,m[6], m[ 9]))),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[4], SkNf_fma(b,m[7], m[10]))),
@@ -691,8 +658,8 @@ STAGE_CTX(matrix_3x4, const float*) {
     g = G;
     b = B;
 }
-STAGE_CTX(matrix_4x5, const float*) {
-    auto m = ctx;
+STAGE(matrix_4x5) {
+    auto m = (const float*)ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[4], SkNf_fma(b,m[ 8], SkNf_fma(a,m[12], m[16])))),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[5], SkNf_fma(b,m[ 9], SkNf_fma(a,m[13], m[17])))),
@@ -703,9 +670,9 @@ STAGE_CTX(matrix_4x5, const float*) {
     b = B;
     a = A;
 }
-STAGE_CTX(matrix_perspective, const float*) {
+STAGE(matrix_perspective) {
     // N.B. unlike the matrix_NxM stages, this takes a row-major matrix.
-    auto m = ctx;
+    auto m = (const float*)ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[1], m[2])),
          G = SkNf_fma(r,m[3], SkNf_fma(g,m[4], m[5])),
@@ -725,10 +692,10 @@ SI SkNf parametric(const SkNf& v, const SkColorSpaceTransferFn& p) {
     // Max(NaN, 0) = 0, but Max(0, NaN) = NaN, so we want this exact order to ensure NaN => 0
     return SkNf::Min(SkNf::Max(SkNf::Load(result), 0.0f), 1.0f);
 }
-STAGE_CTX(parametric_r, const SkColorSpaceTransferFn*) { r = parametric(r, *ctx); }
-STAGE_CTX(parametric_g, const SkColorSpaceTransferFn*) { g = parametric(g, *ctx); }
-STAGE_CTX(parametric_b, const SkColorSpaceTransferFn*) { b = parametric(b, *ctx); }
-STAGE_CTX(parametric_a, const SkColorSpaceTransferFn*) { a = parametric(a, *ctx); }
+STAGE(parametric_r) { r = parametric(r, *(const SkColorSpaceTransferFn*)ctx); }
+STAGE(parametric_g) { g = parametric(g, *(const SkColorSpaceTransferFn*)ctx); }
+STAGE(parametric_b) { b = parametric(b, *(const SkColorSpaceTransferFn*)ctx); }
+STAGE(parametric_a) { a = parametric(a, *(const SkColorSpaceTransferFn*)ctx); }
 
 SI SkNf table(const SkNf& v, const SkTableTransferFn& table) {
     float result[N];
@@ -738,13 +705,13 @@ SI SkNf table(const SkNf& v, const SkTableTransferFn& table) {
     // no need to clamp - tables are by-design [0,1] -> [0,1]
     return SkNf::Load(result);
 }
-STAGE_CTX(table_r, const SkTableTransferFn*) { r = table(r, *ctx); }
-STAGE_CTX(table_g, const SkTableTransferFn*) { g = table(g, *ctx); }
-STAGE_CTX(table_b, const SkTableTransferFn*) { b = table(b, *ctx); }
-STAGE_CTX(table_a, const SkTableTransferFn*) { a = table(a, *ctx); }
+STAGE(table_r) { r = table(r, *(const SkTableTransferFn*)ctx); }
+STAGE(table_g) { g = table(g, *(const SkTableTransferFn*)ctx); }
+STAGE(table_b) { b = table(b, *(const SkTableTransferFn*)ctx); }
+STAGE(table_a) { a = table(a, *(const SkTableTransferFn*)ctx); }
 
-STAGE_CTX(color_lookup_table, const SkColorLookUpTable*) {
-    const SkColorLookUpTable* colorLUT = ctx;
+STAGE(color_lookup_table) {
+    const SkColorLookUpTable* colorLUT = (const SkColorLookUpTable*)ctx;
     SkASSERT(3 == colorLUT->inputChannels() || 4 == colorLUT->inputChannels());
     SkASSERT(3 == colorLUT->outputChannels());
     float result[3][N];
@@ -815,29 +782,33 @@ SI SkNf mirror(const SkNf& v, float l/*imit*/) {
     result = SkNf::Min(result, nextafterf(l, 0));
     return assert_in_tile(result, l);
 }
-STAGE_CTX( clamp_x, const float*) { r = clamp (r, *ctx); }
-STAGE_CTX(repeat_x, const float*) { r = repeat(r, *ctx); }
-STAGE_CTX(mirror_x, const float*) { r = mirror(r, *ctx); }
-STAGE_CTX( clamp_y, const float*) { g = clamp (g, *ctx); }
-STAGE_CTX(repeat_y, const float*) { g = repeat(g, *ctx); }
-STAGE_CTX(mirror_y, const float*) { g = mirror(g, *ctx); }
+STAGE( clamp_x) { r = clamp (r, *(const float*)ctx); }
+STAGE(repeat_x) { r = repeat(r, *(const float*)ctx); }
+STAGE(mirror_x) { r = mirror(r, *(const float*)ctx); }
+STAGE( clamp_y) { g = clamp (g, *(const float*)ctx); }
+STAGE(repeat_y) { g = repeat(g, *(const float*)ctx); }
+STAGE(mirror_y) { g = mirror(g, *(const float*)ctx); }
+
+STAGE(save_xy) {
+    auto sc = (SkImageShaderContext*)ctx;
 
-STAGE_CTX(save_xy, SkImageShaderContext*) {
-    r.store(ctx->x);
-    g.store(ctx->y);
+    r.store(sc->x);
+    g.store(sc->y);
 
     // Whether bilinear or bicubic, all sample points have the same fractional offset (fx,fy).
     // They're either the 4 corners of a logical 1x1 pixel or the 16 corners of a 3x3 grid
     // surrounding (x,y), all (0.5,0.5) off-center.
     auto fract = [](const SkNf& v) { return v - v.floor(); };
-    fract(r + 0.5f).store(ctx->fx);
-    fract(g + 0.5f).store(ctx->fy);
+    fract(r + 0.5f).store(sc->fx);
+    fract(g + 0.5f).store(sc->fy);
 }
 
-STAGE_CTX(accumulate, const SkImageShaderContext*) {
+STAGE(accumulate) {
+    auto sc = (const SkImageShaderContext*)ctx;
+
     // Bilinear and bicubic filtering are both separable, so we'll end up with independent
     // scale contributions in x and y that we multiply together to get each pixel's scale factor.
-    auto scale = SkNf::Load(ctx->scalex) * SkNf::Load(ctx->scaley);
+    auto scale = SkNf::Load(sc->scalex) * SkNf::Load(sc->scaley);
     dr = SkNf_fma(scale, r, dr);
     dg = SkNf_fma(scale, g, dg);
     db = SkNf_fma(scale, b, db);
@@ -849,21 +820,25 @@ STAGE_CTX(accumulate, const SkImageShaderContext*) {
 // At positive offsets, the x-axis contribution to that rectangular area is fx; (1-fx)
 // at negative x offsets.  The y-axis is treated symmetrically.
 template <int Scale>
-SI void bilinear_x(SkImageShaderContext* ctx, SkNf* x) {
-    *x = SkNf::Load(ctx->x) + Scale*0.5f;
-    auto fx = SkNf::Load(ctx->fx);
-    (Scale > 0 ? fx : (1.0f - fx)).store(ctx->scalex);
+SI void bilinear_x(void* ctx, SkNf* x) {
+    auto sc = (SkImageShaderContext*)ctx;
+
+    *x = SkNf::Load(sc->x) + Scale*0.5f;
+    auto fx = SkNf::Load(sc->fx);
+    (Scale > 0 ? fx : (1.0f - fx)).store(sc->scalex);
 }
 template <int Scale>
-SI void bilinear_y(SkImageShaderContext* ctx, SkNf* y) {
-    *y = SkNf::Load(ctx->y) + Scale*0.5f;
-    auto fy = SkNf::Load(ctx->fy);
-    (Scale > 0 ? fy : (1.0f - fy)).store(ctx->scaley);
+SI void bilinear_y(void* ctx, SkNf* y) {
+    auto sc = (SkImageShaderContext*)ctx;
+
+    *y = SkNf::Load(sc->y) + Scale*0.5f;
+    auto fy = SkNf::Load(sc->fy);
+    (Scale > 0 ? fy : (1.0f - fy)).store(sc->scaley);
 }
-STAGE_CTX(bilinear_nx, SkImageShaderContext*) { bilinear_x<-1>(ctx, &r); }
-STAGE_CTX(bilinear_px, SkImageShaderContext*) { bilinear_x<+1>(ctx, &r); }
-STAGE_CTX(bilinear_ny, SkImageShaderContext*) { bilinear_y<-1>(ctx, &g); }
-STAGE_CTX(bilinear_py, SkImageShaderContext*) { bilinear_y<+1>(ctx, &g); }
+STAGE(bilinear_nx) { bilinear_x<-1>(ctx, &r); }
+STAGE(bilinear_px) { bilinear_x<+1>(ctx, &r); }
+STAGE(bilinear_ny) { bilinear_y<-1>(ctx, &g); }
+STAGE(bilinear_py) { bilinear_y<+1>(ctx, &g); }
 
 
 // In bilinear interpolation, the 16 pixels at +/- 0.5 and +/- 1.5 offsets from the sample
@@ -884,87 +859,94 @@ SI SkNf bicubic_far(const SkNf& t) {
 }
 
 template <int Scale>
-SI void bicubic_x(SkImageShaderContext* ctx, SkNf* x) {
-    *x = SkNf::Load(ctx->x) + Scale*0.5f;
-    auto fx = SkNf::Load(ctx->fx);
-    if (Scale == -3) { return bicubic_far (1.0f - fx).store(ctx->scalex); }
-    if (Scale == -1) { return bicubic_near(1.0f - fx).store(ctx->scalex); }
-    if (Scale == +1) { return bicubic_near(       fx).store(ctx->scalex); }
-    if (Scale == +3) { return bicubic_far (       fx).store(ctx->scalex); }
+SI void bicubic_x(void* ctx, SkNf* x) {
+    auto sc = (SkImageShaderContext*)ctx;
+
+    *x = SkNf::Load(sc->x) + Scale*0.5f;
+    auto fx = SkNf::Load(sc->fx);
+    if (Scale == -3) { return bicubic_far (1.0f - fx).store(sc->scalex); }
+    if (Scale == -1) { return bicubic_near(1.0f - fx).store(sc->scalex); }
+    if (Scale == +1) { return bicubic_near(       fx).store(sc->scalex); }
+    if (Scale == +3) { return bicubic_far (       fx).store(sc->scalex); }
     SkDEBUGFAIL("unreachable");
 }
 template <int Scale>
-SI void bicubic_y(SkImageShaderContext* ctx, SkNf* y) {
-    *y = SkNf::Load(ctx->y) + Scale*0.5f;
-    auto fy = SkNf::Load(ctx->fy);
-    if (Scale == -3) { return bicubic_far (1.0f - fy).store(ctx->scaley); }
-    if (Scale == -1) { return bicubic_near(1.0f - fy).store(ctx->scaley); }
-    if (Scale == +1) { return bicubic_near(       fy).store(ctx->scaley); }
-    if (Scale == +3) { return bicubic_far (       fy).store(ctx->scaley); }
+SI void bicubic_y(void* ctx, SkNf* y) {
+    auto sc = (SkImageShaderContext*)ctx;
+
+    *y = SkNf::Load(sc->y) + Scale*0.5f;
+    auto fy = SkNf::Load(sc->fy);
+    if (Scale == -3) { return bicubic_far (1.0f - fy).store(sc->scaley); }
+    if (Scale == -1) { return bicubic_near(1.0f - fy).store(sc->scaley); }
+    if (Scale == +1) { return bicubic_near(       fy).store(sc->scaley); }
+    if (Scale == +3) { return bicubic_far (       fy).store(sc->scaley); }
     SkDEBUGFAIL("unreachable");
 }
-STAGE_CTX(bicubic_n3x, SkImageShaderContext*) { bicubic_x<-3>(ctx, &r); }
-STAGE_CTX(bicubic_n1x, SkImageShaderContext*) { bicubic_x<-1>(ctx, &r); }
-STAGE_CTX(bicubic_p1x, SkImageShaderContext*) { bicubic_x<+1>(ctx, &r); }
-STAGE_CTX(bicubic_p3x, SkImageShaderContext*) { bicubic_x<+3>(ctx, &r); }
+STAGE(bicubic_n3x) { bicubic_x<-3>(ctx, &r); }
+STAGE(bicubic_n1x) { bicubic_x<-1>(ctx, &r); }
+STAGE(bicubic_p1x) { bicubic_x<+1>(ctx, &r); }
+STAGE(bicubic_p3x) { bicubic_x<+3>(ctx, &r); }
 
-STAGE_CTX(bicubic_n3y, SkImageShaderContext*) { bicubic_y<-3>(ctx, &g); }
-STAGE_CTX(bicubic_n1y, SkImageShaderContext*) { bicubic_y<-1>(ctx, &g); }
-STAGE_CTX(bicubic_p1y, SkImageShaderContext*) { bicubic_y<+1>(ctx, &g); }
-STAGE_CTX(bicubic_p3y, SkImageShaderContext*) { bicubic_y<+3>(ctx, &g); }
+STAGE(bicubic_n3y) { bicubic_y<-3>(ctx, &g); }
+STAGE(bicubic_n1y) { bicubic_y<-1>(ctx, &g); }
+STAGE(bicubic_p1y) { bicubic_y<+1>(ctx, &g); }
+STAGE(bicubic_p3y) { bicubic_y<+3>(ctx, &g); }
 
 
 template <typename T>
-SI SkNi offset_and_ptr(T** ptr, const SkImageShaderContext* ctx, const SkNf& x, const SkNf& y) {
+SI SkNi offset_and_ptr(T** ptr, const void* ctx, const SkNf& x, const SkNf& y) {
+    auto sc = (const SkImageShaderContext*)ctx;
+
     SkNi ix = SkNx_cast<int>(x),
          iy = SkNx_cast<int>(y);
-    SkNi offset = iy*ctx->stride + ix;
+    SkNi offset = iy*sc->stride + ix;
 
-    *ptr = (const T*)ctx->pixels;
+    *ptr = (const T*)sc->pixels;
     return offset;
 }
 
-STAGE_CTX(gather_a8, const SkImageShaderContext*) {
+STAGE(gather_a8) {
     const uint8_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     r = g = b = 0.0f;
     a = SkNf_from_byte(gather(tail, p, offset));
 }
-STAGE_CTX(gather_i8, const SkImageShaderContext*) {
+STAGE(gather_i8) {
+    auto sc = (const SkImageShaderContext*)ctx;
     const uint8_t* p;
-    SkNi offset = offset_and_ptr(&p, ctx, r, g);
+    SkNi offset = offset_and_ptr(&p, sc, r, g);
 
     SkNi ix = SkNx_cast<int>(gather(tail, p, offset));
-    from_8888(gather(tail, ctx->ctable->readColors(), ix), &r, &g, &b, &a);
+    from_8888(gather(tail, sc->ctable->readColors(), ix), &r, &g, &b, &a);
 }
-STAGE_CTX(gather_g8, const SkImageShaderContext*) {
+STAGE(gather_g8) {
     const uint8_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     r = g = b = SkNf_from_byte(gather(tail, p, offset));
     a = 1.0f;
 }
-STAGE_CTX(gather_565, const SkImageShaderContext*) {
+STAGE(gather_565) {
     const uint16_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_565(gather(tail, p, offset), &r, &g, &b);
     a = 1.0f;
 }
-STAGE_CTX(gather_4444, const SkImageShaderContext*) {
+STAGE(gather_4444) {
     const uint16_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_4444(gather(tail, p, offset), &r, &g, &b, &a);
 }
-STAGE_CTX(gather_8888, const SkImageShaderContext*) {
+STAGE(gather_8888) {
     const uint32_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_8888(gather(tail, p, offset), &r, &g, &b, &a);
 }
-STAGE_CTX(gather_f16, const SkImageShaderContext*) {
+STAGE(gather_f16) {
     const uint64_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
@@ -985,15 +967,17 @@ SI Fn enum_to_Fn(SkRasterPipeline::StockStage st) {
 
 namespace {
     struct Compiled {
-        Compiled(const SkRasterPipeline::Stage* stages, int nstages) {
-            fProgram.reserve(2*nstages+1);
-            for (int i = 0; i < nstages; i++) {
-                fProgram.push_back((void*)enum_to_Fn(stages[i].stage));
-                if (stages[i].ctx) {
-                    fProgram.push_back(stages[i].ctx);
-                }
+        Compiled(const SkRasterPipeline::Stage* stages, int nstages) : fStages(nstages) {
+            if (nstages == 0) {
+                return;
+            }
+            fStart = enum_to_Fn(stages[0].stage);
+            for (int i = 0; i < nstages-1; i++) {
+                fStages[i].next = enum_to_Fn(stages[i+1].stage);
+                fStages[i].ctx  = stages[i].ctx;
             }
-            fProgram.push_back((void*)just_return);
+            fStages[nstages-1].next = just_return;
+            fStages[nstages-1].ctx  = stages[nstages-1].ctx;
         }
 
         void operator()(size_t x, size_t y, size_t n) {
@@ -1003,20 +987,19 @@ namespace {
                 _0 = SkNf(0),
                 _1 = SkNf(1);
 
-            void** p = fProgram.data();
-            auto start = (Fn)load_and_increment(&p);
             while (n >= N) {
-                start(x*N, p, X,Y,_1,_0, _0,_0,_0,_0);
+                fStart(fStages.data(), x*N, X,Y,_1,_0, _0,_0,_0,_0);
                 X += (float)N;
                 x += N;
                 n -= N;
             }
             if (n) {
-                start(x*N+n, p, X,Y,_1,_0, _0,_0,_0,_0);
+                fStart(fStages.data(), x*N+n, X,Y,_1,_0, _0,_0,_0,_0);
             }
         }
 
-        std::vector<void*> fProgram;
+        Fn fStart = just_return;
+        std::vector<Stage> fStages;
     };
 }
 
@@ -1036,7 +1019,6 @@ namespace SK_OPTS_NS {
 
 #undef SI
 #undef STAGE
-#undef STAGE_CTX
 #undef RGBA_XFERMODE
 #undef RGB_XFERMODE