Roll external/skia 87224bbed..bef05a6db (9 commits)

https://skia.googlesource.com/skia.git/+log/87224bbed..bef05a6db

2019-05-23 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial).
2019-05-23 bsalomon@google.com Disable two new tests on two bots while errors investigated
2019-05-23 brianosman@google.com Interpreter: Disassemble during execution when TRACE is enabled
2019-05-23 brianosman@google.com Support larger compound types in the interpreter
2019-05-23 jvanverth@google.com Remove Metal Perf bot from MacBook Air
2019-05-23 bungeman@google.com Add test of char to glyph round tripping.
2019-05-23 reed@google.com split RGB into YUV planes
2019-05-23 fmalita@chromium.org [skottie] Clamp seek() to the valid frame range
2019-05-23 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial).

The AutoRoll server is located here: https://autoroll-internal.skia.org/r/android-master-autoroll

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md

If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.

Test: Presubmit checks will test this change.
Change-Id: I488d8837ed27547b51d92f7e3e61a49caef89ccd
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/Android.bp b/Android.bp
index bc3286f..4e9f2d1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -271,6 +271,7 @@
         "src/core/SkXfermode.cpp",
         "src/core/SkXfermodeInterpretation.cpp",
         "src/core/SkYUVASizeInfo.cpp",
+        "src/core/SkYUVMath.cpp",
         "src/core/SkYUVPlanesCache.cpp",
         "src/effects/Sk1DPathEffect.cpp",
         "src/effects/Sk2DPathEffect.cpp",
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index ebe64ca..aeba7e9 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -1257,3 +1257,125 @@
 DEF_GM(return new YUVMakeColorSpaceGM();)
 
 }
+
+///////////////
+
+#include "tools/Resources.h"
+#include "src/core/SkYUVMath.h"
+#include "src/core/SkAutoPixmapStorage.h"
+#include "include/effects/SkColorMatrix.h"
+
+static void draw_into_alpha(const SkImage* img, sk_sp<SkColorFilter> cf, const SkPixmap& dst) {
+    auto canvas = SkCanvas::MakeRasterDirect(dst.info(), dst.writable_addr(), dst.rowBytes());
+    canvas->scale(1.0f * dst.width() / img->width(), 1.0f * dst.height() / img->height());
+    SkPaint paint;
+    paint.setFilterQuality(kLow_SkFilterQuality);
+    paint.setColorFilter(cf);
+    paint.setBlendMode(SkBlendMode::kSrc);
+    canvas->drawImage(img, 0, 0, &paint);
+}
+
+static void split_into_yuv(const SkImage* img, SkYUVColorSpace cs, const SkPixmap dst[3]) {
+    float m[20];
+    SkColorMatrix_RGB2YUV(cs, m);
+
+    memcpy(m + 15, m + 0, 5 * sizeof(float));   // copy Y into A
+    draw_into_alpha(img, SkColorFilters::Matrix(m), dst[0]);
+
+    memcpy(m + 15, m + 5, 5 * sizeof(float));   // copy U into A
+    draw_into_alpha(img, SkColorFilters::Matrix(m), dst[1]);
+
+    memcpy(m + 15, m + 10, 5 * sizeof(float));   // copy V into A
+    draw_into_alpha(img, SkColorFilters::Matrix(m), dst[2]);
+}
+
+static void draw_diff(SkCanvas* canvas, SkScalar x, SkScalar y,
+                      const SkImage* a, const SkImage* b) {
+    auto sh = SkShaders::Blend(SkBlendMode::kDifference, a->makeShader(), b->makeShader());
+    SkPaint paint;
+    paint.setShader(sh);
+    canvas->save();
+    canvas->translate(x, y);
+    canvas->drawRect(SkRect::MakeWH(a->width(), a->height()), paint);
+
+    SkColorMatrix cm;
+    cm.setScale(64, 64, 64);
+    paint.setShader(sh->makeWithColorFilter(SkColorFilters::Matrix(cm)));
+    canvas->translate(0, a->height());
+    canvas->drawRect(SkRect::MakeWH(a->width(), a->height()), paint);
+
+    canvas->restore();
+}
+
+// Exercises SkColorMatrix_RGB2YUV for yuv colorspaces, showing the planes, and the
+// resulting (recombined) images (gpu only for now).
+//
+class YUVSplitterGM : public skiagm::GM {
+    sk_sp<SkImage>      fOrig;
+    SkAutoPixmapStorage fStorage[3];
+    SkPixmap            fPM[3];
+
+public:
+    YUVSplitterGM() {}
+
+protected:
+
+    SkString onShortName() override {
+        return SkString("yuv_splitter");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(1024, 768);
+    }
+
+    void onOnceBeforeDraw() override {
+        fOrig = GetResourceAsImage("images/mandrill_256.png");
+
+        SkImageInfo info = SkImageInfo::Make(fOrig->width(), fOrig->height(), kAlpha_8_SkColorType,
+                                             kPremul_SkAlphaType);
+        fStorage[0].alloc(info);
+        if (0) {
+            // if you want to scale U,V down by 1/2
+            info = info.makeWH(info.width()/2, info.height()/2);
+        }
+        fStorage[1].alloc(info);
+        fStorage[2].alloc(info);
+        for (int i = 0; i < 3; ++i) {
+            fPM[i] = fStorage[i];
+        }
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkYUVAIndex indices[4];
+        indices[SkYUVAIndex::kY_Index] = {0, SkColorChannel::kR};
+        indices[SkYUVAIndex::kU_Index] = {1, SkColorChannel::kR};
+        indices[SkYUVAIndex::kV_Index] = {2, SkColorChannel::kR};
+        indices[SkYUVAIndex::kA_Index] = {-1, SkColorChannel::kR};
+
+        canvas->translate(fOrig->width(), 0);
+        canvas->save();
+        for (auto cs : {kRec709_SkYUVColorSpace, kRec601_SkYUVColorSpace, kJPEG_SkYUVColorSpace}) {
+            split_into_yuv(fOrig.get(), cs, fPM);
+            auto img = SkImage::MakeFromYUVAPixmaps(canvas->getGrContext(), cs, fPM, indices,
+                                                    fPM[0].info().dimensions(),
+                                                    kTopLeft_GrSurfaceOrigin,
+                                                    false, false, nullptr);
+            if (img) {
+                canvas->drawImage(img, 0, 0, nullptr);
+                draw_diff(canvas, 0, fOrig->height(), fOrig.get(), img.get());
+            }
+            canvas->translate(fOrig->width(), 0);
+        }
+        canvas->restore();
+        canvas->translate(-fOrig->width(), 0);
+
+        canvas->drawImage(SkImage::MakeRasterCopy(fPM[0]), 0, 0, nullptr);
+        canvas->drawImage(SkImage::MakeRasterCopy(fPM[1]), 0, fPM[0].height(), nullptr);
+        canvas->drawImage(SkImage::MakeRasterCopy(fPM[2]),
+                          0, fPM[0].height() + fPM[1].height(), nullptr);
+    }
+
+private:
+    typedef GM INHERITED;
+};
+DEF_GM( return new YUVSplitterGM; )
diff --git a/gn/core.gni b/gn/core.gni
index 12c549c..3b9fe6e 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -403,6 +403,7 @@
   "$_src/core/SkYUVPlanesCache.cpp",
   "$_src/core/SkYUVPlanesCache.h",
   "$_src/core/SkYUVASizeInfo.cpp",
+  "$_src/core/SkYUVMath.cpp",
 
   "$_src/image/SkImage.cpp",
 
diff --git a/include/effects/SkColorMatrix.h b/include/effects/SkColorMatrix.h
index d7376bb..c86fb68 100644
--- a/include/effects/SkColorMatrix.h
+++ b/include/effects/SkColorMatrix.h
@@ -16,6 +16,14 @@
     void setIdentity();
     void setScale(float rScale, float gScale, float bScale, float aScale = 1.0f);
 
+    void setRowMajor(const float src[20]) {
+        memcpy(fMat, src, sizeof(fMat));
+    }
+
+    void getRowMajor(float dst[20]) const {
+        memcpy(dst, fMat, sizeof(fMat));
+    }
+
     enum Axis {
         kR_Axis = 0,
         kG_Axis = 1,
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index 3e963f7..568f081 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -289,7 +289,6 @@
   "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
   "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All",
   "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer",
-  "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan.json
new file mode 100644
index 0000000..dbb8b37
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan.json
@@ -0,0 +1,1179 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skp",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SKP_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skimage",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/svg",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['/usr/bin/adb.1.0.35', 'push',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SVG_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "/sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\nvalue = int(sys.argv[3])\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\n# If we try to echo 1 to an already online cpu, adb returns exit code 1.\n# So, check the value before trying to write it.\nprior_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif prior_status == str(value):\n  print 'CPU %d online already %d' % (cpu, value)\n  sys.exit()\n\nsubprocess.check_output([ADB, 'shell', 'echo %s > '\n    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])\nactual_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif actual_status != str(value):\n  raise Exception('(actual, expected) (%s, %d)'\n                  % (actual_status, value))\n",
+      "/usr/bin/adb.1.0.35",
+      "0",
+      "1"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Enabling CPU 0",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@value = int(sys.argv[3])@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# If we try to echo 1 to an already online cpu, adb returns exit code 1.@@@",
+      "@@@STEP_LOG_LINE@python.inline@# So, check the value before trying to write it.@@@",
+      "@@@STEP_LOG_LINE@python.inline@prior_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if prior_status == str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print 'CPU %d online already %d' % (cpu, value)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit()@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %s > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_status != str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %d)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_status, value))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\nvalue = int(sys.argv[3])\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\n# If we try to echo 1 to an already online cpu, adb returns exit code 1.\n# So, check the value before trying to write it.\nprior_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif prior_status == str(value):\n  print 'CPU %d online already %d' % (cpu, value)\n  sys.exit()\n\nsubprocess.check_output([ADB, 'shell', 'echo %s > '\n    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])\nactual_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif actual_status != str(value):\n  raise Exception('(actual, expected) (%s, %d)'\n                  % (actual_status, value))\n",
+      "/usr/bin/adb.1.0.35",
+      "1",
+      "1"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Enabling CPU 1",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@value = int(sys.argv[3])@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# If we try to echo 1 to an already online cpu, adb returns exit code 1.@@@",
+      "@@@STEP_LOG_LINE@python.inline@# So, check the value before trying to write it.@@@",
+      "@@@STEP_LOG_LINE@python.inline@prior_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if prior_status == str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print 'CPU %d online already %d' % (cpu, value)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit()@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %s > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_status != str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %d)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_status, value))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\nvalue = int(sys.argv[3])\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\n# If we try to echo 1 to an already online cpu, adb returns exit code 1.\n# So, check the value before trying to write it.\nprior_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif prior_status == str(value):\n  print 'CPU %d online already %d' % (cpu, value)\n  sys.exit()\n\nsubprocess.check_output([ADB, 'shell', 'echo %s > '\n    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])\nactual_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif actual_status != str(value):\n  raise Exception('(actual, expected) (%s, %d)'\n                  % (actual_status, value))\n",
+      "/usr/bin/adb.1.0.35",
+      "2",
+      "1"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Enabling CPU 2",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@value = int(sys.argv[3])@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# If we try to echo 1 to an already online cpu, adb returns exit code 1.@@@",
+      "@@@STEP_LOG_LINE@python.inline@# So, check the value before trying to write it.@@@",
+      "@@@STEP_LOG_LINE@python.inline@prior_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if prior_status == str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print 'CPU %d online already %d' % (cpu, value)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit()@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %s > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_status != str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %d)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_status, value))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\nvalue = int(sys.argv[3])\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\n# If we try to echo 1 to an already online cpu, adb returns exit code 1.\n# So, check the value before trying to write it.\nprior_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif prior_status == str(value):\n  print 'CPU %d online already %d' % (cpu, value)\n  sys.exit()\n\nsubprocess.check_output([ADB, 'shell', 'echo %s > '\n    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])\nactual_status = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()\nif actual_status != str(value):\n  raise Exception('(actual, expected) (%s, %d)'\n                  % (actual_status, value))\n",
+      "/usr/bin/adb.1.0.35",
+      "3",
+      "1"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Enabling CPU 3",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@value = int(sys.argv[3])@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# If we try to echo 1 to an already online cpu, adb returns exit code 1.@@@",
+      "@@@STEP_LOG_LINE@python.inline@# So, check the value before trying to write it.@@@",
+      "@@@STEP_LOG_LINE@python.inline@prior_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if prior_status == str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print 'CPU %d online already %d' % (cpu, value)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit()@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %s > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_status = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_status != str(value):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %d)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_status, value))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/usr/bin/adb.1.0.35",
+      "4",
+      "ondemand"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 4's governor to ondemand",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/usr/bin/adb.1.0.35",
+      "0",
+      "ondemand"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 0's governor to ondemand",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/build/dm",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Adreno418 extra_config Android_Vulkan model Nexus5x os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config vk vkmsaa4 --src tests gm image colorImage svg --blacklist _ gm _ savelayer_clipmask _ test _ ReplaceSurfaceBackendTexture _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW _ gm _ encode-platform --match ~WritePixelsNonTextureMSAA_Gpu ~WritePixelsMSAA_Gpu --noRAW_threading --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/usr/bin/adb.1.0.35', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/usr/bin/adb.1.0.35', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/usr/bin/adb.1.0.35', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/usr/bin/adb.1.0.35', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "adb pull"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "pull",
+      "/sdcard/revenge_of_the_skiabot/dm_out",
+      "[CLEANUP]/adb_pull_tmp_1"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "glob",
+      "[CLEANUP]/adb_pull_tmp_1",
+      "dm_out/*"
+    ],
+    "infra_step": true,
+    "name": "adb pull.list pulled files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_END@glob@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "adb pull.copy 1.png",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "adb pull.copy 2.png",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[CLEANUP]/adb_pull_tmp_1"
+    ],
+    "infra_step": true,
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/usr/bin/adb.1.0.35', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@out = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output(['/usr/bin/adb.1.0.35', 'logcat', '-d'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@for line in log.split('\\n'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  tokens = line.split()@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':@@@",
+      "@@@STEP_LOG_LINE@python.inline@    addr, path = tokens[-2:]@@@",
+      "@@@STEP_LOG_LINE@python.inline@    local = os.path.join(out, os.path.basename(path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if os.path.exists(local):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])@@@",
+      "@@@STEP_LOG_LINE@python.inline@      line = line.replace(addr, addr + ' ' + sym.strip())@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print line@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
index 6732f78..1737d0d 100644
--- a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
@@ -266,6 +266,10 @@
       "svg",
       "--blacklist",
       "_",
+      "test",
+      "_",
+      "AsyncReadPixels",
+      "_",
       "svg",
       "_",
       "svgparse_",
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index 8b80854..3899a2b 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -232,6 +232,15 @@
                          'Chorizo' in bot):
       blacklist(['_', 'gm', '_', 'savelayer_clipmask'])
 
+    # skbug.com/9124
+    if 'GPU' in bot and 'Nexus5x' in bot and "Vulkan" in bot and 'Release' in bot:
+      blacklist(['_', 'test', '_', 'ReplaceSurfaceBackendTexture'])
+
+
+    # skbug.com/9123
+    if 'CommandBuffer' in bot and 'IntelIris5100' in bot:
+      blacklist(['_', 'test', '_', 'AsyncReadPixels'])
+
     # skbug.com/9043 - these devices render this test incorrectly
     # when opList splitting reduction is enabled
     if 'GPU' in bot and 'Vulkan' in bot and ('MoltenVK' in bot or
@@ -1091,6 +1100,7 @@
   'Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA',
   'Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC',
   'Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All',
+  'Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan',
 ]
 
 
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index c34c9f2..86dcc40 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -1464,11 +1464,6 @@
       ],
       "trigger": "master"
     },
-    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal": {
-      "tasks": [
-        "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal"
-      ]
-    },
     "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
       "tasks": [
         "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
@@ -36984,117 +36979,6 @@
         "perf"
       ]
     },
-    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:187"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Release-Metal"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.14.3",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "perf"
-      ]
-    },
     "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
       "caches": [
         {
@@ -89526,106 +89410,6 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_nano_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-Metal"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.8",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
     "Upload-Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "caches": [
         {
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 1956639..5dfaf07 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -14,12 +14,12 @@
   "deps": {
     "depot_tools": {
       "branch": "master",
-      "revision": "181e44c231854a3f201ef0884f6f8462f321ea15",
+      "revision": "94515755316be3fb4fa59d984511c1aeaba40ffb",
       "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     },
     "recipe_engine": {
       "branch": "master",
-      "revision": "71751d787ce1cce2996836a9af823cf961638ccf",
+      "revision": "96bcf6aaa92a254a19c1120b446dd6b35e8dc902",
       "url": "https://chromium.googlesource.com/infra/luci/recipes-py.git"
     }
   },
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 2f8d4f2..5ac0402 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -598,7 +598,10 @@
     if (!fScene)
         return;
 
-    fScene->animate(fInPoint + SkTPin(t, 0.0f, 1.0f) * (fOutPoint - fInPoint));
+    // Per AE/Lottie semantics out_point is exclusive.
+    const auto kLastValidFrame = std::nextafter(fOutPoint, fInPoint);
+
+    fScene->animate(SkTPin(fInPoint + t * (fOutPoint - fInPoint), fInPoint, kLastValidFrame));
 }
 
 sk_sp<Animation> Animation::Make(const char* data, size_t length) {
diff --git a/src/core/SkYUVMath.cpp b/src/core/SkYUVMath.cpp
new file mode 100644
index 0000000..f074515
--- /dev/null
+++ b/src/core/SkYUVMath.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkYUVMath.h"
+#include "include/core/SkMatrix44.h"
+
+// in SkColorMatrix order (row-major)
+// Created by running SkColorMatrix_DumpYUVMatrixTables()
+
+const float Rec709_rgb_to_yuv[] = {
+    0.182586f,  0.614231f,  0.062007f,  0.000000f,  0.062745f,
+   -0.100644f, -0.338572f,  0.439216f,  0.000000f,  0.501961f,
+    0.439216f, -0.398942f, -0.040274f,  0.000000f,  0.501961f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+const float Rec709_yuv_to_rgb[] = {
+    1.164384f,  0.000000f,  1.792741f,  0.000000f, -0.972945f,
+    1.164384f, -0.213249f, -0.532909f,  0.000000f,  0.301483f,
+    1.164384f,  2.112402f,  0.000000f,  0.000000f, -1.133402f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+const float Rec601_rgb_to_yuv[] = {
+    0.256788f,  0.504129f,  0.097906f,  0.000000f,  0.062745f,
+   -0.148223f, -0.290993f,  0.439216f,  0.000000f,  0.501961f,
+    0.439216f, -0.367788f, -0.071427f,  0.000000f,  0.501961f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+const float Rec601_yuv_to_rgb[] = {
+    1.164384f,  0.000000f,  1.596027f,  0.000000f, -0.874202f,
+    1.164384f, -0.391762f, -0.812968f,  0.000000f,  0.531668f,
+    1.164384f,  2.017232f,  0.000000f,  0.000000f, -1.085631f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+const float JPEG_rgb_to_yuv[] = {
+    0.299000f,  0.587000f,  0.114000f,  0.000000f,  0.000000f,
+   -0.168736f, -0.331264f,  0.500000f,  0.000000f,  0.501961f,
+    0.500000f, -0.418688f, -0.081312f,  0.000000f,  0.501961f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+const float JPEG_yuv_to_rgb[] = {
+    1.000000f,  0.000000f,  1.402000f,  0.000000f, -0.703749f,
+    1.000000f, -0.344136f, -0.714136f,  0.000000f,  0.531211f,
+    1.000000f,  1.772000f,  0.000000f,  0.000000f, -0.889475f,
+    0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
+};
+
+static_assert(kJPEG_SkYUVColorSpace   == 0, "");
+static_assert(kRec601_SkYUVColorSpace == 1, "");
+static_assert(kRec709_SkYUVColorSpace == 2, "");
+
+const float* yuv_to_rgb_array[] = {
+    JPEG_yuv_to_rgb,
+    Rec601_yuv_to_rgb,
+    Rec709_yuv_to_rgb,
+};
+
+const float* rgb_to_yuv_array[] = {
+    JPEG_rgb_to_yuv,
+    Rec601_rgb_to_yuv,
+    Rec709_rgb_to_yuv,
+};
+
+constexpr size_t kSizeOfColorMatrix = 20 * sizeof(float);
+
+void SkColorMatrix_RGB2YUV(SkYUVColorSpace cs, float m[20]) {
+    if ((unsigned)cs < (unsigned)kIdentity_SkYUVColorSpace) {
+        memcpy(m, rgb_to_yuv_array[(unsigned)cs], kSizeOfColorMatrix);
+    } else {
+        memset(m, 0, kSizeOfColorMatrix);
+        m[0] = m[6] = m[12] = m[18] = 1;
+    }
+}
+
+void SkColorMatrix_YUV2RGB(SkYUVColorSpace cs, float m[20]) {
+    if ((unsigned)cs < (unsigned)kIdentity_SkYUVColorSpace) {
+        memcpy(m, yuv_to_rgb_array[(unsigned)cs], kSizeOfColorMatrix);
+    } else {
+        memset(m, 0, kSizeOfColorMatrix);
+        m[0] = m[6] = m[12] = m[18] = 1;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// we just drop the alpha rol/col from the colormatrix
+// output is |        tr |
+//           |  3x3   tg |
+//           |        tb |
+//           | 0 0 0  1  |
+static void colormatrix_to_matrix44(const float src[20], SkMatrix44* dst) {
+    for (int r = 0; r < 3; ++r) {
+        for (int c = 0; c < 3; ++c) {
+            dst->set(r, c, src[r*5 + c]);
+        }
+        dst->set(r, 3, src[r*5 + 4]);
+    }
+    dst->set(3, 0, 0);
+    dst->set(3, 1, 0);
+    dst->set(3, 2, 0);
+    dst->set(3, 3, 1);
+}
+
+// input: ignore the bottom row
+// output: inject identity row/column for alpha
+static void matrix44_to_colormatrix(const SkMatrix44& src, float dst[20]) {
+    for (int r = 0; r < 3; ++r) {
+        for (int c = 0; c < 3; ++c) {
+            dst[r*5 + c] = src.get(r, c);
+        }
+        dst[r*5 + 3] = 0;   // scale alpha
+        dst[r*5 + 4] = src.get(r, 3);  // translate
+    }
+    dst[15] = dst[16] = dst[17] = dst[19] = 0;
+    dst[18] = 1;
+}
+
+static void scale3(float m[], float s) {
+    for (int i = 0; i < 3; ++i) {
+        m[i] *= s;
+    }
+}
+
+namespace {
+struct YUVCoeff {
+    float   Kr, Kb;
+    float   Cr, Cb;
+    float   scaleY, addY;
+    float   scaleUV;
+};
+}   // namespace
+
+const YUVCoeff gCoeff[] = {
+    // kJPEG_SkYUVColorSpace
+    { 0.299f,  0.114f,  1/1.772f,  1/1.402f,          1,        0,         1, },
+
+    // kRec601_SkYUVColorSpace
+    { 0.299f,  0.114f,  1/1.772f,  1/1.402f,  219/255.f, 16/255.f, 224/255.f, },
+
+    // kRec709_SkYUVColorSpace
+    { 0.2126f, 0.0722f, 1/1.8556f, 1/1.5748f, 219/255.f, 16/255.f, 224/255.f, },
+};
+
+static void make_rgb_to_yuv_matrix(float mx[20], const YUVCoeff& c) {
+    const float Kr = c.Kr;
+    const float Kb = c.Kb;
+    const float Kg = 1.0f - Kr - Kb;
+
+    float m[20] = {
+          Kr,  Kg,   Kb,  0,    c.addY,
+         -Kr, -Kg, 1-Kb,  0, 128/255.f,
+        1-Kr, -Kg,  -Kb,  0, 128/255.f,
+           0,   0,    0,  1,         0,
+    };
+    memcpy(mx, m, sizeof(m));
+    scale3(mx +  0, c.scaleY);
+    scale3(mx +  5, c.Cr * c.scaleUV);
+    scale3(mx + 10, c.Cb * c.scaleUV);
+}
+
+static void dump(const float m[20], SkYUVColorSpace cs, bool rgb2yuv) {
+    const char* names[] = {
+        "JPEG", "Rec601", "Rec709",
+    };
+    const char* dirnames[] = {
+        "yuv_to_rgb", "rgb_to_yuv",
+    };
+    SkDebugf("const float %s_%s[] = {\n", names[cs], dirnames[rgb2yuv]);
+    for (int i = 0; i < 4; ++i) {
+        SkDebugf("    ");
+        for (int j = 0; j < 5; ++j) {
+            SkDebugf(" %9.6ff,", m[i * 5 + j]);
+        }
+        SkDebugf("\n");
+    }
+    SkDebugf("};\n");
+}
+
+// Used to create the prebuilt tables for each colorspace.
+// Don't remove this function, in case we want to recompute those tables in the future.
+void SkColorMatrix_DumpYUVMatrixTables() {
+    for (auto cs : {kRec709_SkYUVColorSpace, kRec601_SkYUVColorSpace, kJPEG_SkYUVColorSpace}) {
+        float m[20];
+        make_rgb_to_yuv_matrix(m, gCoeff[(unsigned)cs]);
+        dump(m, cs, true);
+        SkMatrix44 m44, im44;
+        colormatrix_to_matrix44(m, &m44);
+        float im[20];
+#ifdef SK_DEBUG
+        // be sure our coversion between matrix44 and colormatrix is perfect
+        matrix44_to_colormatrix(m44, im);
+        SkASSERT(memcmp(m, im, sizeof(im)) == 0);
+#endif
+        SkAssertResult(m44.invert(&im44));
+        matrix44_to_colormatrix(im44, im);
+        dump(im, cs, false);
+    }
+}
diff --git a/src/core/SkYUVMath.h b/src/core/SkYUVMath.h
new file mode 100644
index 0000000..9ecd2c8
--- /dev/null
+++ b/src/core/SkYUVMath.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkYUVMath_DEFINED
+#define SkYUVMath_DEFINED
+
+#include "include/core/SkImageInfo.h"
+
+void SkColorMatrix_RGB2YUV(SkYUVColorSpace, float m[20]);
+void SkColorMatrix_YUV2RGB(SkYUVColorSpace, float m[20]);
+
+// Used to create the pre-compiled tables in SkYUVMath.cpp
+void SkColorMatrix_DumpYUVMatrixTables();
+
+#endif
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index 146e9f5..fb02f34 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -240,6 +240,10 @@
         GrContext* context, SkYUVColorSpace yuvColorSpace, const SkPixmap yuvaPixmaps[],
         const SkYUVAIndex yuvaIndices[4], SkISize imageSize, GrSurfaceOrigin imageOrigin,
         bool buildMips, bool limitToMaxTextureSize, sk_sp<SkColorSpace> imageColorSpace) {
+    if (!context) {
+        return nullptr; // until we impl this for raster backend
+    }
+
     int numPixmaps;
     if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numPixmaps)) {
         return nullptr;
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index ae53f6a..cd0f29a 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -1597,7 +1597,10 @@
             if (CTFontGetGlyphsForCharacters(ctFont, utf16, glyphs, count)) {
                 SkASSERT(glyphs[1] == 0);
                 SkASSERT(glyphs[0] < glyphCount);
-                if (glyphToUnicode[glyphs[0]] == 0) {
+                // CTFontCopyCharacterSet and CTFontGetGlyphsForCharacters seem to add 'support'
+                // for characters 0x9, 0xA, and 0xD mapping them to the glyph for character 0x20?
+                // Prefer mappings to codepoints at or above 0x20.
+                if (glyphToUnicode[glyphs[0]] < 0x20) {
                     glyphToUnicode[glyphs[0]] = codepoint;
                 }
             }
@@ -1605,8 +1608,6 @@
     }
 }
 // Construct Glyph to Unicode table.
-// Unicode code points that require conjugate pairs in utf16 are not
-// supported.
 static void populate_glyph_to_unicode(CTFontRef ctFont, CFIndex glyphCount,
                                       SkUnichar* glyphToUnicode) {
     sk_bzero(glyphToUnicode, sizeof(SkUnichar) * glyphCount);
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index 0d35136..111bebb 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -1889,7 +1889,27 @@
     WORD outGlyphs[maxGlyphs];
     WORD logClust[numWCHAR];
     int numGlyphs;
-    HRZM(ScriptShape(hdc, scriptCache, utf16, numWCHAR, maxGlyphs, &si[0].a,
+    SCRIPT_ANALYSIS& script = si[0].a;
+    script.eScript = SCRIPT_UNDEFINED;
+    script.fRTL = FALSE;
+    script.fLayoutRTL = FALSE;
+    script.fLinkBefore = FALSE;
+    script.fLinkAfter = FALSE;
+    script.fLogicalOrder = FALSE;
+    script.fNoGlyphIndex = FALSE;
+    script.s.uBidiLevel = 0;
+    script.s.fOverrideDirection = 0;
+    script.s.fInhibitSymSwap = TRUE;
+    script.s.fCharShape = FALSE;
+    script.s.fDigitSubstitute = FALSE;
+    script.s.fInhibitLigate = FALSE;
+    script.s.fDisplayZWG = TRUE;
+    script.s.fArabicNumContext = FALSE;
+    script.s.fGcpClusters = FALSE;
+    script.s.fReserved = 0;
+    script.s.fEngineReserved = 0;
+    // For the future, 0x80040200 from here is USP_E_SCRIPT_NOT_IN_FONT
+    HRZM(ScriptShape(hdc, scriptCache, utf16, numWCHAR, maxGlyphs, &script,
                      outGlyphs, logClust, vsa, &numGlyphs),
          "Could not shape character.");
     if (1 == numGlyphs) {
diff --git a/src/sksl/SkSLByteCode.h b/src/sksl/SkSLByteCode.h
index 852b477..fe1474e 100644
--- a/src/sksl/SkSLByteCode.h
+++ b/src/sksl/SkSLByteCode.h
@@ -61,12 +61,18 @@
     VECTOR(kDivideU),
     // Duplicates the top stack value
     VECTOR(kDup),
-    // All kLoad* are followed by a byte indicating the local/global slot to load
+    // Followed by count byte. Duplicates that many values
+    kDupN,
+    // kLoad/kLoadGlobal are followed by a byte indicating the local/global slot to load
     VECTOR(kLoad),
     VECTOR(kLoadGlobal),
-    // As above, then a count byte (1-4), and then one byte per swizzle component (0-3).
+    // As kLoad/kLoadGlobal, then a count byte (1-4), and then one byte per swizzle component (0-3).
     kLoadSwizzle,
     kLoadSwizzleGlobal,
+    // kLoadExtended* are fallback load ops when we lack a specialization. They are followed by a
+    // count byte, and get the slot to load from the top of the stack.
+    kLoadExtended,
+    kLoadExtendedGlobal,
     VECTOR(kNegateF),
     VECTOR(kNegateI),
     VECTOR(kMultiplyF),
@@ -75,6 +81,8 @@
     VECTOR(kOrB),
     VECTOR(kOrI),
     VECTOR(kPop),
+    // Followed by count byte
+    kPopN,
     // Followed by a 32 bit value containing the value to push
     kPushImmediate,
     // Followed by a byte indicating external value to read
@@ -86,14 +94,20 @@
     kReturn,
     VECTOR(kSin),
     VECTOR(kSqrt),
-    // All kStore* are followed by a byte indicating the local/global slot to store
+    // kStore/kStoreGlobal are followed by a byte indicating the local/global slot to store
     VECTOR(kStore),
     VECTOR(kStoreGlobal),
-    // As above, then a count byte (1-4), and then one byte per swizzle component (0-3).
+    // Fallback stores. Followed by count byte, and get the slot to store from the top of the stack
+    kStoreExtended,
+    kStoreExtendedGlobal,
+    // As kStore/kStoreGlobal, then a count byte (1-4), then one byte per swizzle component (0-3).
     // Expects the stack to look like: ... v1 v2 v3 v4, where the number of 'v's is equal to the
     // number of swizzle components. After the store, all v's are popped from the stack.
     kStoreSwizzle,
     kStoreSwizzleGlobal,
+    // As above, but gets the store slot from the top of the stack (before values to be stored)
+    kStoreSwizzleIndirect,
+    kStoreSwizzleIndirectGlobal,
     // Followed by two count bytes (1-4), and then one byte per swizzle component (0-3). The first
     // count byte provides the current vector size (the vector is the top n stack elements), and the
     // second count byte provides the swizzle component count.
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
index bc3aaea..0070674 100644
--- a/src/sksl/SkSLByteCodeGenerator.cpp
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -21,8 +21,23 @@
     fIntrinsics["tan"]  = ByteCodeInstruction::kTan;
 }
 
-static int slot_count(const Type& type) {
-    return type.columns() * type.rows();
+int ByteCodeGenerator::SlotCount(const Type& type) {
+    if (type.kind() == Type::kStruct_Kind) {
+        int slots = 0;
+        for (const auto& f : type.fields()) {
+            slots += SlotCount(*f.fType);
+        }
+        SkASSERT(slots <= 255);
+        return slots;
+    } else if (type.kind() == Type::kArray_Kind) {
+        int columns = type.columns();
+        SkASSERT(columns >= 0);
+        int slots = columns * SlotCount(type.componentType());
+        SkASSERT(slots <= 255);
+        return slots;
+    } else {
+        return type.columns() * type.rows();
+    }
 }
 
 bool ByteCodeGenerator::generateCode() {
@@ -44,11 +59,11 @@
                         continue;
                     }
                     if (declVar->fModifiers.fFlags & Modifiers::kIn_Flag) {
-                        for (int i = slot_count(declVar->fType); i > 0; --i) {
+                        for (int i = SlotCount(declVar->fType); i > 0; --i) {
                             fOutput->fInputSlots.push_back(fOutput->fGlobalCount++);
                         }
                     } else {
-                        fOutput->fGlobalCount += slot_count(declVar->fType);
+                        fOutput->fGlobalCount += SlotCount(declVar->fType);
                     }
                 }
                 break;
@@ -70,7 +85,7 @@
     std::unique_ptr<ByteCodeFunction> result(new ByteCodeFunction(&f.fDeclaration));
     fParameterCount = 0;
     for (const auto& p : f.fDeclaration.fParameters) {
-        fParameterCount += p->fType.columns() * p->fType.rows();
+        fParameterCount += SlotCount(p->fType);
     }
     fCode = &result->fCode;
     this->writeStatement(*f.fBody);
@@ -80,7 +95,7 @@
     result->fLocalCount = fLocals.size();
     const Type& returnType = f.fDeclaration.fReturnType;
     if (returnType != *fContext.fVoid_Type) {
-        result->fReturnCount = returnType.columns() * returnType.rows();
+        result->fReturnCount = SlotCount(returnType);
     }
     fLocals.clear();
     fFunction = nullptr;
@@ -127,7 +142,7 @@
             }
             int result = fParameterCount + fLocals.size();
             fLocals.push_back(&var);
-            for (int i = 0; i < slot_count(var.fType) - 1; ++i) {
+            for (int i = 0; i < SlotCount(var.fType) - 1; ++i) {
                 fLocals.push_back(nullptr);
             }
             SkASSERT(result <= 255);
@@ -140,7 +155,7 @@
                     SkASSERT(offset <= 255);
                     return offset;
                 }
-                offset += slot_count(p->fType);
+                offset += SlotCount(p->fType);
             }
             SkASSERT(false);
             return 0;
@@ -159,7 +174,7 @@
                             SkASSERT(offset <= 255);
                             return offset;
                         }
-                        offset += slot_count(declVar->fType);
+                        offset += SlotCount(declVar->fType);
                     }
                 }
             }
@@ -172,6 +187,62 @@
     }
 }
 
+int ByteCodeGenerator::getLocation(const Expression& expr, Variable::Storage* storage) {
+    switch (expr.fKind) {
+        case Expression::kFieldAccess_Kind: {
+            const FieldAccess& f = (const FieldAccess&)expr;
+            int baseAddr = this->getLocation(*f.fBase, storage);
+            int offset = 0;
+            for (int i = 0; i < f.fFieldIndex; ++i) {
+                offset += SlotCount(*f.fBase->fType.fields()[i].fType);
+            }
+            if (baseAddr < 0) {
+                this->write(ByteCodeInstruction::kPushImmediate);
+                this->write32(offset);
+                this->write(ByteCodeInstruction::kAddI);
+                return -1;
+            } else {
+                return baseAddr + offset;
+            }
+        }
+        case Expression::kIndex_Kind: {
+            const IndexExpression& i = (const IndexExpression&)expr;
+            int stride = SlotCount(i.fType);
+            int offset = -1;
+            if (i.fIndex->isConstant()) {
+                offset = i.fIndex->getConstantInt() * stride;
+            } else {
+                this->writeExpression(*i.fIndex);
+                this->write(ByteCodeInstruction::kPushImmediate);
+                this->write32(stride);
+                this->write(ByteCodeInstruction::kMultiplyI);
+            }
+            int baseAddr = this->getLocation(*i.fBase, storage);
+            if (baseAddr >= 0 && offset >= 0) {
+                return baseAddr + offset;
+            }
+            if (baseAddr >= 0) {
+                this->write(ByteCodeInstruction::kPushImmediate);
+                this->write32(baseAddr);
+            }
+            if (offset >= 0) {
+                this->write(ByteCodeInstruction::kPushImmediate);
+                this->write32(offset);
+            }
+            this->write(ByteCodeInstruction::kAddI);
+            return -1;
+        }
+        case Expression::kVariableReference_Kind: {
+            const Variable& var = ((const VariableReference&)expr).fVariable;
+            *storage = var.fStorage;
+            return this->getLocation(var);
+        }
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
 void ByteCodeGenerator::write8(uint8_t b) {
     fCode->push_back(b);
 }
@@ -193,6 +264,7 @@
 }
 
 static ByteCodeInstruction vector_instruction(ByteCodeInstruction base, int count) {
+    SkASSERT(count >= 1 && count <= 4);
     return ((ByteCodeInstruction) ((int) base + count - 1));
 }
 
@@ -244,7 +316,7 @@
             this->write(ByteCodeInstruction::kDup);
         }
     }
-    int count = slot_count(b.fType);
+    int count = SlotCount(b.fType);
     switch (op) {
         case Token::Kind::EQEQ:
             this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareIEQ,
@@ -364,12 +436,12 @@
     int argumentCount = 0;
     for (const auto& arg : f.fArguments) {
         this->writeExpression(*arg);
-        argumentCount += slot_count(arg->fType);
+        argumentCount += SlotCount(arg->fType);
     }
     this->write(ByteCodeInstruction::kCallExternal);
     SkASSERT(argumentCount <= 255);
     this->write8(argumentCount);
-    this->write8(slot_count(f.fType));
+    this->write8(SlotCount(f.fType));
     int index = fOutput->fExternalValues.size();
     fOutput->fExternalValues.push_back(f.fFunction);
     SkASSERT(index <= 255);
@@ -378,16 +450,32 @@
 
 void ByteCodeGenerator::writeExternalValue(const ExternalValueReference& e) {
     this->write(vector_instruction(ByteCodeInstruction::kReadExternal,
-                                   slot_count(e.fValue->type())));
+                                   SlotCount(e.fValue->type())));
     int index = fOutput->fExternalValues.size();
     fOutput->fExternalValues.push_back(e.fValue);
     SkASSERT(index <= 255);
     this->write8(index);
 }
 
-void ByteCodeGenerator::writeFieldAccess(const FieldAccess& f) {
-    // not yet implemented
-    abort();
+void ByteCodeGenerator::writeVariableExpression(const Expression& expr) {
+    Variable::Storage storage;
+    int location = this->getLocation(expr, &storage);
+    bool isGlobal = storage == Variable::kGlobal_Storage;
+    int count = SlotCount(expr.fType);
+    if (location < 0 || count > 4) {
+        if (location >= 0) {
+            this->write(ByteCodeInstruction::kPushImmediate);
+            this->write32(location);
+        }
+        this->write(isGlobal ? ByteCodeInstruction::kLoadExtendedGlobal
+                             : ByteCodeInstruction::kLoadExtended);
+        this->write8(count);
+    } else {
+        this->write(vector_instruction(isGlobal ? ByteCodeInstruction::kLoadGlobal
+                                                : ByteCodeInstruction::kLoad,
+                                       count));
+        this->write8(location);
+    }
 }
 
 void ByteCodeGenerator::writeFloatLiteral(const FloatLiteral& f) {
@@ -408,7 +496,7 @@
         case ByteCodeInstruction::kTan:
             SkASSERT(c.fArguments.size() == 1);
             this->write((ByteCodeInstruction) ((int) found->second +
-                        slot_count(c.fArguments[0]->fType) - 1));
+                        SlotCount(c.fArguments[0]->fType) - 1));
             break;
         default:
             SkASSERT(false);
@@ -427,11 +515,6 @@
     fCallTargets.emplace_back(this, f.fFunction);
 }
 
-void ByteCodeGenerator::writeIndexExpression(const IndexExpression& i) {
-    // not yet implemented
-    abort();
-}
-
 void ByteCodeGenerator::writeIntLiteral(const IntLiteral& i) {
     this->write(ByteCodeInstruction::kPushImmediate);
     this->write32(i.fValue);
@@ -446,7 +529,7 @@
     switch (p.fOperator) {
         case Token::Kind::PLUSPLUS: // fall through
         case Token::Kind::MINUSMINUS: {
-            SkASSERT(slot_count(p.fOperand->fType) == 1);
+            SkASSERT(SlotCount(p.fOperand->fType) == 1);
             std::unique_ptr<LValue> lvalue = this->getLValue(*p.fOperand);
             lvalue->load();
             this->write(ByteCodeInstruction::kPushImmediate);
@@ -474,7 +557,7 @@
                                         ByteCodeInstruction::kNegateI,
                                         ByteCodeInstruction::kNegateI,
                                         ByteCodeInstruction::kNegateF,
-                                        slot_count(p.fOperand->fType));
+                                        SlotCount(p.fOperand->fType));
             break;
         }
         default:
@@ -486,7 +569,7 @@
     switch (p.fOperator) {
         case Token::Kind::PLUSPLUS: // fall through
         case Token::Kind::MINUSMINUS: {
-            SkASSERT(slot_count(p.fOperand->fType) == 1);
+            SkASSERT(SlotCount(p.fOperand->fType) == 1);
             std::unique_ptr<LValue> lvalue = this->getLValue(*p.fOperand);
             lvalue->load();
             this->write(ByteCodeInstruction::kDup);
@@ -540,14 +623,6 @@
     }
 }
 
-void ByteCodeGenerator::writeVariableReference(const VariableReference& v) {
-    this->write(vector_instruction(v.fVariable.fStorage == Variable::kGlobal_Storage
-                                                                  ? ByteCodeInstruction::kLoadGlobal
-                                                                  : ByteCodeInstruction::kLoad,
-                                   slot_count(v.fType)));
-    this->write8(this->getLocation(v.fVariable));
-}
-
 void ByteCodeGenerator::writeTernaryExpression(const TernaryExpression& t) {
     this->writeExpression(*t.fTest);
     this->write(ByteCodeInstruction::kConditionalBranch);
@@ -578,7 +653,9 @@
             this->writeExternalValue((ExternalValueReference&) e);
             break;
         case Expression::kFieldAccess_Kind:
-            this->writeFieldAccess((FieldAccess&) e);
+        case Expression::kIndex_Kind:
+        case Expression::kVariableReference_Kind:
+            this->writeVariableExpression(e);
             break;
         case Expression::kFloatLiteral_Kind:
             this->writeFloatLiteral((FloatLiteral&) e);
@@ -586,9 +663,6 @@
         case Expression::kFunctionCall_Kind:
             this->writeFunctionCall((FunctionCall&) e);
             break;
-        case Expression::kIndex_Kind:
-            this->writeIndexExpression((IndexExpression&) e);
-            break;
         case Expression::kIntLiteral_Kind:
             this->writeIntLiteral((IntLiteral&) e);
             break;
@@ -604,9 +678,6 @@
         case Expression::kSwizzle_Kind:
             this->writeSwizzle((Swizzle&) e);
             break;
-        case Expression::kVariableReference_Kind:
-            this->writeVariableReference((VariableReference&) e);
-            break;
         case Expression::kTernary_Kind:
             this->writeTernaryExpression((TernaryExpression&) e);
             break;
@@ -620,7 +691,7 @@
 public:
     ByteCodeExternalValueLValue(ByteCodeGenerator* generator, ExternalValue& value, int index)
         : INHERITED(*generator)
-        , fCount(slot_count(value.type()))
+        , fCount(ByteCodeGenerator::SlotCount(value.type()))
         , fIndex(index) {}
 
     void load() override {
@@ -646,22 +717,26 @@
 public:
     ByteCodeSwizzleLValue(ByteCodeGenerator* generator, const Swizzle& swizzle)
         : INHERITED(*generator)
-        , fSwizzle(swizzle) {
-        SkASSERT(fSwizzle.fBase->fKind == Expression::kVariableReference_Kind);
-    }
+        , fSwizzle(swizzle) {}
 
     void load() override {
         fGenerator.writeSwizzle(fSwizzle);
     }
 
     void store() override {
-        const Variable& var = ((VariableReference&)*fSwizzle.fBase).fVariable;
         fGenerator.write(vector_instruction(ByteCodeInstruction::kDup,
                                             fSwizzle.fComponents.size()));
-        fGenerator.write(var.fStorage == Variable::kGlobal_Storage
-                            ? ByteCodeInstruction::kStoreSwizzleGlobal
-                            : ByteCodeInstruction::kStoreSwizzle);
-        fGenerator.write8(fGenerator.getLocation(var));
+        Variable::Storage storage;
+        int location = fGenerator.getLocation(*fSwizzle.fBase, &storage);
+        bool isGlobal = storage == Variable::kGlobal_Storage;
+        if (location < 0) {
+            fGenerator.write(isGlobal ? ByteCodeInstruction::kStoreSwizzleIndirectGlobal
+                                      : ByteCodeInstruction::kStoreSwizzleIndirect);
+        } else {
+            fGenerator.write(isGlobal ? ByteCodeInstruction::kStoreSwizzleGlobal
+                                      : ByteCodeInstruction::kStoreSwizzle);
+            fGenerator.write8(location);
+        }
         fGenerator.write8(fSwizzle.fComponents.size());
         for (int c : fSwizzle.fComponents) {
             fGenerator.write8(c);
@@ -674,36 +749,47 @@
     typedef LValue INHERITED;
 };
 
-class ByteCodeVariableLValue : public ByteCodeGenerator::LValue {
+class ByteCodeExpressionLValue : public ByteCodeGenerator::LValue {
 public:
-    ByteCodeVariableLValue(ByteCodeGenerator* generator, const Variable& var)
+    ByteCodeExpressionLValue(ByteCodeGenerator* generator, const Expression& expr)
         : INHERITED(*generator)
-        , fCount(slot_count(var.fType))
-        , fLocation(generator->getLocation(var))
-        , fIsGlobal(var.fStorage == Variable::kGlobal_Storage) {
-    }
+        , fExpression(expr) {}
 
     void load() override {
-        fGenerator.write(vector_instruction(fIsGlobal ? ByteCodeInstruction::kLoadGlobal
-                                                      : ByteCodeInstruction::kLoad,
-                                            fCount));
-        fGenerator.write8(fLocation);
+        fGenerator.writeVariableExpression(fExpression);
     }
 
     void store() override {
-        fGenerator.write(vector_instruction(ByteCodeInstruction::kDup, fCount));
-        fGenerator.write(vector_instruction(fIsGlobal ? ByteCodeInstruction::kStoreGlobal
-                                                      : ByteCodeInstruction::kStore,
-                                            fCount));
-        fGenerator.write8(fLocation);
+        int count = ByteCodeGenerator::SlotCount(fExpression.fType);
+        if (count > 4) {
+            fGenerator.write(ByteCodeInstruction::kDupN);
+            fGenerator.write8(count);
+        } else {
+            fGenerator.write(vector_instruction(ByteCodeInstruction::kDup, count));
+        }
+        Variable::Storage storage;
+        int location = fGenerator.getLocation(fExpression, &storage);
+        bool isGlobal = storage == Variable::kGlobal_Storage;
+        if (location < 0 || count > 4) {
+            if (location >= 0) {
+                fGenerator.write(ByteCodeInstruction::kPushImmediate);
+                fGenerator.write32(location);
+            }
+            fGenerator.write(isGlobal ? ByteCodeInstruction::kStoreExtendedGlobal
+                                      : ByteCodeInstruction::kStoreExtended);
+            fGenerator.write8(count);
+        } else {
+            fGenerator.write(vector_instruction(isGlobal ? ByteCodeInstruction::kStoreGlobal
+                                                         : ByteCodeInstruction::kStore,
+                                                count));
+            fGenerator.write8(location);
+        }
     }
 
 private:
     typedef LValue INHERITED;
 
-    int fCount;
-    int fLocation;
-    bool fIsGlobal;
+    const Expression& fExpression;
 };
 
 std::unique_ptr<ByteCodeGenerator::LValue> ByteCodeGenerator::getLValue(const Expression& e) {
@@ -715,12 +801,10 @@
             SkASSERT(index <= 255);
             return std::unique_ptr<LValue>(new ByteCodeExternalValueLValue(this, *value, index));
         }
+        case Expression::kFieldAccess_Kind:
         case Expression::kIndex_Kind:
-            // not yet implemented
-            abort();
         case Expression::kVariableReference_Kind:
-            return std::unique_ptr<LValue>(new ByteCodeVariableLValue(this,
-                                                               ((VariableReference&) e).fVariable));
+            return std::unique_ptr<LValue>(new ByteCodeExpressionLValue(this, e));
         case Expression::kSwizzle_Kind:
             return std::unique_ptr<LValue>(new ByteCodeSwizzleLValue(this, (Swizzle&) e));
         case Expression::kTernary_Kind:
@@ -790,7 +874,7 @@
         this->setContinueTargets();
         if (f.fNext) {
             this->writeExpression(*f.fNext);
-            this->write(vector_instruction(ByteCodeInstruction::kPop, slot_count(f.fNext->fType)));
+            this->write(vector_instruction(ByteCodeInstruction::kPop, SlotCount(f.fNext->fType)));
         }
         this->write(ByteCodeInstruction::kBranch);
         this->write16(start);
@@ -800,7 +884,7 @@
         this->setContinueTargets();
         if (f.fNext) {
             this->writeExpression(*f.fNext);
-            this->write(vector_instruction(ByteCodeInstruction::kPop, slot_count(f.fNext->fType)));
+            this->write(vector_instruction(ByteCodeInstruction::kPop, SlotCount(f.fNext->fType)));
         }
         this->write(ByteCodeInstruction::kBranch);
         this->write16(start);
@@ -834,7 +918,7 @@
 void ByteCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
     this->writeExpression(*r.fExpression);
     this->write(ByteCodeInstruction::kReturn);
-    this->write8(r.fExpression->fType.columns() * r.fExpression->fType.rows());
+    this->write8(SlotCount(r.fExpression->fType));
 }
 
 void ByteCodeGenerator::writeSwitchStatement(const SwitchStatement& r) {
@@ -850,9 +934,16 @@
         int location = getLocation(*decl.fVar);
         if (decl.fValue) {
             this->writeExpression(*decl.fValue);
-            this->write(vector_instruction(ByteCodeInstruction::kStore,
-                                           slot_count(decl.fValue->fType)));
-            this->write8(location);
+            int count = SlotCount(decl.fValue->fType);
+            if (count > 4) {
+                this->write(ByteCodeInstruction::kPushImmediate);
+                this->write32(location);
+                this->write(ByteCodeInstruction::kStoreExtended);
+                this->write8(count);
+            } else {
+                this->write(vector_instruction(ByteCodeInstruction::kStore, count));
+                this->write8(location);
+            }
         }
     }
 }
@@ -893,7 +984,13 @@
         case Statement::kExpression_Kind: {
             const Expression& expr = *((ExpressionStatement&) s).fExpression;
             this->writeExpression(expr);
-            this->write(vector_instruction(ByteCodeInstruction::kPop, slot_count(expr.fType)));
+            int count = SlotCount(expr.fType);
+            if (count > 4) {
+                this->write(ByteCodeInstruction::kPopN);
+                this->write8(count);
+            } else {
+                this->write(vector_instruction(ByteCodeInstruction::kPop, count));
+            }
             break;
         }
         case Statement::kFor_Kind:
diff --git a/src/sksl/SkSLByteCodeGenerator.h b/src/sksl/SkSLByteCodeGenerator.h
index 3a2306c..4d16997 100644
--- a/src/sksl/SkSLByteCodeGenerator.h
+++ b/src/sksl/SkSLByteCodeGenerator.h
@@ -96,6 +96,8 @@
     void writeTypedInstruction(const Type& type, ByteCodeInstruction s, ByteCodeInstruction u,
                                ByteCodeInstruction f, int count);
 
+    static int SlotCount(const Type& type);
+
 private:
     // reserves 16 bits in the output code, to be filled in later with an address once we determine
     // it
@@ -171,11 +173,18 @@
      */
     int getLocation(const Variable& var);
 
+    /**
+     * As above, but computes the (possibly dynamic) address of an expression involving indexing &
+     * field access. If the address is known, it's returned. If not, -1 is returned, and the
+     * location will be left on the top of the stack.
+     */
+    int getLocation(const Expression& expr, Variable::Storage* storage);
+
     std::unique_ptr<ByteCodeFunction> writeFunction(const FunctionDefinition& f);
 
     void writeVarDeclarations(const VarDeclarations& decl);
 
-    void writeVariableReference(const VariableReference& ref);
+    void writeVariableExpression(const Expression& expr);
 
     void writeExpression(const Expression& expr);
 
@@ -195,16 +204,12 @@
 
     void writeExternalValue(const ExternalValueReference& r);
 
-    void writeFieldAccess(const FieldAccess& f);
-
     void writeSwizzle(const Swizzle& swizzle);
 
     void writeBinaryExpression(const BinaryExpression& b);
 
     void writeTernaryExpression(const TernaryExpression& t);
 
-    void writeIndexExpression(const IndexExpression& expr);
-
     void writeLogicalAnd(const BinaryExpression& b);
 
     void writeLogicalOr(const BinaryExpression& o);
@@ -268,7 +273,7 @@
     std::unordered_map<String, ByteCodeInstruction> fIntrinsics;
 
     friend class DeferredLocation;
-    friend class ByteCodeVariableLValue;
+    friend class ByteCodeExpressionLValue;
     friend class ByteCodeSwizzleLValue;
 
     typedef CodeGenerator INHERITED;
diff --git a/src/sksl/SkSLInterpreter.cpp b/src/sksl/SkSLInterpreter.cpp
index 13a62b6..c97ecae 100644
--- a/src/sksl/SkSLInterpreter.cpp
+++ b/src/sksl/SkSLInterpreter.cpp
@@ -8,6 +8,7 @@
 #ifndef SKSL_STANDALONE
 
 #include "src/core/SkRasterPipeline.h"
+#include "src/sksl/SkSLByteCodeGenerator.h"
 #include "src/sksl/SkSLExternalValue.h"
 #include "src/sksl/SkSLInterpreter.h"
 #include "src/sksl/ir/SkSLBinaryExpression.h"
@@ -64,8 +65,7 @@
     this->innerRun(f, stack, outReturn);
 
     for (const Variable* p : f.fDeclaration.fParameters) {
-        const int nvalues = p->fType.columns()
-                          * p->fType.rows();
+        const int nvalues = ByteCodeGenerator::SlotCount(p->fType);
         if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
             memcpy(args, stack, nvalues * sizeof(Value));
         }
@@ -85,152 +85,181 @@
 #define READ16() (ip += 2, unaligned_load<uint16_t>(ip - 2))
 #define READ32() (ip += 4, unaligned_load<uint32_t>(ip - 4))
 
-#define VECTOR_DISASSEMBLE(op, text)                               \
+#define VECTOR_DISASSEMBLE(op, text)                          \
     case ByteCodeInstruction::op: printf(text); break;        \
     case ByteCodeInstruction::op##2: printf(text "2"); break; \
     case ByteCodeInstruction::op##3: printf(text "3"); break; \
     case ByteCodeInstruction::op##4: printf(text "4"); break;
 
+static const uint8_t* disassemble_instruction(const uint8_t* ip) {
+    switch ((ByteCodeInstruction) READ16()) {
+        VECTOR_DISASSEMBLE(kAddF, "addf")
+        VECTOR_DISASSEMBLE(kAddI, "addi")
+        case ByteCodeInstruction::kAndB: printf("andb"); break;
+        case ByteCodeInstruction::kAndI: printf("andi"); break;
+        case ByteCodeInstruction::kBranch: printf("branch %d", READ16()); break;
+        case ByteCodeInstruction::kCall: printf("call %d", READ8()); break;
+        case ByteCodeInstruction::kCallExternal: {
+            int argumentCount = READ8();
+            int returnCount = READ8();
+            int externalValue = READ8();
+            printf("callexternal %d, %d, %d", argumentCount, returnCount, externalValue);
+            break;
+        }
+        VECTOR_DISASSEMBLE(kCompareIEQ, "compareieq")
+        VECTOR_DISASSEMBLE(kCompareINEQ, "compareineq")
+        VECTOR_DISASSEMBLE(kCompareFEQ, "comparefeq")
+        VECTOR_DISASSEMBLE(kCompareFNEQ, "comparefneq")
+        VECTOR_DISASSEMBLE(kCompareFGT, "comparefgt")
+        VECTOR_DISASSEMBLE(kCompareFGTEQ, "comparefgteq")
+        VECTOR_DISASSEMBLE(kCompareFLT, "compareflt")
+        VECTOR_DISASSEMBLE(kCompareFLTEQ, "compareflteq")
+        VECTOR_DISASSEMBLE(kCompareSGT, "comparesgt")
+        VECTOR_DISASSEMBLE(kCompareSGTEQ, "comparesgteq")
+        VECTOR_DISASSEMBLE(kCompareSLT, "compareslt")
+        VECTOR_DISASSEMBLE(kCompareSLTEQ, "compareslteq")
+        VECTOR_DISASSEMBLE(kCompareUGT, "compareugt")
+        VECTOR_DISASSEMBLE(kCompareUGTEQ, "compareugteq")
+        VECTOR_DISASSEMBLE(kCompareULT, "compareult")
+        VECTOR_DISASSEMBLE(kCompareULTEQ, "compareulteq")
+        case ByteCodeInstruction::kConditionalBranch:
+            printf("conditionalbranch %d", READ16());
+            break;
+        VECTOR_DISASSEMBLE(kConvertFtoI, "convertftoi")
+        VECTOR_DISASSEMBLE(kConvertStoF, "convertstof")
+        VECTOR_DISASSEMBLE(kConvertUtoF, "convertutof")
+        VECTOR_DISASSEMBLE(kCos, "cos")
+        case ByteCodeInstruction::kDebugPrint: printf("debugprint"); break;
+        VECTOR_DISASSEMBLE(kDivideF, "dividef")
+        VECTOR_DISASSEMBLE(kDivideS, "divideS")
+        VECTOR_DISASSEMBLE(kDivideU, "divideu")
+        VECTOR_DISASSEMBLE(kDup, "dup")
+        case ByteCodeInstruction::kDupN: printf("dupN %d", READ8()); break;
+        case ByteCodeInstruction::kLoad: printf("load %d", READ8()); break;
+        case ByteCodeInstruction::kLoad2: printf("load2 %d", READ8()); break;
+        case ByteCodeInstruction::kLoad3: printf("load3 %d", READ8()); break;
+        case ByteCodeInstruction::kLoad4: printf("load4 %d", READ8()); break;
+        case ByteCodeInstruction::kLoadGlobal: printf("loadglobal %d", READ8()); break;
+        case ByteCodeInstruction::kLoadGlobal2: printf("loadglobal2 %d", READ8()); break;
+        case ByteCodeInstruction::kLoadGlobal3: printf("loadglobal3 %d", READ8()); break;
+        case ByteCodeInstruction::kLoadGlobal4: printf("loadglobal4 %d", READ8()); break;
+        case ByteCodeInstruction::kLoadSwizzle: {
+            int target = READ8();
+            int count = READ8();
+            printf("loadswizzle %d %d", target, count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kLoadSwizzleGlobal: {
+            int target = READ8();
+            int count = READ8();
+            printf("loadswizzleglobal %d %d", target, count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kLoadExtended: printf("loadextended %d", READ8()); break;
+        case ByteCodeInstruction::kLoadExtendedGlobal: printf("loadextendedglobal %d", READ8());
+            break;
+        VECTOR_DISASSEMBLE(kMultiplyF, "multiplyf")
+        VECTOR_DISASSEMBLE(kMultiplyI, "multiplyi")
+        VECTOR_DISASSEMBLE(kNegateF, "negatef")
+        VECTOR_DISASSEMBLE(kNegateI, "negatei")
+        VECTOR_DISASSEMBLE(kNot, "not")
+        VECTOR_DISASSEMBLE(kOrB, "orb")
+        VECTOR_DISASSEMBLE(kOrI, "ori")
+        VECTOR_DISASSEMBLE(kPop, "pop")
+        case ByteCodeInstruction::kPopN: printf("popN %d", READ8()); break;
+        case ByteCodeInstruction::kPushImmediate: {
+            uint32_t v = READ32();
+            union { uint32_t u; float f; } pun = { v };
+            printf("pushimmediate %s", (to_string(v) + "(" + to_string(pun.f) + ")").c_str());
+            break;
+        }
+        case ByteCodeInstruction::kReadExternal: printf("readexternal %d", READ8()); break;
+        case ByteCodeInstruction::kReadExternal2: printf("readexternal2 %d", READ8()); break;
+        case ByteCodeInstruction::kReadExternal3: printf("readexternal3 %d", READ8()); break;
+        case ByteCodeInstruction::kReadExternal4: printf("readexternal4 %d", READ8()); break;
+        VECTOR_DISASSEMBLE(kRemainderF, "remainderf")
+        VECTOR_DISASSEMBLE(kRemainderS, "remainders")
+        VECTOR_DISASSEMBLE(kRemainderU, "remainderu")
+        case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break;
+        VECTOR_DISASSEMBLE(kSin, "sin")
+        VECTOR_DISASSEMBLE(kSqrt, "sqrt")
+        case ByteCodeInstruction::kStore: printf("store %d", READ8()); break;
+        case ByteCodeInstruction::kStore2: printf("store2 %d", READ8()); break;
+        case ByteCodeInstruction::kStore3: printf("store3 %d", READ8()); break;
+        case ByteCodeInstruction::kStore4: printf("store4 %d", READ8()); break;
+        case ByteCodeInstruction::kStoreGlobal: printf("storeglobal %d", READ8()); break;
+        case ByteCodeInstruction::kStoreGlobal2: printf("storeglobal2 %d", READ8()); break;
+        case ByteCodeInstruction::kStoreGlobal3: printf("storeglobal3 %d", READ8()); break;
+        case ByteCodeInstruction::kStoreGlobal4: printf("storeglobal4 %d", READ8()); break;
+        case ByteCodeInstruction::kStoreSwizzle: {
+            int target = READ8();
+            int count = READ8();
+            printf("storeswizzle %d %d", target, count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kStoreSwizzleGlobal: {
+            int target = READ8();
+            int count = READ8();
+            printf("storeswizzleglobal %d %d", target, count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kStoreSwizzleIndirect: {
+            int count = READ8();
+            printf("storeswizzleindirect %d", count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: {
+            int count = READ8();
+            printf("storeswizzleindirectglobal %d", count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        case ByteCodeInstruction::kStoreExtended: printf("storeextended %d", READ8()); break;
+        case ByteCodeInstruction::kStoreExtendedGlobal: printf("storeextendedglobal %d", READ8());
+            break;
+        VECTOR_DISASSEMBLE(kSubtractF, "subtractf")
+        VECTOR_DISASSEMBLE(kSubtractI, "subtracti")
+        case ByteCodeInstruction::kSwizzle: {
+            printf("swizzle %d, ", READ8());
+            int count = READ8();
+            printf("%d", count);
+            for (int i = 0; i < count; ++i) {
+                printf(", %d", READ8());
+            }
+            break;
+        }
+        VECTOR_DISASSEMBLE(kTan, "tan")
+        case ByteCodeInstruction::kWriteExternal: printf("writeexternal %d", READ8()); break;
+        case ByteCodeInstruction::kWriteExternal2: printf("writeexternal2 %d", READ8()); break;
+        case ByteCodeInstruction::kWriteExternal3: printf("writeexternal3 %d", READ8()); break;
+        case ByteCodeInstruction::kWriteExternal4: printf("writeexternal4 %d", READ8()); break;
+        default: printf("unknown(%d)\n", *(ip - 1)); SkASSERT(false);
+    }
+    return ip;
+}
+
 void Interpreter::disassemble(const ByteCodeFunction& f) {
     const uint8_t* ip = f.fCode.data();
     while (ip < f.fCode.data() + f.fCode.size()) {
         printf("%d: ", (int) (ip - f.fCode.data()));
-        switch ((ByteCodeInstruction) READ16()) {
-            VECTOR_DISASSEMBLE(kAddF, "addf")
-            VECTOR_DISASSEMBLE(kAddI, "addi")
-            case ByteCodeInstruction::kAndB: printf("andb"); break;
-            case ByteCodeInstruction::kAndI: printf("andi"); break;
-            case ByteCodeInstruction::kBranch: printf("branch %d", READ16()); break;
-            case ByteCodeInstruction::kCall: printf("call %d", READ8()); break;
-            case ByteCodeInstruction::kCallExternal: {
-                int argumentCount = READ8();
-                int returnCount = READ8();
-                int externalValue = READ8();
-                printf("callexternal %d, %d, %d", argumentCount, returnCount, externalValue);
-                break;
-            }
-            VECTOR_DISASSEMBLE(kCompareIEQ, "compareieq")
-            VECTOR_DISASSEMBLE(kCompareINEQ, "compareineq")
-            VECTOR_DISASSEMBLE(kCompareFEQ, "comparefeq")
-            VECTOR_DISASSEMBLE(kCompareFNEQ, "comparefneq")
-            VECTOR_DISASSEMBLE(kCompareFGT, "comparefgt")
-            VECTOR_DISASSEMBLE(kCompareFGTEQ, "comparefgteq")
-            VECTOR_DISASSEMBLE(kCompareFLT, "compareflt")
-            VECTOR_DISASSEMBLE(kCompareFLTEQ, "compareflteq")
-            VECTOR_DISASSEMBLE(kCompareSGT, "comparesgt")
-            VECTOR_DISASSEMBLE(kCompareSGTEQ, "comparesgteq")
-            VECTOR_DISASSEMBLE(kCompareSLT, "compareslt")
-            VECTOR_DISASSEMBLE(kCompareSLTEQ, "compareslteq")
-            VECTOR_DISASSEMBLE(kCompareUGT, "compareugt")
-            VECTOR_DISASSEMBLE(kCompareUGTEQ, "compareugteq")
-            VECTOR_DISASSEMBLE(kCompareULT, "compareult")
-            VECTOR_DISASSEMBLE(kCompareULTEQ, "compareulteq")
-            case ByteCodeInstruction::kConditionalBranch:
-                printf("conditionalbranch %d", READ16());
-                break;
-            VECTOR_DISASSEMBLE(kConvertFtoI, "convertftoi")
-            VECTOR_DISASSEMBLE(kConvertStoF, "convertstof")
-            VECTOR_DISASSEMBLE(kConvertUtoF, "convertutof")
-            VECTOR_DISASSEMBLE(kCos, "cos")
-            case ByteCodeInstruction::kDebugPrint: printf("debugprint"); break;
-            VECTOR_DISASSEMBLE(kDivideF, "dividef")
-            VECTOR_DISASSEMBLE(kDivideS, "divideS")
-            VECTOR_DISASSEMBLE(kDivideU, "divideu")
-            VECTOR_DISASSEMBLE(kDup, "dup")
-            case ByteCodeInstruction::kLoad: printf("load %d", READ8()); break;
-            case ByteCodeInstruction::kLoad2: printf("load2 %d", READ8()); break;
-            case ByteCodeInstruction::kLoad3: printf("load3 %d", READ8()); break;
-            case ByteCodeInstruction::kLoad4: printf("load4 %d", READ8()); break;
-            case ByteCodeInstruction::kLoadGlobal: printf("loadglobal %d", READ8()); break;
-            case ByteCodeInstruction::kLoadGlobal2: printf("loadglobal2 %d", READ8()); break;
-            case ByteCodeInstruction::kLoadGlobal3: printf("loadglobal3 %d", READ8()); break;
-            case ByteCodeInstruction::kLoadGlobal4: printf("loadglobal4 %d", READ8()); break;
-            case ByteCodeInstruction::kLoadSwizzle: {
-                int target = READ8();
-                int count = READ8();
-                printf("loadswizzle %d %d", target, count);
-                for (int i = 0; i < count; ++i) {
-                    printf(", %d", READ8());
-                }
-                break;
-            }
-            case ByteCodeInstruction::kLoadSwizzleGlobal: {
-                int target = READ8();
-                int count = READ8();
-                printf("loadswizzleglobal %d %d", target, count);
-                for (int i = 0; i < count; ++i) {
-                    printf(", %d", READ8());
-                }
-                break;
-            }
-            VECTOR_DISASSEMBLE(kMultiplyF, "multiplyf")
-            VECTOR_DISASSEMBLE(kMultiplyI, "multiplyi")
-            VECTOR_DISASSEMBLE(kNegateF, "negatef")
-            VECTOR_DISASSEMBLE(kNegateI, "negatei")
-            VECTOR_DISASSEMBLE(kNot, "not")
-            VECTOR_DISASSEMBLE(kOrB, "orb")
-            VECTOR_DISASSEMBLE(kOrI, "ori")
-            VECTOR_DISASSEMBLE(kPop, "pop")
-            case ByteCodeInstruction::kPushImmediate: {
-                uint32_t v = READ32();
-                union { uint32_t u; float f; } pun = { v };
-                printf("pushimmediate %s", (to_string(v) + "(" + to_string(pun.f) + ")").c_str());
-                break;
-            }
-            case ByteCodeInstruction::kReadExternal: printf("readexternal %d", READ8()); break;
-            case ByteCodeInstruction::kReadExternal2: printf("readexternal2 %d", READ8()); break;
-            case ByteCodeInstruction::kReadExternal3: printf("readexternal3 %d", READ8()); break;
-            case ByteCodeInstruction::kReadExternal4: printf("readexternal4 %d", READ8()); break;
-            VECTOR_DISASSEMBLE(kRemainderF, "remainderf")
-            VECTOR_DISASSEMBLE(kRemainderS, "remainders")
-            VECTOR_DISASSEMBLE(kRemainderU, "remainderu")
-            case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break;
-            VECTOR_DISASSEMBLE(kSin, "sin")
-            VECTOR_DISASSEMBLE(kSqrt, "sqrt")
-            case ByteCodeInstruction::kStore: printf("store %d", READ8()); break;
-            case ByteCodeInstruction::kStore2: printf("store2 %d", READ8()); break;
-            case ByteCodeInstruction::kStore3: printf("store3 %d", READ8()); break;
-            case ByteCodeInstruction::kStore4: printf("store4 %d", READ8()); break;
-            case ByteCodeInstruction::kStoreGlobal: printf("storeglobal %d", READ8()); break;
-            case ByteCodeInstruction::kStoreGlobal2: printf("storeglobal2 %d", READ8()); break;
-            case ByteCodeInstruction::kStoreGlobal3: printf("storeglobal3 %d", READ8()); break;
-            case ByteCodeInstruction::kStoreGlobal4: printf("storeglobal4 %d", READ8()); break;
-            case ByteCodeInstruction::kStoreSwizzle: {
-                int target = READ8();
-                int count = READ8();
-                printf("storeswizzle %d %d", target, count);
-                for (int i = 0; i < count; ++i) {
-                    printf(", %d", READ8());
-                }
-                break;
-            }
-            case ByteCodeInstruction::kStoreSwizzleGlobal: {
-                int target = READ8();
-                int count = READ8();
-                printf("storeswizzleglobal %d %d", target, count);
-                for (int i = 0; i < count; ++i) {
-                    printf(", %d", READ8());
-                }
-                break;
-            }
-            VECTOR_DISASSEMBLE(kSubtractF, "subtractf")
-            VECTOR_DISASSEMBLE(kSubtractI, "subtracti")
-            case ByteCodeInstruction::kSwizzle: {
-                printf("swizzle %d, ", READ8());
-                int count = READ8();
-                printf("%d", count);
-                for (int i = 0; i < count; ++i) {
-                    printf(", %d", READ8());
-                }
-                break;
-            }
-            VECTOR_DISASSEMBLE(kTan, "tan")
-            case ByteCodeInstruction::kWriteExternal: printf("writeexternal %d", READ8()); break;
-            case ByteCodeInstruction::kWriteExternal2: printf("writeexternal2 %d", READ8()); break;
-            case ByteCodeInstruction::kWriteExternal3: printf("writeexternal3 %d", READ8()); break;
-            case ByteCodeInstruction::kWriteExternal4: printf("writeexternal4 %d", READ8()); break;
-            default: printf("unknown(%d)\n", *(ip - 1)); SkASSERT(false);
-        }
+        ip = disassemble_instruction(ip);
         printf("\n");
     }
 }
@@ -304,7 +333,9 @@
 
     for (;;) {
 #ifdef TRACE
-        printf("at %d\n", (int) (ip - code));
+        printf("at %3d  ", (int) (ip - code));
+        disassemble_instruction(ip);
+        printf("\n");
 #endif
         ByteCodeInstruction inst = (ByteCodeInstruction) READ16();
         switch (inst) {
@@ -404,6 +435,13 @@
             case ByteCodeInstruction::kDup : PUSH(sp[(int)ByteCodeInstruction::kDup - (int)inst]);
                                              break;
 
+            case ByteCodeInstruction::kDupN: {
+                int count = READ8();
+                memcpy(sp + 1, sp - count + 1, count * sizeof(Value));
+                sp += count;
+                break;
+            }
+
             case ByteCodeInstruction::kLoad4: sp[4] = stack[*ip + 3];
             case ByteCodeInstruction::kLoad3: sp[3] = stack[*ip + 2];
             case ByteCodeInstruction::kLoad2: sp[2] = stack[*ip + 1];
@@ -417,9 +455,27 @@
             case ByteCodeInstruction::kLoadGlobal2: sp[2] = fGlobals[*ip + 1];
             case ByteCodeInstruction::kLoadGlobal : sp[1] = fGlobals[*ip + 0];
                                                     ++ip;
-                                                    sp += (int)inst - (int)ByteCodeInstruction::kLoadGlobal + 1;
+                                                    sp += (int)inst -
+                                                          (int)ByteCodeInstruction::kLoadGlobal + 1;
                                                     break;
 
+            case ByteCodeInstruction::kLoadExtended: {
+                int count = READ8();
+                int src = POP().fSigned;
+                memcpy(sp + 1, &stack[src], count * sizeof(Value));
+                sp += count;
+                break;
+            }
+
+            case ByteCodeInstruction::kLoadExtendedGlobal: {
+                int count = READ8();
+                int src = POP().fSigned;
+                SkASSERT(src + count <= (int) fGlobals.size());
+                memcpy(sp + 1, &fGlobals[src], count * sizeof(Value));
+                sp += count;
+                break;
+            }
+
             case ByteCodeInstruction::kLoadSwizzle: {
                 int src = READ8();
                 int count = READ8();
@@ -466,6 +522,10 @@
             case ByteCodeInstruction::kPop : POP();
                                              break;
 
+            case ByteCodeInstruction::kPopN:
+                sp -= READ8();
+                break;
+
             case ByteCodeInstruction::kPushImmediate:
                 PUSH(READ32());
                 break;
@@ -525,6 +585,22 @@
                                                      ++ip;
                                                      break;
 
+            case ByteCodeInstruction::kStoreExtended: {
+                int count = READ8();
+                int target = POP().fSigned;
+                memcpy(&stack[target], sp - count + 1, count * sizeof(Value));
+                sp -= count;
+                break;
+            }
+            case ByteCodeInstruction::kStoreExtendedGlobal: {
+                int count = READ8();
+                int target = POP().fSigned;
+                SkASSERT(target + count <= (int) fGlobals.size());
+                memcpy(&fGlobals[target], sp - count + 1, count * sizeof(Value));
+                sp -= count;
+                break;
+            }
+
             case ByteCodeInstruction::kStoreSwizzle: {
                 int target = READ8();
                 int count = READ8();
@@ -544,6 +620,24 @@
                 ip += count;
                 break;
             }
+            case ByteCodeInstruction::kStoreSwizzleIndirect: {
+                int target = POP().fSigned;
+                int count = READ8();
+                for (int i = count - 1; i >= 0; --i) {
+                    stack[target + *(ip + i)] = POP();
+                }
+                ip += count;
+                break;
+            }
+            case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: {
+                int target = POP().fSigned;
+                int count = READ8();
+                for (int i = count - 1; i >= 0; --i) {
+                    fGlobals[target + *(ip + i)] = POP();
+                }
+                ip += count;
+                break;
+            }
 
             VECTOR_BINARY_OP(kSubtractI, fSigned, -)
             VECTOR_BINARY_OP(kSubtractF, fFloat, -)
diff --git a/tests/SkSLInterpreterTest.cpp b/tests/SkSLInterpreterTest.cpp
index 2bcd829..26db0bc 100644
--- a/tests/SkSLInterpreterTest.cpp
+++ b/tests/SkSLInterpreterTest.cpp
@@ -348,6 +348,144 @@
     REPORTER_ASSERT(r, out == 5.0f);
 }
 
+DEF_TEST(SkSLInterpreterCompound, r) {
+    struct RectAndColor { SkIRect fRect; SkColor4f fColor; };
+    struct ManyRects { int fNumRects; RectAndColor fRects[4]; };
+
+    const char* src =
+        // Some struct definitions
+        "struct Point { int x; int y; };\n"
+        "struct Rect {  Point p0; Point p1; };\n"
+        "struct RectAndColor { Rect r; float4 color; };\n"
+
+        // Structs as globals, parameters, return values
+        "RectAndColor temp;\n"
+        "int rect_height(Rect r) { return r.p1.y - r.p0.y; }\n"
+        "RectAndColor make_blue_rect(int w, int h) {\n"
+        "  temp.r.p0.x = temp.r.p0.y = 0;\n"
+        "  temp.r.p1.x = w; temp.r.p1.y = h;\n"
+        "  temp.color = float4(0, 1, 0, 1);\n"
+        "  return temp;\n"
+        "}\n"
+
+        // Initialization and assignment of types larger than 4 slots
+        "RectAndColor init_big(RectAndColor r) { RectAndColor s = r; return s; }\n"
+        "RectAndColor copy_big(RectAndColor r) { RectAndColor s; s = r; return s; }\n"
+
+        // Same for arrays, including some non-constant indexing
+        "float tempFloats[8];\n"
+        "int median(int a[15]) { return a[7]; }\n"
+        "float[8] sums(float a[8]) {\n"
+        "  float tempFloats[8];\n"
+        "  tempFloats[0] = a[0];\n"
+        "  for (int i = 1; i < 8; ++i) { tempFloats[i] = tempFloats[i - 1] + a[i]; }\n"
+        "  return tempFloats;\n"
+        "}\n"
+
+        // Uniforms, array-of-structs, dynamic indices
+        "in uniform Rect gRects[4];\n"
+        "Rect get_rect(int i) { return gRects[i]; }\n"
+
+        // Kitchen sink (swizzles, inout, SoAoS)
+        "struct ManyRects { int numRects; RectAndColor rects[4]; };\n"
+        "void fill_rects(inout ManyRects mr) {\n"
+        "  for (int i = 0; i < mr.numRects; ++i) {\n"
+        "    mr.rects[i].r = gRects[i];\n"
+        "    float b = mr.rects[i].r.p1.y;\n"
+        "    mr.rects[i].color = float4(b, b, b, b);\n"
+        "  }\n"
+        "}\n";
+
+    SkSL::Compiler compiler;
+    SkSL::Program::Settings settings;
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                             SkSL::Program::kGeneric_Kind,
+                                                             SkSL::String(src), settings);
+    REPORTER_ASSERT(r, program);
+
+    std::unique_ptr<SkSL::ByteCode> byteCode = compiler.toByteCode(*program);
+    REPORTER_ASSERT(r, !compiler.errorCount());
+
+    auto rect_height    = byteCode->getFunction("rect_height"),
+         make_blue_rect = byteCode->getFunction("make_blue_rect"),
+         median         = byteCode->getFunction("median"),
+         sums           = byteCode->getFunction("sums"),
+         get_rect       = byteCode->getFunction("get_rect"),
+         fill_rects     = byteCode->getFunction("fill_rects");
+
+    SkIRect gRects[4] = { { 1,2,3,4 }, { 5,6,7,8 }, { 9,10,11,12 }, { 13,14,15,16 } };
+
+    SkSL::Interpreter interpreter(std::move(program), std::move(byteCode),
+                                  (SkSL::Interpreter::Value*)gRects);
+
+    {
+        SkIRect in = SkIRect::MakeXYWH(10, 10, 20, 30);
+        int out = 0;
+        interpreter.run(*rect_height,
+                        (SkSL::Interpreter::Value*)&in,
+                        (SkSL::Interpreter::Value*)&out);
+        REPORTER_ASSERT(r, out == 30);
+    }
+
+    {
+        int in[2] = { 15, 25 };
+        RectAndColor out;
+        interpreter.run(*make_blue_rect,
+                        (SkSL::Interpreter::Value*)in,
+                        (SkSL::Interpreter::Value*)&out);
+        REPORTER_ASSERT(r, out.fRect.width() == 15);
+        REPORTER_ASSERT(r, out.fRect.height() == 25);
+        SkColor4f blue = { 0.0f, 1.0f, 0.0f, 1.0f };
+        REPORTER_ASSERT(r, out.fColor == blue);
+    }
+
+    {
+        int in[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+        int out = 0;
+        interpreter.run(*median,
+                        (SkSL::Interpreter::Value*)in,
+                        (SkSL::Interpreter::Value*)&out);
+        REPORTER_ASSERT(r, out == 8);
+    }
+
+    {
+        float in[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+        float out[8] = { 0 };
+        interpreter.run(*sums,
+                        (SkSL::Interpreter::Value*)in,
+                        (SkSL::Interpreter::Value*)out);
+        for (int i = 0; i < 8; ++i) {
+            REPORTER_ASSERT(r, out[i] == static_cast<float>((i + 1) * (i + 2) / 2));
+        }
+    }
+
+    {
+        int in = 2;
+        SkIRect out = SkIRect::MakeEmpty();
+        interpreter.run(*get_rect,
+                        (SkSL::Interpreter::Value*)&in,
+                        (SkSL::Interpreter::Value*)&out);
+        REPORTER_ASSERT(r, out == gRects[2]);
+    }
+
+    {
+        ManyRects in;
+        memset(&in, 0, sizeof(in));
+        in.fNumRects = 2;
+        interpreter.run(*fill_rects,
+                        (SkSL::Interpreter::Value*)&in,
+                        nullptr);
+        ManyRects expected;
+        memset(&expected, 0, sizeof(expected));
+        expected.fNumRects = 2;
+        for (int i = 0; i < 2; ++i) {
+            expected.fRects[i].fRect = gRects[i];
+            float c = gRects[i].fBottom;
+            expected.fRects[i].fColor = { c, c, c, c };
+        }
+        REPORTER_ASSERT(r, memcmp(&in, &expected, sizeof(in)) == 0);
+    }
+}
 
 DEF_TEST(SkSLInterpreterFunctions, r) {
     const char* src =
diff --git a/tests/TypefaceTest.cpp b/tests/TypefaceTest.cpp
index b1c330b..e1dfff6 100644
--- a/tests/TypefaceTest.cpp
+++ b/tests/TypefaceTest.cpp
@@ -10,15 +10,20 @@
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkStream.h"
 #include "include/core/SkTypeface.h"
+#include "include/ports/SkTypeface_win.h"
 #include "include/private/SkFixed.h"
 #include "src/core/SkAdvancedTypefaceMetrics.h"
 #include "src/core/SkFontDescriptor.h"
+#include "src/core/SkFontMgrPriv.h"
+#include "src/core/SkFontPriv.h"
 #include "src/core/SkMakeUnique.h"
 #include "src/core/SkTypefaceCache.h"
 #include "src/sfnt/SkOTTable_OS_2.h"
 #include "src/sfnt/SkSFNTHeader.h"
+#include "src/utils/SkUTF.h"
 #include "tests/Test.h"
 #include "tools/Resources.h"
+#include "tools/ToolUtils.h"
 #include "tools/fonts/TestEmptyTypeface.h"
 
 #include <memory>
@@ -323,3 +328,38 @@
 
 }
 
+DEF_TEST(Typeface_glyph_to_char, reporter) {
+    SkFont font(ToolUtils::emoji_typeface(), 12);
+    SkASSERT(font.getTypeface());
+    char const * text = ToolUtils::emoji_sample_text();
+    size_t const textLen = strlen(text);
+    size_t const codepointCount = SkUTF::CountUTF8(text, textLen);
+    char const * const textEnd = text + textLen;
+    std::unique_ptr<SkUnichar[]> originalCodepoints(new SkUnichar[codepointCount]);
+    for (size_t i = 0; i < codepointCount; ++i) {
+        originalCodepoints[i] = SkUTF::NextUTF8(&text, textEnd);
+    }
+    std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[codepointCount]);
+    font.unicharsToGlyphs(originalCodepoints.get(), codepointCount, glyphs.get());
+
+    std::unique_ptr<SkUnichar[]> newCodepoints(new SkUnichar[codepointCount]);
+    SkFontPriv::GlyphsToUnichars(font, glyphs.get(), codepointCount, newCodepoints.get());
+
+    SkString familyName;
+    font.getTypeface()->getFamilyName(&familyName);
+    for (size_t i = 0; i < codepointCount; ++i) {
+#if defined(SK_BUILD_FOR_WIN)
+        // GDI does not support character to glyph mapping outside BMP.
+        if (gSkFontMgr_DefaultFactory == &SkFontMgr_New_GDI &&
+            0xFFFF < originalCodepoints[i] && newCodepoints[i] == 0)
+        {
+            continue;
+        }
+#endif
+        // If two codepoints map to the same glyph then this assert is not valid.
+        // However, the emoji test font should never have multiple characters map to the same glyph.
+        REPORTER_ASSERT(reporter, originalCodepoints[i] == newCodepoints[i],
+                        "name:%s i:%d original:%d new:%d glyph:%d", familyName.c_str(), i,
+                        originalCodepoints[i], newCodepoints[i], glyphs[i]);
+    }
+}
diff --git a/tests/YUVTest.cpp b/tests/YUVTest.cpp
index 644450d..0180f07 100644
--- a/tests/YUVTest.cpp
+++ b/tests/YUVTest.cpp
@@ -124,3 +124,42 @@
     // A PNG should fail.
     codec_yuv(r, "images/arrow.png", nullptr);
 }
+
+#include "include/effects/SkColorMatrix.h"
+#include "src/core/SkYUVMath.h"
+
+// Be sure that the two matrices are inverses of each other
+// (i.e. rgb2yuv and yuv2rgb
+DEF_TEST(YUVMath, reporter) {
+    const SkYUVColorSpace spaces[] = {
+        kJPEG_SkYUVColorSpace,
+        kRec601_SkYUVColorSpace,
+        kRec709_SkYUVColorSpace,
+        kIdentity_SkYUVColorSpace,
+    };
+
+    // Not sure what the theoretical precision we can hope for is, so pick a big value that
+    // passes (when I think we're correct).
+    const float tolerance = 1.0f/(1 << 18);
+
+    for (auto cs : spaces) {
+        float r2y[20], y2r[20];
+        SkColorMatrix_RGB2YUV(cs, r2y);
+        SkColorMatrix_YUV2RGB(cs, y2r);
+
+        SkColorMatrix r2ym, y2rm;
+        r2ym.setRowMajor(r2y);
+        y2rm.setRowMajor(y2r);
+        r2ym.postConcat(y2rm);
+
+        float tmp[20];
+        r2ym.getRowMajor(tmp);
+        for (int i = 0; i < 20; ++i) {
+            float expected = 0;
+            if (i % 6 == 0) {   // diagonal
+                expected = 1;
+            }
+            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tmp[i], expected, tolerance));
+        }
+    }
+}