[skjson] More short string micro-optimizations

When possible, load 8 chars and mask out the tag and \0 terminator.

~19% faster on a Z840, ~5% faster on a PixelC.

Change-Id: I12d4b7d86c92c887b00f5d2480d3ff05a7042df1
Reviewed-on: https://skia-review.googlesource.com/135624
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/modules/skjson/src/SkJSON.cpp b/modules/skjson/src/SkJSON.cpp
index 23a3d14..2548f4e 100644
--- a/modules/skjson/src/SkJSON.cpp
+++ b/modules/skjson/src/SkJSON.cpp
@@ -109,25 +109,73 @@
 //
 // The string data plus a null-char terminator are copied over.
 //
-StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) {
+namespace {
+
+// An internal string builder with a fast 8 byte short string load path
+// (for the common case where the string is not at the end of the stream).
+class FastString final : public Value {
+public:
+    FastString(const char* src, size_t size, const char* eos, SkArenaAlloc& alloc) {
+        SkASSERT(src <= eos);
+
+        if (size > kMaxInlineStringSize) {
+            this->initLongString(src, size, alloc);
+            SkASSERT(this->getTag() == Tag::kString);
+            return;
+        }
+
+        static_assert(static_cast<uint8_t>(Tag::kShortString) == 0, "please don't break this");
+        static_assert(sizeof(Value) == 8, "");
+
+        // TODO: LIKELY
+        if (src + 7 <= eos) {
+            this->initFastShortString(src, size);
+        } else {
+            this->initShortString(src, size);
+        }
+
+        SkASSERT(this->getTag() == Tag::kShortString);
+    }
+
+private:
     static constexpr size_t kMaxInlineStringSize = sizeof(Value) - 1;
-    if (size > kMaxInlineStringSize) {
+
+    void initLongString(const char* src, size_t size, SkArenaAlloc& alloc) {
+        SkASSERT(size > kMaxInlineStringSize);
+
         this->init_tagged_pointer(Tag::kString, MakeVector<char, 1>(src, size, alloc));
 
         auto* data = this->cast<VectorValue<char, Value::Type::kString>>()->begin();
         const_cast<char*>(data)[size] = '\0';
-        SkASSERT(this->getTag() == Tag::kString);
-        return;
     }
 
-    this->init_tagged(Tag::kShortString);
-    sk_careful_memcpy(this->cast<char>(), src, size);
+    void initShortString(const char* src, size_t size) {
+        SkASSERT(size <= kMaxInlineStringSize);
 
-    // Null terminator provided by init_tagged() above (fData8 is zero-initialized).
-    // This is safe because kShortString is also 0 and can act as a terminator when size == 7.
-    static_assert(static_cast<uint8_t>(Tag::kShortString) == 0, "please don't break this");
+        this->init_tagged(Tag::kShortString);
+        sk_careful_memcpy(this->cast<char>(), src, size);
+        // Null terminator provided by init_tagged() above (fData8 is zero-initialized).
+    }
 
-    SkASSERT(this->getTag() == Tag::kShortString);
+    void initFastShortString(const char* src, size_t size) {
+        SkASSERT(size <= kMaxInlineStringSize);
+
+        // Load 8 chars and mask out the tag and \0 terminator.
+        uint64_t* s64 = this->cast<uint64_t>();
+        memcpy(s64, src, 8);
+
+#if defined(SK_CPU_LENDIAN)
+        *s64 &= 0x00ffffffffffffffULL >> ((kMaxInlineStringSize - size) * 8);
+#else
+        static_assert(false, "Big-endian builds are not supported at this time.");
+#endif
+    }
+};
+
+} // namespace
+
+StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) {
+    new (this) FastString(src, size, src, alloc);
 }
 
 ObjectValue::ObjectValue(const Member* src, size_t size, SkArenaAlloc& alloc) {
@@ -269,8 +317,8 @@
         p = skip_ws(p);
         if (*p != '"') return this->error(NullValue(), p, "expected object key");
 
-        p = this->matchString(p, p_stop, [this](const char* key, size_t size) {
-            this->pushObjectKey(key, size);
+        p = this->matchString(p, p_stop, [this](const char* key, size_t size, const char* eos) {
+            this->pushObjectKey(key, size, eos);
         });
         if (!p) return NullValue();
 
@@ -287,8 +335,8 @@
         case '\0':
             return this->error(NullValue(), p, "unexpected input end");
         case '"':
-            p = this->matchString(p, p_stop, [this](const char* str, size_t size) {
-                this->pushString(str, size);
+            p = this->matchString(p, p_stop, [this](const char* str, size_t size, const char* eos) {
+                this->pushString(str, size, eos);
             });
             break;
         case '[':
@@ -469,11 +517,11 @@
         )
     }
 
-    void pushObjectKey(const char* key, size_t size) {
+    void pushObjectKey(const char* key, size_t size, const char* eos) {
         SkASSERT(fScopeStack.back() >= 0);
         SkASSERT(fValueStack.size() >= SkTo<size_t>(fScopeStack.back()));
         SkASSERT(!((fValueStack.size() - SkTo<size_t>(fScopeStack.back())) & 1));
-        this->pushString(key, size);
+        this->pushString(key, size, eos);
     }
 
     void pushTrue() {
@@ -488,8 +536,8 @@
         fValueStack.push_back(NullValue());
     }
 
-    void pushString(const char* s, size_t size) {
-        fValueStack.push_back(StringValue(s, size, fAlloc));
+    void pushString(const char* s, size_t size, const char* eos) {
+        fValueStack.push_back(FastString(s, size, eos, fAlloc));
     }
 
     void pushInt32(int32_t i) {
@@ -555,7 +603,7 @@
 
             if (*p == '"') {
                 // Valid string found.
-                func(s_begin, p - s_begin);
+                func(s_begin, p - s_begin, p_stop);
                 return p + 1;
             }