@@ -1363,6 +1363,32 @@ inline size_t AdvanceToNextTagX86Optimized(const uint8_t** ip_p, size_t* tag) {
13631363 return tag_type;
13641364}
13651365
1366+ SNAPPY_ATTRIBUTE_ALWAYS_INLINE
1367+ inline size_t AdvanceToNextTagRVOptimized (const uint8_t ** ip_p, size_t * tag) {
1368+ const uint8_t *& ip = *ip_p;
1369+ // This section is crucial for the throughput of the decompression loop.
1370+ // The latency of an iteration is fundamentally constrained by the data chain on ip:
1371+ // ip -> c = *tag -> literal_len = c >> 2, tag_type = c & 3
1372+ // -> literal_advance = literal_len + 2, copy_advance = tag_type + 1
1373+ // -> next_ip = ip + literal_advance OR ip + copy_advance (literal vs copy)
1374+ // -> *tag = byte at (next_ip - 1); ip = next_ip
1375+ //
1376+ // Base RISC-V has no x86-style cmov and no AArch64 csinc on the same shape; this
1377+ // computes both candidate advances and both load offsets, then selects with
1378+ // (is_literal ? ... : ...). With the Zicond extension (czero.eqz / czero.nez), those
1379+ // selections typically lower to branchless conditional-zero ops instead of a
1380+ // hard-to-predict literal/copy branch, which is why this form tends to win there.
1381+ const size_t literal_len = *tag >> 2 ;
1382+ const size_t tag_type = *tag & 3 ;
1383+ const bool is_literal = (tag_type == 0 );
1384+ const size_t copy_advance = tag_type + 1 ;
1385+ const size_t literal_advance = literal_len + 2 ;
1386+ const uint8_t * next_ip = is_literal ? (ip + literal_advance) : (ip + copy_advance);
1387+ *tag = is_literal ? ip[literal_advance - 1 ] : ip[copy_advance - 1 ];
1388+ ip = next_ip;
1389+ return tag_type;
1390+ }
1391+
13661392// Extract the offset for copy-1 and copy-2 returns 0 for literals or copy-4.
13671393inline uint32_t ExtractOffset (uint32_t val, size_t tag_type) {
13681394 // For x86 non-static storage works better. For ARM static storage is better.
@@ -1439,6 +1465,11 @@ std::pair<const uint8_t*, ptrdiff_t> DecompressBranchless(
14391465 // We never need more than 16 bits. Doing a Load16 allows the compiler
14401466 // to elide the masking operation in ExtractOffset.
14411467 next = LittleEndian::Load16 (old_ip);
1468+ #elif defined(__riscv)
1469+ size_t tag_type = AdvanceToNextTagRVOptimized (&ip, &tag);
1470+ // We never need more than 16 bits. Doing a Load16 allows the compiler
1471+ // to elide the masking operation in ExtractOffset.
1472+ next = LittleEndian::Load16 (old_ip);
14421473#else
14431474 size_t tag_type = AdvanceToNextTagX86Optimized (&ip, &tag);
14441475 next = LittleEndian::Load32 (old_ip);
0 commit comments