Repository: sabnzbd/sabctools (language: python)

Issue:
Incorrect CRC32 failure
@animetosho @mnightingale sorry to bother you again for help, but I am not sure how to fix this.

A user reported SABnzbd failing an NZB due to article CRC errors, while another downloader succeeded.
When I disable the article CRC32 handling in SABnzbd, indeed the download succeeds and verifies using par2, so `sabctools` seems wrong.
I _think_ it has to do with the formatting of the CRC32:
```
=yend size=409600 part=1 pcrc32=ffffffff79b5066a
```
If I use the Python verification of the decoded content it produces `79b5066a`, so it matches, minus the `ffffffff`.

I added a test-case in this branch:
https://github.com/sabnzbd/sabctools/commit/a2eddde4c19f7dcde1c36439820d4f9a94d8b565

How do we handle that in `sabctools`?


Discussion / hints:
`ffffffff79b5066a` isn't a valid CRC32, so failing seems sensible to me.

It's not obvious to me how that CRC32 was produced.  
Given that the value is a 64-bit number, a thought could be that the client mistakenly sign-extended the CRC32 to 64-bit, and serialised it as such. However, it doesn't quite seem right, given the first hex digit (7) is < 8.  

CRC32 does require a bit flip at the end; perhaps the client computed the CRC32 correctly, but performed the bit flip as a 64-bit integer, without truncating back to 32-bit.

	crc: int64 = ~0                  # initial CRC
	crc = compute_crc32(crc, data)  # computes valid CRC32
	crc = ~crc                      # final bit flip; not correctly truncated to 32 bit because this is an int64 type

If you really do want to make this work, truncating to the last 8 hex digits seems to be the most sensible (and padding the left with 0s if <8 digits), though when you're dealing with invalid values, it's anyone's guess what is correct.
Can you do a head request and see if it identifies the poster tool?

I agree with the above and I'd guess it should be made to work because the posts are out there now and will probably continue to be posted in this broken way.

To start with I'd find the end of crc32=  and do something like https://gist.github.com/mnightingale/5628136d1016d47ae405b52c684d86cb to take the last 8 characters.

I needed to define YENC_SP (space = 0x20) to allow the new and I think another test to pass, I'm not sure if the original had a space at the end but I think it's worth allowing anyway.

I was confused with another broken test, test_crc_pickles and test_small_file_pickles should probably glob `crc_*.pickle` etc. it is picking up your new .yenc file.

We could add support for < 8 characters but that seems unlikely compared to recieving exactly 8 or 16, but the yEnc specification is quite unspecific about what to expect.
> the yEnc specification is quite unspecific about what to expect

In general, the specification is rather horrible. It doesn't even clarify that the CRC32 should be hex encoded; one could guess the intention from the examples provided, but a good specification would clarify without making one guess like this.  
It also doesn't specify which CRC32 polynomial to use, though I could maybe forgive them based on when the document was written.

I've always wondered about fixing the spec, but it's been that way for >20 years and wouldn't really change anything now.
@mnightingale example article-ID is: `0097HW.2A714583000502E630DE1A38F@news.newsleecher.com`, I wasn't able to do a `HEAD` myself.
Header dump of that article:

	Path: not-for-mail
	From: Bismarck <bnwink@hotmail.com>
	User-Agent: Hogwasher/5.24
	Message-ID: <0097HW.2A714583000502E630DE1A38F@news.newsleecher.com>
	Subject: Stackridge The Man In The Bowler Hat 1973 - "103 The Last Plimsoll.flac" yEnc (13/69) [27427K]
	Newsgroups: alt.binaries.sounds.lossless
	Reply-To: Bismarck
	Lines: 3277
	X-Complaints-To: http://www.newsleecher.com/support/
	NNTP-Posting-Date: Wed, 26 Jul 2023 12:15:11 UTC
	Bytes: 426075
	Date: Wed, 26 Jul 2023 07:15:11 -0500
	X-Received-Bytes: 426218
	
	=ybegin part=13 total=69 line=128 size=28084872 name=103 The Last Plimsoll.flac
	=ypart begin=4915201 end=5324800
	
	[yEnc body]
	
	=yend size=409600 part=13 pcrc32=ffffffff13997c4c

Homepage of the mentioned user agent if you want to try it: https://www.asar.com/hogwasher.html  
Or you can try [submitting a support request](https://www.asar.com/request_hw.html).
Just to add to what @animetosho found, I'd mention the following

- 64 bit representation of CRC32
- yend line ends **200D0A**0D0A2E0D0A rather than 0D0A2E0D0A (extra " \r\n" - "0x20\r\n")
Nice finds!

The start of the yEnc body looks fine to me?

![dump](https://github.com/sabnzbd/sabctools/assets/12937885/edab8597-25c5-4684-8eb3-35a378699f7f)

The end is a bit weird, though presumably acceptable for yEnc.

Looking at the code, I see what the problem is - the CRC32 is sign extended to 64-bit *before* the bit flip, where the bit flip is only performed on the bottom 32 bits, leaving the top 32 bits as all 1s.

![disasm](https://github.com/sabnzbd/sabctools/assets/12937885/5dba5203-4d3e-4c6d-b0ba-d83c2ffa6748)

(side observation: the fact that the size and part number are also sign extended, suggests that this client would have trouble with files over 2GB or more than 32767 parts)
Strange, not sure what happened there; I'm just seeing "\r\n\r\n" as expected now.

The yend space isn't too important but I can see it breaking other clients if they are not handling the possibility of another key/value after crc32.
So what can we do about it, without allowing too much non-standard stuff?
Still @mnightingale's patch? 
I would say both.

- The patch in SAB to solve it now. The posts based on Hogwasher/5.24 will be there until retention ends (10 years from now)
- Report a bug to Hogwasher/5.24 maker ... and see what happens ...

Sounds good. Could you submit a PR @mnightingale?
Sure I'll take a look this evening
I've filed a bug report with Asar / Hogwash. Webpage says:

```
We have received your request.
We will review your submission and contact you via email if your submission type was “Need Assistance”. If this was a bug or crash report, we will investigate the problem and contact you if further clarification is needed.
```

And now we wait ...

(No ticket number, no email confirmation)
Thanks all for the help!

Fix patch:
diff --git a/setup.py b/setup.py
index 68ea2a9..d00f9d5 100644
--- a/setup.py
+++ b/setup.py
@@ -84,8 +84,12 @@ def build_extension(self, ext: Extension):
         # Determine compiler flags
         gcc_arm_neon_flags = []
         gcc_arm_crc_flags = []
+        gcc_arm_crc_pmull_flags = []
         gcc_vpclmulqdq_flags = []
         gcc_vbmi2_flags = []
+        gcc_avx10_flags = []
+        gcc_rvv_flags = []
+        gcc_rvzbkc_flags = []
         gcc_macros = []
         if self.compiler.compiler_type == "msvc":
             # LTCG not enabled due to issues seen with code generation where
@@ -132,9 +136,11 @@ def build_extension(self, ext: Extension):
                     IS_AARCH64 = False
                 if autoconf_check(self.compiler, flag_check="-march=armv8-a+crc"):
                     gcc_arm_crc_flags.append("-march=armv8-a+crc")
+                    gcc_arm_crc_pmull_flags.append("-march=armv8-a+crc+crypto")
                     # Resolve problems on armv7, see issue #56
                     if not IS_AARCH64:
                         gcc_arm_crc_flags.append("-fno-lto")
+                        gcc_arm_crc_pmull_flags.append("-fno-lto")
                 if not IS_AARCH64 and autoconf_check(self.compiler, flag_check="-mfpu=neon"):
                     gcc_arm_neon_flags.append("-mfpu=neon")
                     # Resolve problems on armv7, see issue #56
@@ -165,6 +171,16 @@ def build_extension(self, ext: Extension):
                     "-mlzcnt",
                 ]
 
+            if IS_X86 and autoconf_check(self.compiler, flag_check="-mno-evex512"):
+                gcc_avx10_flags = ["-mno-evex512"]
+
+            if machine.startswith("riscv"):
+                arch_flag = "-march=rv" + ("32" if machine.startswith("riscv32") else "64") + "gc"
+                if autoconf_check(self.compiler, flag_check=arch_flag+"v"):
+                    gcc_rvv_flags = [arch_flag+"v"]
+                if autoconf_check(self.compiler, flag_check=arch_flag+"_zbkc"):
+                    gcc_rvzbkc_flags = [arch_flag+"_zbkc"]
+
         srcdeps_crc_common = ["src/yencode/common.h", "src/yencode/crc_common.h", "src/yencode/crc.h"]
         srcdeps_dec_common = ["src/yencode/common.h", "src/yencode/decoder_common.h", "src/yencode/decoder.h"]
         srcdeps_enc_common = ["src/yencode/common.h", "src/yencode/encoder_common.h", "src/yencode/encoder.h"]
@@ -240,13 +256,13 @@ def build_extension(self, ext: Extension):
             {
                 "sources": ["src/yencode/encoder_vbmi2.cc"],
                 "depends": srcdeps_enc_common + ["encoder_avx_base.h"],
-                "gcc_x86_flags": gcc_vbmi2_flags,
+                "gcc_x86_flags": gcc_vbmi2_flags + gcc_avx10_flags,
                 "msvc_x86_flags": ["/arch:AVX512"],
             },
             {
                 "sources": ["src/yencode/decoder_vbmi2.cc"],
                 "depends": srcdeps_dec_common + ["decoder_avx2_base.h"],
-                "gcc_x86_flags": gcc_vbmi2_flags,
+                "gcc_x86_flags": gcc_vbmi2_flags + gcc_avx10_flags,
                 "msvc_x86_flags": ["/arch:AVX512"],
             },
             {
@@ -264,6 +280,21 @@ def build_extension(self, ext: Extension):
                 "depends": srcdeps_crc_common,
                 "gcc_arm_flags": gcc_arm_crc_flags,
             },
+            {
+                "sources": ["src/yencode/crc_arm_pmull.cc"],
+                "depends": srcdeps_crc_common,
+                "gcc_arm_flags": gcc_arm_crc_pmull_flags,
+            },
+            {
+                "sources": ["src/yencode/encoder_rvv.cc", "src/yencode/decoder_rvv.cc"],
+                "depends": srcdeps_enc_common + srcdeps_dec_common,
+                "gcc_rv_flags": gcc_rvv_flags,
+            },
+            {
+                "sources": ["src/yencode/crc_riscv.cc"],
+                "depends": srcdeps_crc_common,
+                "gcc_rv_flags": gcc_rvzbkc_flags,
+            },
             {
                 "sources": [
                     "src/crcutil-1.0/code/crc32c_sse4.cc",
diff --git a/src/crc32.cc b/src/crc32.cc
index d47aa31..212b60d 100644
--- a/src/crc32.cc
+++ b/src/crc32.cc
@@ -17,68 +17,66 @@
  */
 
 #include "crc32.h"
-#include "crcutil-1.0/examples/interface.h"
-
-extern crcutil_interface::CRC *crc;
+#include "yencode/crc.h"
 
 PyObject* crc32_combine(PyObject *self, PyObject *args) {
-    crcutil_interface::UINT64 crc1, crc2;
+    unsigned long crc1, crc2;
     unsigned long long length;
 
-    if(!PyArg_ParseTuple(args, "KKK:crc32_combine", &crc1, &crc2, &length)) {
+    if(!PyArg_ParseTuple(args, "kkK:crc32_combine", &crc1, &crc2, &length)) {
         return NULL;
     }
 
-    crc->Concatenate(crc2, 0, length, &crc1);
+    crc1 = RapidYenc::crc32_combine(crc1, crc2, length);
 
-    return PyLong_FromUnsignedLong((uint32_t) crc1);
+    return PyLong_FromUnsignedLong(crc1);
 }
 
 PyObject* crc32_multiply(PyObject *self, PyObject *args) {
-    crcutil_interface::UINT64 crc1, crc2;
+    unsigned long crc1, crc2;
 
-    if(!PyArg_ParseTuple(args, "KK:crc32_multiply", &crc1, &crc2)) {
+    if(!PyArg_ParseTuple(args, "kk:crc32_multiply", &crc1, &crc2)) {
         return NULL;
     }
 
-    crc->Multiply(crc2, &crc1);
+    crc1 = RapidYenc::crc32_multiply(crc1, crc2);
 
-    return PyLong_FromUnsignedLong((uint32_t)crc1);
+    return PyLong_FromUnsignedLong(crc1);
 }
 
 PyObject* crc32_zero_unpad(PyObject *self, PyObject *args) {
-    crcutil_interface::UINT64 crc1;
+    unsigned long crc1;
     unsigned long long length;
 
-    if(!PyArg_ParseTuple(args, "KK:crc32_zero_unpad", &crc1, &length)) {
+    if(!PyArg_ParseTuple(args, "kK:crc32_zero_unpad", &crc1, &length)) {
         return NULL;
     }
 
-    crc->ZeroUnpad(length, &crc1);
+    crc1 = RapidYenc::crc32_unzero(crc1, length);
 
-    return PyLong_FromUnsignedLong((uint32_t) crc1);
+    return PyLong_FromUnsignedLong(crc1);
 }
 
 PyObject* crc32_xpown(PyObject* self, PyObject* arg) {
-    crcutil_interface::UINT64 n = PyLong_AsUnsignedLongLong(arg) % 0xffffffff;
+    long long n = PyLong_AsLongLong(arg);
 
     if (PyErr_Occurred()) {
         return NULL;
     }
 
-    crc->XpowN(&n);
+    unsigned long result = RapidYenc::crc32_2pow(n);
 
-    return PyLong_FromUnsignedLong(n);
+    return PyLong_FromUnsignedLong(result);
 }
 
 PyObject* crc32_xpow8n(PyObject* self, PyObject* arg) {
-    crcutil_interface::UINT64 n = PyLong_AsUnsignedLongLong(arg) % 0xffffffff;
+    unsigned long long n = PyLong_AsUnsignedLongLong(arg);
 
     if (PyErr_Occurred()) {
         return NULL;
     }
 
-    crc->Xpow8N(&n);
+    unsigned long result = RapidYenc::crc32_256pow(n);
 
-    return PyLong_FromUnsignedLong(n);
+    return PyLong_FromUnsignedLong(result);
 }
diff --git a/src/sabctools.cc b/src/sabctools.cc
index 6135a1c..6a4ae4f 100644
--- a/src/sabctools.cc
+++ b/src/sabctools.cc
@@ -99,12 +99,46 @@ static struct PyModuleDef sabctools_definition = {
     sabctools_methods
 };
 
+static const char* simd_detected(void) {
+    int level = RapidYenc::decode_isa_level();
+#ifdef PLATFORM_X86
+    if(level >= ISA_LEVEL_VBMI2)
+        return "AVX512VL+VBMI2";
+    if(level >= ISA_LEVEL_AVX3)
+        return "AVX512VL";
+    if(level >= ISA_LEVEL_AVX2)
+        return "AVX2";
+    if(level >= ISA_LEVEL_AVX)
+        return "AVX";
+    if(level >= ISA_LEVEL_SSE4_POPCNT)
+        return "SSE4.1+POPCNT";
+    if(level >= ISA_LEVEL_SSE41)
+        return "SSE4.1";
+    if(level >= ISA_LEVEL_SSSE3)
+        return "SSSE3";
+    if(level >= (ISA_LEVEL_SSE2 | ISA_FEATURE_POPCNT | ISA_FEATURE_LZCNT))
+        return "SSE2+ABM";
+    return "SSE2";
+#endif
+#ifdef PLATFORM_ARM
+    if(level >= ISA_LEVEL_NEON) {
+        return "NEON";
+    }
+#endif
+#ifdef __riscv
+    if(level >= ISA_LEVEL_RVV) {
+        return "RVV";
+    }
+#endif
+    return "";
+}
+
 PyMODINIT_FUNC PyInit_sabctools(void) {
     // Initialize and add version / SIMD information
     Py_Initialize();
-    encoder_init();
-    decoder_init();
-    crc_init();
+    RapidYenc::encoder_init();
+    RapidYenc::decoder_init();
+    RapidYenc::crc32_init();
     openssl_init();
     sparse_init();
 
diff --git a/src/yenc.cc b/src/yenc.cc
index 1604f21..2e24a98 100644
--- a/src/yenc.cc
+++ b/src/yenc.cc
@@ -215,9 +215,9 @@ PyObject* yenc_decode(PyObject* self, PyObject* Py_memoryview_obj) {
     Py_BEGIN_ALLOW_THREADS;
 
     // send to decoder
-    YencDecoderState state = YDEC_STATE_CRLF;
-    output_len = do_decode(1, (unsigned char*) start_loc, (unsigned char*) dest_loc, yenc_data_length, &state);
-    crc = do_crc32(dest_loc, output_len, crc);
+    RapidYenc::YencDecoderState state = RapidYenc::YDEC_STATE_CRLF;
+    output_len = RapidYenc::decode(1, start_loc, dest_loc, yenc_data_length, &state);
+    crc = RapidYenc::crc32(dest_loc, output_len, crc);
 
     // Return GIL to perform Python modifications
     Py_END_ALLOW_THREADS;
@@ -289,8 +289,8 @@ PyObject* yenc_encode(PyObject* self, PyObject* Py_input_string)
 
     // Encode result
     int column = 0;
-    output_len = do_encode(YENC_LINESIZE, &column, (unsigned char*)input_buffer, (unsigned char*)output_buffer, input_len, 1);
-    crc = do_crc32(input_buffer, input_len, 0);
+    output_len = RapidYenc::encode(YENC_LINESIZE, &column, input_buffer, output_buffer, input_len, 1);
+    crc = RapidYenc::crc32(input_buffer, input_len, 0);
 
     // Restore GIL so we can build Python strings
     Py_END_ALLOW_THREADS;
diff --git a/src/yencode/common.h b/src/yencode/common.h
index e4fd489..90423eb 100644
--- a/src/yencode/common.h
+++ b/src/yencode/common.h
@@ -9,7 +9,7 @@
     defined(_M_X64    ) || \
     defined(_M_AMD64  ) || \
     (defined(_WIN64) && !defined(_M_ARM64))
-#define PLATFORM_AMD64 1
+	#define PLATFORM_AMD64 1
 #endif
 #if defined(PLATFORM_AMD64) || \
     defined(__i386__  ) || \
@@ -19,7 +19,7 @@
     defined(_M_I86    ) || \
     defined(_M_IX86   ) || \
     (defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64))
-#define PLATFORM_X86 1
+	#define PLATFORM_X86 1
 #endif
 #if defined(__aarch64__) || \
     defined(__armv7__  ) || \
@@ -31,7 +31,7 @@
     defined(__ARM_ARCH_7A__) || \
     defined(__ARM_ARCH_8A__) || \
     (defined(__ARM_ARCH    ) && __ARM_ARCH >= 6)
-#define PLATFORM_ARM 1
+	#define PLATFORM_ARM 1
 #endif
 
 #if defined(UNSUPPORTED_PLATFORM_ARM)
@@ -40,61 +40,61 @@
 
 #include <stdlib.h>
 #if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
-    // MSVC doesn't support C11 aligned_alloc: https://stackoverflow.com/a/62963007
-    #define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = _aligned_malloc((len), align)
-    #define ALIGN_FREE _aligned_free
+	// MSVC doesn't support C11 aligned_alloc: https://stackoverflow.com/a/62963007
+	#define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = _aligned_malloc((len), align)
+	#define ALIGN_FREE _aligned_free
 #elif defined(_ISOC11_SOURCE)
-    // C11 method
-    // len needs to be a multiple of alignment, although it sometimes works if it isn't...
-    #define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = aligned_alloc(align, ((len) + (align)-1) & ~((align)-1))
-    #define ALIGN_FREE free
+	// C11 method
+	// len needs to be a multiple of alignment, although it sometimes works if it isn't...
+	#define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = aligned_alloc(align, ((len) + (align)-1) & ~((align)-1))
+	#define ALIGN_FREE free
 #elif defined(__cplusplus) && __cplusplus >= 201700
-    // C++17 method
-    #include <cstdlib>
-    #define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = std::aligned_alloc(align, ((len) + (align)-1) & ~((align)-1))
-    #define ALIGN_FREE free
+	// C++17 method
+	#include <cstdlib>
+	#define ALIGN_ALLOC(buf, len, align) *(void**)&(buf) = std::aligned_alloc(align, ((len) + (align)-1) & ~((align)-1))
+	#define ALIGN_FREE free
 #else
-    #define ALIGN_ALLOC(buf, len, align) if(posix_memalign((void**)&(buf), align, (len))) (buf) = NULL
-    #define ALIGN_FREE free
+	#define ALIGN_ALLOC(buf, len, align) if(posix_memalign((void**)&(buf), align, (len))) (buf) = NULL
+	#define ALIGN_FREE free
 #endif
 
 
 // MSVC compatibility
-#if ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(_M_X64)) && !defined(__clang__)
-#define __SSE2__ 1
-#define __SSSE3__ 1
-#define __SSE4_1__ 1
-#if defined(_MSC_VER) && _MSC_VER >= 1600
-#define __POPCNT__ 1
-#define __LZCNT__ 1
-#endif
-#if !defined(__AVX__) && (_MSC_VER >= 1700 && defined(__SSE2__))
-#define __AVX__ 1
-#endif
-#if !defined(__AVX2__) && (_MSC_VER >= 1800 && defined(__SSE2__))
-#define __AVX2__ 1
-#define __BMI2__ 1
-#endif
-/* AVX512 requires VS 15.3 */
-#if !defined(__AVX512F__) && (_MSC_VER >= 1911 && defined(__AVX__))
-#define __AVX512BW__ 1
-#define __AVX512F__ 1
-#endif
-/* AVX512VL not available until VS 15.5 */
-#if defined(__AVX512F__) && _MSC_VER >= 1912
-#define __AVX512VL__ 1
-#endif
-#if defined(__AVX512F__) && _MSC_VER >= 1920
-#define __AVX512VBMI__ 1
-#define __AVX512VBMI2__ 1
-#endif
+#if ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(_M_X64)) && defined(_MSC_VER) && !defined(__clang__)
+	#define __SSE2__ 1
+	#define __SSSE3__ 1
+	#define __SSE4_1__ 1
+	#if _MSC_VER >= 1600 && defined(__SSE2__)
+		#define __POPCNT__ 1
+		#define __LZCNT__ 1
+	#endif
+	#if !defined(__AVX__) && (_MSC_VER >= 1700 && defined(__SSE2__))
+		#define __AVX__ 1
+	#endif
+	#if !defined(__AVX2__) && (_MSC_VER >= 1800 && defined(__AVX__))
+		#define __AVX2__ 1
+		#define __BMI2__ 1
+	#endif
+	/* AVX512 requires VS 15.3 */
+	#if !defined(__AVX512F__) && (_MSC_VER >= 1911 && defined(__AVX__))
+		#define __AVX512BW__ 1
+		#define __AVX512F__ 1
+	#endif
+	/* AVX512VL not available until VS 15.5 */
+	#if defined(__AVX512F__) && _MSC_VER >= 1912
+		#define __AVX512VL__ 1
+	#endif
+	#if defined(__AVX512F__) && _MSC_VER >= 1920
+		#define __AVX512VBMI__ 1
+		#define __AVX512VBMI2__ 1
+	#endif
 #endif
 #if defined(_M_ARM64)
-#define __aarch64__ 1
-#define __ARM_NEON 1
+	#define __aarch64__ 1
+	#define __ARM_NEON 1
 #endif
 #if defined(_M_ARM)
-#define __ARM_NEON 1
+	#define __ARM_NEON 1
 #endif
 #ifdef _MSC_VER
 # ifndef __BYTE_ORDER__
@@ -148,40 +148,47 @@
 
 #endif
 
+#if defined(__ARM_NEON) && defined(__has_include)
+# if !__has_include(<arm_neon.h>)
+#  undef __ARM_NEON
+HEDLEY_WARNING("NEON has been disabled due to missing arm_neon.h");
+# endif
+#endif
+
 #ifdef __ARM_NEON
 # include <arm_neon.h>
 
 // ARM provides no standard way to inline define a vector :(
 static HEDLEY_ALWAYS_INLINE uint8x8_t vmake_u8(
-    uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h
+	uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h
 ) {
 # if defined(_MSC_VER)
-    uint8_t t[] = {a,b,c,d,e,f,g,h};
-    return vld1_u8(t);
+	uint8_t t[] = {a,b,c,d,e,f,g,h};
+	return vld1_u8(t);
 # else
-    return (uint8x8_t){a,b,c,d,e,f,g,h};
+	return (uint8x8_t){a,b,c,d,e,f,g,h};
 # endif
 }
 static HEDLEY_ALWAYS_INLINE uint8x16_t vmakeq_u8(
-    uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h,
-    uint8_t i, uint8_t j, uint8_t k, uint8_t l, uint8_t m, uint8_t n, uint8_t o, uint8_t p
+	uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h,
+	uint8_t i, uint8_t j, uint8_t k, uint8_t l, uint8_t m, uint8_t n, uint8_t o, uint8_t p
 ) {
 # if defined(_MSC_VER)
-    uint8_t t[] = {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
-    return vld1q_u8(t);
+	uint8_t t[] = {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
+	return vld1q_u8(t);
 # else
-    return (uint8x16_t){a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
+	return (uint8x16_t){a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
 # endif
 }
 static HEDLEY_ALWAYS_INLINE int8x16_t vmakeq_s8(
-    int8_t a, int8_t b, int8_t c, int8_t d, int8_t e, int8_t f, int8_t g, int8_t h,
-    int8_t i, int8_t j, int8_t k, int8_t l, int8_t m, int8_t n, int8_t o, int8_t p
+	int8_t a, int8_t b, int8_t c, int8_t d, int8_t e, int8_t f, int8_t g, int8_t h,
+	int8_t i, int8_t j, int8_t k, int8_t l, int8_t m, int8_t n, int8_t o, int8_t p
 ) {
 # if defined(_MSC_VER)
-    int8_t t[] = {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
-    return vld1q_s8(t);
+	int8_t t[] = {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
+	return vld1q_s8(t);
 # else
-    return (int8x16_t){a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
+	return (int8x16_t){a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p};
 # endif
 }
 
@@ -191,21 +198,23 @@ static HEDLEY_ALWAYS_INLINE int8x16_t vmakeq_s8(
 #  define _CREATE_TUPLE(type, ...) (type){{ __VA_ARGS__ }}
 # endif
 static HEDLEY_ALWAYS_INLINE uint8x16x2_t vcreate2_u8(uint8x16_t a, uint8x16_t b) {
-    return _CREATE_TUPLE(uint8x16x2_t, a, b);
+	return _CREATE_TUPLE(uint8x16x2_t, a, b);
 }
 static HEDLEY_ALWAYS_INLINE int8x16x2_t vcreate2_s8(int8x16_t a, int8x16_t b) {
-    return _CREATE_TUPLE(int8x16x2_t, a, b);
+	return _CREATE_TUPLE(int8x16x2_t, a, b);
 }
 static HEDLEY_ALWAYS_INLINE uint8x16x3_t vcreate3_u8(uint8x16_t a, uint8x16_t b, uint8x16_t c) {
-    return _CREATE_TUPLE(uint8x16x3_t, a, b, c);
+	return _CREATE_TUPLE(uint8x16x3_t, a, b, c);
 }
 static HEDLEY_ALWAYS_INLINE uint8x16x4_t vcreate4_u8(uint8x16_t a, uint8x16_t b, uint8x16_t c, uint8x16_t d) {
-    return _CREATE_TUPLE(uint8x16x4_t, a, b, c, d);
+	return _CREATE_TUPLE(uint8x16x4_t, a, b, c, d);
 }
 # undef _CREATE_TUPLE
 #endif
 #ifdef PLATFORM_ARM
-bool cpu_supports_neon();
+namespace RapidYenc {
+	bool cpu_supports_neon();
+}
 #endif
 
 #ifdef _MSC_VER
@@ -217,17 +226,40 @@ bool cpu_supports_neon();
 
 #ifdef PLATFORM_X86
 enum YEncDecIsaLevel {
-    ISA_FEATURE_POPCNT = 0x1,
-    ISA_FEATURE_LZCNT = 0x2,
-    ISA_LEVEL_SSE2 = 0x100,
-    ISA_LEVEL_SSSE3 = 0x200,
-    ISA_LEVEL_SSE41 = 0x300,
-    ISA_LEVEL_SSE4_POPCNT = 0x301,
-    ISA_LEVEL_AVX = 0x381, // same as above, just used as a differentiator for `cpu_supports_isa`
-    ISA_LEVEL_AVX2 = 0x403, // also includes BMI1/2 and LZCNT
-    ISA_LEVEL_AVX3 = 0x503, // SKX variant; AVX512VL + AVX512BW
-    ISA_LEVEL_VBMI2 = 0x603 // ICL
+	ISA_GENERIC = 0,
+	ISA_FEATURE_POPCNT = 0x1,
+	ISA_FEATURE_LZCNT = 0x2,
+	ISA_FEATURE_EVEX512 = 0x4, // AVX512 support
+	ISA_LEVEL_SSE2 = 0x100,
+	ISA_LEVEL_SSSE3 = 0x200,
+	ISA_LEVEL_SSE41 = 0x300,
+	ISA_LEVEL_SSE4_POPCNT = 0x301,
+	ISA_LEVEL_PCLMUL = 0x340,
+	ISA_LEVEL_AVX = 0x381, // same as above, just used as a differentiator for `cpu_supports_isa`
+	ISA_LEVEL_AVX2 = 0x403, // also includes BMI1/2 and LZCNT
+	ISA_LEVEL_VPCLMUL = 0x440,
+	ISA_LEVEL_AVX3 = 0x507, // SKX variant; AVX512VL + AVX512BW
+	ISA_LEVEL_VBMI2 = 0x603 // ICL, AVX10
+};
+#elif defined(PLATFORM_ARM)
+enum YEncDecIsaLevel {
+	ISA_GENERIC = 0,
+	ISA_FEATURE_CRC = 8,
+	ISA_FEATURE_PMULL = 0x40,
+	ISA_LEVEL_NEON = 0x1000
+};
+#elif defined(__riscv)
+enum YEncDecIsaLevel {
+	ISA_GENERIC = 0,
+	ISA_FEATURE_ZBC = 16,
+	ISA_LEVEL_RVV = 0x10000
 };
+#else
+enum YEncDecIsaLevel {
+	ISA_GENERIC = 0
+};
+#endif
+#ifdef PLATFORM_X86
 #ifdef _MSC_VER
 // native tuning not supported in MSVC
 # define ISA_NATIVE ISA_LEVEL_SSE2
@@ -256,19 +288,52 @@ enum YEncDecIsaLevel {
 # endif
 #endif
 
-int cpu_supports_isa();
+namespace RapidYenc {
+	int cpu_supports_isa();
+	int cpu_supports_crc_isa();
+}
 #endif // PLATFORM_X86
 
-#include <string.h>
 
-#if !defined(_MSC_VER) || defined(_STDINT) || _MSC_VER >= 1900
+#ifdef __riscv
+namespace RapidYenc {
+	bool cpu_supports_rvv();
+}
+#endif
+#if defined(__riscv_vector) && defined(HEDLEY_GCC_VERSION) && !HEDLEY_GCC_VERSION_CHECK(13,0,0)
+// GCC added RVV intrinsics in GCC13
+# undef __riscv_vector
+#elif defined(__riscv_vector) && defined(HEDLEY_GCC_VERSION) && !HEDLEY_GCC_VERSION_CHECK(14,0,0)
+// ...however, GCC13 lacks necessary mask<>vector vreinterpret casts, and it crashes on type punning, so I can't be bothered trying to make it work
+# undef __riscv_vector
+#endif
+#ifdef __riscv_vector
+# include <riscv_vector.h>
+# ifdef __riscv_v_intrinsic
+#  define RV(f) __riscv_##f
+# else
+#  define RV(f) f
+# endif
+# if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 12000
+#  define RV_MASK_CAST(masksz, vecsz, vec) RV(vreinterpret_v_u##vecsz##m1_b##masksz)(vec)
+#  define RV_VEC_U8MF4_CAST(vec) RV(vlmul_trunc_v_u8m1_u8mf4)(RV(vreinterpret_v_b4_u8m1)(vec))
+# else
+#  define RV_MASK_CAST(masksz, vecsz, vec) *(vbool##masksz##_t*)(&(vec))
+#  define RV_VEC_U8MF4_CAST(vec) *(vuint8mf4_t*)(&(vec))
+# endif
+#endif
 
+#include <string.h>
+#if !defined(_MSC_VER) || defined(_STDINT) || _MSC_VER >= 1900
 # include <stdint.h>
 # include <stddef.h>
-
 #else
-/* Workaround for older MSVC not including stdint.h */
-# include "stdint.h"
+/* Workaround for older MSVC not supporting stdint.h - just pull it from V8 */
+# if defined(NODE_GYP_MODULE_NAME) || defined(V8_DEPRECATION_WARNINGS)
+#  include <v8.h>
+# else
+#  include "stdint.h"
+# endif
 #endif
 
 
@@ -288,6 +353,7 @@ int cpu_supports_isa();
 #endif
 
 
+
 #ifdef __GNUC__
 # if __GNUC__ >= 9
 #  define LIKELIHOOD(p, c) (HEDLEY_PREDICT(!!(c), 1, p))
diff --git a/src/yencode/crc.cc b/src/yencode/crc.cc
index 288f79d..e895f86 100644
--- a/src/yencode/crc.cc
+++ b/src/yencode/crc.cc
@@ -1,38 +1,197 @@
 #include "crc_common.h"
 
+#if defined(PLATFORM_X86) && !defined(__ILP32__) && !defined(YENC_DISABLE_CRCUTIL)
+// Use crcutil for computing CRC32 (generic implementation)
+
 #include "interface.h"
+crcutil_interface::CRC* crc = NULL;
+#define GENERIC_CRC_INIT crc = crcutil_interface::CRC::Create(0xEDB88320, 0, 32, true, 0, 0, 0, 0, NULL)
+// instance never deleted... oh well...
+
+static uint32_t do_crc32_incremental_generic(const void* data, size_t length, uint32_t init) {
+	// use optimised ASM on x86 platforms
+	crcutil_interface::UINT64 tmp = init;
+	crc->Compute(data, length, &tmp);
+	return (uint32_t)tmp;
+}
+
+#else
+// don't use crcutil
 
-crcutil_interface::CRC *crc = NULL;
+static uint32_t* HEDLEY_RESTRICT crc_slice_table;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# if defined(__GNUC__) || defined(__clang__)
+#  define bswap32 __builtin_bswap32
+# else
+static inline uint32_t bswap32(uint32_t x) {
+	return (x >> 24) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) | (x << 24);
+}
+# endif
+#endif
 
-static uint32_t do_crc32_incremental_generic(const void *data, size_t length, uint32_t init) {
-    crcutil_interface::UINT64 tmp = init;
-    crc->Compute(data, length, &tmp);
-    return (uint32_t) tmp;
+#define CRC32_GENERIC_CHAINS 4 // newer processors may prefer 8
+static uint32_t do_crc32_incremental_generic(const void* data, size_t length, uint32_t init) {
+	const uint32_t* crc_base_table = crc_slice_table + 4*256; // this also seems to help MSVC's optimiser, which otherwise keeps trying to add to crc_slice_table every time it's referenced
+	uint32_t crc[CRC32_GENERIC_CHAINS]; // Clang seems to be more spill happy with an array over individual variables :(
+	crc[0] = ~init;
+	uint8_t* current8 = (uint8_t*)data;
+	
+	// align to multiple of 4
+	if(((uintptr_t)current8 & 1) && length >= 1) {
+		crc[0] = (crc[0] >> 8) ^ crc_base_table[(crc[0] & 0xFF) ^ *current8++];
+		length--;
+	}
+	if(((uintptr_t)current8 & 2) && length >= 2) {
+		crc[0] = (crc[0] >> 8) ^ crc_base_table[(crc[0] & 0xFF) ^ *current8++];
+		crc[0] = (crc[0] >> 8) ^ crc_base_table[(crc[0] & 0xFF) ^ *current8++];
+		length -= 2;
+	}
+	
+	uint8_t* end8 = current8 + length;
+	uint32_t* current = (uint32_t*)current8;
+	if(length >= 8*CRC32_GENERIC_CHAINS-4) {
+		size_t lenMain = ((length-(CRC32_GENERIC_CHAINS-1)*4) / 4);
+		uint32_t* end = current + (lenMain / CRC32_GENERIC_CHAINS) * CRC32_GENERIC_CHAINS;
+		for(int c=1; c<CRC32_GENERIC_CHAINS; c++)
+			crc[c] = 0;
+		while(current != end) {
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+			#define CRC_PROC4(v, in) \
+				v ^= bswap32(in); \
+				v = crc_slice_table[v >> 24] ^ crc_slice_table[0x100L + ((v >> 16) & 0xff)] ^ crc_slice_table[0x200L + ((v >> 8) & 0xff)] ^ crc_slice_table[0x300L + (v & 0xff)]
+#else
+			#define CRC_PROC4(v, in) \
+				v ^= (in); \
+				v = crc_slice_table[v >> 24] ^ crc_slice_table[0x100L + ((v >> 16) & 0xff)] ^ crc_slice_table[0x200L + ((v >> 8) & 0xff)] ^ crc_slice_table[0x300L + (v & 0xff)]
+#endif
+			for(int c=0; c<CRC32_GENERIC_CHAINS; c++) {
+				CRC_PROC4(crc[c], *current);
+				current++;
+			}
+		}
+		// aggregate accumulators
+		current8 = (uint8_t*)current;
+		#if (CRC32_GENERIC_CHAINS & (CRC32_GENERIC_CHAINS-1)) == 0
+		// assume that lengths which are a multiple of 4/8/16/32 are common
+		if((end8 - current8) & (CRC32_GENERIC_CHAINS*4)) {
+			CRC_PROC4(crc[0], *current);
+			current8 += 4;
+			
+			for(int c=1; c<CRC32_GENERIC_CHAINS; c++) {
+				for(int i=0; i<4; i++)
+					crc[c] = (crc[c] >> 8) ^ crc_base_table[(crc[c] & 0xff) ^ *current8++];
+				crc[(c+1) & ~CRC32_GENERIC_CHAINS] ^= crc[c];
+			}
+		} else
+		#endif
+		#undef CRC_PROC4
+		for(int c=1; c<CRC32_GENERIC_CHAINS; c++) {
+			for(int i=0; i<4; i++)
+				crc[0] = (crc[0] >> 8) ^ crc_base_table[(crc[0] & 0xff) ^ *current8++];
+			crc[0] ^= crc[c];
+		}
+	}
+	
+	// tail loop
+	while(current8 != end8) {
+		crc[0] = (crc[0] >> 8) ^ crc_base_table[(crc[0] & 0xFF) ^ *current8++];
+	}
+	return ~crc[0];
+}
+static void generate_crc32_slice_table() {
+	crc_slice_table = (uint32_t*)malloc(5*256*sizeof(uint32_t));
+	// generate standard byte-by-byte table
+	uint32_t* crc_base_table = crc_slice_table + 4*256;
+	for(int v=0; v<256; v++) {
+		uint32_t crc = v;
+		for(int j = 0; j < 8; j++) {
+			crc = (crc >> 1) ^ (-(int32_t)(crc & 1) & 0xEDB88320);
+		}
+		crc_base_table[v] = crc;
+	}
+	
+	// generate slice-by-4 shifted across for X independent chains
+	for(int v=0; v<256; v++) {
+		uint32_t crc = crc_base_table[v];
+		#if CRC32_GENERIC_CHAINS > 1
+		for(int i=0; i<4*CRC32_GENERIC_CHAINS-5; i++)
+			crc = (crc >> 8) ^ crc_base_table[crc & 0xff];
+		for(int i=0; i<4; i++) {
+			crc = (crc >> 8) ^ crc_base_table[crc & 0xff];
+			crc_slice_table[i*256 + v] = crc;
+		}
+		#else
+		for(int i=0; i<4; i++) {
+			crc_slice_table[i*256 + v] = crc;
+			crc = (crc >> 8) ^ crc_base_table[crc & 0xff];
+		}
+		#endif
+	}
 }
 
-crc_func _do_crc32_incremental = &do_crc32_incremental_generic;
+#define GENERIC_CRC_INIT generate_crc32_slice_table()
+#endif
 
 
-uint32_t do_crc32_combine(uint32_t crc1, uint32_t crc2, size_t len2) {
-    crcutil_interface::UINT64 crc1_ = crc1, crc2_ = crc2;
-    crc->Concatenate(crc2_, 0, len2, &crc1_);
-    return (uint32_t) crc1_;
+namespace RapidYenc {
+
+// workaround MSVC complaining "unary minus operator applied to unsigned type, result still unsigned"
+#define NEGATE(n) (uint32_t)(-((int32_t)(n)))
+uint32_t crc32_multiply_generic(uint32_t a, uint32_t b) {
+	uint32_t res = 0;
+	for(int i=0; i<31; i++) {
+		res ^= NEGATE(b>>31) & a;
+		a = ((a >> 1) ^ (0xEDB88320 & NEGATE(a&1)));
+		b <<= 1;
+	}
+	res ^= NEGATE(b>>31) & a;
+	return res;
 }
+#undef NEGATE
+
+const uint32_t crc_power[32] = { // pre-computed 2^(2^n)
+	0x40000000, 0x20000000, 0x08000000, 0x00800000, 0x00008000, 0xedb88320, 0xb1e6b092, 0xa06a2517,
+	0xed627dae, 0x88d14467, 0xd7bbfe6a, 0xec447f11, 0x8e7ea170, 0x6427800e, 0x4d47bae0, 0x09fe548f,
+	0x83852d0f, 0x30362f1a, 0x7b5a9cc3, 0x31fec169, 0x9fec022a, 0x6c8dedc4, 0x15d6874d, 0x5fde7a4e,
+	0xbad90e37, 0x2e4e5eef, 0x4eaba214, 0xa8a472c0, 0x429a969e, 0x148d302a, 0xc40ba6d0, 0xc4e22c3c
+};
 
-uint32_t do_crc32_zeros(uint32_t crc1, size_t len) {
-    crcutil_interface::UINT64 crc_ = crc1;
-    crc->CrcOfZeroes(len, &crc_);
-    return (uint32_t) crc_;
+uint32_t crc32_shift_generic(uint32_t crc1, uint32_t n) {
+	uint32_t result = crc1;
+#ifdef __GNUC__
+	while(n) {
+		result = crc32_multiply_generic(result, crc_power[__builtin_ctz(n)]);
+		n &= n-1;
+	}
+#elif defined(_MSC_VER)
+	unsigned long power;
+	while(_BitScanForward(&power, n)) {
+		result = crc32_multiply_generic(result, crc_power[power]);
+		n &= n-1;
+	}
+#else
+	unsigned power = 0;
+	while(n) {
+		if(n & 1) {
+			result = crc32_multiply_generic(result, crc_power[power]);
+		}
+		n >>= 1;
+		power++;
+	}
+#endif
+	return result;
 }
+} // namespace
 
-void crc_clmul_set_funcs(crc_func *);
-void crc_clmul256_set_funcs(crc_func *);
 
-void crc_arm_set_funcs(crc_func *);
+namespace RapidYenc {
+	crc_func _do_crc32_incremental = &do_crc32_incremental_generic;
+	crc_mul_func _crc32_shift = &crc32_shift_generic;
+	crc_mul_func _crc32_multiply = &crc32_multiply_generic;
+	int _crc32_isa = ISA_GENERIC;
+}
+
 
-#ifdef PLATFORM_X86
-int cpu_supports_crc_isa();
-#endif
 
 #if defined(PLATFORM_ARM) && defined(_WIN32)
 # define WIN32_LEAN_AND_MEAN
@@ -41,64 +200,98 @@ int cpu_supports_crc_isa();
 #ifdef PLATFORM_ARM
 # ifdef __ANDROID__
 #  include <cpu-features.h>
-# elif defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 12)
-#  include <sys/auxv.h>
-#  include <asm/hwcap.h>
-# elif (defined(__FreeBSD__) && __FreeBSD__ < 12)
-#  include <sys/sysctl.h>
-#  include <asm/hwcap.h>
 # elif defined(__APPLE__)
 #  include <sys/types.h>
 #  include <sys/sysctl.h>
-# endif
-# ifdef __FreeBSD__
+# elif defined(__has_include)
+#  if __has_include(<sys/auxv.h>)
+#   include <sys/auxv.h>
+#   ifdef __FreeBSD__
 static unsigned long getauxval(unsigned long cap) {
-    unsigned long ret;
-    elf_aux_info(cap, &ret, sizeof(ret));
-    return ret;
+	unsigned long ret;
+	elf_aux_info(cap, &ret, sizeof(ret));
+	return ret;
 }
+#   endif
+#   if __has_include(<asm/hwcap.h>)
+#    include <asm/hwcap.h>
+#   endif
+#  endif
+# endif
+#endif
+#if defined(__riscv) && defined(__has_include)
+# if __has_include(<asm/hwprobe.h>)
+#  include <asm/hwprobe.h>
+#  include <asm/unistd.h>
+#  include <unistd.h>
 # endif
 #endif
 
-void crc_init() {
-    crc = crcutil_interface::CRC::Create(
-            0xEDB88320, 0, 32, true, 0, 0, 0, 0, NULL);
-    // instance never deleted... oh well...
-
+void RapidYenc::crc32_init() {
+	GENERIC_CRC_INIT;
+	
 #ifdef PLATFORM_X86
-    int support = cpu_supports_crc_isa();
-    if(support == 2)
-        crc_clmul256_set_funcs(&_do_crc32_incremental);
-    else if(support == 1)
-        crc_clmul_set_funcs(&_do_crc32_incremental);
+	int support = cpu_supports_crc_isa();
+	if(support == 2)
+		crc_clmul256_set_funcs();
+	else if(support == 1)
+		crc_clmul_set_funcs();
 #endif
 #ifdef PLATFORM_ARM
 # ifdef __APPLE__
-    int supported = 0;
-    size_t len = sizeof(supported);
-    if(sysctlbyname("hw.optional.armv8_crc32", &supported, &len, NULL, 0))
-        supported = 0;
-# endif
-    if(
-# if defined(AT_HWCAP2) && defined(HWCAP2_CRC32)
-        getauxval(AT_HWCAP2) & HWCAP2_CRC32
-# elif defined(AT_HWCAP) && defined(HWCAP_CRC32)
-        getauxval(AT_HWCAP) & HWCAP_CRC32
-# elif defined(ANDROID_CPU_FAMILY_ARM) && defined(__aarch64__)
-        android_getCpuFeatures() & ANDROID_CPU_ARM64_FEATURE_CRC32
-# elif defined(ANDROID_CPU_FAMILY_ARM) /* aarch32 */
-        android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_CRC32
-# elif defined(_WIN32)
-        IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
-# elif defined(__APPLE__)
-        supported
-# elif defined(__ARM_FEATURE_CRC32)
-        true /* assume available if compiled as such */
+	int supports_crc = 0;
+	int supports_pmull = 0;
+	size_t len = sizeof(supports_crc);
+	if(sysctlbyname("hw.optional.armv8_crc32", &supports_crc, &len, NULL, 0))
+		supports_crc = 0;
+	if(sysctlbyname("hw.optional.arm.FEAT_PMULL", &supports_pmull, &len, NULL, 0))
+		supports_pmull = 0;
 # else
-        false
+	bool supports_crc = false;
+	bool supports_pmull = false;
+#  if defined(AT_HWCAP2) && defined(HWCAP2_CRC32)
+	supports_crc = getauxval(AT_HWCAP2) & HWCAP2_CRC32;
+#  elif defined(AT_HWCAP) && defined(HWCAP_CRC32)
+	supports_crc = getauxval(AT_HWCAP) & HWCAP_CRC32;
+#  elif defined(ANDROID_CPU_FAMILY_ARM) && defined(__aarch64__)
+	supports_crc = android_getCpuFeatures() & ANDROID_CPU_ARM64_FEATURE_CRC32;
+	supports_pmull = android_getCpuFeatures() & ANDROID_CPU_ARM64_FEATURE_PMULL;
+#  elif defined(ANDROID_CPU_FAMILY_ARM) /* aarch32 */
+	supports_crc = android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_CRC32;
+	supports_pmull = android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_PMULL;
+#  elif defined(_WIN32)
+	supports_crc = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE);
+	supports_pmull = IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
+#  else
+	#ifdef __ARM_FEATURE_CRC32
+	supports_crc = true; /* assume available if compiled as such */
+	#endif
+	#ifdef __ARM_FEATURE_CRYPTO
+	supports_pmull = true;
+	#endif
+#  endif
+#  if defined(AT_HWCAP2) && defined(HWCAP2_PMULL)
+	supports_pmull = getauxval(AT_HWCAP2) & HWCAP2_PMULL;
+#  elif defined(AT_HWCAP) && defined(HWCAP_PMULL)
+	supports_pmull = getauxval(AT_HWCAP) & HWCAP_PMULL;
+#  endif
+# endif
+	
+	if(supports_crc) {
+		crc_arm_set_funcs();
+		if(supports_pmull) crc_pmull_set_funcs();
+	}
+#endif
+#ifdef __riscv
+# if defined(RISCV_HWPROBE_KEY_IMA_EXT_0) && defined(__NR_riscv_hwprobe)
+	const int rv_hwprobe_ext_zbc = 1 << 7, rv_hwprobe_ext_zbkc = 1 << 9;
+	struct riscv_hwprobe p;
+	p.key = RISCV_HWPROBE_KEY_IMA_EXT_0;
+	if(!syscall(__NR_riscv_hwprobe, &p, 1, 0, NULL, 0)) {
+		if(p.value & (rv_hwprobe_ext_zbc | rv_hwprobe_ext_zbkc)) {
+			crc_riscv_set_funcs();
+		}
+	}
 # endif
-    ) {
-        crc_arm_set_funcs(&_do_crc32_incremental);
-    }
 #endif
 }
diff --git a/src/yencode/crc.h b/src/yencode/crc.h
index ef8f380..27f66f0 100644
--- a/src/yencode/crc.h
+++ b/src/yencode/crc.h
@@ -1,24 +1,91 @@
 #ifndef __YENC_CRC_H
 #define __YENC_CRC_H
+#include <stdlib.h> // for llabs
 
-#ifdef __cplusplus
-extern "C" {
+#if !defined(__GNUC__) && defined(_MSC_VER)
+# include <intrin.h>
 #endif
 
+namespace RapidYenc {
 
-typedef uint32_t (*crc_func)(const void *, size_t, uint32_t);
 
+typedef uint32_t (*crc_func)(const void*, size_t, uint32_t);
 extern crc_func _do_crc32_incremental;
-#define do_crc32 (*_do_crc32_incremental)
 
-uint32_t do_crc32_combine(uint32_t crc1, const uint32_t crc2, size_t len2);
+extern int _crc32_isa;
+static inline uint32_t crc32(const void* data, size_t length, uint32_t init) {
+	return (*_do_crc32_incremental)(data, length, init);
+}
+static inline int crc32_isa_level() {
+	return _crc32_isa;
+}
 
-uint32_t do_crc32_zeros(uint32_t crc1, size_t len);
 
-void crc_init();
+// computes `n % 0xffffffff` (well, almost), using some bit-hacks
+static inline uint32_t crc32_powmod(uint64_t n) {
+#ifdef __GNUC__
+	unsigned res;
+	unsigned carry = __builtin_uadd_overflow(n >> 32, n, &res);
+	res += carry;
+	return res;
+#elif defined(_MSC_VER) && defined(PLATFORM_X86)
+	unsigned res;
+	unsigned char carry = _addcarry_u32(0, n >> 32, n, &res);
+	_addcarry_u32(carry, res, 0, &res);
+	return res;
+#else
+	n = (n >> 32) + (n & 0xffffffff);
+	n += n >> 32;
+	return n;
+#endif
+}
+// computes `crc32_powmod(n*8)` avoiding overflow
+static inline uint32_t crc32_bytepow(uint64_t n) {
+#if defined(__GNUC__) || defined(_MSC_VER)
+	unsigned res = crc32_powmod(n);
+# ifdef _MSC_VER
+	return _rotl(res, 3);
+# else
+	return (res << 3) | (res >> 29);
+# endif
+#else
+	n = (n >> 32) + (n & 0xffffffff);
+	n <<= 3;
+	n += n >> 32;
+	return n;
+#endif
+}
 
+typedef uint32_t (*crc_mul_func)(uint32_t, uint32_t);
+extern crc_mul_func _crc32_shift;
+extern crc_mul_func _crc32_multiply;
+static inline uint32_t crc32_shift(uint32_t a, uint32_t b) {
+	return (*_crc32_shift)(a, b);
+}
+static inline uint32_t crc32_multiply(uint32_t a, uint32_t b) {
+	return (*_crc32_multiply)(a, b);
+}
 
-#ifdef __cplusplus
+static inline uint32_t crc32_combine(uint32_t crc1, uint32_t crc2, uint64_t len2) {
+	return crc32_shift(crc1, crc32_bytepow(len2)) ^ crc2;
 }
-#endif
-#endif
+static inline uint32_t crc32_zeros(uint32_t crc1, uint64_t len) {
+	return ~crc32_shift(~crc1, crc32_bytepow(len));
+}
+static inline uint32_t crc32_unzero(uint32_t crc1, uint64_t len) {
+	return ~crc32_shift(~crc1, ~crc32_bytepow(len));
+}
+static inline uint32_t crc32_2pow(int64_t n) {
+	uint32_t sign = (uint32_t)(n >> 63);
+	return crc32_shift(0x80000000, crc32_powmod(llabs(n)) ^ sign);
+}
+static inline uint32_t crc32_256pow(uint64_t n) {
+	return crc32_shift(0x80000000, crc32_bytepow(n));
+}
+
+void crc32_init();
+
+
+
+} // namespace
+#endif // defined(__YENC_CRC_H)
diff --git a/src/yencode/crc_arm.cc b/src/yencode/crc_arm.cc
index f362860..6eb6815 100644
--- a/src/yencode/crc_arm.cc
+++ b/src/yencode/crc_arm.cc
@@ -16,6 +16,12 @@ HEDLEY_WARNING("CRC32 acceleration has been disabled due to broken arm_acle.h sh
 HEDLEY_WARNING("CRC32 acceleration has been disabled due to broken arm_acle.h shipped in GCC 9.4 [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100985]. If you need this feature, please use a different compiler or version of GCC");
 # endif
 #endif
+#if defined(__ARM_FEATURE_CRC32) && defined(__has_include)
+# if !__has_include(<arm_acle.h>)
+#  undef __ARM_FEATURE_CRC32
+HEDLEY_WARNING("CRC32 acceleration has been disabled due to missing arm_acle.h");
+# endif
+#endif
 
 #if defined(__ARM_FEATURE_CRC32) || (defined(_M_ARM64) && !defined(__clang__)) // MSVC doesn't support CRC for ARM32
 
@@ -53,153 +59,168 @@ HEDLEY_WARNING("CRC32 acceleration has been disabled due to broken arm_acle.h sh
 #endif
 
 
+
+#ifdef __aarch64__
+static uint32_t crc32_multiply_arm(uint32_t a, uint32_t b) {
+	// perform PMULL
+	uint64_t res = 0;
+	uint64_t a64 = (uint64_t)a << 32;
+	int64_t b64 = (int64_t)b << 32;
+	for(int i=0; i<32; i++) {
+		res ^= a64 & (b64 >> 63);
+		b64 += b64;
+		a64 >>= 1;
+	}
+	// reduction via CRC
+	res = __crc32w(0, res) ^ (res >> 32);
+	return res;
+}
+#endif
+// regular multiply is probably better for AArch32
+
+
 // exploit CPU pipelining during CRC computation; unfortunately I haven't been able to measure any benefit
 // - Neoverse N1: no noticeable difference
 // - Cortex A53: actually runs a bit slower
 //#define ENABLE_PIPELINE_OPT 1
 
 #ifdef ENABLE_PIPELINE_OPT
-// workaround MSVC complaining "unary minus operator applied to unsigned type, result still unsigned"
-#define NEGATE(n) (uint32_t)(-((int32_t)(n)))
-
-static HEDLEY_ALWAYS_INLINE uint32_t crc_multiply(uint32_t a, uint32_t b) {
-    uint32_t res = 0;
-    for(int i=0; i<31; i++) {
-        res ^= NEGATE(b>>31) & a;
-        a = ((a >> 1) ^ (0xEDB88320 & NEGATE(a&1)));
-        b <<= 1;
-    }
-    res ^= NEGATE(b>>31) & a;
-    return res;
-}
-
-static const uint32_t crc_power[] = { // pre-computed 2^n, with first 3 entries removed (saves a shift)
-    0x00800000, 0x00008000, 0xedb88320, 0xb1e6b092, 0xa06a2517, 0xed627dae, 0x88d14467, 0xd7bbfe6a,
-    0xec447f11, 0x8e7ea170, 0x6427800e, 0x4d47bae0, 0x09fe548f, 0x83852d0f, 0x30362f1a, 0x7b5a9cc3,
-    0x31fec169, 0x9fec022a, 0x6c8dedc4, 0x15d6874d, 0x5fde7a4e, 0xbad90e37, 0x2e4e5eef, 0x4eaba214,
-    0xa8a472c0, 0x429a969e, 0x148d302a, 0xc40ba6d0, 0xc4e22c3c, 0x40000000, 0x20000000, 0x08000000
-};
-/* above table can be computed with
-    int main(void) {
-        uint32_t k = 0x80000000 >> 1;
-        for (size_t i = 0; i < 32+3; ++i) {
-            if(i>2) printf("0x%08x, ", k);
-            k = crc_multiply(k, k);
-        }
-        return 0;
-    }
-*/
+#ifndef __aarch64__
+# define crc32_multiply_arm RapidYenc::crc32_multiply_generic
 #endif
+#endif
+
 
 
 // inspired/stolen off https://github.com/jocover/crc32_armv8/blob/master/crc32_armv8.c
 static uint32_t arm_crc_calc(uint32_t crc, const unsigned char *src, long len) {
-
-    // initial alignment
-    if (len >= 16) { // 16 is an arbitrary number; it just needs to be >=8
-        if ((uintptr_t)src & sizeof(uint8_t)) {
-            crc = __crc32b(crc, *src);
-            src++;
-            len--;
-        }
-        if ((uintptr_t)src & sizeof(uint16_t)) {
-            crc = __crc32h(crc, _LE16(*((uint16_t *)src)));
-            src += sizeof(uint16_t);
-            len -= sizeof(uint16_t);
-        }
+	
+	// initial alignment
+	if (len >= 16) { // 16 is an arbitrary number; it just needs to be >=8
+		if ((uintptr_t)src & sizeof(uint8_t)) {
+			crc = __crc32b(crc, *src);
+			src++;
+			len--;
+		}
+		if ((uintptr_t)src & sizeof(uint16_t)) {
+			crc = __crc32h(crc, _LE16(*((uint16_t *)src)));
+			src += sizeof(uint16_t);
+			len -= sizeof(uint16_t);
+		}
 #ifdef __aarch64__
-        if ((uintptr_t)src & sizeof(uint32_t)) {
-            crc = __crc32w(crc, _LE32(*((uint32_t *)src)));
-            src += sizeof(uint32_t);
-            len -= sizeof(uint32_t);
-        }
+		if ((uintptr_t)src & sizeof(uint32_t)) {
+			crc = __crc32w(crc, _LE32(*((uint32_t *)src)));
+			src += sizeof(uint32_t);
+			len -= sizeof(uint32_t);
+		}
 #endif
-    }
-
-    const WORD_T* srcW = (const WORD_T*)src;
-
+	}
+	
+	const WORD_T* srcW = (const WORD_T*)src;
+	
 #ifdef ENABLE_PIPELINE_OPT
-    // uses ideas from https://github.com/komrad36/crc#option-13-golden
-    // (this is a slightly less efficient, but much simpler implementation of the idea)
-    const unsigned SPLIT_WORDS_LOG = 10;  // make sure it's at least 2
-    const unsigned SPLIT_WORDS = 1<<SPLIT_WORDS_LOG;
-    while(len >= (long)(sizeof(WORD_T)*SPLIT_WORDS*2)) {
-        // compute 2x CRCs concurrently to leverage piplining
-        uint32_t crc2 = 0;
-        for(unsigned i=0; i<SPLIT_WORDS; i+=4) {
-            crc = CRC_WORD(crc, *srcW);
-            crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
-            srcW++;
-            crc = CRC_WORD(crc, *srcW);
-            crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
-            srcW++;
-            crc = CRC_WORD(crc, *srcW);
-            crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
-            srcW++;
-            crc = CRC_WORD(crc, *srcW);
-            crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
-            srcW++;
-        }
-        // merge the CRCs
-        // since we're multiplying by a fixed number, it could be sped up with some lookup tables
-        crc = crc_multiply(crc, crc_power[SPLIT_WORDS_LOG + WORDSIZE_LOG]) ^ crc2;
-        srcW += SPLIT_WORDS;
-        len -= sizeof(WORD_T)*SPLIT_WORDS*2;
-    }
+	// uses ideas from https://github.com/komrad36/crc#option-13-golden
+	// (this is a slightly less efficient, but much simpler implementation of the idea)
+	const unsigned SPLIT_WORDS_LOG = 10;  // make sure it's at least 2
+	const unsigned SPLIT_WORDS = 1<<SPLIT_WORDS_LOG;
+	const unsigned blockCoeff = RapidYenc::crc_power[SPLIT_WORDS_LOG + WORDSIZE_LOG + 3];
+	while(len >= (long)(sizeof(WORD_T)*SPLIT_WORDS*2)) {
+		// compute 2x CRCs concurrently to leverage piplining
+		uint32_t crc2 = 0;
+		for(unsigned i=0; i<SPLIT_WORDS; i+=4) {
+			crc = CRC_WORD(crc, *srcW);
+			crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
+			srcW++;
+			crc = CRC_WORD(crc, *srcW);
+			crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
+			srcW++;
+			crc = CRC_WORD(crc, *srcW);
+			crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
+			srcW++;
+			crc = CRC_WORD(crc, *srcW);
+			crc2 = CRC_WORD(crc2, *(srcW + SPLIT_WORDS));
+			srcW++;
+		}
+		// merge the CRCs
+		crc = crc32_multiply_arm(crc, blockCoeff) ^ crc2;
+		srcW += SPLIT_WORDS;
+		len -= sizeof(WORD_T)*SPLIT_WORDS*2;
+	}
 #endif
-
-    while ((len -= sizeof(WORD_T)*8) >= 0) {
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-    }
-    if (len & sizeof(WORD_T)*4) {
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-    }
-    if (len & sizeof(WORD_T)*2) {
-        crc = CRC_WORD(crc, *(srcW++));
-        crc = CRC_WORD(crc, *(srcW++));
-    }
-    if (len & sizeof(WORD_T)) {
-        crc = CRC_WORD(crc, *(srcW++));
-    }
-    src = (const unsigned char*)srcW;
-
+	
+	while ((len -= sizeof(WORD_T)*8) >= 0) {
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+	}
+	if (len & sizeof(WORD_T)*4) {
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+	}
+	if (len & sizeof(WORD_T)*2) {
+		crc = CRC_WORD(crc, *(srcW++));
+		crc = CRC_WORD(crc, *(srcW++));
+	}
+	if (len & sizeof(WORD_T)) {
+		crc = CRC_WORD(crc, *(srcW++));
+	}
+	src = (const unsigned char*)srcW;
+	
 #ifdef __aarch64__
-    if (len & sizeof(uint32_t)) {
-        crc = __crc32w(crc, _LE32(*((uint32_t *)src)));
-        src += sizeof(uint32_t);
-    }
+	if (len & sizeof(uint32_t)) {
+		crc = __crc32w(crc, _LE32(*((uint32_t *)src)));
+		src += sizeof(uint32_t);
+	}
 #endif
-    if (len & sizeof(uint16_t)) {
-        crc = __crc32h(crc, _LE16(*((uint16_t *)src)));
-        src += sizeof(uint16_t);
-    }
-    if (len & sizeof(uint8_t))
-        crc = __crc32b(crc, *src);
-
-    return crc;
+	if (len & sizeof(uint16_t)) {
+		crc = __crc32h(crc, _LE16(*((uint16_t *)src)));
+		src += sizeof(uint16_t);
+	}
+	if (len & sizeof(uint8_t))
+		crc = __crc32b(crc, *src);
+	
+	return crc;
 }
 
 static uint32_t do_crc32_incremental_arm(const void* data, size_t length, uint32_t init) {
-    return ~arm_crc_calc(~init, (const unsigned char*)data, (long)length);
+	return ~arm_crc_calc(~init, (const unsigned char*)data, (long)length);
 }
 
-void crc_arm_set_funcs(crc_func* _do_crc32_incremental) {
-    *_do_crc32_incremental = &do_crc32_incremental_arm;
-}
-#else
 
-void crc_arm_set_funcs(crc_func *_do_crc32_incremental) {
-    (void) _do_crc32_incremental;
+#if defined(__aarch64__) && (defined(__GNUC__) || defined(_MSC_VER))
+static uint32_t crc32_shift_arm(uint32_t crc1, uint32_t n) {
+	uint32_t result = crc1;
+	uint64_t prod = result;
+	prod <<= 32 - (n&31);
+	result = __crc32w(0, prod) ^ (prod >> 32);
+	n &= ~31;
+	
+	while(n) {
+		result = crc32_multiply_arm(result, RapidYenc::crc_power[ctz32(n)]);
+		n &= n-1;
+	}
+	return result;
 }
+#endif
 
+
+void RapidYenc::crc_arm_set_funcs() {
+	_do_crc32_incremental = &do_crc32_incremental_arm;
+#ifdef __aarch64__
+	_crc32_multiply = &crc32_multiply_arm;
+# if defined(__GNUC__) || defined(_MSC_VER)
+	_crc32_shift = &crc32_shift_arm;
+# endif
+#endif
+	_crc32_isa = ISA_FEATURE_CRC;
+}
+#else
+void RapidYenc::crc_arm_set_funcs() {}
 #endif
diff --git a/src/yencode/crc_arm_pmull.cc b/src/yencode/crc_arm_pmull.cc
new file mode 100644
index 0000000..a426d34
--- /dev/null
+++ b/src/yencode/crc_arm_pmull.cc
@@ -0,0 +1,215 @@
+#include "crc_common.h"
+
+// exclude broken/missing arm_acle.h
+#if defined(__ARM_FEATURE_CRYPTO) && defined(HEDLEY_GCC_VERSION)
+# if !defined(__aarch64__) && HEDLEY_GCC_VERSION_CHECK(7,0,0) && !HEDLEY_GCC_VERSION_CHECK(8,1,1)
+#  undef __ARM_FEATURE_CRYPTO
+# endif
+# if defined(__aarch64__) && HEDLEY_GCC_VERSION_CHECK(9,4,0) && !HEDLEY_GCC_VERSION_CHECK(9,5,0)
+#  undef __ARM_FEATURE_CRYPTO
+# endif
+#endif
+#if defined(__ARM_FEATURE_CRYPTO) && defined(__has_include)
+# if !__has_include(<arm_acle.h>)
+#  undef __ARM_FEATURE_CRYPTO
+# endif
+#endif
+
+// ARM's intrinsics guide seems to suggest that vmull_p64 is available on A32, but neither Clang/GCC seem to support it on AArch32
+#if (defined(__ARM_FEATURE_CRYPTO) && defined(__ARM_FEATURE_CRC32) && defined(__aarch64__)) || (defined(_M_ARM64) && !defined(__clang__))
+
+#include <arm_neon.h>
+#if defined(_MSC_VER) && !defined(__clang__)
+# include <intrin.h>
+
+# ifdef _M_ARM64
+// MSVC may detect this pattern: https://devblogs.microsoft.com/cppblog/a-tour-of-4-msvc-backend-improvements/#byteswap-identification
+static HEDLEY_ALWAYS_INLINE uint64_t rbit64(uint64_t x) {
+	x = _byteswap_uint64(x);
+	x = (x & 0xaaaaaaaaaaaaaaaa) >> 1 | (x & 0x5555555555555555) << 1;
+	x = (x & 0xcccccccccccccccc) >> 2 | (x & 0x3333333333333333) << 2;
+	x = (x & 0xf0f0f0f0f0f0f0f0) >> 4 | (x & 0x0f0f0f0f0f0f0f0f) << 4;
+	return x;
+}
+// ...whilst this seems to work best for 32-bit RBIT
+static HEDLEY_ALWAYS_INLINE uint32_t rbit32(uint32_t x) {
+	uint64_t r = rbit64(x);
+	return r >> 32;
+}
+# else
+#  define rbit32 _arm_rbit
+# endif
+#else
+# include <arm_acle.h>
+// __rbit not present before GCC 11.4.0 or 12.2.0; for ARM32, requires GCC 14
+# if defined(HEDLEY_GCC_VERSION) && !HEDLEY_GCC_VERSION_CHECK(14,0,0) && (!defined(__aarch64__) || !HEDLEY_GCC_VERSION_CHECK(11,3,0) || (HEDLEY_GCC_VERSION_CHECK(12,0,0) && !HEDLEY_GCC_VERSION_CHECK(12,2,0)))
+#  ifdef __aarch64__
+static HEDLEY_ALWAYS_INLINE uint64_t rbit64(uint64_t x) {
+	uint64_t r;
+	__asm__ ("rbit %0,%1\n"
+		: "=r"(r) : "r"(x)
+		: /* No clobbers */);
+	return r;
+}
+#  endif
+static HEDLEY_ALWAYS_INLINE uint32_t rbit32(uint32_t x) {
+	uint32_t r;
+	__asm__ (
+#  ifdef __aarch64__
+		"rbit %w0,%w1\n"
+#  else
+		"rbit %0,%1\n"
+#  endif
+		: "=r"(r) : "r"(x)
+		: /* No clobbers */);
+	return r;
+}
+# else
+#  define rbit32 __rbit
+#  define rbit64 __rbitll
+# endif
+#endif
+
+
+// MSVC doesn't have poly64/poly128 types, so always use uint64 instead
+
+#ifdef __aarch64__
+# if defined(__GNUC__) || defined(__clang__)
+static HEDLEY_ALWAYS_INLINE uint64x2_t pmull_low(uint64x1_t a, uint64x1_t b) {
+	uint64x2_t result;
+	__asm__ ("pmull %0.1q,%1.1d,%2.1d"
+		: "=w"(result)
+		: "w"(a), "w"(b)
+		: /* No clobbers */);
+	return result;
+}
+static HEDLEY_ALWAYS_INLINE uint64x2_t pmull_high(uint64x2_t a, uint64x2_t b) {
+	uint64x2_t result;
+	__asm__ ("pmull2 %0.1q,%1.2d,%2.2d"
+		: "=w"(result)
+		: "w"(a), "w"(b)
+		: /* No clobbers */);
+	return result;
+}
+# elif defined(_MSC_VER) && !defined(__clang__)
+#  define pmull_low vmull_p64
+#  define pmull_high vmull_high_p64
+# else
+#  define pmull_low(x, y) vreinterpretq_u64_p128(vmull_p64(vreinterpret_p64_u64(x), vreinterpret_p64_u64(y)))
+#  define pmull_high(x, y) vreinterpretq_u64_p128(vmull_high_p64(vreinterpretq_p64_u64(x), vreinterpretq_p64_u64(y)))
+# endif
+#else
+# if defined(_MSC_VER) && !defined(__clang__)
+#  define pmull_low vmull_p64
+#  define pmull_high(x, y) vmull_p64(vget_high_u64(x), vget_high_u64(y))
+# else
+#  define pmull_low(x, y) vreinterpretq_u64_p128(vmull_p64(x, y))
+#  define pmull_high(x, y) vreinterpretq_u64_p128(vmull_p64(vget_high_p64(vreinterpretq_p64_u64(x)), vget_high_p64(vreinterpretq_p64_u64(y))))
+# endif
+#endif
+
+
+static uint32_t crc32_multiply_pmull(uint32_t a, uint32_t b) {
+	uint64x1_t prod = vget_low_u64(pmull_low(
+		vreinterpret_u64_u32(vset_lane_u32(a, vdup_n_u32(0), 0)),
+		vreinterpret_u64_u32(vset_lane_u32(b, vdup_n_u32(0), 0))
+	));
+	#ifdef __aarch64__
+	uint64_t p = vget_lane_u64(prod, 0);
+	return __crc32w(0, p+p) ^ (p >> 31);
+	#else
+	prod = vadd_u64(prod, prod);
+	uint32x2_t prod32 = vreinterpret_u32_u64(prod);
+	return __crc32w(0, vget_lane_u32(prod32, 0)) ^ vget_lane_u32(prod32, 1);
+	#endif
+}
+
+
+
+static const uint32_t crc_power_rev[32] = { // bit-reversed crc_power
+	0x00000002, 0x00000004, 0x00000010, 0x00000100, 0x00010000, 0x04c11db7, 0x490d678d, 0xe8a45605,
+	0x75be46b7, 0xe6228b11, 0x567fddeb, 0x88fe2237, 0x0e857e71, 0x7001e426, 0x075de2b2, 0xf12a7f90,
+	0xf0b4a1c1, 0x58f46c0c, 0xc3395ade, 0x96837f8c, 0x544037f9, 0x23b7b136, 0xb2e16ba8, 0x725e7bfa,
+	0xec709b5d, 0xf77a7274, 0x2845d572, 0x034e2515, 0x79695942, 0x540cb128, 0x0b65d023, 0x3c344723
+};
+
+
+static HEDLEY_ALWAYS_INLINE uint64x1_t crc32_shift_pmull_mulred(uint64x1_t a, uint64x1_t b) {
+	uint64x2_t r = pmull_low(a, b);
+	uint64x2_t h = pmull_high(r, vdupq_n_u64(0x490d678d));
+	return veor_u64(vget_low_u64(r), vget_low_u64(h));
+}
+
+
+static uint32_t crc32_shift_pmull(uint32_t crc1, uint32_t n) {
+	crc1 = rbit32(crc1);
+	
+	uint64x1_t res;
+	#ifdef __aarch64__
+	uint64_t crc = (uint64_t)crc1 << (n & 31);
+	res = vset_lane_u64(crc, vdup_n_u64(0), 0);
+	#else
+	res = vreinterpret_u64_u32(vset_lane_u32(crc1, vdup_n_u32(0), 0));
+	res = vshl_u64(res, vdup_n_u64(n&31));
+	#endif
+	n &= ~31;
+	
+	if(n) {
+		#define LOAD_NEXT_POWER vreinterpret_u64_u32(vset_lane_u32(crc_power_rev[ctz32(n)], vdup_n_u32(0), 0))
+		uint64x1_t res2 = LOAD_NEXT_POWER;
+		n &= n-1;
+		
+		if(n) {
+			// first multiply doesn't need reduction
+			res2 = vget_low_u64(pmull_low(res2, LOAD_NEXT_POWER));
+			n &= n-1;
+			
+			while(n) {
+				res = crc32_shift_pmull_mulred(res, LOAD_NEXT_POWER);
+				n &= n-1;
+				
+				if(n) {
+					res2 = crc32_shift_pmull_mulred(res2, LOAD_NEXT_POWER);
+					n &= n-1;
+				}
+			}
+		}
+		#undef LOAD_NEXT_POWER
+		
+		// merge two results
+		uint64x2_t prod = pmull_low(res, res2);
+		// weirdly, vrbitq_u8 is missing in ARM32 MSVC
+		prod = vreinterpretq_u64_u8(vrev64q_u8(vrbitq_u8(vreinterpretq_u8_u64(prod))));
+		#ifdef __aarch64__
+		crc = __crc32d(0, vgetq_lane_u64(prod, 1));
+		uint64_t rem = vgetq_lane_u64(prod, 0);
+		crc = __crc32w(rem, crc) ^ (rem >> 32);
+		#else
+		uint32x4_t prod32 = vreinterpretq_u32_u64(prod);
+		uint32_t crc = __crc32w(0, vgetq_lane_u32(prod32, 2));
+		crc = __crc32w(vgetq_lane_u32(prod32, 3), crc);
+		crc = __crc32w(vgetq_lane_u32(prod32, 0), crc) ^ vgetq_lane_u32(prod32, 1);
+		#endif
+		return crc;
+	} else {
+		#ifdef __aarch64__
+		crc = rbit64(crc);
+		crc = __crc32w(0, crc) ^ (crc >> 32);
+		return crc;
+		#else
+		uint32x2_t r = vreinterpret_u32_u64(res);
+		return __crc32w(0, rbit32(vget_lane_u32(r, 1))) ^ rbit32(vget_lane_u32(r, 0));
+		#endif
+	}
+}
+
+
+void RapidYenc::crc_pmull_set_funcs() {
+	_crc32_multiply = &crc32_multiply_pmull;
+	_crc32_shift = &crc32_shift_pmull;
+	_crc32_isa &= ISA_FEATURE_PMULL;
+}
+
+#else
+void RapidYenc::crc_pmull_set_funcs() {}
+#endif /* defined(__ARM_FEATURE_CRYPTO) && defined(__ARM_FEATURE_CRC32) */
diff --git a/src/yencode/crc_common.h b/src/yencode/crc_common.h
index bfa758b..e653d37 100644
--- a/src/yencode/crc_common.h
+++ b/src/yencode/crc_common.h
@@ -2,3 +2,25 @@
 #include <stddef.h> // for size_t
 #include "crc.h"
 
+#ifdef __GNUC__
+# define ctz32 __builtin_ctz
+#elif defined(_MSC_VER)
+static HEDLEY_ALWAYS_INLINE unsigned ctz32(uint32_t n) {
+	unsigned long r;
+	_BitScanForward(&r, n);
+	return r;
+}
+#endif
+
+namespace RapidYenc {
+	void crc_clmul_set_funcs();
+	void crc_clmul256_set_funcs();
+	void crc_arm_set_funcs();
+	void crc_pmull_set_funcs();
+	void crc_riscv_set_funcs();
+	
+	extern const uint32_t crc_power[32];
+	uint32_t crc32_multiply_generic(uint32_t a, uint32_t b);
+	uint32_t crc32_shift_generic(uint32_t crc1, uint32_t n);
+	
+}
\ No newline at end of file
diff --git a/src/yencode/crc_folding.cc b/src/yencode/crc_folding.cc
index 3018257..1af8b14 100644
--- a/src/yencode/crc_folding.cc
+++ b/src/yencode/crc_folding.cc
@@ -1,6 +1,6 @@
 // taken from zlib-ng / Intel's zlib patch, modified to remove zlib dependencies
 /*
- * Compute the CRC32 using a parallelized folding approach with the PCLMULQDQ
+ * Compute the CRC32 using a parallelized folding approach with the PCLMULQDQ 
  * instruction.
  *
  * A white paper describing this algorithm can be found at:
@@ -18,7 +18,7 @@
  */
 
 #include "crc_common.h"
-
+ 
 #if (defined(__PCLMUL__) && defined(__SSSE3__) && defined(__SSE4_1__)) || (defined(_MSC_VER) && _MSC_VER >= 1600 && defined(PLATFORM_X86) && !defined(__clang__))
 #include <inttypes.h>
 #include <immintrin.h>
@@ -36,7 +36,7 @@
 # define fold_xor _mm_xor_si128
 #else
 static __m128i fold_xor(__m128i a, __m128i b) {
-    return _mm_castps_si128(_mm_xor_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b)));
+	return _mm_castps_si128(_mm_xor_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b)));
 }
 #endif
 
@@ -140,21 +140,11 @@ static uint32_t crc_fold(const unsigned char *src, long len, uint32_t initial) {
     unsigned long algn_diff;
     __m128i xmm_t0, xmm_t1, xmm_t2, xmm_t3;
 
-    // TODO: consider calculating this via a LUT instead (probably faster)
-    // info from https://www.reddit.com/r/ReverseEngineering/comments/2zwhl3/mystery_constant_0x9db42487_in_intels_crc32ieee/
-    // firstly, calculate: xmm_crc0 = (intial * 0x487b9c8a) mod 0x104c11db7, where 0x487b9c8a = inverse(1<<512) mod 0x104c11db7
-    xmm_t0 = _mm_cvtsi32_si128(~initial);
-
-    xmm_t0 = _mm_clmulepi64_si128(xmm_t0, _mm_set_epi32(0, 0, 0xa273bc24, 0), 0);  // reverse(0x487b9c8a)<<1 == 0xa273bc24
-    xmm_t2 = _mm_set_epi32( // polynomial reduction factors
-      1, 0xdb710640, // G* = 0x04c11db7
-      0, 0xf7011641  // Q+ = 0x04d101df  (+1 to save an additional xor operation)
-    );
-    xmm_t1 = _mm_clmulepi64_si128(xmm_t0, xmm_t2, 0);
-    xmm_t1 = _mm_clmulepi64_si128(xmm_t1, xmm_t2, 0x10);
-
-    __m128i xmm_crc0 = _mm_srli_si128(_mm_xor_si128(xmm_t0, xmm_t1), 8);
-
+    // since the initial value will immediately be multiplied by around 2^512, we need to roll it backwards
+    // this is done by dividing the initial value by 2^480
+    // the constant used here is reverse(2^-480)<<1 == 0xdfded7ec
+    __m128i xmm_crc0 = _mm_clmulepi64_si128(_mm_cvtsi32_si128(~initial), _mm_cvtsi32_si128(0xdfded7ec), 0);
+    
     __m128i xmm_crc1 = _mm_setzero_si128();
     __m128i xmm_crc2 = _mm_setzero_si128();
     __m128i xmm_crc3 = _mm_setzero_si128();
@@ -362,17 +352,162 @@ static uint32_t crc_fold(const unsigned char *src, long len, uint32_t initial) {
 }
 
 static uint32_t do_crc32_incremental_clmul(const void* data, size_t length, uint32_t init) {
-    return crc_fold((const unsigned char*)data, (long)length, init);
+	return crc_fold((const unsigned char*)data, (long)length, init);
+}
+
+
+static HEDLEY_ALWAYS_INLINE __m128i crc32_reduce(__m128i prod) {
+	// do Barrett reduction back into 32-bit field
+	const __m128i reduction_const = _mm_load_si128((__m128i*)crc_k + 2);
+	__m128i t = _mm_clmulepi64_si128(prod, reduction_const, 0);
+	t = _mm_clmulepi64_si128(t, reduction_const, 0x10);
+	t = _mm_xor_si128(t, prod);
+	return t;
 }
 
-void crc_clmul_set_funcs(crc_func* _do_crc32_incremental) {
-    *_do_crc32_incremental = &do_crc32_incremental_clmul;
+static uint32_t crc32_multiply_clmul(uint32_t a, uint32_t b) {
+	// do the actual multiply
+	__m128i prod = _mm_clmulepi64_si128(_mm_cvtsi32_si128(a), _mm_cvtsi32_si128(b), 0);
+	
+	// prepare product for reduction
+	prod = _mm_add_epi64(prod, prod); // bit alignment fix, due to CRC32 being bit-reversal
+	prod = _mm_slli_si128(prod, 4);   // straddle low/high halves across 64-bit boundary - this provides automatic truncation during reduction
+	
+	prod = crc32_reduce(prod);
+	return _mm_extract_epi32(prod, 2);
 }
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+static HEDLEY_ALWAYS_INLINE __m128i reverse_bits_epi8(__m128i src) {
+#if defined(__GFNI__) && defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE!=0
+    return _mm_gf2p8affine_epi64_epi8(src, _mm_set_epi32(
+      0x80402010, 0x08040201,
+      0x80402010, 0x08040201
+    ), 0);
+/*
+#elif defined(ENABLE_AVX512)
+    // !! this only processes the bottom 32 bits !!
+    src = _mm_maskz_mov_epi32(1, src);
+    src = _mm_ternarylogic_epi32(src, _mm_slli_epi64(src, 28), _mm_set1_epi8(0xf), 0xa8); // (a|b)&c
+    src = _mm_shuffle_epi8(_mm_set_epi8(
+      -16, 112, -80, 48, -48, 80, -112, 16, -32, 96, -96, 32, -64, 64, -128, 0
+    ), src);
+    return _mm_maskz_or_epi32(1, src, _mm_srli_epi64(src, 36));
+*/
 #else
+    __m128i xmm_t0 = _mm_and_si128(src, _mm_set1_epi8(0x0f));
+    __m128i xmm_t1 = _mm_and_si128(_mm_srli_epi16(src, 4), _mm_set1_epi8(0x0f));
+    xmm_t0 = _mm_shuffle_epi8(_mm_set_epi8(
+      -16, 112, -80, 48, -48, 80, -112, 16, -32, 96, -96, 32, -64, 64, -128, 0
+      //0xf0, 0x70, 0xb0, 0x30, 0xd0, 0x50, 0x90, 0x10, 0xe0, 0x60, 0xa0, 0x20, 0xc0, 0x40, 0x80, 0
+    ), xmm_t0);
+    xmm_t1 = _mm_shuffle_epi8(_mm_set_epi8(
+      15, 7, 11, 3, 13, 5, 9, 1, 14, 6, 10, 2, 12, 4, 8, 0
+    ), xmm_t1);
+    return _mm_or_si128(xmm_t0, xmm_t1);
+#endif
+}
+
+#ifdef _MSC_VER
+// because MSVC doesn't use BSWAP unless you specifically tell it to...
+# include <stdlib.h>
+# define BSWAP32 _byteswap_ulong
+#else
+# define BSWAP32(n) ((((n)&0xff)<<24) | (((n)&0xff00)<<8) | (((n)&0xff0000)>>8) | (((n)&0xff000000)>>24))
+#endif
 
-void crc_clmul_set_funcs(crc_func *_do_crc32_incremental) {
-    (void) _do_crc32_incremental;
+
+
+static const uint32_t crc_power_rev[32] = { // bit-reversed crc_power
+	0x00000002, 0x00000004, 0x00000010, 0x00000100, 0x00010000, 0x04c11db7, 0x490d678d, 0xe8a45605,
+	0x75be46b7, 0xe6228b11, 0x567fddeb, 0x88fe2237, 0x0e857e71, 0x7001e426, 0x075de2b2, 0xf12a7f90,
+	0xf0b4a1c1, 0x58f46c0c, 0xc3395ade, 0x96837f8c, 0x544037f9, 0x23b7b136, 0xb2e16ba8, 0x725e7bfa,
+	0xec709b5d, 0xf77a7274, 0x2845d572, 0x034e2515, 0x79695942, 0x540cb128, 0x0b65d023, 0x3c344723
+};
+
+static HEDLEY_ALWAYS_INLINE __m128i crc32_shift_clmul_mulred(unsigned pos, __m128i prod) {
+	// this multiplies a 64-bit `prod` with a 32-bit CRC power
+	// compared with crc32_multiply_clmul, this only reduces the result to 64-bit, saving a multiply
+	__m128i coeff = _mm_cvtsi32_si128(crc_power_rev[pos]);
+	
+	const __m128i fold_const = _mm_set_epi32(0, 0x490d678d, 0, 0xf200aa66);
+	prod = _mm_clmulepi64_si128(prod, coeff, 0);
+	__m128i hi = _mm_clmulepi64_si128(prod, fold_const, 0x11);
+	return _mm_xor_si128(hi, prod);
+}
+
+static uint32_t crc32_shift_clmul(uint32_t crc1, uint32_t n) {
+	if(!n) return crc1;
+	
+	__m128i result = _mm_cvtsi32_si128(BSWAP32(crc1));
+	result = reverse_bits_epi8(result);
+	
+	// handle n < 32 with a shift
+	result = _mm_sll_epi64(result, _mm_cvtsi32_si128(n & 31));
+	n &= ~31;
+	
+	__m128i t;
+	if(n) {
+		// use a second accumulator to leverage some IPC from slow CLMUL
+		__m128i result2 = _mm_cvtsi32_si128(crc_power_rev[ctz32(n)]);
+		n &= n-1;
+		
+		if(n) {
+			// first multiply doesn't need reduction
+			result2 = _mm_clmulepi64_si128(result2, _mm_cvtsi32_si128(crc_power_rev[ctz32(n)]), 0);
+			n &= n-1;
+			
+			while(n) {
+				result = crc32_shift_clmul_mulred(ctz32(n), result);
+				n &= n-1;
+				
+				if(n) {
+					result2 = crc32_shift_clmul_mulred(ctz32(n), result2);
+					n &= n-1;
+				}
+			}
+		}
+		
+		const __m128i fold_const = _mm_set_epi32(0, 0x490d678d, 0, 0xf200aa66);
+		
+		// merge two results
+		result = _mm_clmulepi64_si128(result, result2, 0);
+		
+		// do 128b reduction
+		t = _mm_unpackhi_epi32(result, _mm_setzero_si128());
+		// fold [127:96] -> [63:0]
+		__m128i hi = _mm_clmulepi64_si128(t, fold_const, 1);
+		// fold [95:64] -> [63:0]
+		__m128i lo = _mm_clmulepi64_si128(t, fold_const, 0x10);
+#ifdef ENABLE_AVX512
+		result = _mm_ternarylogic_epi32(result, hi, lo, 0x96);
+#else
+		result = _mm_xor_si128(result, hi);
+		result = _mm_xor_si128(result, lo);
+#endif
+	}
+	
+	// do Barrett reduction back into 32-bit field
+	const __m128i reduction_const = _mm_set_epi32(0, 0x04c11db7, 1, 0x04d101df);
+	t = _mm_clmulepi64_si128(_mm_blend_epi16(_mm_setzero_si128(), result, 0x3c), reduction_const, 0);
+	t = _mm_clmulepi64_si128(t, reduction_const, 0x11);
+	result = _mm_xor_si128(t, result);
+	
+	result = reverse_bits_epi8(result);
+	return BSWAP32(_mm_cvtsi128_si32(result));
 }
+#endif
+
 
+void RapidYenc::crc_clmul_set_funcs() {
+	_do_crc32_incremental = &do_crc32_incremental_clmul;
+	_crc32_multiply = &crc32_multiply_clmul;
+#if defined(__GNUC__) || defined(_MSC_VER)
+	_crc32_shift = &crc32_shift_clmul;
+#endif
+	_crc32_isa = ISA_LEVEL_PCLMUL;
+}
+#else
+void RapidYenc::crc_clmul_set_funcs() {}
 #endif
 
diff --git a/src/yencode/crc_folding_256.cc b/src/yencode/crc_folding_256.cc
index b66f812..28b0757 100644
--- a/src/yencode/crc_folding_256.cc
+++ b/src/yencode/crc_folding_256.cc
@@ -1,7 +1,7 @@
 // 256-bit version of crc_folding
 
 #include "crc_common.h"
-
+ 
 #if !defined(YENC_DISABLE_AVX256) && ((defined(__VPCLMULQDQ__) && defined(__AVX2__) && defined(__PCLMUL__)) || (defined(_MSC_VER) && _MSC_VER >= 1920 && defined(PLATFORM_X86) && !defined(__clang__)))
 #include <inttypes.h>
 #include <immintrin.h>
@@ -12,33 +12,32 @@
 #endif
 
 static __m256i do_one_fold(__m256i src, __m256i data) {
-    const __m256i fold4 = _mm256_set_epi32(
-        0x00000001, 0x54442bd4,
-        0x00000001, 0xc6e41596,
-        0x00000001, 0x54442bd4,
-        0x00000001, 0xc6e41596
-    );
+	const __m256i fold4 = _mm256_set_epi32(
+		0x00000001, 0x54442bd4,
+		0x00000001, 0xc6e41596,
+		0x00000001, 0x54442bd4,
+		0x00000001, 0xc6e41596
+	);
 #ifdef ENABLE_AVX512
-    return _mm256_ternarylogic_epi32(
-      _mm256_clmulepi64_epi128(src, fold4, 0x01),
-      _mm256_clmulepi64_epi128(src, fold4, 0x10),
-      data,
-      0x96
-    );
+	return _mm256_ternarylogic_epi32(
+	  _mm256_clmulepi64_epi128(src, fold4, 0x01),
+	  _mm256_clmulepi64_epi128(src, fold4, 0x10),
+	  data,
+	  0x96
+	);
 #else
-    return _mm256_xor_si256(data, _mm256_xor_si256(
-      _mm256_clmulepi64_epi128(src, fold4, 0x01),
-      _mm256_clmulepi64_epi128(src, fold4, 0x10)
-    ));
+	return _mm256_xor_si256(_mm256_xor_si256(
+	  data, _mm256_clmulepi64_epi128(src, fold4, 0x01)
+	), _mm256_clmulepi64_epi128(src, fold4, 0x10));
 #endif
 }
 
 ALIGN_TO(32, static const uint8_t  pshufb_rot_table[]) = {
-    0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
-    16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
+	0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
+	16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
 };
 // _mm256_castsi128_si256, but upper is defined to be 0
-#if (defined(__clang__) && __clang_major__ >= 5 && (!defined(__APPLE__) || __clang_major__ >= 7)) || (defined(__GNUC__) && __GNUC__ >= 10)
+#if (defined(__clang__) && __clang_major__ >= 5 && (!defined(__APPLE__) || __clang_major__ >= 7)) || (defined(__GNUC__) && __GNUC__ >= 10) || (defined(_MSC_VER) && _MSC_VER >= 1910)
 // intrinsic unsupported in GCC 9 and MSVC < 2017
 # define zext128_256 _mm256_zextsi128_si256
 #else
@@ -60,171 +59,165 @@ ALIGN_TO(32, static const uint8_t  pshufb_rot_table[]) = {
 #endif
 
 static void partial_fold(const size_t len, __m256i *crc0, __m256i *crc1, __m256i crc_part) {
-    __m256i shuf = _mm256_broadcastsi128_si256(_mm_loadu_si128((__m128i*)(pshufb_rot_table + (len&15))));
-    __m256i mask = _mm256_cmpgt_epi8(shuf, _mm256_set1_epi8(15));
-
-    *crc0 = _mm256_shuffle_epi8(*crc0, shuf);
-    *crc1 = _mm256_shuffle_epi8(*crc1, shuf);
-    crc_part = _mm256_shuffle_epi8(crc_part, shuf);
-
-    __m256i crc_out = _mm256_permute2x128_si256(*crc0, *crc0, 0x08);  // move bottom->top
-    __m256i crc01, crc1p;
-    if(len >= 16) {
-        crc_out = MM256_BLENDV(crc_out, *crc0, mask);
-        crc01 = *crc1;
-        crc1p = crc_part;
-        *crc0 = _mm256_permute2x128_si256(*crc0, *crc1, 0x21);
-        *crc1 = _mm256_permute2x128_si256(*crc1, crc_part, 0x21);
-        crc_part = zext128_256(_mm256_extracti128_si256(crc_part, 1));
-    } else {
-        crc_out = _mm256_and_si256(crc_out, mask);
-        crc01 = _mm256_permute2x128_si256(*crc0, *crc1, 0x21);
-        crc1p = _mm256_permute2x128_si256(*crc1, crc_part, 0x21);
-    }
-
-    *crc0 = MM256_BLENDV(*crc0, crc01, mask);
-    *crc1 = MM256_BLENDV(*crc1, crc1p, mask);
-
-    *crc1 = do_one_fold(crc_out, *crc1);
+	__m256i shuf = _mm256_broadcastsi128_si256(_mm_loadu_si128((__m128i*)(pshufb_rot_table + (len&15))));
+	__m256i mask = _mm256_cmpgt_epi8(shuf, _mm256_set1_epi8(15));
+	
+	*crc0 = _mm256_shuffle_epi8(*crc0, shuf);
+	*crc1 = _mm256_shuffle_epi8(*crc1, shuf);
+	crc_part = _mm256_shuffle_epi8(crc_part, shuf);
+	
+	__m256i crc_out = _mm256_permute2x128_si256(*crc0, *crc0, 0x08);  // move bottom->top
+	__m256i crc01, crc1p;
+	if(len >= 16) {
+		crc_out = MM256_BLENDV(crc_out, *crc0, mask);
+		crc01 = *crc1;
+		crc1p = crc_part;
+		*crc0 = _mm256_permute2x128_si256(*crc0, *crc1, 0x21);
+		*crc1 = _mm256_permute2x128_si256(*crc1, crc_part, 0x21);
+		crc_part = zext128_256(_mm256_extracti128_si256(crc_part, 1));
+	} else {
+		crc_out = _mm256_and_si256(crc_out, mask);
+		crc01 = _mm256_permute2x128_si256(*crc0, *crc1, 0x21);
+		crc1p = _mm256_permute2x128_si256(*crc1, crc_part, 0x21);
+	}
+	
+	*crc0 = MM256_BLENDV(*crc0, crc01, mask);
+	*crc1 = MM256_BLENDV(*crc1, crc1p, mask);
+	
+	*crc1 = do_one_fold(crc_out, *crc1);
 }
 
 
 ALIGN_TO(16, static const unsigned crc_k[]) = {
-    0xccaa009e, 0x00000000, /* rk1 */
-    0x751997d0, 0x00000001, /* rk2 */
-    0xccaa009e, 0x00000000, /* rk5 */
-    0x63cd6124, 0x00000001, /* rk6 */
-    0xf7011641, 0x00000000, /* rk7 */
-    0xdb710640, 0x00000001  /* rk8 */
+	0xccaa009e, 0x00000000, /* rk1 */
+	0x751997d0, 0x00000001, /* rk2 */
+	0xccaa009e, 0x00000000, /* rk5 */
+	0x63cd6124, 0x00000001, /* rk6 */
+	0xf7011641, 0x00000000, /* rk7 */
+	0xdb710640, 0x00000001  /* rk8 */
 };
 
 
 static uint32_t crc_fold(const unsigned char *src, long len, uint32_t initial) {
-    // info from https://www.reddit.com/r/ReverseEngineering/comments/2zwhl3/mystery_constant_0x9db42487_in_intels_crc32ieee/
-    // firstly, calculate: xmm_crc0 = (intial * 0x487b9c8a) mod 0x104c11db7, where 0x487b9c8a = inverse(1<<512) mod 0x104c11db7
-    __m128i xmm_t0 = _mm_cvtsi32_si128(~initial);
-
-    xmm_t0 = _mm_clmulepi64_si128(xmm_t0, _mm_set_epi32(0, 0, 0xa273bc24, 0), 0);  // reverse(0x487b9c8a)<<1 == 0xa273bc24
-    __m128i reduction = _mm_set_epi32( // polynomial reduction factors
-      1, 0xdb710640, // G* = 0x04c11db7
-      0, 0xf7011641  // Q+ = 0x04d101df  (+1 to save an additional xor operation)
-    );
-    __m128i xmm_t1 = _mm_clmulepi64_si128(xmm_t0, reduction, 0);
-    xmm_t1 = _mm_clmulepi64_si128(xmm_t1, reduction, 0x10);
-
-    xmm_t0 = _mm_srli_si128(_mm_xor_si128(xmm_t0, xmm_t1), 8);
-    __m256i crc0 = zext128_256(xmm_t0);
-    __m256i crc1 = _mm256_setzero_si256();
-
-    if (len < 32) {
-        if (len == 0)
-            return initial;
-        __m256i crc_part = _mm256_setzero_si256();
-        memcpy(&crc_part, src, len);
-        partial_fold(len, &crc0, &crc1, crc_part);
-    } else {
-        uintptr_t algn_diff = (0 - (uintptr_t)src) & 0x1F;
-        if (algn_diff) {
-            partial_fold(algn_diff, &crc0, &crc1, _mm256_loadu_si256((__m256i *)src));
-            src += algn_diff;
-            len -= algn_diff;
-        }
-
-        while (len >= 64) {
-            crc0 = do_one_fold(crc0, _mm256_load_si256((__m256i*)src));
-            crc1 = do_one_fold(crc1, _mm256_load_si256((__m256i*)src + 1));
-            src += 64;
-            len -= 64;
-        }
-
-        if (len >= 32) {
-            __m256i old = crc1;
-            crc1 = do_one_fold(crc0, _mm256_load_si256((__m256i*)src));
-            crc0 = old;
-
-            len -= 32;
-            src += 32;
-        }
-
-        if(len != 0) {
-            partial_fold(len, &crc0, &crc1, _mm256_load_si256((__m256i *)src));
-        }
-    }
-
-    const __m128i xmm_mask = _mm_set_epi32(-1,-1,-1,0);
-    __m128i x_tmp0, x_tmp1, x_tmp2, crc_fold;
-
-    __m128i xmm_crc0 = _mm256_castsi256_si128(crc0);
-    __m128i xmm_crc1 = _mm256_extracti128_si256(crc0, 1);
-    __m128i xmm_crc2 = _mm256_castsi256_si128(crc1);
-    __m128i xmm_crc3 = _mm256_extracti128_si256(crc1, 1);
-
-    /*
-     * k1
-     */
-    crc_fold = _mm_load_si128((__m128i *)crc_k);
-
-    x_tmp0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x10);
-    xmm_crc0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x01);
-    xmm_crc1 = MM_2XOR(xmm_crc1, x_tmp0, xmm_crc0);
-
-    x_tmp1 = _mm_clmulepi64_si128(xmm_crc1, crc_fold, 0x10);
-    xmm_crc1 = _mm_clmulepi64_si128(xmm_crc1, crc_fold, 0x01);
-    xmm_crc2 = MM_2XOR(xmm_crc2, x_tmp1, xmm_crc1);
-
-    x_tmp2 = _mm_clmulepi64_si128(xmm_crc2, crc_fold, 0x10);
-    xmm_crc2 = _mm_clmulepi64_si128(xmm_crc2, crc_fold, 0x01);
-    xmm_crc3 = MM_2XOR(xmm_crc3, x_tmp2, xmm_crc2);
-
-    /*
-     * k5
-     */
-    crc_fold = _mm_load_si128((__m128i *)crc_k + 1);
-
-    xmm_crc0 = xmm_crc3;
-    xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0);
-    xmm_crc0 = _mm_srli_si128(xmm_crc0, 8);
-    xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
-
-    xmm_crc0 = xmm_crc3;
-    xmm_crc3 = _mm_slli_si128(xmm_crc3, 4);
-    xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0x10);
+	__m128i xmm_t0 = _mm_clmulepi64_si128(
+		_mm_cvtsi32_si128(~initial),
+		_mm_cvtsi32_si128(0xdfded7ec),
+		0
+	);
+	
+	__m256i crc0 = zext128_256(xmm_t0);
+	__m256i crc1 = _mm256_setzero_si256();
+	
+	if (len < 32) {
+		if (len == 0)
+			return initial;
+		__m256i crc_part = _mm256_setzero_si256();
+		memcpy(&crc_part, src, len);
+		partial_fold(len, &crc0, &crc1, crc_part);
+	} else {
+		uintptr_t algn_diff = (0 - (uintptr_t)src) & 0x1F;
+		if (algn_diff) {
+			partial_fold(algn_diff, &crc0, &crc1, _mm256_loadu_si256((__m256i *)src));
+			src += algn_diff;
+			len -= algn_diff;
+		}
+		
+		while (len >= 64) {
+			crc0 = do_one_fold(crc0, _mm256_load_si256((__m256i*)src));
+			crc1 = do_one_fold(crc1, _mm256_load_si256((__m256i*)src + 1));
+			src += 64;
+			len -= 64;
+		}
+		
+		if (len >= 32) {
+			__m256i old = crc1;
+			crc1 = do_one_fold(crc0, _mm256_load_si256((__m256i*)src));
+			crc0 = old;
+			
+			len -= 32;
+			src += 32;
+		}
+		
+		if(len != 0) {
+			partial_fold(len, &crc0, &crc1, _mm256_load_si256((__m256i *)src));
+		}
+	}
+	
+	const __m128i xmm_mask = _mm_set_epi32(-1,-1,-1,0);
+	__m128i x_tmp0, x_tmp1, x_tmp2, crc_fold;
+	
+	__m128i xmm_crc0 = _mm256_castsi256_si128(crc0);
+	__m128i xmm_crc1 = _mm256_extracti128_si256(crc0, 1);
+	__m128i xmm_crc2 = _mm256_castsi256_si128(crc1);
+	__m128i xmm_crc3 = _mm256_extracti128_si256(crc1, 1);
+
+	/*
+	 * k1
+	 */
+	crc_fold = _mm_load_si128((__m128i *)crc_k);
+
+	x_tmp0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x10);
+	xmm_crc0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x01);
+	xmm_crc1 = MM_2XOR(xmm_crc1, x_tmp0, xmm_crc0);
+
+	x_tmp1 = _mm_clmulepi64_si128(xmm_crc1, crc_fold, 0x10);
+	xmm_crc1 = _mm_clmulepi64_si128(xmm_crc1, crc_fold, 0x01);
+	xmm_crc2 = MM_2XOR(xmm_crc2, x_tmp1, xmm_crc1);
+
+	x_tmp2 = _mm_clmulepi64_si128(xmm_crc2, crc_fold, 0x10);
+	xmm_crc2 = _mm_clmulepi64_si128(xmm_crc2, crc_fold, 0x01);
+	xmm_crc3 = MM_2XOR(xmm_crc3, x_tmp2, xmm_crc2);
+
+	/*
+	 * k5
+	 */
+	crc_fold = _mm_load_si128((__m128i *)crc_k + 1);
+
+	xmm_crc0 = xmm_crc3;
+	xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0);
+	xmm_crc0 = _mm_srli_si128(xmm_crc0, 8);
+	xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
+
+	xmm_crc0 = xmm_crc3;
+	xmm_crc3 = _mm_slli_si128(xmm_crc3, 4);
+	xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0x10);
 #ifdef ENABLE_AVX512
-    //xmm_crc3 = _mm_maskz_xor_epi32(14, xmm_crc3, xmm_crc0);
-    xmm_crc3 = _mm_ternarylogic_epi32(xmm_crc3, xmm_crc0, xmm_mask, 0x28);
+	//xmm_crc3 = _mm_maskz_xor_epi32(14, xmm_crc3, xmm_crc0);
+	xmm_crc3 = _mm_ternarylogic_epi32(xmm_crc3, xmm_crc0, xmm_mask, 0x28);
 #else
-    xmm_crc0 = _mm_and_si128(xmm_crc0, xmm_mask);
-    xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
+	xmm_crc0 = _mm_and_si128(xmm_crc0, xmm_mask);
+	xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
 #endif
 
-    /*
-     * k7
-     */
-    xmm_crc1 = xmm_crc3;
-    crc_fold = _mm_load_si128((__m128i *)crc_k + 2);
+	/*
+	 * k7
+	 */
+	xmm_crc1 = xmm_crc3;
+	crc_fold = _mm_load_si128((__m128i *)crc_k + 2);
 
-    xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0);
-    xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0x10);
+	xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0);
+	xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, crc_fold, 0x10);
 #ifdef ENABLE_AVX512
-    xmm_crc3 = _mm_ternarylogic_epi32(xmm_crc3, xmm_crc1, xmm_crc1, 0xC3); // NOT(xmm_crc3 ^ xmm_crc1)
+	xmm_crc3 = _mm_ternarylogic_epi32(xmm_crc3, xmm_crc1, xmm_crc1, 0xC3); // NOT(xmm_crc3 ^ xmm_crc1)
 #else
-    xmm_crc1 = _mm_xor_si128(xmm_crc1, xmm_mask);
-    xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc1);
+	xmm_crc1 = _mm_xor_si128(xmm_crc1, xmm_mask);
+	xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc1);
 #endif
-    return _mm_extract_epi32(xmm_crc3, 2);
+	return _mm_extract_epi32(xmm_crc3, 2);
 }
 
-static uint32_t do_crc32_incremental_clmul(const void *data, size_t length, uint32_t init) {
-    return crc_fold((const unsigned char*)data, (long)length, init);
+static uint32_t do_crc32_incremental_clmul(const void* data, size_t length, uint32_t init) {
+	return crc_fold((const unsigned char*)data, (long)length, init);
 }
 
-void crc_clmul256_set_funcs(crc_func *_do_crc32_incremental) {
-    *_do_crc32_incremental = &do_crc32_incremental_clmul;
+void RapidYenc::crc_clmul256_set_funcs() {
+	crc_clmul_set_funcs(); // set multiply/shift function
+	_do_crc32_incremental = &do_crc32_incremental_clmul;
+	_crc32_isa = ISA_LEVEL_VPCLMUL;
 }
 #else
-void crc_clmul_set_funcs(crc_func *_do_crc32_incremental);
-void crc_clmul256_set_funcs(crc_func *_do_crc32_incremental) {
-    crc_clmul_set_funcs(_do_crc32_incremental);
+void RapidYenc::crc_clmul256_set_funcs() {
+	crc_clmul_set_funcs();
 }
 #endif
 
diff --git a/src/yencode/crc_riscv.cc b/src/yencode/crc_riscv.cc
new file mode 100644
index 0000000..34ad2bf
--- /dev/null
+++ b/src/yencode/crc_riscv.cc
@@ -0,0 +1,251 @@
+#include "crc_common.h"
+
+#if defined(__riscv) && defined(__GNUC__) && (defined(__riscv_zbkc) || defined(__riscv_zbc))
+
+#if __has_include(<riscv_bitmanip.h>)
+# include <riscv_bitmanip.h>
+# if __riscv_xlen == 64
+#  define rv_clmul __riscv_clmul_64
+#  define rv_clmulh __riscv_clmulh_64
+# else
+#  define rv_clmul __riscv_clmul_32
+#  define rv_clmulh __riscv_clmulh_32
+# endif
+#else
+static HEDLEY_ALWAYS_INLINE uintptr_t rv_clmul(uintptr_t x, uintptr_t y) {
+	uintptr_t r;
+	__asm__("clmul %0, %1, %2\n"
+		: "=r"(r)
+		: "r"(x), "r"(y)
+		:);
+	return r;
+}
+static HEDLEY_ALWAYS_INLINE uintptr_t rv_clmulh(uintptr_t x, uintptr_t y) {
+	uintptr_t r;
+	__asm__("clmulh %0, %1, %2\n"
+		: "=r"(r)
+		: "r"(x), "r"(y)
+		:);
+	return r;
+}
+#endif
+
+// TODO: test big-endian
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# if __riscv_xlen == 64
+#  define SWAP __builtin_bswap64
+# else
+#  define SWAP __builtin_bswap32
+# endif
+#else
+# define SWAP(d) (d)
+#endif
+static HEDLEY_ALWAYS_INLINE uintptr_t read_partial(const void* p, unsigned sz) {
+	uintptr_t data = 0;
+	memcpy(&data, p, sz);
+	return SWAP(data);
+}
+static HEDLEY_ALWAYS_INLINE uintptr_t read_full(const uintptr_t* p) {
+	return SWAP(*p);
+}
+#undef SWAP
+
+static uint32_t rv_crc_calc(uint32_t crc, const unsigned char *src, long len) {
+	uintptr_t accum[4] = {};
+	
+	// note: constants here are bit-reflected and shifted left by 1
+	// Zbc does also have clmulr to avoid the shift, but:
+	// - there's no clmulhr, so for XLEN=64, just shift the constant instead to get the same result
+	// - it's unavailable in Zbkc
+	// - for XLEN=32, 2x constants is likely worth it to avoid the additional XORs in the loop
+	
+#if __riscv_xlen == 64
+	const uint64_t MUL_HI = 0x15a546366 /*2^224*/, MUL_LO = 0xf1da05aa /*2^288*/;
+	#define CLMULL rv_clmul
+	#define CLMULH rv_clmulh
+	
+	accum[3] = rv_clmul(crc, 0xb66b1fa6); // 2^-32
+#elif __riscv_xlen == 32
+	const uint64_t MUL_HI = 0x140d44a2e /*2^128*/,  MUL_LO = 0x1751997d0 /*2^160*/;
+	#define CLMULL(x, k) rv_clmul(x, k & 0xffffffff)
+	#define CLMULH(x, k) (rv_clmulh(x, k & 0xffffffff) ^ (k > 0xffffffffULL ? (x) : 0))
+	
+	accum[2] = rv_clmul(crc, 0xb66b1fa6);
+	accum[3] = rv_clmulh(crc, 0xb66b1fa6);
+#else
+	#error "Unknown __riscv_xlen"
+#endif
+	const size_t WS = sizeof(uintptr_t);
+	
+	// if src isn't word-aligned, process until it is so
+	long initial_alignment = ((uintptr_t)src & (WS-1));
+	long initial_process = WS - initial_alignment;
+	if(initial_alignment && len >= initial_process) {
+		unsigned shl = initial_alignment * 8, shr = initial_process * 8;
+#if __riscv_xlen == 64
+		accum[2] = accum[3] << shl;
+#else
+		accum[1] = accum[2] << shl;
+		accum[2] = (accum[3] << shl) | (accum[2] >> shr);
+#endif
+		accum[3] = (read_partial(src, initial_process) << shl) | (accum[3] >> shr);
+		src += initial_process;
+		len -= initial_process;
+	}
+	
+	// main processing loop
+	const uintptr_t* srcW = (const uintptr_t*)src;
+	while((len -= WS*4) >= 0) {
+		uintptr_t tmpHi, tmpLo;
+		tmpLo = CLMULL(accum[0], MUL_LO) ^ CLMULL(accum[1], MUL_HI);
+		tmpHi = CLMULH(accum[0], MUL_LO) ^ CLMULH(accum[1], MUL_HI);
+		accum[0] = tmpLo ^ read_full(srcW++);
+		accum[1] = tmpHi ^ read_full(srcW++);
+		
+		tmpLo = CLMULL(accum[2], MUL_LO) ^ CLMULL(accum[3], MUL_HI);
+		tmpHi = CLMULH(accum[2], MUL_LO) ^ CLMULH(accum[3], MUL_HI);
+		accum[2] = tmpLo ^ read_full(srcW++);
+		accum[3] = tmpHi ^ read_full(srcW++);
+	}
+	
+	// process trailing bytes
+	if(len & (WS*2)) {
+		uintptr_t tmpLo = CLMULL(accum[0], MUL_LO) ^ CLMULL(accum[1], MUL_HI);
+		uintptr_t tmpHi = CLMULH(accum[0], MUL_LO) ^ CLMULH(accum[1], MUL_HI);
+		accum[0] = accum[2];
+		accum[1] = accum[3];
+		accum[2] = tmpLo ^ read_full(srcW++);
+		accum[3] = tmpHi ^ read_full(srcW++);
+	}
+	if(len & WS) {
+		uintptr_t tmpLo = CLMULL(accum[0], MUL_HI);
+		uintptr_t tmpHi = CLMULH(accum[0], MUL_HI);
+		accum[0] = accum[1];
+		accum[1] = accum[2];
+		accum[2] = accum[3] ^ tmpLo;
+		accum[3] = tmpHi ^ read_full(srcW++);
+	}
+	
+	size_t tail = len & (WS-1);
+	if(tail) {
+		unsigned shl = ((WS - tail) * 8), shr = tail * 8;
+		uintptr_t tmp = accum[0] << shl;
+		uintptr_t tmpLo = CLMULL(tmp, MUL_HI);
+		uintptr_t tmpHi = CLMULH(tmp, MUL_HI);
+		accum[0] = (accum[0] >> shr) | (accum[1] << shl);
+		accum[1] = (accum[1] >> shr) | (accum[2] << shl);
+		accum[2] = (accum[2] >> shr) | (accum[3] << shl);
+		accum[3] = (accum[3] >> shr) | (read_partial(srcW, tail) << shl);
+		accum[2] ^= tmpLo;
+		accum[3] ^= tmpHi;
+	}
+	
+	
+	// done processing: fold everything down
+#if __riscv_xlen == 64
+	// fold 0,1 -> 2,3
+	accum[2] ^= rv_clmul(accum[0], 0x1751997d0) ^ rv_clmul(accum[1], 0xccaa009e);
+	accum[3] ^= rv_clmulh(accum[0], 0x1751997d0) ^ rv_clmulh(accum[1], 0xccaa009e);
+	
+	// fold 2->3
+	accum[0] = rv_clmulh(accum[2], 0xccaa009e);
+	accum[3] ^= rv_clmul(accum[2], 0xccaa009e);
+	
+	// fold 64b->32b
+	accum[1] = rv_clmul(accum[3] & 0xffffffff, 0x163cd6124);
+	accum[0] ^= accum[1] >> 32;
+	accum[3] = accum[1] ^ (accum[3] >> 32);
+	accum[3] <<= 32;
+#else
+	// fold 0,1 -> 2,3
+	accum[2] ^= rv_clmul(accum[0], 0xccaa009e) ^ CLMULL(accum[1], 0x163cd6124);
+	accum[3] ^= rv_clmulh(accum[0], 0xccaa009e) ^ CLMULH(accum[1], 0x163cd6124);
+	
+	// fold 2->3
+	accum[0] = CLMULH(accum[2], 0x163cd6124);
+	accum[3] ^= CLMULL(accum[2], 0x163cd6124);
+#endif
+	
+	// reduction
+	accum[3] = CLMULL(accum[3], 0xf7011641);
+	accum[3] = CLMULH(accum[3], 0x1db710640);  // maybe consider clmulr for XLEN=32
+	crc = accum[0] ^ accum[3];
+	return crc;
+	#undef CLMULL
+	#undef CLMULH
+}
+
+static uint32_t do_crc32_incremental_rv_zbc(const void* data, size_t length, uint32_t init) {
+	return ~rv_crc_calc(~init, (const unsigned char*)data, (long)length);
+}
+
+
+#if __riscv_xlen == 64
+	// note that prod is shifted by 1 place to the right, due to bit-reflection
+static uint32_t crc32_reduce_rv_zbc(uint64_t prod) {
+	uint64_t t = rv_clmul(prod << 33, 0xf7011641);
+	t = rv_clmulh(t, 0x1db710640);
+	t ^= prod >> 31;
+	return t;
+}
+#endif
+static uint32_t crc32_multiply_rv_zbc(uint32_t a, uint32_t b) {
+#if __riscv_xlen == 64
+	uint64_t t = crc32_reduce_rv_zbc(rv_clmul(a, b));
+#else
+	uint32_t prodLo = rv_clmul(a, b);
+	uint32_t prodHi = rv_clmulh(a, b);
+	
+	// fix prodHi for bit-reflection (clmulr would be ideal here)
+	prodHi += prodHi;
+	prodHi |= prodLo >> 31;
+	prodLo += prodLo;
+	
+	uint32_t t = rv_clmul(prodLo, 0xf7011641);
+	t ^= rv_clmulh(t, 0xdb710640);
+	t ^= prodHi;
+#endif
+	return t;
+}
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+static uint32_t crc32_shift_rv_zbc(uint32_t crc1, uint32_t n) {
+	// TODO: require Zbb for ctz
+	uint32_t result = crc1;
+#if __riscv_xlen == 64
+	// for n<32, can shift directly
+	uint64_t prod = result;
+	prod <<= 31 ^ (n&31);
+	n &= ~31;
+	result = crc32_reduce_rv_zbc(prod);
+#endif
+	if(!n) return result;
+	
+	uint32_t result2 = RapidYenc::crc_power[ctz32(n)];
+	n &= n-1;
+	
+	while(n) {
+		result = crc32_multiply_rv_zbc(result, RapidYenc::crc_power[ctz32(n)]);
+		n &= n-1;
+		
+		if(n) {
+			result2 = crc32_multiply_rv_zbc(result2, RapidYenc::crc_power[ctz32(n)]);
+			n &= n-1;
+		}
+	}
+	return crc32_multiply_rv_zbc(result, result2);
+}
+#endif
+
+
+void RapidYenc::crc_riscv_set_funcs() {
+	_do_crc32_incremental = &do_crc32_incremental_rv_zbc;
+	_crc32_multiply = &crc32_multiply_rv_zbc;
+#if defined(__GNUC__) || defined(_MSC_VER)
+	_crc32_shift = &crc32_shift_rv_zbc;
+#endif
+	_crc32_isa = ISA_FEATURE_ZBC;
+}
+#else
+void RapidYenc::crc_riscv_set_funcs() {}
+#endif
diff --git a/src/yencode/decoder.cc b/src/yencode/decoder.cc
index fc9432b..6dda4ef 100644
--- a/src/yencode/decoder.cc
+++ b/src/yencode/decoder.cc
@@ -3,103 +3,427 @@
 #include "decoder_common.h"
 #include "decoder.h"
 
-extern "C" {
-YencDecoderEnd (*_do_decode)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t,
-                             YencDecoderState *) = &do_decode_scalar<false, false>;
-YencDecoderEnd (*_do_decode_raw)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t,
-                                 YencDecoderState *) = &do_decode_scalar<true, false>;
-YencDecoderEnd (*_do_decode_end_raw)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t,
-                                     YencDecoderState *) = &do_decode_end_scalar<true>;
-int _decode_simd_level = 0;
-}
 
-void decoder_set_sse2_funcs();
 
-void decoder_set_ssse3_funcs();
+// TODO: add branch probabilities
 
-void decoder_set_avx_funcs();
 
-void decoder_set_avx2_funcs();
+// state var: refers to the previous state - only used for incremental processing
+template<bool isRaw>
+static size_t do_decode_noend_scalar(const unsigned char* src, unsigned char* dest, size_t len, RapidYenc::YencDecoderState* state) {
+	using namespace RapidYenc;
+	
+	const unsigned char *es = src + len; // end source pointer
+	unsigned char *p = dest; // destination pointer
+	long i = -(long)len; // input position
+	unsigned char c; // input character
+	
+	if(len < 1) return 0;
+	
+	if(isRaw) {
+		
+		if(state) switch(*state) {
+			case YDEC_STATE_EQ:
+				c = es[i];
+				*p++ = c - 42 - 64;
+				i++;
+				if(c == '\r') {
+					*state = YDEC_STATE_CR;
+					if(i >= 0) return 0;
+				} else {
+					*state = YDEC_STATE_NONE;
+					break;
+				}
+				// fall-thru
+			case YDEC_STATE_CR:
+				if(es[i] != '\n') break;
+				i++;
+				*state = YDEC_STATE_CRLF;
+				if(i >= 0) return 0;
+				// Else fall-thru
+			case YDEC_STATE_CRLF:
+				// skip past first dot
+				if(es[i] == '.') i++;
+				// fall-thru
+			default: break; // silence compiler warnings
+		} else // treat as YDEC_STATE_CRLF
+			if(es[i] == '.') i++;
+		
+		for(; i < -2; i++) {
+			c = es[i];
+			switch(c) {
+				case '\r':
+					// skip past \r\n. sequences
+					//i += (es[i+1] == '\n' && es[i+2] == '.') << 1;
+					if(es[i+1] == '\n' && es[i+2] == '.')
+						i += 2;
+					// fall-thru
+				case '\n':
+					continue;
+				case '=':
+					c = es[i+1];
+					*p++ = c - 42 - 64;
+					i += (c != '\r'); // if we have a \r, reprocess character to deal with \r\n. case
+					continue;
+				default:
+					*p++ = c - 42;
+			}
+		}
+		if(state) *state = YDEC_STATE_NONE;
+		
+		if(i == -2) { // 2nd last char
+			c = es[i];
+			switch(c) {
+				case '\r':
+					if(state && es[i+1] == '\n') {
+						*state = YDEC_STATE_CRLF;
+						return p - dest;
+					}
+					// Else fall-thru
+				case '\n':
+					break;
+				case '=':
+					c = es[i+1];
+					*p++ = c - 42 - 64;
+					i += (c != '\r');
+					break;
+				default:
+					*p++ = c - 42;
+			}
+			i++;
+		}
+		
+		// do final char; we process this separately to prevent an overflow if the final char is '='
+		if(i == -1) {
+			c = es[i];
+			if(c != '\n' && c != '\r' && c != '=') {
+				*p++ = c - 42;
+			} else if(state) {
+				if(c == '=') *state = YDEC_STATE_EQ;
+				else if(c == '\r') *state = YDEC_STATE_CR;
+				else *state = YDEC_STATE_NONE;
+			}
+		}
+		
+	} else {
+		
+		if(state && *state == YDEC_STATE_EQ) {
+			*p++ = es[i] - 42 - 64;
+			i++;
+			*state = YDEC_STATE_NONE;
+		}
+		
+		/*for(i = 0; i < len - 1; i++) {
+			c = src[i];
+			if(c == '\n' || c == '\r') continue;
+			unsigned char isEquals = (c == '=');
+			i += isEquals;
+			*p++ = src[i] - (42 + (isEquals << 6));
+		}*/
+		for(; i < -1; i++) {
+			c = es[i];
+			switch(c) {
+				case '\n': case '\r': continue;
+				case '=':
+					i++;
+					c = es[i] - 64;
+			}
+			*p++ = c - 42;
+		}
+		if(state) *state = YDEC_STATE_NONE;
+		// do final char; we process this separately to prevent an overflow if the final char is '='
+		if(i == -1) {
+			c = es[i];
+			if(c != '\n' && c != '\r' && c != '=') {
+				*p++ = c - 42;
+			} else
+				if(state) *state = (c == '=' ? YDEC_STATE_EQ : YDEC_STATE_NONE);
+		}
+		
+	}
+	
+	return p - dest;
+}
 
-void decoder_set_vbmi2_funcs();
+template<bool isRaw>
+static RapidYenc::YencDecoderEnd do_decode_end_scalar(const unsigned char** src, unsigned char** dest, size_t len, RapidYenc::YencDecoderState* state) {
+	using namespace RapidYenc;
+	
+	const unsigned char *es = (*src) + len; // end source pointer
+	unsigned char *p = *dest; // destination pointer
+	long i = -(long)len; // input position
+	unsigned char c; // input character
+	
+	if(len < 1) return YDEC_END_NONE;
+	
+#define YDEC_CHECK_END(s) if(i == 0) { \
+	*state = s; \
+	*src = es; \
+	*dest = p; \
+	return YDEC_END_NONE; \
+}
+	if(state) switch(*state) {
+		case YDEC_STATE_CRLFEQ: do_decode_endable_scalar_ceq:
+			if(es[i] == 'y') {
+				*state = YDEC_STATE_NONE;
+				*src = es+i+1;
+				*dest = p;
+				return YDEC_END_CONTROL;
+			} // Else fall-thru
+		case YDEC_STATE_EQ:
+			c = es[i];
+			*p++ = c - 42 - 64;
+			i++;
+			if(c != '\r') break;
+			YDEC_CHECK_END(YDEC_STATE_CR)
+			// fall-through
+		case YDEC_STATE_CR:
+			if(es[i] != '\n') break;
+			i++;
+			YDEC_CHECK_END(YDEC_STATE_CRLF)
+			// fall-through
+		case YDEC_STATE_CRLF: do_decode_endable_scalar_c0:
+			if(es[i] == '.' && isRaw) {
+				i++;
+				YDEC_CHECK_END(YDEC_STATE_CRLFDT)
+			} else if(es[i] == '=') {
+				i++;
+				YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
+				goto do_decode_endable_scalar_ceq;
+			} else
+				break;
+			// fall-through
+		case YDEC_STATE_CRLFDT:
+			if(isRaw && es[i] == '\r') {
+				i++;
+				YDEC_CHECK_END(YDEC_STATE_CRLFDTCR)
+			} else if(isRaw && es[i] == '=') { // check for dot-stuffed ending: \r\n.=y
+				i++;
+				YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
+				goto do_decode_endable_scalar_ceq;
+			} else
+				break;
+			// fall-through
+		case YDEC_STATE_CRLFDTCR:
+			if(es[i] == '\n') {
+				if(isRaw) {
+					*state = YDEC_STATE_CRLF;
+					*src = es + i + 1;
+					*dest = p;
+					return YDEC_END_ARTICLE;
+				} else {
+					i++;
+					YDEC_CHECK_END(YDEC_STATE_CRLF)
+					goto do_decode_endable_scalar_c0; // handle as CRLF
+				}
+			} else
+				break;
+		case YDEC_STATE_NONE: break; // silence compiler warning
+	} else // treat as YDEC_STATE_CRLF
+		goto do_decode_endable_scalar_c0;
+	
+	for(; i < -2; i++) {
+		c = es[i];
+		switch(c) {
+			case '\r': if(es[i+1] == '\n') {
+				if(isRaw && es[i+2] == '.') {
+					// skip past \r\n. sequences
+					i += 3;
+					YDEC_CHECK_END(YDEC_STATE_CRLFDT)
+					// check for end
+					if(es[i] == '\r') {
+						i++;
+						YDEC_CHECK_END(YDEC_STATE_CRLFDTCR)
+						if(es[i] == '\n') {
+							*src = es + i + 1;
+							*dest = p;
+							*state = YDEC_STATE_CRLF;
+							return YDEC_END_ARTICLE;
+						} else i--;
+					} else if(es[i] == '=') {
+						i++;
+						YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
+						if(es[i] == 'y') {
+							*src = es + i + 1;
+							*dest = p;
+							*state = YDEC_STATE_NONE;
+							return YDEC_END_CONTROL;
+						} else {
+							// escape char & continue
+							c = es[i];
+							*p++ = c - 42 - 64;
+							i -= (c == '\r');
+						}
+					} else i--;
+				}
+				else if(es[i+2] == '=') {
+					i += 3;
+					YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
+					if(es[i] == 'y') {
+						// ended
+						*src = es + i + 1;
+						*dest = p;
+						*state = YDEC_STATE_NONE;
+						return YDEC_END_CONTROL;
+					} else {
+						// escape char & continue
+						c = es[i];
+						*p++ = c - 42 - 64;
+						i -= (c == '\r');
+					}
+				}
+			} // fall-thru
+			case '\n':
+				continue;
+			case '=':
+				c = es[i+1];
+				*p++ = c - 42 - 64;
+				i += (c != '\r'); // if we have a \r, reprocess character to deal with \r\n. case
+				continue;
+			default:
+				*p++ = c - 42;
+		}
+	}
+	if(state) *state = YDEC_STATE_NONE;
+	
+	if(i == -2) { // 2nd last char
+		c = es[i];
+		switch(c) {
+			case '\r':
+				if(state && es[i+1] == '\n') {
+					*state = YDEC_STATE_CRLF;
+					*src = es;
+					*dest = p;
+					return YDEC_END_NONE;
+				}
+				// Else fall-thru
+			case '\n':
+				break;
+			case '=':
+				c = es[i+1];
+				*p++ = c - 42 - 64;
+				i += (c != '\r');
+				break;
+			default:
+				*p++ = c - 42;
+		}
+		i++;
+	}
+	
+	// do final char; we process this separately to prevent an overflow if the final char is '='
+	if(i == -1) {
+		c = es[i];
+		if(c != '\n' && c != '\r' && c != '=') {
+			*p++ = c - 42;
+		} else if(state) {
+			if(c == '=') *state = YDEC_STATE_EQ;
+			else if(c == '\r') *state = YDEC_STATE_CR;
+			else *state = YDEC_STATE_NONE;
+		}
+	}
+#undef YDEC_CHECK_END
+	
+	*src = es;
+	*dest = p;
+	return YDEC_END_NONE;
+}
 
-void decoder_set_neon_funcs();
+template<bool isRaw, bool searchEnd>
+RapidYenc::YencDecoderEnd RapidYenc::do_decode_scalar(const unsigned char** src, unsigned char** dest, size_t len, RapidYenc::YencDecoderState* state) {
+	if(searchEnd)
+		return do_decode_end_scalar<isRaw>(src, dest, len, state);
+	*dest += do_decode_noend_scalar<isRaw>(*src, *dest, len, state);
+	*src += len;
+	return YDEC_END_NONE;
+}
 
 
-#if defined(PLATFORM_X86) && defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE != 0
+namespace RapidYenc {
+	YencDecoderEnd (*_do_decode)(const unsigned char**, unsigned char**, size_t, YencDecoderState*) = &do_decode_scalar<false, false>;
+	YencDecoderEnd (*_do_decode_raw)(const unsigned char**, unsigned char**, size_t, YencDecoderState*) = &do_decode_scalar<true, false>;
+	YencDecoderEnd (*_do_decode_end_raw)(const unsigned char**, unsigned char**, size_t, YencDecoderState*) = &do_decode_end_scalar<true>;
+	
+	int _decode_isa = ISA_GENERIC;
+	
+	template YencDecoderEnd do_decode_scalar<true, true>(const unsigned char**, unsigned char**, size_t, YencDecoderState*);
+}
+
+
+#if defined(PLATFORM_X86) && defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE!=0
 # if defined(__AVX2__) && !defined(YENC_DISABLE_AVX256)
 #  include "decoder_avx2_base.h"
 static inline void decoder_set_native_funcs() {
-    ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_NATIVE> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_NATIVE> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_NATIVE> >;
-    _decode_simd_level = ISA_NATIVE;
+	ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
+	using namespace RapidYenc;
+	decoder_init_lut(lookups->compact);
+	_do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_NATIVE> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_NATIVE> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_NATIVE> >;
+	_decode_isa = ISA_NATIVE;
 }
 # else
 #  include "decoder_sse_base.h"
 static inline void decoder_set_native_funcs() {
-    decoder_sse_init();
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_NATIVE> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_NATIVE> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_NATIVE> >;
-    _decode_simd_level = ISA_NATIVE;
+	using namespace RapidYenc;
+	decoder_sse_init(lookups);
+	decoder_init_lut(lookups->compact);
+	_do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_NATIVE> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_NATIVE> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_NATIVE> >;
+	_decode_isa = ISA_NATIVE;
 }
 # endif
 #endif
 
-void decoder_init() {
+
+#if defined(PLATFORM_X86) || defined(PLATFORM_ARM)
+void RapidYenc::decoder_init_lut(void* compactLUT) {
+	#ifdef YENC_DEC_USE_THINTABLE
+	const int tableSize = 8;
+	#else
+	const int tableSize = 16;
+	#endif
+	for(int i=0; i<(tableSize==8?256:32768); i++) {
+		int k = i;
+		uint8_t* res = (uint8_t*)compactLUT + i*tableSize;
+		int p = 0;
+		for(int j=0; j<tableSize; j++) {
+			if(!(k & 1)) {
+				res[p++] = j;
+			}
+			k >>= 1;
+		}
+		for(; p<tableSize; p++)
+			res[p] = 0x80;
+	}
+}
+#endif
+
+
+void RapidYenc::decoder_init() {
 #ifdef PLATFORM_X86
 # if defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE!=0
-    decoder_set_native_funcs();
+	decoder_set_native_funcs();
 # else
-    int use_isa = cpu_supports_isa();
-    if(use_isa >= ISA_LEVEL_VBMI2) {
-        decoder_set_vbmi2_funcs();
-    } else if(use_isa >= ISA_LEVEL_AVX2) {
-        decoder_set_avx2_funcs();
-    } else if(use_isa >= ISA_LEVEL_AVX) {
-        decoder_set_avx_funcs();
-    } else if(use_isa >= ISA_LEVEL_SSSE3) {
-        decoder_set_ssse3_funcs();
-    } else {
-        decoder_set_sse2_funcs();
-    }
+	int use_isa = cpu_supports_isa();
+	if(use_isa >= ISA_LEVEL_VBMI2 && (decoder_has_avx10 || (use_isa & ISA_FEATURE_EVEX512)))
+		decoder_set_vbmi2_funcs();
+	else if(use_isa >= ISA_LEVEL_AVX2)
+		decoder_set_avx2_funcs();
+	else if(use_isa >= ISA_LEVEL_AVX)
+		decoder_set_avx_funcs();
+	else if(use_isa >= ISA_LEVEL_SSSE3)
+		decoder_set_ssse3_funcs();
+	else
+		decoder_set_sse2_funcs();
 # endif
 #endif
 #ifdef PLATFORM_ARM
-    if(cpu_supports_neon()) {
-        decoder_set_neon_funcs();
-    }
+	if(cpu_supports_neon())
+		decoder_set_neon_funcs();
 #endif
-}
-
-const char* simd_detected() {
-#ifdef PLATFORM_X86
-    if(_decode_simd_level >= ISA_LEVEL_VBMI2)
-        return "AVX512VL+VBMI2";
-    if(_decode_simd_level >= ISA_LEVEL_AVX3)
-        return "AVX512VL";
-    if(_decode_simd_level >= ISA_LEVEL_AVX2)
-        return "AVX2";
-    if(_decode_simd_level >= ISA_LEVEL_AVX)
-        return "AVX";
-    if(_decode_simd_level >= ISA_LEVEL_SSE4_POPCNT)
-        return "SSE4.1+POPCNT";
-    if(_decode_simd_level >= ISA_LEVEL_SSE41)
-        return "SSE4.1";
-    if(_decode_simd_level >= ISA_LEVEL_SSSE3)
-        return "SSSE3";
-    if(_decode_simd_level >= (ISA_LEVEL_SSE2 | ISA_FEATURE_POPCNT | ISA_FEATURE_LZCNT))
-        return "SSE2+ABM";
-    return "SSE2";
-#endif
-#ifdef PLATFORM_ARM
-    if(_decode_simd_level >= 1) {
-        return "NEON";
-    }
+#ifdef __riscv
+	if(cpu_supports_rvv())
+		decoder_set_rvv_funcs();
 #endif
-    return "";
 }
diff --git a/src/yencode/decoder.h b/src/yencode/decoder.h
index 3f9381c..60a4d25 100644
--- a/src/yencode/decoder.h
+++ b/src/yencode/decoder.h
@@ -1,65 +1,53 @@
 #ifndef __YENC_DECODER_H
 #define __YENC_DECODER_H
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include "hedley.h"
+
+namespace RapidYenc {
 
 
 // the last state that the decoder was in (i.e. last few characters processed)
 // the state is needed for incremental decoders as its behavior is affected by what it processed last
 // acronyms: CR = carriage return (\r), LF = line feed (\n), EQ = equals char, DT = dot char (.)
 typedef enum {
-    YDEC_STATE_CRLF, // default
-    YDEC_STATE_EQ,
-    YDEC_STATE_CR,
-    YDEC_STATE_NONE,
-    YDEC_STATE_CRLFDT,
-    YDEC_STATE_CRLFDTCR,
-    YDEC_STATE_CRLFEQ // may actually be "\r\n.=" in raw decoder
+	YDEC_STATE_CRLF, // default
+	YDEC_STATE_EQ,
+	YDEC_STATE_CR,
+	YDEC_STATE_NONE,
+	YDEC_STATE_CRLFDT,
+	YDEC_STATE_CRLFDTCR,
+	YDEC_STATE_CRLFEQ // may actually be "\r\n.=" in raw decoder
 } YencDecoderState;
 
 // end result for incremental processing (whether the end of the yEnc data was reached)
 typedef enum {
-    YDEC_END_NONE,    // end not reached
-    YDEC_END_CONTROL, // \r\n=y sequence found, src points to byte after 'y'
-    YDEC_END_ARTICLE  // \r\n.\r\n sequence found, src points to byte after last '\n'
+	YDEC_END_NONE,    // end not reached
+	YDEC_END_CONTROL, // \r\n=y sequence found, src points to byte after 'y'
+	YDEC_END_ARTICLE  // \r\n.\r\n sequence found, src points to byte after last '\n'
 } YencDecoderEnd;
 
-#include "hedley.h"
-
-extern YencDecoderEnd
-(*_do_decode)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t, YencDecoderState *);
 
-extern YencDecoderEnd
-(*_do_decode_raw)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t, YencDecoderState *);
+extern YencDecoderEnd (*_do_decode)(const unsigned char**, unsigned char**, size_t, YencDecoderState*);
+extern YencDecoderEnd (*_do_decode_raw)(const unsigned char**, unsigned char**, size_t, YencDecoderState*);
+extern YencDecoderEnd (*_do_decode_end_raw)(const unsigned char**, unsigned char**, size_t, YencDecoderState*);
+extern int _decode_isa;
 
-extern YencDecoderEnd
-(*_do_decode_end_raw)(const unsigned char *HEDLEY_RESTRICT *, unsigned char *HEDLEY_RESTRICT *, size_t,
-                      YencDecoderState *);
-
-extern int _decode_simd_level;
-
-static inline size_t
-do_decode(int isRaw, const unsigned char *HEDLEY_RESTRICT src, unsigned char *HEDLEY_RESTRICT dest, size_t len,
-          YencDecoderState *state) {
-    unsigned char *ds = dest;
-    (*(isRaw ? _do_decode_raw : _do_decode))(&src, &ds, len, state);
-    return ds - dest;
+static inline size_t decode(int isRaw, const void* src, void* dest, size_t len, YencDecoderState* state) {
+	unsigned char* ds = (unsigned char*)dest;
+	(*(isRaw ? _do_decode_raw : _do_decode))((const unsigned char**)&src, &ds, len, state);
+	return ds - (unsigned char*)dest;
 }
 
-static inline YencDecoderEnd
-do_decode_end(const unsigned char *HEDLEY_RESTRICT *src, unsigned char *HEDLEY_RESTRICT *dest, size_t len,
-              YencDecoderState *state) {
-    return _do_decode_end_raw(src, dest, len, state);
+static inline YencDecoderEnd decode_end(const void** src, void** dest, size_t len, YencDecoderState* state) {
+	return _do_decode_end_raw((const unsigned char**)src, (unsigned char**)dest, len, state);
 }
 
 void decoder_init();
 
-const char* simd_detected();
+static inline int decode_isa_level() {
+	return _decode_isa;
+}
 
 
-#ifdef __cplusplus
-}
-#endif
-#endif
+} // namespace
+#endif // defined(__YENC_DECODER_H)
diff --git a/src/yencode/decoder_avx.cc b/src/yencode/decoder_avx.cc
index c957206..443552a 100644
--- a/src/yencode/decoder_avx.cc
+++ b/src/yencode/decoder_avx.cc
@@ -1,22 +1,18 @@
 #include "common.h"
 
-#if defined(__AVX__) && defined(__POPCNT__)
 #include "decoder_common.h"
+#if defined(__AVX__) && defined(__POPCNT__)
 #include "decoder_sse_base.h"
-void decoder_set_avx_funcs() {
-    decoder_sse_init();
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSE4_POPCNT> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSE4_POPCNT> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSE4_POPCNT> >;
-    _decode_simd_level = ISA_LEVEL_AVX;
+void RapidYenc::decoder_set_avx_funcs() {
+	decoder_sse_init(lookups);
+	decoder_init_lut(lookups->compact);
+	_do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSE4_POPCNT> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSE4_POPCNT> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSE4_POPCNT> >;
+	_decode_isa = ISA_LEVEL_AVX;
 }
 #else
-
-void decoder_set_ssse3_funcs();
-
-void decoder_set_avx_funcs() {
-    decoder_set_ssse3_funcs();
+void RapidYenc::decoder_set_avx_funcs() {
+	decoder_set_ssse3_funcs();
 }
-
 #endif
diff --git a/src/yencode/decoder_avx2.cc b/src/yencode/decoder_avx2.cc
index c4c67db..638bb12 100644
--- a/src/yencode/decoder_avx2.cc
+++ b/src/yencode/decoder_avx2.cc
@@ -1,22 +1,18 @@
 #include "common.h"
 
-#if defined(__AVX2__) && !defined(YENC_DISABLE_AVX256)
 #include "decoder_common.h"
+#if defined(__AVX2__) && !defined(YENC_DISABLE_AVX256)
 #include "decoder_avx2_base.h"
-void decoder_set_avx2_funcs() {
-    ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_LEVEL_AVX2> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_LEVEL_AVX2> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_LEVEL_AVX2> >;
-    _decode_simd_level = ISA_LEVEL_AVX2;
+void RapidYenc::decoder_set_avx2_funcs() {
+	ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
+	decoder_init_lut(lookups->compact);
+	RapidYenc::_do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_LEVEL_AVX2> >;
+	RapidYenc::_do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_LEVEL_AVX2> >;
+	RapidYenc::_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_LEVEL_AVX2> >;
+	RapidYenc::_decode_isa = ISA_LEVEL_AVX2;
 }
 #else
-
-void decoder_set_avx_funcs();
-
-void decoder_set_avx2_funcs() {
-    decoder_set_avx_funcs();
+void RapidYenc::decoder_set_avx2_funcs() {
+	decoder_set_avx_funcs();
 }
-
 #endif
diff --git a/src/yencode/decoder_avx2_base.h b/src/yencode/decoder_avx2_base.h
index c8dc825..ca46a27 100644
--- a/src/yencode/decoder_avx2_base.h
+++ b/src/yencode/decoder_avx2_base.h
@@ -14,23 +14,22 @@
 
 #pragma pack(16)
 static struct {
-    /*align16*/ struct { char bytes[16]; } compact[32768];
-    uint8_t eqFix[256];
+	/*align16*/ struct { char bytes[16]; } compact[32768];
 } * HEDLEY_RESTRICT lookups;
 #pragma pack()
 
 
 static HEDLEY_ALWAYS_INLINE __m256i force_align_read_256(const void* p) {
 #ifdef _MSC_VER
-    // MSVC complains about casting away volatile
-    return *(__m256i *)(p);
+	// MSVC complains about casting away volatile
+	return *(__m256i *)(p);
 #else
-    return *(volatile __m256i *)(p);
+	return *(volatile __m256i *)(p);
 #endif
 }
 
 // _mm256_castsi128_si256, but upper is defined to be 0
-#if (defined(__clang__) && __clang_major__ >= 5 && (!defined(__APPLE__) || __clang_major__ >= 7)) || (defined(__GNUC__) && __GNUC__ >= 10)
+#if (defined(__clang__) && __clang_major__ >= 5 && (!defined(__APPLE__) || __clang_major__ >= 7)) || (defined(__GNUC__) && __GNUC__ >= 10) || (defined(_MSC_VER) && _MSC_VER >= 1910)
 // intrinsic unsupported in GCC 9 and MSVC < 2017
 # define zext128_256 _mm256_zextsi128_si256
 #else
@@ -43,584 +42,577 @@ static HEDLEY_ALWAYS_INLINE __m256i force_align_read_256(const void* p) {
 # endif
 #endif
 
+#if defined(__tune_icelake_client__) || defined(__tune_icelake_server__) || defined(__tune_tigerlake__) || defined(__tune_rocketlake__) || defined(__tune_alderlake__) || defined(__tune_sapphirerapids__)
+# define COMPRESS_STORE _mm256_mask_compressstoreu_epi8
+#else
+// avoid uCode on Zen4
+# define COMPRESS_STORE(dst, mask, vec) _mm256_storeu_si256((__m256i*)(dst), _mm256_maskz_compress_epi8(mask, vec))
+#endif
 
-template<bool isRaw, bool searchEnd, enum YEncDecIsaLevel use_isa>
-HEDLEY_ALWAYS_INLINE void do_decode_avx2(const uint8_t* HEDLEY_RESTRICT src, long& len, unsigned char* HEDLEY_RESTRICT & p, unsigned char& _escFirst, uint16_t& _nextMask) {
-    HEDLEY_ASSUME(_escFirst == 0 || _escFirst == 1);
-    HEDLEY_ASSUME(_nextMask == 0 || _nextMask == 1 || _nextMask == 2);
-    uintptr_t escFirst = _escFirst;
-    __m256i yencOffset = escFirst ? _mm256_set_epi8(
-        -42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,
-        -42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42-64
-    ) : _mm256_set1_epi8(-42);
-    __m256i minMask = _mm256_set1_epi8('.');
-    if(_nextMask && isRaw) {
-        minMask = _mm256_set_epi8(
-            '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
-            '.','.','.','.','.','.','.','.','.','.','.','.','.','.',_nextMask==2?0:'.',_nextMask==1?0:'.'
-        );
-    }
+namespace RapidYenc {
 
-    // for some reason, MSVC Win32 seems to crash when trying to compile _mm256_mask_cmpeq_epi8_mask
-    // the crash can be fixed by switching the order of the last two arguments, but it seems to generate wrong code
-    // so just disable the optimisation as it seems to be problematic there
+template<bool isRaw, bool searchEnd, enum YEncDecIsaLevel use_isa>
+HEDLEY_ALWAYS_INLINE void do_decode_avx2(const uint8_t* src, long& len, unsigned char*& p, unsigned char& _escFirst, uint16_t& _nextMask) {
+	HEDLEY_ASSUME(_escFirst == 0 || _escFirst == 1);
+	HEDLEY_ASSUME(_nextMask == 0 || _nextMask == 1 || _nextMask == 2);
+	uintptr_t escFirst = _escFirst;
+	__m256i yencOffset = escFirst ? _mm256_set_epi8(
+		-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,
+		-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42-64
+	) : _mm256_set1_epi8(-42);
+	__m256i minMask = _mm256_set1_epi8('.');
+	if(_nextMask && isRaw) {
+		minMask = _mm256_set_epi8(
+			'.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
+			'.','.','.','.','.','.','.','.','.','.','.','.','.','.',_nextMask==2?0:'.',_nextMask==1?0:'.'
+		);
+	}
+	
+	decoder_set_nextMask<isRaw>(src, len, _nextMask); // set this before the loop because we can't check src after it's been overwritten
+	
+	// for some reason, MSVC Win32 seems to crash when trying to compile _mm256_mask_cmpeq_epi8_mask
+	// the crash can be fixed by switching the order of the last two arguments, but it seems to generate wrong code
+	// so just disable the optimisation as it seems to be problematic there
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
 # if defined(_MSC_VER) && !defined(PLATFORM_AMD64) && !defined(__clang__)
-    const bool useAVX3MaskCmp = false;
+	const bool useAVX3MaskCmp = false;
 # else
-    const bool useAVX3MaskCmp = (use_isa >= ISA_LEVEL_AVX3);
+	const bool useAVX3MaskCmp = (use_isa >= ISA_LEVEL_AVX3);
 # endif
 #endif
-    intptr_t i;
-    for(i = -len; i; i += sizeof(__m256i)*2) {
-        __m256i oDataA = _mm256_load_si256((__m256i *)(src+i));
-        __m256i oDataB = _mm256_load_si256((__m256i *)(src+i) + 1);
-
-        // search for special chars
-        __m256i cmpA = _mm256_cmpeq_epi8(oDataA, _mm256_shuffle_epi8(
-            _mm256_set_epi8(
-                -1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.',
-                -1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'
-            ),
-            _mm256_min_epu8(oDataA, minMask)
-        ));
-        __m256i cmpB = _mm256_cmpeq_epi8(oDataB, _mm256_shuffle_epi8(
-            _mm256_set_epi8(
-                -1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.',
-                -1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'
-            ),
-            _mm256_min_epu8(oDataB, _mm256_set1_epi8('.'))
-        ));
-
-        // TODO: can OR the vectors together to save generating a mask, but may not be worth it
-        uint64_t mask = (uint32_t)_mm256_movemask_epi8(cmpB); // not the most accurate mask if we have invalid sequences; we fix this up later
-        mask = (mask << 32) | (uint32_t)_mm256_movemask_epi8(cmpA);
-        __m256i dataA, dataB;
-        if(use_isa >= ISA_LEVEL_AVX3)
-            dataA = _mm256_add_epi8(oDataA, yencOffset);
-
-        if (mask != 0) {
-            __m256i cmpEqA = _mm256_cmpeq_epi8(oDataA, _mm256_set1_epi8('='));
-            __m256i cmpEqB = _mm256_cmpeq_epi8(oDataB, _mm256_set1_epi8('='));
-            uint64_t maskEq = (uint32_t)_mm256_movemask_epi8(cmpEqB);
-            maskEq = (maskEq << 32) | (uint32_t)_mm256_movemask_epi8(cmpEqA);
-
-            // handle \r\n. sequences
-            // RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
-            if((isRaw || searchEnd) && LIKELIHOOD(0.45, mask != maskEq)) {
+	intptr_t i;
+	for(i = -len; i; i += sizeof(__m256i)*2) {
+		__m256i oDataA = _mm256_load_si256((__m256i *)(src+i));
+		__m256i oDataB = _mm256_load_si256((__m256i *)(src+i) + 1);
+		
+		// search for special chars
+		__m256i cmpA = _mm256_cmpeq_epi8(oDataA, _mm256_shuffle_epi8(
+			_mm256_set_epi8(
+				-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.',
+				-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'
+			),
+			_mm256_min_epu8(oDataA, minMask)
+		));
+		__m256i cmpB = _mm256_cmpeq_epi8(oDataB, _mm256_shuffle_epi8(
+			_mm256_set_epi8(
+				-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.',
+				-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'
+			),
+			_mm256_min_epu8(oDataB, _mm256_set1_epi8('.'))
+		));
+		
+		// TODO: can OR the vectors together to save generating a mask, but may not be worth it
+		uint64_t mask = (uint32_t)_mm256_movemask_epi8(cmpB); // not the most accurate mask if we have invalid sequences; we fix this up later
+		mask = (mask << 32) | (uint32_t)_mm256_movemask_epi8(cmpA);
+		__m256i dataA, dataB;
+		if(use_isa >= ISA_LEVEL_AVX3)
+			dataA = _mm256_add_epi8(oDataA, yencOffset);
+		
+		if (mask != 0) {
+			__m256i cmpEqA = _mm256_cmpeq_epi8(oDataA, _mm256_set1_epi8('='));
+			__m256i cmpEqB = _mm256_cmpeq_epi8(oDataB, _mm256_set1_epi8('='));
+			uint64_t maskEq = (uint32_t)_mm256_movemask_epi8(cmpEqB);
+			maskEq = (maskEq << 32) | (uint32_t)_mm256_movemask_epi8(cmpEqA);
+			
+			// handle \r\n. sequences
+			// RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
+			if((isRaw || searchEnd) && LIKELIHOOD(0.45, mask != maskEq)) {
 #if 0
-                // prefer shuffling data over unaligned loads on Zen (unknown if worth it on Zen2/Excavator)
-                // unfortunately not beneficial, probably due to available register pressure; this is left here because it could be beneficial if we figure out how to use fewer registers
-                __m256i nextDataA, nextDataB;
-                if(searchEnd) {
-                    nextDataA = _mm256_inserti128_si256(
-                        _mm256_castsi128_si256(_mm256_extracti128_si256(oDataA, 1)),
-                        _mm256_castsi256_si128(oDataB),
-                        1
-                    );
-                    nextDataB = _mm256_inserti128_si256(
-                        _mm256_castsi128_si256(_mm256_extracti128_si256(oDataB, 1)),
-                        _mm_load_si128((__m128i*)(src+i+sizeof(__m256i)*2)),
-                        1
-                    );
-                }
+				// prefer shuffling data over unaligned loads on Zen (unknown if worth it on Zen2/Excavator)
+				// unfortunately not beneficial, probably due to available register pressure; this is left here because it could be beneficial if we figure out how to use fewer registers
+				__m256i nextDataA, nextDataB;
+				if(searchEnd) {
+					nextDataA = _mm256_inserti128_si256(
+						_mm256_castsi128_si256(_mm256_extracti128_si256(oDataA, 1)),
+						_mm256_castsi256_si128(oDataB),
+						1
+					);
+					nextDataB = _mm256_inserti128_si256(
+						_mm256_castsi128_si256(_mm256_extracti128_si256(oDataB, 1)),
+						_mm_load_si128((__m128i*)(src+i+sizeof(__m256i)*2)),
+						1
+					);
+				}
 # define SHIFT_DATA_A(offs) (searchEnd ? _mm256_alignr_epi8(nextDataA, oDataA, offs) : _mm256_loadu_si256((__m256i *)(src+i+offs)))
 # define SHIFT_DATA_B(offs) (searchEnd ? _mm256_alignr_epi8(nextDataB, oDataB, offs) : _mm256_loadu_si256((__m256i *)(src+i+offs) + 1))
 #else
 # define SHIFT_DATA_A(offs) _mm256_loadu_si256((__m256i *)(src+i+offs))
 # define SHIFT_DATA_B(offs) _mm256_loadu_si256((__m256i *)(src+i+offs) + 1)
 #endif
-                __m256i tmpData2A = SHIFT_DATA_A(2);
-                __m256i tmpData2B = SHIFT_DATA_B(2);
-                __m256i match2EqA, match2EqB;
+				__m256i tmpData2A = SHIFT_DATA_A(2);
+				__m256i tmpData2B = SHIFT_DATA_B(2);
+				__m256i match2EqA, match2EqB;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                __mmask32 match2EqMaskA, match2EqMaskB;
-                __mmask32 match0CrMaskA, match0CrMaskB;
-                __mmask32 match2CrXDtMaskA, match2CrXDtMaskB;
-                if(useAVX3MaskCmp && searchEnd) {
-                    match2EqMaskA = _mm256_cmpeq_epi8_mask(_mm256_set1_epi8('='), tmpData2A);
-                    match2EqMaskB = _mm256_cmpeq_epi8_mask(_mm256_set1_epi8('='), tmpData2B);
-                } else
+				__mmask32 match2EqMaskA, match2EqMaskB;
+				__mmask32 match0CrMaskA, match0CrMaskB;
+				__mmask32 match2CrXDtMaskA, match2CrXDtMaskB;
+				if(useAVX3MaskCmp && searchEnd) {
+					match2EqMaskA = _mm256_cmpeq_epi8_mask(_mm256_set1_epi8('='), tmpData2A);
+					match2EqMaskB = _mm256_cmpeq_epi8_mask(_mm256_set1_epi8('='), tmpData2B);
+				} else
 #endif
-                if(searchEnd) {
-                    match2EqA = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), tmpData2A);
-                    match2EqB = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), tmpData2B);
-                }
-
-                int partialKillDotFound;
-                __m256i match2CrXDtA, match2CrXDtB;
-                if(isRaw) {
-                    // find patterns of \r_.
-
+				if(searchEnd) {
+					match2EqA = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), tmpData2A);
+					match2EqB = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), tmpData2B);
+				}
+				
+				int partialKillDotFound;
+				__m256i match2CrXDtA, match2CrXDtB;
+				if(isRaw) {
+					// find patterns of \r_.
+					
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(useAVX3MaskCmp) {
-                        match0CrMaskA = _mm256_cmpeq_epi8_mask(oDataA, _mm256_set1_epi8('\r'));
-                        match0CrMaskB = _mm256_cmpeq_epi8_mask(oDataB, _mm256_set1_epi8('\r'));
-                        match2CrXDtMaskA = _mm256_mask_cmpeq_epi8_mask(match0CrMaskA, tmpData2A, _mm256_set1_epi8('.'));
-                        match2CrXDtMaskB = _mm256_mask_cmpeq_epi8_mask(match0CrMaskB, tmpData2B, _mm256_set1_epi8('.'));
-                        partialKillDotFound = KORTEST32(match2CrXDtMaskA, match2CrXDtMaskB);
-                    } else
+					if(useAVX3MaskCmp) {
+						match0CrMaskA = _mm256_cmpeq_epi8_mask(oDataA, _mm256_set1_epi8('\r'));
+						match0CrMaskB = _mm256_cmpeq_epi8_mask(oDataB, _mm256_set1_epi8('\r'));
+						match2CrXDtMaskA = _mm256_mask_cmpeq_epi8_mask(match0CrMaskA, tmpData2A, _mm256_set1_epi8('.'));
+						match2CrXDtMaskB = _mm256_mask_cmpeq_epi8_mask(match0CrMaskB, tmpData2B, _mm256_set1_epi8('.'));
+						partialKillDotFound = KORTEST32(match2CrXDtMaskA, match2CrXDtMaskB);
+					} else
 #endif
-                    {
-                        match2CrXDtA = _mm256_and_si256(
-                            _mm256_cmpeq_epi8(oDataA, _mm256_set1_epi8('\r')),
-                            _mm256_cmpeq_epi8(tmpData2A, _mm256_set1_epi8('.'))
-                        );
-                        match2CrXDtB = _mm256_and_si256(
-                            _mm256_cmpeq_epi8(oDataB, _mm256_set1_epi8('\r')),
-                            _mm256_cmpeq_epi8(tmpData2B, _mm256_set1_epi8('.'))
-                        );
-                        partialKillDotFound = _mm256_movemask_epi8(_mm256_or_si256(
-                            match2CrXDtA, match2CrXDtB
-                        ));
-                    }
-                }
-
-                if(isRaw && LIKELIHOOD(0.002, partialKillDotFound)) {
-                    // merge matches for \r\n.
-                    __m256i match2NlDotA, match1NlA;
-                    __m256i match2NlDotB, match1NlB;
+					{
+						match2CrXDtA = _mm256_and_si256(
+							_mm256_cmpeq_epi8(oDataA, _mm256_set1_epi8('\r')),
+							_mm256_cmpeq_epi8(tmpData2A, _mm256_set1_epi8('.'))
+						);
+						match2CrXDtB = _mm256_and_si256(
+							_mm256_cmpeq_epi8(oDataB, _mm256_set1_epi8('\r')),
+							_mm256_cmpeq_epi8(tmpData2B, _mm256_set1_epi8('.'))
+						);
+						partialKillDotFound = _mm256_movemask_epi8(_mm256_or_si256(
+							match2CrXDtA, match2CrXDtB
+						));
+					}
+				}
+				
+				if(isRaw && LIKELIHOOD(0.002, partialKillDotFound)) {
+					// merge matches for \r\n.
+					__m256i match2NlDotA, match1NlA;
+					__m256i match2NlDotB, match1NlB;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    __mmask32 match1NlMaskA, match1NlMaskB;
-                    __mmask32 match2NlDotMaskA, match2NlDotMaskB;
-                    if(useAVX3MaskCmp) {
-                        match1NlMaskA = _mm256_mask_cmpeq_epi8_mask(
-                            match0CrMaskA,
-                            _mm256_set1_epi8('\n'),
-                            SHIFT_DATA_A(1)
-                        );
-                        match1NlMaskB = _mm256_mask_cmpeq_epi8_mask(
-                            match0CrMaskB,
-                            _mm256_set1_epi8('\n'),
-                            SHIFT_DATA_B(1)
-                        );
-                        match2NlDotMaskA = KAND32(match2CrXDtMaskA, match1NlMaskA);
-                        match2NlDotMaskB = KAND32(match2CrXDtMaskB, match1NlMaskB);
-                    } else
+					__mmask32 match1NlMaskA, match1NlMaskB;
+					__mmask32 match2NlDotMaskA, match2NlDotMaskB;
+					if(useAVX3MaskCmp) {
+						match1NlMaskA = _mm256_mask_cmpeq_epi8_mask(
+							match0CrMaskA,
+							_mm256_set1_epi8('\n'),
+							SHIFT_DATA_A(1)
+						);
+						match1NlMaskB = _mm256_mask_cmpeq_epi8_mask(
+							match0CrMaskB,
+							_mm256_set1_epi8('\n'),
+							SHIFT_DATA_B(1)
+						);
+						match2NlDotMaskA = KAND32(match2CrXDtMaskA, match1NlMaskA);
+						match2NlDotMaskB = KAND32(match2CrXDtMaskB, match1NlMaskB);
+					} else
 #endif
-                    {
-                        __m256i match1LfA = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('\n'),
-                            SHIFT_DATA_A(1)
-                        );
-                        __m256i match1LfB = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('\n'),
-                            SHIFT_DATA_B(1)
-                        );
-                        // force re-computing these to avoid register spills elsewhere
-                        match1NlA = _mm256_and_si256(match1LfA, _mm256_cmpeq_epi8(force_align_read_256(src+i), _mm256_set1_epi8('\r')));
-                        match1NlB = _mm256_and_si256(match1LfB, _mm256_cmpeq_epi8(force_align_read_256(src+i + sizeof(__m256i)), _mm256_set1_epi8('\r')));
-                        match2NlDotA = _mm256_and_si256(match2CrXDtA, match1NlA);
-                        match2NlDotB = _mm256_and_si256(match2CrXDtB, match1NlB);
-                    }
-                    if(searchEnd) {
-                        __m256i tmpData4A;
+					{
+						__m256i match1LfA = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('\n'),
+							SHIFT_DATA_A(1)
+						);
+						__m256i match1LfB = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('\n'),
+							SHIFT_DATA_B(1)
+						);
+						// force re-computing these to avoid register spills elsewhere
+						match1NlA = _mm256_and_si256(match1LfA, _mm256_cmpeq_epi8(force_align_read_256(src+i), _mm256_set1_epi8('\r')));
+						match1NlB = _mm256_and_si256(match1LfB, _mm256_cmpeq_epi8(force_align_read_256(src+i + sizeof(__m256i)), _mm256_set1_epi8('\r')));
+						match2NlDotA = _mm256_and_si256(match2CrXDtA, match1NlA);
+						match2NlDotB = _mm256_and_si256(match2CrXDtB, match1NlB);
+					}
+					if(searchEnd) {
+						__m256i tmpData4A;
 #if defined(__AVX512VL__) && defined(PLATFORM_AMD64)
-                        if(use_isa >= ISA_LEVEL_AVX3)
-                            // AVX512 with 32 registers shouldn't have any issue with holding onto oData* in registers
-                            tmpData4A = _mm256_alignr_epi32(oDataB, oDataA, 1);
-                        else
+						if(use_isa >= ISA_LEVEL_AVX3)
+							// AVX512 with 32 registers shouldn't have any issue with holding onto oData* in registers
+							tmpData4A = _mm256_alignr_epi32(oDataB, oDataA, 1);
+						else
 #endif
-                            tmpData4A = SHIFT_DATA_A(4);
-                        __m256i tmpData4B = SHIFT_DATA_B(4);
-                        // match instances of \r\n.\r\n and \r\n.=y
-                        __m256i match3CrA = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('\r'),
-                            SHIFT_DATA_A(3)
-                        );
-                        __m256i match3CrB = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('\r'),
-                            SHIFT_DATA_B(3)
-                        );
-                        __m256i match4LfA = _mm256_cmpeq_epi8(tmpData4A, _mm256_set1_epi8('\n'));
-                        __m256i match4LfB = _mm256_cmpeq_epi8(tmpData4B, _mm256_set1_epi8('\n'));
-                        __m256i match4EqYA = _mm256_cmpeq_epi16(tmpData4A, _mm256_set1_epi16(0x793d)); // =y
-                        __m256i match4EqYB = _mm256_cmpeq_epi16(tmpData4B, _mm256_set1_epi16(0x793d)); // =y
-
-                        int matchEnd;
+							tmpData4A = SHIFT_DATA_A(4);
+						__m256i tmpData4B = SHIFT_DATA_B(4);
+						// match instances of \r\n.\r\n and \r\n.=y
+						__m256i match3CrA = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('\r'),
+							SHIFT_DATA_A(3)
+						);
+						__m256i match3CrB = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('\r'),
+							SHIFT_DATA_B(3)
+						);
+						__m256i match4LfA = _mm256_cmpeq_epi8(tmpData4A, _mm256_set1_epi8('\n'));
+						__m256i match4LfB = _mm256_cmpeq_epi8(tmpData4B, _mm256_set1_epi8('\n'));
+						__m256i match4EqYA = _mm256_cmpeq_epi16(tmpData4A, _mm256_set1_epi16(0x793d)); // =y
+						__m256i match4EqYB = _mm256_cmpeq_epi16(tmpData4B, _mm256_set1_epi16(0x793d)); // =y
+						
+						int matchEnd;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                        if(useAVX3MaskCmp) {
-                            __mmask32 match3EqYMaskA = _mm256_mask_cmpeq_epi8_mask(
-                                match2EqMaskA,
-                                _mm256_set1_epi8('y'),
-                                SHIFT_DATA_A(3)
-                            );
-                            __mmask32 match3EqYMaskB = _mm256_mask_cmpeq_epi8_mask(
-                                match2EqMaskB,
-                                _mm256_set1_epi8('y'),
-                                SHIFT_DATA_B(3)
-                            );
-                            __m256i match34EqYA, match34EqYB;
+						if(useAVX3MaskCmp) {
+							__mmask32 match3EqYMaskA = _mm256_mask_cmpeq_epi8_mask(
+								match2EqMaskA,
+								_mm256_set1_epi8('y'),
+								SHIFT_DATA_A(3)
+							);
+							__mmask32 match3EqYMaskB = _mm256_mask_cmpeq_epi8_mask(
+								match2EqMaskB,
+								_mm256_set1_epi8('y'),
+								SHIFT_DATA_B(3)
+							);
+							__m256i match34EqYA, match34EqYB;
 # ifdef __AVX512VBMI2__
-                            if(use_isa >= ISA_LEVEL_VBMI2) {
-                                match34EqYA = _mm256_shrdi_epi16(_mm256_movm_epi8(match3EqYMaskA), match4EqYA, 8);
-                                match34EqYB = _mm256_shrdi_epi16(_mm256_movm_epi8(match3EqYMaskB), match4EqYB, 8);
-                            } else
+							if(use_isa >= ISA_LEVEL_VBMI2) {
+								match34EqYA = _mm256_shrdi_epi16(_mm256_movm_epi8(match3EqYMaskA), match4EqYA, 8);
+								match34EqYB = _mm256_shrdi_epi16(_mm256_movm_epi8(match3EqYMaskB), match4EqYB, 8);
+							} else
 # endif
-                            {
-                                // (match4EqY & 0xff00) | (match3EqY >> 8)
-                                match34EqYA = _mm256_mask_blend_epi8(match3EqYMaskA>>1, _mm256_and_si256(match4EqYA, _mm256_set1_epi16(-0xff)), _mm256_set1_epi8(-1));
-                                match34EqYB = _mm256_mask_blend_epi8(match3EqYMaskB>>1, _mm256_and_si256(match4EqYB, _mm256_set1_epi16(-0xff)), _mm256_set1_epi8(-1));
-                            }
-                            // merge \r\n and =y matches for tmpData4
-                            __m256i match4EndA = _mm256_ternarylogic_epi32(match34EqYA, match3CrA, match4LfA, 0xF8); // (match3Cr & match4Lf) | match34EqY
-                            __m256i match4EndB = _mm256_ternarylogic_epi32(match34EqYB, match3CrB, match4LfB, 0xF8);
-                            // merge with \r\n. and combine
-                            matchEnd = KORTEST32(
-                                KOR32(
-                                    _mm256_mask_test_epi8_mask(match2NlDotMaskA, match4EndA, match4EndA),
-                                    KAND32(match3EqYMaskA, match1NlMaskA)
-                                ),
-                                KOR32(
-                                    _mm256_mask_test_epi8_mask(match2NlDotMaskB, match4EndB, match4EndB),
-                                    KAND32(match3EqYMaskB, match1NlMaskB)
-                                )
-                            );
-                        } else
+							{
+								// (match4EqY & 0xff00) | (match3EqY >> 8)
+								match34EqYA = _mm256_mask_blend_epi8(match3EqYMaskA>>1, _mm256_and_si256(match4EqYA, _mm256_set1_epi16(-0xff)), _mm256_set1_epi8(-1));
+								match34EqYB = _mm256_mask_blend_epi8(match3EqYMaskB>>1, _mm256_and_si256(match4EqYB, _mm256_set1_epi16(-0xff)), _mm256_set1_epi8(-1));
+							}
+							// merge \r\n and =y matches for tmpData4
+							__m256i match4EndA = _mm256_ternarylogic_epi32(match34EqYA, match3CrA, match4LfA, 0xF8); // (match3Cr & match4Lf) | match34EqY
+							__m256i match4EndB = _mm256_ternarylogic_epi32(match34EqYB, match3CrB, match4LfB, 0xF8);
+							// merge with \r\n. and combine
+							matchEnd = KORTEST32(
+								KOR32(
+									_mm256_mask_test_epi8_mask(match2NlDotMaskA, match4EndA, match4EndA),
+									KAND32(match3EqYMaskA, match1NlMaskA)
+								),
+								KOR32(
+									_mm256_mask_test_epi8_mask(match2NlDotMaskB, match4EndB, match4EndB),
+									KAND32(match3EqYMaskB, match1NlMaskB)
+								)
+							);
+						} else
 #endif
-                        {
-                            __m256i match3EqYA = _mm256_and_si256(match2EqA, _mm256_cmpeq_epi8(
-                                _mm256_set1_epi8('y'),
-                                SHIFT_DATA_A(3)
-                            ));
-                            __m256i match3EqYB = _mm256_and_si256(match2EqB, _mm256_cmpeq_epi8(
-                                _mm256_set1_epi8('y'),
-                                SHIFT_DATA_B(3)
-                            ));
-                            match4EqYA = _mm256_slli_epi16(match4EqYA, 8); // TODO: also consider using PBLENDVB here with shifted match3EqY instead
-                            match4EqYB = _mm256_slli_epi16(match4EqYB, 8);
-                            // merge \r\n and =y matches for tmpData4
-                            __m256i match4EndA = _mm256_or_si256(
-                                _mm256_and_si256(match3CrA, match4LfA),
-                                _mm256_or_si256(match4EqYA, _mm256_srli_epi16(match3EqYA, 8)) // _mm256_srli_si256 by 1 also works
-                            );
-                            __m256i match4EndB = _mm256_or_si256(
-                                _mm256_and_si256(match3CrB, match4LfB),
-                                _mm256_or_si256(match4EqYB, _mm256_srli_epi16(match3EqYB, 8))
-                            );
-                            // merge with \r\n.
-                            match4EndA = _mm256_and_si256(match4EndA, match2NlDotA);
-                            match4EndB = _mm256_and_si256(match4EndB, match2NlDotB);
-                            // match \r\n=y
-                            __m256i match3EndA = _mm256_and_si256(match3EqYA, match1NlA);
-                            __m256i match3EndB = _mm256_and_si256(match3EqYB, match1NlB);
-                            // combine match sequences
-                            matchEnd = _mm256_movemask_epi8(_mm256_or_si256(
-                                _mm256_or_si256(match4EndA, match3EndA),
-                                _mm256_or_si256(match4EndB, match3EndB)
-                            ));
-                        }
-                        if(LIKELIHOOD(0.002, matchEnd)) {
-                            // terminator found
-                            // there's probably faster ways to do this, but reverting to scalar code should be good enough
-                            len += (long)i;
-                            break;
-                        }
-                    }
+						{
+							__m256i match3EqYA = _mm256_and_si256(match2EqA, _mm256_cmpeq_epi8(
+								_mm256_set1_epi8('y'),
+								SHIFT_DATA_A(3)
+							));
+							__m256i match3EqYB = _mm256_and_si256(match2EqB, _mm256_cmpeq_epi8(
+								_mm256_set1_epi8('y'),
+								SHIFT_DATA_B(3)
+							));
+							match4EqYA = _mm256_slli_epi16(match4EqYA, 8); // TODO: also consider using PBLENDVB here with shifted match3EqY instead
+							match4EqYB = _mm256_slli_epi16(match4EqYB, 8);
+							// merge \r\n and =y matches for tmpData4
+							__m256i match4EndA = _mm256_or_si256(
+								_mm256_and_si256(match3CrA, match4LfA),
+								_mm256_or_si256(match4EqYA, _mm256_srli_epi16(match3EqYA, 8)) // _mm256_srli_si256 by 1 also works
+							);
+							__m256i match4EndB = _mm256_or_si256(
+								_mm256_and_si256(match3CrB, match4LfB),
+								_mm256_or_si256(match4EqYB, _mm256_srli_epi16(match3EqYB, 8))
+							);
+							// merge with \r\n.
+							match4EndA = _mm256_and_si256(match4EndA, match2NlDotA);
+							match4EndB = _mm256_and_si256(match4EndB, match2NlDotB);
+							// match \r\n=y
+							__m256i match3EndA = _mm256_and_si256(match3EqYA, match1NlA);
+							__m256i match3EndB = _mm256_and_si256(match3EqYB, match1NlB);
+							// combine match sequences
+							matchEnd = _mm256_movemask_epi8(_mm256_or_si256(
+								_mm256_or_si256(match4EndA, match3EndA),
+								_mm256_or_si256(match4EndB, match3EndB)
+							));
+						}
+						if(LIKELIHOOD(0.002, matchEnd)) {
+							// terminator found
+							// there's probably faster ways to do this, but reverting to scalar code should be good enough
+							len += (long)i;
+							_nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(useAVX3MaskCmp) {
-                        mask |= (uint64_t)match2NlDotMaskA << 2;
-                        mask |= (uint64_t)match2NlDotMaskB << 34;
-                        minMask = _mm256_maskz_mov_epi8(~(match2NlDotMaskB>>30), _mm256_set1_epi8('.'));
-                    } else
+					if(useAVX3MaskCmp) {
+						mask |= (uint64_t)match2NlDotMaskA << 2;
+						mask |= (uint64_t)match2NlDotMaskB << 34;
+						minMask = _mm256_maskz_mov_epi8(~(match2NlDotMaskB>>30), _mm256_set1_epi8('.'));
+					} else
 #endif
-                    {
-                        mask |= (uint64_t)((uint32_t)_mm256_movemask_epi8(match2NlDotA)) << 2;
-                        mask |= (uint64_t)((uint32_t)_mm256_movemask_epi8(match2NlDotB)) << 34;
-                        match2NlDotB = zext128_256(_mm_srli_si128(_mm256_extracti128_si256(match2NlDotB, 1), 14));
-                        minMask = _mm256_subs_epu8(_mm256_set1_epi8('.'), match2NlDotB);
-                    }
-                }
-                else if(searchEnd) {
-                    bool partialEndFound;
-                    __m256i match3EqYA, match3EqYB;
+					{
+						mask |= (uint64_t)((uint32_t)_mm256_movemask_epi8(match2NlDotA)) << 2;
+						mask |= (uint64_t)((uint32_t)_mm256_movemask_epi8(match2NlDotB)) << 34;
+						match2NlDotB = zext128_256(_mm_srli_si128(_mm256_extracti128_si256(match2NlDotB, 1), 14));
+						minMask = _mm256_subs_epu8(_mm256_set1_epi8('.'), match2NlDotB);
+					}
+				}
+				else if(searchEnd) {
+					bool partialEndFound;
+					__m256i match3EqYA, match3EqYB;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    __mmask32 match3EqYMaskA, match3EqYMaskB;
-                    if(useAVX3MaskCmp) {
-                        match3EqYMaskA = _mm256_mask_cmpeq_epi8_mask(
-                            match2EqMaskA,
-                            _mm256_set1_epi8('y'),
-                            SHIFT_DATA_A(3)
-                        );
-                        match3EqYMaskB = _mm256_mask_cmpeq_epi8_mask(
-                            match2EqMaskB,
-                            _mm256_set1_epi8('y'),
-                            SHIFT_DATA_B(3)
-                        );
-                        partialEndFound = KORTEST32(match3EqYMaskA, match3EqYMaskB);
-                    } else
+					__mmask32 match3EqYMaskA, match3EqYMaskB;
+					if(useAVX3MaskCmp) {
+						match3EqYMaskA = _mm256_mask_cmpeq_epi8_mask(
+							match2EqMaskA,
+							_mm256_set1_epi8('y'),
+							SHIFT_DATA_A(3)
+						);
+						match3EqYMaskB = _mm256_mask_cmpeq_epi8_mask(
+							match2EqMaskB,
+							_mm256_set1_epi8('y'),
+							SHIFT_DATA_B(3)
+						);
+						partialEndFound = KORTEST32(match3EqYMaskA, match3EqYMaskB);
+					} else
 #endif
-                    {
-                        __m256i match3YA = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('y'),
-                            SHIFT_DATA_A(3)
-                        );
-                        __m256i match3YB = _mm256_cmpeq_epi8(
-                            _mm256_set1_epi8('y'),
-                            SHIFT_DATA_B(3)
-                        );
-                        match3EqYA = _mm256_and_si256(match2EqA, match3YA);
-                        match3EqYB = _mm256_and_si256(match2EqB, match3YB);
-                        partialEndFound = _mm256_movemask_epi8(_mm256_or_si256(match3EqYA, match3EqYB));
-                    }
-                    if(LIKELIHOOD(0.002, partialEndFound)) {
-                        bool endFound;
+					{
+						__m256i match3YA = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('y'),
+							SHIFT_DATA_A(3)
+						);
+						__m256i match3YB = _mm256_cmpeq_epi8(
+							_mm256_set1_epi8('y'),
+							SHIFT_DATA_B(3)
+						);
+						match3EqYA = _mm256_and_si256(match2EqA, match3YA);
+						match3EqYB = _mm256_and_si256(match2EqB, match3YB);
+						partialEndFound = _mm256_movemask_epi8(_mm256_or_si256(match3EqYA, match3EqYB));
+					}
+					if(LIKELIHOOD(0.002, partialEndFound)) {
+						bool endFound;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                        if(useAVX3MaskCmp) {
-                            __mmask32 match3LfEqYMaskA = _mm256_mask_cmpeq_epi8_mask(
-                                match3EqYMaskA,
-                                _mm256_set1_epi8('\n'),
-                                SHIFT_DATA_A(1)
-                            );
-                            __mmask32 match3LfEqYMaskB = _mm256_mask_cmpeq_epi8_mask(
-                                match3EqYMaskB,
-                                _mm256_set1_epi8('\n'),
-                                SHIFT_DATA_B(1)
-                            );
-
-                            endFound = KORTEST32(
-                                _mm256_mask_cmpeq_epi8_mask(match3LfEqYMaskA, oDataA, _mm256_set1_epi8('\r')),
-                                _mm256_mask_cmpeq_epi8_mask(match3LfEqYMaskB, oDataB, _mm256_set1_epi8('\r'))
-                            );
-                        } else
+						if(useAVX3MaskCmp) {
+							__mmask32 match3LfEqYMaskA = _mm256_mask_cmpeq_epi8_mask(
+								match3EqYMaskA,
+								_mm256_set1_epi8('\n'),
+								SHIFT_DATA_A(1)
+							);
+							__mmask32 match3LfEqYMaskB = _mm256_mask_cmpeq_epi8_mask(
+								match3EqYMaskB,
+								_mm256_set1_epi8('\n'),
+								SHIFT_DATA_B(1)
+							);
+							
+							endFound = KORTEST32(
+								_mm256_mask_cmpeq_epi8_mask(match3LfEqYMaskA, oDataA, _mm256_set1_epi8('\r')),
+								_mm256_mask_cmpeq_epi8_mask(match3LfEqYMaskB, oDataB, _mm256_set1_epi8('\r'))
+							);
+						} else
 #endif
-                        {
-                            __m256i match1LfA = _mm256_cmpeq_epi8(
-                                _mm256_set1_epi8('\n'),
-                                SHIFT_DATA_A(1)
-                            );
-                            __m256i match1LfB = _mm256_cmpeq_epi8(
-                                _mm256_set1_epi8('\n'),
-                                SHIFT_DATA_B(1)
-                            );
-                            endFound = _mm256_movemask_epi8(_mm256_or_si256(
-                                _mm256_and_si256(
-                                    match3EqYA,
-                                    _mm256_and_si256(match1LfA, _mm256_cmpeq_epi8(force_align_read_256(src+i), _mm256_set1_epi8('\r')))
-                                ),
-                                _mm256_and_si256(
-                                    match3EqYB,
-                                    _mm256_and_si256(match1LfB, _mm256_cmpeq_epi8(force_align_read_256(src+i + sizeof(__m256i)), _mm256_set1_epi8('\r')))
-                                )
-                            ));
-                        }
-                        if(endFound) {
-                            len += (long)i;
-                            break;
-                        }
-                    }
-                    if(isRaw) minMask = _mm256_set1_epi8('.');
-                }
-                else if(isRaw) // no \r_. found
-                    minMask = _mm256_set1_epi8('.');
-            }
+						{
+							__m256i match1LfA = _mm256_cmpeq_epi8(
+								_mm256_set1_epi8('\n'),
+								SHIFT_DATA_A(1)
+							);
+							__m256i match1LfB = _mm256_cmpeq_epi8(
+								_mm256_set1_epi8('\n'),
+								SHIFT_DATA_B(1)
+							);
+							endFound = _mm256_movemask_epi8(_mm256_or_si256(
+								_mm256_and_si256(
+									match3EqYA,
+									_mm256_and_si256(match1LfA, _mm256_cmpeq_epi8(force_align_read_256(src+i), _mm256_set1_epi8('\r')))
+								),
+								_mm256_and_si256(
+									match3EqYB,
+									_mm256_and_si256(match1LfB, _mm256_cmpeq_epi8(force_align_read_256(src+i + sizeof(__m256i)), _mm256_set1_epi8('\r')))
+								)
+							));
+						}
+						if(endFound) {
+							len += (long)i;
+							_nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
+					if(isRaw) minMask = _mm256_set1_epi8('.');
+				}
+				else if(isRaw) // no \r_. found
+					minMask = _mm256_set1_epi8('.');
+			}
 #undef SHIFT_DATA_A
 #undef SHIFT_DATA_B
-
-            if(use_isa >= ISA_LEVEL_AVX3)
-                dataB = _mm256_add_epi8(oDataB, _mm256_set1_epi8(-42));
-
-            if(LIKELIHOOD(0.0001, (mask & ((maskEq << 1) + escFirst)) != 0)) {
-                unsigned tmp = lookups->eqFix[(maskEq&0xff) & ~(uint64_t)escFirst];
-                uint64_t maskEq2 = tmp;
-                for(int j=8; j<64; j+=8) {
-                    tmp = lookups->eqFix[(unsigned)((maskEq>>j)&0xff) & ~(tmp>>7)];
-                    maskEq2 |= (uint64_t)tmp<<j;
-                }
-                maskEq = maskEq2;
-
-                mask &= ~(uint64_t)escFirst;
-                escFirst = tmp>>7;
-                // next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
-                maskEq <<= 1;
-                mask &= ~maskEq;
-
-                // unescape chars following `=`
+			
+			if(use_isa >= ISA_LEVEL_AVX3)
+				dataB = _mm256_add_epi8(oDataB, _mm256_set1_epi8(-42));
+			
+			uint64_t maskEqShift1 = (maskEq << 1) + escFirst;
+			if(LIKELIHOOD(0.0001, (mask & maskEqShift1) != 0)) {
+				maskEq = fix_eqMask<uint64_t>(maskEq, maskEqShift1);
+				mask &= ~(uint64_t)escFirst;
+				escFirst = maskEq>>63;
+				// next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
+				maskEq <<= 1;
+				mask &= ~maskEq;
+				
+				// unescape chars following `=`
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
-                    // GCC < 7 seems to generate rubbish assembly for this
-                    dataA = _mm256_mask_add_epi8(
-                        dataA,
-                        (__mmask32)maskEq,
-                        dataA,
-                        _mm256_set1_epi8(-64)
-                    );
-                    dataB = _mm256_mask_add_epi8(
-                        dataB,
-                        (__mmask32)(maskEq>>32),
-                        dataB,
-                        _mm256_set1_epi8(-64)
-                    );
-                } else
+				if(use_isa >= ISA_LEVEL_AVX3) {
+					// GCC < 7 seems to generate rubbish assembly for this
+					dataA = _mm256_mask_add_epi8(
+						dataA,
+						(__mmask32)maskEq,
+						dataA,
+						_mm256_set1_epi8(-64)
+					);
+					dataB = _mm256_mask_add_epi8(
+						dataB,
+						(__mmask32)(maskEq>>32),
+						dataB,
+						_mm256_set1_epi8(-64)
+					);
+				} else
 #endif
-                {
-                    // convert maskEq into vector form (i.e. reverse pmovmskb)
+				{
+					// convert maskEq into vector form (i.e. reverse pmovmskb)
 #ifdef PLATFORM_AMD64
-                    __m256i vMaskEq = _mm256_broadcastq_epi64(_mm_cvtsi64_si128(maskEq));
+					__m256i vMaskEq = _mm256_broadcastq_epi64(_mm_cvtsi64_si128(maskEq));
 #else
-                    __m256i vMaskEq = _mm256_permute4x64_epi64(_mm256_insert_epi32(
-                        _mm256_set_epi32(0,0,0,0, 0,0,0, maskEq & 0xffffffff),
-                        maskEq >> 32,
-                        1
-                    ), 0);
+					__m256i vMaskEq = _mm256_permute4x64_epi64(_mm256_insert_epi32(
+						_mm256_set_epi32(0,0,0,0, 0,0,0, maskEq & 0xffffffff),
+						maskEq >> 32,
+						1
+					), 0);
 #endif
-                    __m256i vMaskEqA = _mm256_shuffle_epi8(vMaskEq, _mm256_set_epi32(
-                        0x03030303, 0x03030303, 0x02020202, 0x02020202,
-                        0x01010101, 0x01010101, 0x00000000, 0x00000000
-                    ));
-                    __m256i vMaskEqB = _mm256_shuffle_epi8(vMaskEq, _mm256_set_epi32(
-                        0x07070707, 0x07070707, 0x06060606, 0x06060606,
-                        0x05050505, 0x05050505, 0x04040404, 0x04040404
-                    ));
-                    vMaskEqA = _mm256_cmpeq_epi8(
-                        _mm256_and_si256(vMaskEqA, _mm256_set1_epi64x(0x8040201008040201ULL)),
-                        _mm256_set1_epi64x(0x8040201008040201ULL)
-                    );
-                    vMaskEqB = _mm256_cmpeq_epi8(
-                        _mm256_and_si256(vMaskEqB, _mm256_set1_epi64x(0x8040201008040201ULL)),
-                        _mm256_set1_epi64x(0x8040201008040201ULL)
-                    );
-                    dataA = _mm256_add_epi8(oDataA, _mm256_blendv_epi8(yencOffset, _mm256_set1_epi8(-42-64), vMaskEqA));
-                    dataB = _mm256_add_epi8(oDataB, _mm256_blendv_epi8(_mm256_set1_epi8(-42), _mm256_set1_epi8(-42-64), vMaskEqB));
-                }
-            } else {
-                escFirst = (maskEq >> 63);
-
+					__m256i vMaskEqA = _mm256_shuffle_epi8(vMaskEq, _mm256_set_epi32(
+						0x03030303, 0x03030303, 0x02020202, 0x02020202,
+						0x01010101, 0x01010101, 0x00000000, 0x00000000
+					));
+					__m256i vMaskEqB = _mm256_shuffle_epi8(vMaskEq, _mm256_set_epi32(
+						0x07070707, 0x07070707, 0x06060606, 0x06060606,
+						0x05050505, 0x05050505, 0x04040404, 0x04040404
+					));
+					vMaskEqA = _mm256_cmpeq_epi8(
+						_mm256_and_si256(vMaskEqA, _mm256_set1_epi64x(0x8040201008040201ULL)),
+						_mm256_set1_epi64x(0x8040201008040201ULL)
+					);
+					vMaskEqB = _mm256_cmpeq_epi8(
+						_mm256_and_si256(vMaskEqB, _mm256_set1_epi64x(0x8040201008040201ULL)),
+						_mm256_set1_epi64x(0x8040201008040201ULL)
+					);
+					dataA = _mm256_add_epi8(oDataA, _mm256_blendv_epi8(yencOffset, _mm256_set1_epi8(-42-64), vMaskEqA));
+					dataB = _mm256_add_epi8(oDataB, _mm256_blendv_epi8(_mm256_set1_epi8(-42), _mm256_set1_epi8(-42-64), vMaskEqB));
+				}
+			} else {
+				escFirst = (maskEq >> 63);
+				
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
-                    dataA = _mm256_mask_add_epi8(
-                        dataA,
-                        (__mmask32)(maskEq << 1),
-                        dataA,
-                        _mm256_set1_epi8(-64)
-                    );
-                    dataB = _mm256_mask_add_epi8(
-                        dataB,
-                        (__mmask32)(maskEq >> 31),
-                        dataB,
-                        _mm256_set1_epi8(-64)
-                    );
-                } else
+				if(use_isa >= ISA_LEVEL_AVX3) {
+					dataA = _mm256_mask_add_epi8(
+						dataA,
+						(__mmask32)(maskEq << 1),
+						dataA,
+						_mm256_set1_epi8(-64)
+					);
+					dataB = _mm256_mask_add_epi8(
+						dataB,
+						(__mmask32)(maskEq >> 31),
+						dataB,
+						_mm256_set1_epi8(-64)
+					);
+				} else
 #endif
-                {
-                    // << 1 byte
-                    cmpEqA = _mm256_alignr_epi8(cmpEqA, _mm256_inserti128_si256(
-                        _mm256_set1_epi8('='), _mm256_castsi256_si128(cmpEqA), 1
-                    ), 15);
-                    cmpEqB = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), _mm256_loadu_si256((__m256i *)(src+i-1) + 1));
-                    dataA = _mm256_add_epi8(
-                        oDataA,
-                        _mm256_blendv_epi8(
-                            yencOffset,
-                            _mm256_set1_epi8(-42-64),
-                            cmpEqA
-                        )
-                    );
-                    dataB = _mm256_add_epi8(
-                        oDataB,
-                        _mm256_blendv_epi8(
-                            _mm256_set1_epi8(-42),
-                            _mm256_set1_epi8(-42-64),
-                            cmpEqB
-                        )
-                    );
-                }
-            }
-            // subtract 64 from first element if escFirst == 1
+				{
+					// << 1 byte
+					cmpEqA = _mm256_alignr_epi8(cmpEqA, _mm256_inserti128_si256(
+						_mm256_set1_epi8('='), _mm256_castsi256_si128(cmpEqA), 1
+					), 15);
+					cmpEqB = _mm256_cmpeq_epi8(_mm256_set1_epi8('='), _mm256_loadu_si256((__m256i *)(src+i-1) + 1));
+					dataA = _mm256_add_epi8(
+						oDataA,
+						_mm256_blendv_epi8(
+							yencOffset,
+							_mm256_set1_epi8(-42-64),
+							cmpEqA
+						)
+					);
+					dataB = _mm256_add_epi8(
+						oDataB,
+						_mm256_blendv_epi8(
+							_mm256_set1_epi8(-42),
+							_mm256_set1_epi8(-42-64),
+							cmpEqB
+						)
+					);
+				}
+			}
+			// subtract 64 from first element if escFirst == 1
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_AVX3) {
-                yencOffset = _mm256_mask_add_epi8(_mm256_set1_epi8(-42), (__mmask32)escFirst, _mm256_set1_epi8(-42), _mm256_set1_epi8(-64));
-            } else
+			if(use_isa >= ISA_LEVEL_AVX3) {
+				yencOffset = _mm256_mask_add_epi8(_mm256_set1_epi8(-42), (__mmask32)escFirst, _mm256_set1_epi8(-42), _mm256_set1_epi8(-64));
+			} else
 #endif
-            {
-                yencOffset = _mm256_xor_si256(_mm256_set1_epi8(-42), zext128_256(
-                    _mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
-                ));
-            }
-
-            // all that's left is to 'compress' the data (skip over masked chars)
+			{
+				yencOffset = _mm256_xor_si256(_mm256_set1_epi8(-42), zext128_256(
+					_mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
+				));
+			}
+			
+			// all that's left is to 'compress' the data (skip over masked chars)
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__)
-            if(use_isa >= ISA_LEVEL_VBMI2) {
-                _mm256_mask_compressstoreu_epi8(p, KNOT32(mask), dataA);
-                p -= popcnt32(mask & 0xffffffff);
-                _mm256_mask_compressstoreu_epi8((p + XMM_SIZE*2), KNOT32(mask>>32), dataB);
-                p += XMM_SIZE*4 - popcnt32(mask >> 32);
-            } else
+			if(use_isa >= ISA_LEVEL_VBMI2) {
+				COMPRESS_STORE(p, KNOT32(mask), dataA);
+				p -= popcnt32(mask & 0xffffffff);
+				COMPRESS_STORE((p + XMM_SIZE*2), KNOT32(mask>>32), dataB);
+				p += XMM_SIZE*4 - popcnt32(mask >> 32);
+			} else
 #endif
-            {
-                // lookup compress masks and shuffle
-                __m256i shuf = _mm256_inserti128_si256(
-                    _mm256_castsi128_si256(_mm_load_si128((__m128i*)(lookups->compact + (mask & 0x7fff)))),
-                    *(__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0)),
-                    1
-                );
-                dataA = _mm256_shuffle_epi8(dataA, shuf);
-
-                _mm_storeu_si128((__m128i*)p, _mm256_castsi256_si128(dataA));
-                // increment output position
-                p -= popcnt32(mask & 0xffff);
-
-                _mm_storeu_si128((__m128i*)(p + XMM_SIZE), _mm256_extracti128_si256(dataA, 1));
-                p -= popcnt32(mask & 0xffff0000);
-
+			{
+				// lookup compress masks and shuffle
+				__m256i shuf = _mm256_inserti128_si256(
+					_mm256_castsi128_si256(_mm_load_si128((__m128i*)(lookups->compact + (mask & 0x7fff)))),
+					*(__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0)),
+					1
+				);
+				dataA = _mm256_shuffle_epi8(dataA, shuf);
+				
+				_mm_storeu_si128((__m128i*)p, _mm256_castsi256_si128(dataA));
+				// increment output position
+				p -= popcnt32(mask & 0xffff);
+				
+				_mm_storeu_si128((__m128i*)(p + XMM_SIZE), _mm256_extracti128_si256(dataA, 1));
+				p -= popcnt32(mask & 0xffff0000);
+				
 #ifdef PLATFORM_AMD64
-                mask >>= 28;
-                shuf = _mm256_inserti128_si256(
-                    _mm256_castsi128_si256(_mm_load_si128((__m128i*)((char*)lookups->compact + (mask & 0x7fff0)))),
-                    *(__m128i*)((char*)lookups->compact + ((mask >> 16) & 0x7fff0)),
-                    1
-                );
-                dataB = _mm256_shuffle_epi8(dataB, shuf);
-
-                _mm_storeu_si128((__m128i*)(p + XMM_SIZE*2), _mm256_castsi256_si128(dataB));
-                p -= popcnt32(mask & 0xffff0);
-
-                _mm_storeu_si128((__m128i*)(p + XMM_SIZE*3), _mm256_extracti128_si256(dataB, 1));
-                p -= popcnt32((unsigned int)(mask >> 20));
+				mask >>= 28;
+				shuf = _mm256_inserti128_si256(
+					_mm256_castsi128_si256(_mm_load_si128((__m128i*)((char*)lookups->compact + (mask & 0x7fff0)))),
+					*(__m128i*)((char*)lookups->compact + ((mask >> 16) & 0x7fff0)),
+					1
+				);
+				dataB = _mm256_shuffle_epi8(dataB, shuf);
+				
+				_mm_storeu_si128((__m128i*)(p + XMM_SIZE*2), _mm256_castsi256_si128(dataB));
+				p -= popcnt32(mask & 0xffff0);
+				
+				_mm_storeu_si128((__m128i*)(p + XMM_SIZE*3), _mm256_extracti128_si256(dataB, 1));
+				p -= popcnt32((unsigned int)(mask >> 20));
 #else
-                mask >>= 32;
-                shuf = _mm256_inserti128_si256(
-                    _mm256_castsi128_si256(_mm_load_si128((__m128i*)(lookups->compact + (mask & 0x7fff)))),
-                    *(__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0)),
-                    1
-                );
-                dataB = _mm256_shuffle_epi8(dataB, shuf);
-
-                _mm_storeu_si128((__m128i*)(p + XMM_SIZE*2), _mm256_castsi256_si128(dataB));
-                p -= popcnt32(mask & 0xffff);
-
-                _mm_storeu_si128((__m128i*)(p + XMM_SIZE*3), _mm256_extracti128_si256(dataB, 1));
-                p -= popcnt32(mask & 0xffff0000);
+				mask >>= 32;
+				shuf = _mm256_inserti128_si256(
+					_mm256_castsi128_si256(_mm_load_si128((__m128i*)(lookups->compact + (mask & 0x7fff)))),
+					*(__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0)),
+					1
+				);
+				dataB = _mm256_shuffle_epi8(dataB, shuf);
+				
+				_mm_storeu_si128((__m128i*)(p + XMM_SIZE*2), _mm256_castsi256_si128(dataB));
+				p -= popcnt32(mask & 0xffff);
+				
+				_mm_storeu_si128((__m128i*)(p + XMM_SIZE*3), _mm256_extracti128_si256(dataB, 1));
+				p -= popcnt32(mask & 0xffff0000);
 #endif
-                p += XMM_SIZE*4;
-            }
-        } else {
-            if(use_isa < ISA_LEVEL_AVX3)
-                dataA = _mm256_add_epi8(oDataA, yencOffset);
-            dataB = _mm256_add_epi8(oDataB, _mm256_set1_epi8(-42));
-
-            _mm256_storeu_si256((__m256i*)p, dataA);
-            _mm256_storeu_si256((__m256i*)p + 1, dataB);
-            p += sizeof(__m256i)*2;
-            escFirst = 0;
-            yencOffset = _mm256_set1_epi8(-42);
-        }
-    }
-    _escFirst = (unsigned char)escFirst;
-    if(isRaw) {
-        // this would be the trivial solution, but requires the compiler holding onto minMask throughout the loop:
-        //_nextMask = ~(uint16_t)_mm256_movemask_epi8(_mm256_cmpeq_epi8(minMask, _mm256_set1_epi8('.')));
-        // instead, just scan the memory to determine what to set nextMask to
-        if(len != 0) { // have to gone through at least one loop cycle
-            if(src[i-2] == '\r' && src[i-1] == '\n' && src[i] == '.')
-                _nextMask = 1;
-            else if(src[i-1] == '\r' && src[i] == '\n' && src[i+1] == '.')
-                _nextMask = 2;
-            else
-                _nextMask = 0;
-        }
-    } else
-        _nextMask = 0;
-    _mm256_zeroupper();
+				p += XMM_SIZE*4;
+			}
+		} else {
+			if(use_isa < ISA_LEVEL_AVX3)
+				dataA = _mm256_add_epi8(oDataA, yencOffset);
+			dataB = _mm256_add_epi8(oDataB, _mm256_set1_epi8(-42));
+			
+			_mm256_storeu_si256((__m256i*)p, dataA);
+			_mm256_storeu_si256((__m256i*)p + 1, dataB);
+			p += sizeof(__m256i)*2;
+			escFirst = 0;
+			yencOffset = _mm256_set1_epi8(-42);
+		}
+	}
+	_escFirst = (unsigned char)escFirst;
+	_mm256_zeroupper();
 }
+} // namespace
 #endif
diff --git a/src/yencode/decoder_common.h b/src/yencode/decoder_common.h
index bf9ce6a..0febde1 100644
--- a/src/yencode/decoder_common.h
+++ b/src/yencode/decoder_common.h
@@ -1,528 +1,220 @@
 #include "decoder.h"
 
-// TODO: need to support max output length somehow
-// TODO: add branch probabilities
-
-
-// state var: refers to the previous state - only used for incremental processing
-template<bool isRaw>
-size_t do_decode_noend_scalar(const unsigned char *HEDLEY_RESTRICT src, unsigned char *HEDLEY_RESTRICT dest, size_t len,
-                              YencDecoderState *state) {
-    const unsigned char *es = src + len; // end source pointer
-    unsigned char *p = dest; // destination pointer
-    long i = -(long) len; // input position
-    unsigned char c; // input character
-
-    if (len < 1) return 0;
-
-    if (isRaw) {
-
-        if (state)
-            switch (*state) {
-                case YDEC_STATE_EQ:
-                    c = es[i];
-                    *p++ = c - 42 - 64;
-                    i++;
-                    if (c == '\r') {
-                        *state = YDEC_STATE_CR;
-                        if (i >= 0) return 0;
-                    } else {
-                        *state = YDEC_STATE_NONE;
-                        break;
-                    }
-                    // fall-thru
-                case YDEC_STATE_CR:
-                    if (es[i] != '\n') break;
-                    i++;
-                    *state = YDEC_STATE_CRLF;
-                    if (i >= 0) return 0;
-                    // Else fall-thru
-                case YDEC_STATE_CRLF:
-                    // skip past first dot
-                    if (es[i] == '.') i++;
-                    // fall-thru
-                default:
-                    break; // silence compiler warnings
-            }
-        else // treat as YDEC_STATE_CRLF
-        if (es[i] == '.') i++;
-
-        for (; i < -2; i++) {
-            c = es[i];
-            switch (c) {
-                case '\r':
-                    // skip past \r\n. sequences
-                    //i += (es[i+1] == '\n' && es[i+2] == '.') << 1;
-                    if (es[i + 1] == '\n' && es[i + 2] == '.')
-                        i += 2;
-                    // fall-thru
-                case '\n':
-                    continue;
-                case '=':
-                    c = es[i + 1];
-                    *p++ = c - 42 - 64;
-                    i += (c != '\r'); // if we have a \r, reprocess character to deal with \r\n. case
-                    continue;
-                default:
-                    *p++ = c - 42;
-            }
-        }
-        if (state) *state = YDEC_STATE_NONE;
-
-        if (i == -2) { // 2nd last char
-            c = es[i];
-            switch (c) {
-                case '\r':
-                    if (state && es[i + 1] == '\n') {
-                        *state = YDEC_STATE_CRLF;
-                        return p - dest;
-                    }
-                    // Else fall-thru
-                case '\n':
-                    break;
-                case '=':
-                    c = es[i + 1];
-                    *p++ = c - 42 - 64;
-                    i += (c != '\r');
-                    break;
-                default:
-                    *p++ = c - 42;
-            }
-            i++;
-        }
+namespace RapidYenc {
+	void decoder_set_sse2_funcs();
+	void decoder_set_ssse3_funcs();
+	void decoder_set_avx_funcs();
+	void decoder_set_avx2_funcs();
+	void decoder_set_vbmi2_funcs();
+	extern const bool decoder_has_avx10;
+	void decoder_set_neon_funcs();
+	void decoder_set_rvv_funcs();
+	
+	template<bool isRaw, bool searchEnd>
+	YencDecoderEnd do_decode_scalar(const unsigned char** src, unsigned char** dest, size_t len, YencDecoderState* state);
+}
 
-        // do final char; we process this separately to prevent an overflow if the final char is '='
-        if (i == -1) {
-            c = es[i];
-            if (c != '\n' && c != '\r' && c != '=') {
-                *p++ = c - 42;
-            } else if (state) {
-                if (c == '=') *state = YDEC_STATE_EQ;
-                else if (c == '\r') *state = YDEC_STATE_CR;
-                else *state = YDEC_STATE_NONE;
-            }
-        }
 
-    } else {
+#if defined(PLATFORM_ARM) && !defined(__aarch64__)
+#define YENC_DEC_USE_THINTABLE 1
+#endif
 
-        if (state && *state == YDEC_STATE_EQ) {
-            *p++ = es[i] - 42 - 64;
-            i++;
-            *state = YDEC_STATE_NONE;
-        }
+// TODO: need to support max output length somehow
 
-        /*for(i = 0; i < len - 1; i++) {
-            c = src[i];
-            if(c == '\n' || c == '\r') continue;
-            unsigned char isEquals = (c == '=');
-            i += isEquals;
-            *p++ = src[i] - (42 + (isEquals << 6));
-        }*/
-        for (; i < -1; i++) {
-            c = es[i];
-            switch (c) {
-                case '\n':
-                case '\r':
-                    continue;
-                case '=':
-                    i++;
-                    c = es[i] - 64;
-            }
-            *p++ = c - 42;
-        }
-        if (state) *state = YDEC_STATE_NONE;
-        // do final char; we process this separately to prevent an overflow if the final char is '='
-        if (i == -1) {
-            c = es[i];
-            if (c != '\n' && c != '\r' && c != '=') {
-                *p++ = c - 42;
-            } else if (state) *state = (c == '=' ? YDEC_STATE_EQ : YDEC_STATE_NONE);
-        }
 
-    }
 
-    return p - dest;
+template<bool isRaw, bool searchEnd, void(&kernel)(const uint8_t*, long&, unsigned char*&, unsigned char&, uint16_t&)>
+static inline RapidYenc::YencDecoderEnd _do_decode_simd(size_t width, const unsigned char** src, unsigned char** dest, size_t len, RapidYenc::YencDecoderState* state) {
+	using namespace RapidYenc;
+	
+	if(len <= width*2) return do_decode_scalar<isRaw, searchEnd>(src, dest, len, state);
+	
+	YencDecoderState tState = YDEC_STATE_CRLF;
+	YencDecoderState* pState = state ? state : &tState;
+	if((uintptr_t)(*src) & ((width-1))) {
+		// find source memory alignment
+		unsigned char* aSrc = (unsigned char*)(((uintptr_t)(*src) + (width-1)) & ~(width-1));
+		int amount = (int)(aSrc - *src);
+		len -= amount;
+		YencDecoderEnd ended = do_decode_scalar<isRaw, searchEnd>(src, dest, amount, pState);
+		if(ended) return ended;
+	}
+	
+	size_t lenBuffer = width -1;
+	if(searchEnd) lenBuffer += 3 + (isRaw?1:0);
+	else if(isRaw) lenBuffer += 2;
+	
+	if(len > lenBuffer) {
+		unsigned char *p = *dest; // destination pointer
+		unsigned char escFirst = 0; // input character; first char needs escaping
+		uint16_t nextMask = 0;
+		// handle finicky case of special sequences straddled across initial boundary
+		switch(*pState) {
+			case YDEC_STATE_CRLF:
+				if(isRaw && **src == '.') {
+					nextMask = 1;
+					if(searchEnd && *(uint16_t*)(*src +1) == UINT16_PACK('\r','\n')) {
+						(*src) += 3;
+						*pState = YDEC_STATE_CRLF;
+						return YDEC_END_ARTICLE;
+					}
+					if(searchEnd && *(uint16_t*)(*src +1) == UINT16_PACK('=','y')) {
+						(*src) += 3;
+						*pState = YDEC_STATE_NONE;
+						return YDEC_END_CONTROL;
+					}
+				}
+				else if(searchEnd && *(uint16_t*)(*src) == UINT16_PACK('=','y')) {
+					(*src) += 2;
+					*pState = YDEC_STATE_NONE;
+					return YDEC_END_CONTROL;
+				}
+				break;
+			case YDEC_STATE_CR:
+				if(isRaw && *(uint16_t*)(*src) == UINT16_PACK('\n','.')) {
+					nextMask = 2;
+					if(searchEnd && *(uint16_t*)(*src +2) == UINT16_PACK('\r','\n')) {
+						(*src) += 4;
+						*pState = YDEC_STATE_CRLF;
+						return YDEC_END_ARTICLE;
+					}
+					if(searchEnd && *(uint16_t*)(*src +2) == UINT16_PACK('=','y')) {
+						(*src) += 4;
+						*pState = YDEC_STATE_NONE;
+						return YDEC_END_CONTROL;
+					}
+				}
+				else if(searchEnd && (*(uint32_t*)(*src) & 0xffffff) == UINT32_PACK('\n','=','y',0)) {
+					(*src) += 3;
+					*pState = YDEC_STATE_NONE;
+					return YDEC_END_CONTROL;
+				}
+				break;
+			case YDEC_STATE_CRLFDT:
+				if(searchEnd && isRaw && *(uint16_t*)(*src) == UINT16_PACK('\r','\n')) {
+					(*src) += 2;
+					*pState = YDEC_STATE_CRLF;
+					return YDEC_END_ARTICLE;
+				}
+				if(searchEnd && isRaw && *(uint16_t*)(*src) == UINT16_PACK('=','y')) {
+					(*src) += 2;
+					*pState = YDEC_STATE_NONE;
+					return YDEC_END_CONTROL;
+				}
+				break;
+			case YDEC_STATE_CRLFDTCR:
+				if(searchEnd && isRaw && **src == '\n') {
+					(*src) += 1;
+					*pState = YDEC_STATE_CRLF;
+					return YDEC_END_ARTICLE;
+				}
+				break;
+			case YDEC_STATE_CRLFEQ:
+				if(searchEnd && **src == 'y') {
+					(*src) += 1;
+					*pState = YDEC_STATE_NONE;
+					return YDEC_END_CONTROL;
+				}
+				break;
+			default: break; // silence compiler warning
+		}
+		escFirst = (*pState == YDEC_STATE_EQ || *pState == YDEC_STATE_CRLFEQ);
+		
+		// our algorithm may perform an aligned load on the next part, of which we consider 2 bytes (for \r\n. sequence checking)
+		long dLen = (long)(len - lenBuffer);
+		dLen = (dLen + (width-1)) & ~(width-1);
+		
+		kernel((const uint8_t*)(*src) + dLen, dLen, p, escFirst, nextMask);
+		
+		if(escFirst) *pState = YDEC_STATE_EQ; // escape next character
+		else if(nextMask == 1) *pState = YDEC_STATE_CRLF; // next character is '.', where previous two were \r\n
+		else if(nextMask == 2) *pState = YDEC_STATE_CR; // next characters are '\n.', previous is \r
+		else *pState = YDEC_STATE_NONE;
+		
+		*src += dLen;
+		len -= dLen;
+		*dest = p;
+	}
+	
+	// end alignment
+	if(len)
+		return do_decode_scalar<isRaw, searchEnd>(src, dest, len, pState);
+	/** for debugging: ensure that the SIMD routine doesn't exit early
+	if(len && !searchEnd) {
+		const uint8_t* s = *src;
+		unsigned char* p = *dest;
+		int ended = do_decode_scalar<isRaw, searchEnd>(src, dest, len, pState);
+		if(*src - s > width*2) {
+			// this shouldn't happen, corrupt some data to fail the test
+			while(p < *dest)
+				*p++ = 0;
+		}
+		return ended;
+	}
+	*/
+	return YDEC_END_NONE;
 }
 
-template<bool isRaw>
-YencDecoderEnd
-do_decode_end_scalar(const unsigned char *HEDLEY_RESTRICT *src, unsigned char *HEDLEY_RESTRICT *dest, size_t len,
-                     YencDecoderState *state) {
-    const unsigned char *es = (*src) + len; // end source pointer
-    unsigned char *p = *dest; // destination pointer
-    long i = -(long) len; // input position
-    unsigned char c; // input character
-
-    if (len < 1) return YDEC_END_NONE;
-
-#define YDEC_CHECK_END(s) if(i == 0) { \
-    *state = s; \
-    *src = es; \
-    *dest = p; \
-    return YDEC_END_NONE; \
+template<bool isRaw, bool searchEnd, size_t width, void(&kernel)(const uint8_t*, long&, unsigned char*&, unsigned char&, uint16_t&)>
+static RapidYenc::YencDecoderEnd do_decode_simd(const unsigned char** src, unsigned char** dest, size_t len, RapidYenc::YencDecoderState* state) {
+	return _do_decode_simd<isRaw, searchEnd, kernel>(width, src, dest, len, state);
+}
+template<bool isRaw, bool searchEnd, size_t(&getWidth)(), void(&kernel)(const uint8_t*, long&, unsigned char*&, unsigned char&, uint16_t&)>
+static RapidYenc::YencDecoderEnd do_decode_simd(const unsigned char** src, unsigned char** dest, size_t len, RapidYenc::YencDecoderState* state) {
+	return _do_decode_simd<isRaw, searchEnd, kernel>(getWidth(), src, dest, len, state);
 }
-    if (state)
-        switch (*state) {
-            case YDEC_STATE_CRLFEQ:
-            do_decode_endable_scalar_ceq:
-                if (es[i] == 'y') {
-                    *state = YDEC_STATE_NONE;
-                    *src = es + i + 1;
-                    *dest = p;
-                    return YDEC_END_CONTROL;
-                } // Else fall-thru
-            case YDEC_STATE_EQ:
-                c = es[i];
-                *p++ = c - 42 - 64;
-                i++;
-                if (c != '\r') break;
-                YDEC_CHECK_END(YDEC_STATE_CR)
-                // fall-through
-            case YDEC_STATE_CR:
-                if (es[i] != '\n') break;
-                i++;
-                YDEC_CHECK_END(YDEC_STATE_CRLF)
-                // fall-through
-            case YDEC_STATE_CRLF:
-            do_decode_endable_scalar_c0:
-                if (es[i] == '.' && isRaw) {
-                    i++;
-                    YDEC_CHECK_END(YDEC_STATE_CRLFDT)
-                    // fall-through
-                } else if (es[i] == '=') {
-                    i++;
-                    YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
-                    goto do_decode_endable_scalar_ceq;
-                } else
-                    break;
-            case YDEC_STATE_CRLFDT:
-                if (isRaw && es[i] == '\r') {
-                    i++;
-                    YDEC_CHECK_END(YDEC_STATE_CRLFDTCR)
-                    // fall-through
-                } else if (isRaw && es[i] == '=') { // check for dot-stuffed ending: \r\n.=y
-                    i++;
-                    YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
-                    goto do_decode_endable_scalar_ceq;
-                } else
-                    break;
-            case YDEC_STATE_CRLFDTCR:
-                if (es[i] == '\n') {
-                    if (isRaw) {
-                        *state = YDEC_STATE_CRLF;
-                        *src = es + i + 1;
-                        *dest = p;
-                        return YDEC_END_ARTICLE;
-                    } else {
-                        i++;
-                        YDEC_CHECK_END(YDEC_STATE_CRLF)
-                        goto do_decode_endable_scalar_c0; // handle as CRLF
-                    }
-                } else
-                    break;
-            case YDEC_STATE_NONE:
-                break; // silence compiler warning
-        }
-    else // treat as YDEC_STATE_CRLF
-        goto do_decode_endable_scalar_c0;
-
-    for (; i < -2; i++) {
-        c = es[i];
-        switch (c) {
-            case '\r':
-                if (es[i + 1] == '\n') {
-                    if (isRaw && es[i + 2] == '.') {
-                        // skip past \r\n. sequences
-                        i += 3;
-                        YDEC_CHECK_END(YDEC_STATE_CRLFDT)
-                        // check for end
-                        if (es[i] == '\r') {
-                            i++;
-                            YDEC_CHECK_END(YDEC_STATE_CRLFDTCR)
-                            if (es[i] == '\n') {
-                                *src = es + i + 1;
-                                *dest = p;
-                                *state = YDEC_STATE_CRLF;
-                                return YDEC_END_ARTICLE;
-                            } else i--;
-                        } else if (es[i] == '=') {
-                            i++;
-                            YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
-                            if (es[i] == 'y') {
-                                *src = es + i + 1;
-                                *dest = p;
-                                *state = YDEC_STATE_NONE;
-                                return YDEC_END_CONTROL;
-                            } else {
-                                // escape char & continue
-                                c = es[i];
-                                *p++ = c - 42 - 64;
-                                i -= (c == '\r');
-                            }
-                        } else i--;
-                    } else if (es[i + 2] == '=') {
-                        i += 3;
-                        YDEC_CHECK_END(YDEC_STATE_CRLFEQ)
-                        if (es[i] == 'y') {
-                            // ended
-                            *src = es + i + 1;
-                            *dest = p;
-                            *state = YDEC_STATE_NONE;
-                            return YDEC_END_CONTROL;
-                        } else {
-                            // escape char & continue
-                            c = es[i];
-                            *p++ = c - 42 - 64;
-                            i -= (c == '\r');
-                        }
-                    }
-                } // fall-thru
-            case '\n':
-                continue;
-            case '=':
-                c = es[i + 1];
-                *p++ = c - 42 - 64;
-                i += (c != '\r'); // if we have a \r, reprocess character to deal with \r\n. case
-                continue;
-            default:
-                *p++ = c - 42;
-        }
-    }
-    if (state) *state = YDEC_STATE_NONE;
-
-    if (i == -2) { // 2nd last char
-        c = es[i];
-        switch (c) {
-            case '\r':
-                if (state && es[i + 1] == '\n') {
-                    *state = YDEC_STATE_CRLF;
-                    *src = es;
-                    *dest = p;
-                    return YDEC_END_NONE;
-                }
-                // Else fall-thru
-            case '\n':
-                break;
-            case '=':
-                c = es[i + 1];
-                *p++ = c - 42 - 64;
-                i += (c != '\r');
-                break;
-            default:
-                *p++ = c - 42;
-        }
-        i++;
-    }
 
-    // do final char; we process this separately to prevent an overflow if the final char is '='
-    if (i == -1) {
-        c = es[i];
-        if (c != '\n' && c != '\r' && c != '=') {
-            *p++ = c - 42;
-        } else if (state) {
-            if (c == '=') *state = YDEC_STATE_EQ;
-            else if (c == '\r') *state = YDEC_STATE_CR;
-            else *state = YDEC_STATE_NONE;
-        }
-    }
-#undef YDEC_CHECK_END
 
-    *src = es;
-    *dest = p;
-    return YDEC_END_NONE;
+#if defined(PLATFORM_X86) || defined(PLATFORM_ARM)
+namespace RapidYenc {
+	void decoder_init_lut(void* compactLUT);
 }
+#endif
 
-template<bool isRaw, bool searchEnd>
-YencDecoderEnd
-do_decode_scalar(const unsigned char *HEDLEY_RESTRICT *src, unsigned char *HEDLEY_RESTRICT *dest, size_t len,
-                 YencDecoderState *state) {
-    if (searchEnd)
-        return do_decode_end_scalar<isRaw>(src, dest, len, state);
-    *dest += do_decode_noend_scalar<isRaw>(*src, *dest, len, state);
-    *src += len;
-    return YDEC_END_NONE;
+template<bool isRaw>
+static inline void decoder_set_nextMask(const uint8_t* src, size_t len, uint16_t& nextMask) {
+	if(isRaw) {
+		if(len != 0) { // have to gone through at least one loop cycle
+			if(src[-2] == '\r' && src[-1] == '\n' && src[0] == '.')
+				nextMask = 1;
+			else if(src[-1] == '\r' && src[0] == '\n' && src[1] == '.')
+				nextMask = 2;
+			else
+				nextMask = 0;
+		}
+	} else
+		nextMask = 0;
 }
 
-
-template<bool isRaw, bool searchEnd, int width, void(&kernel)(const uint8_t * HEDLEY_RESTRICT, long &,
-                                                              unsigned char *HEDLEY_RESTRICT &, unsigned char &,
-                                                              uint16_t &)>
-YencDecoderEnd
-do_decode_simd(const unsigned char *HEDLEY_RESTRICT *src, unsigned char *HEDLEY_RESTRICT *dest, size_t len,
-               YencDecoderState *state) {
-    if (len <= width * 2) return do_decode_scalar<isRaw, searchEnd>(src, dest, len, state);
-
-    YencDecoderState tState = YDEC_STATE_CRLF;
-    YencDecoderState *pState = state ? state : &tState;
-    if ((uintptr_t)(*src) & ((width - 1))) {
-        // find source memory alignment
-        unsigned char *aSrc = (unsigned char *) (((uintptr_t)(*src) + (width - 1)) & ~(width - 1));
-        int amount = (int) (aSrc - *src);
-        len -= amount;
-        YencDecoderEnd ended = do_decode_scalar<isRaw, searchEnd>(src, dest, amount, pState);
-        if (ended) return ended;
-    }
-
-    size_t lenBuffer = width - 1;
-    if (searchEnd) lenBuffer += 3 + (isRaw ? 1 : 0);
-    else if (isRaw) lenBuffer += 2;
-
-    if (len > lenBuffer) {
-        unsigned char *p = *dest; // destination pointer
-        unsigned char escFirst = 0; // input character; first char needs escaping
-        uint16_t nextMask = 0;
-        // handle finicky case of special sequences straddled across initial boundary
-        switch (*pState) {
-            case YDEC_STATE_CRLF:
-                if (isRaw && **src == '.') {
-                    nextMask = 1;
-                    if (searchEnd && *(uint16_t * )(*src + 1) == UINT16_PACK('\r', '\n')) {
-                        (*src) += 3;
-                        *pState = YDEC_STATE_CRLF;
-                        return YDEC_END_ARTICLE;
-                    }
-                    if (searchEnd && *(uint16_t * )(*src + 1) == UINT16_PACK('=', 'y')) {
-                        (*src) += 3;
-                        *pState = YDEC_STATE_NONE;
-                        return YDEC_END_CONTROL;
-                    }
-                } else if (searchEnd && *(uint16_t * )(*src) == UINT16_PACK('=', 'y')) {
-                    (*src) += 2;
-                    *pState = YDEC_STATE_NONE;
-                    return YDEC_END_CONTROL;
-                }
-                break;
-            case YDEC_STATE_CR:
-                if (isRaw && *(uint16_t * )(*src) == UINT16_PACK('\n', '.')) {
-                    nextMask = 2;
-                    if (searchEnd && *(uint16_t * )(*src + 2) == UINT16_PACK('\r', '\n')) {
-                        (*src) += 4;
-                        *pState = YDEC_STATE_CRLF;
-                        return YDEC_END_ARTICLE;
-                    }
-                    if (searchEnd && *(uint16_t * )(*src + 2) == UINT16_PACK('=', 'y')) {
-                        (*src) += 4;
-                        *pState = YDEC_STATE_NONE;
-                        return YDEC_END_CONTROL;
-                    }
-                } else if (searchEnd && (*(uint32_t * )(*src) & 0xffffff) == UINT32_PACK('\n', '=', 'y', 0)) {
-                    (*src) += 3;
-                    *pState = YDEC_STATE_NONE;
-                    return YDEC_END_CONTROL;
-                }
-                break;
-            case YDEC_STATE_CRLFDT:
-                if (searchEnd && isRaw && *(uint16_t * )(*src) == UINT16_PACK('\r', '\n')) {
-                    (*src) += 2;
-                    *pState = YDEC_STATE_CRLF;
-                    return YDEC_END_ARTICLE;
-                }
-                if (searchEnd && isRaw && *(uint16_t * )(*src) == UINT16_PACK('=', 'y')) {
-                    (*src) += 2;
-                    *pState = YDEC_STATE_NONE;
-                    return YDEC_END_CONTROL;
-                }
-                break;
-            case YDEC_STATE_CRLFDTCR:
-                if (searchEnd && isRaw && **src == '\n') {
-                    (*src) += 1;
-                    *pState = YDEC_STATE_CRLF;
-                    return YDEC_END_ARTICLE;
-                }
-                break;
-            case YDEC_STATE_CRLFEQ:
-                if (searchEnd && **src == 'y') {
-                    (*src) += 1;
-                    *pState = YDEC_STATE_NONE;
-                    return YDEC_END_CONTROL;
-                }
-                break;
-            default:
-                break; // silence compiler warning
-        }
-        escFirst = (*pState == YDEC_STATE_EQ || *pState == YDEC_STATE_CRLFEQ);
-
-        // our algorithm may perform an aligned load on the next part, of which we consider 2 bytes (for \r\n. sequence checking)
-        long dLen = (long) (len - lenBuffer);
-        dLen = (dLen + (width - 1)) & ~(width - 1);
-
-        kernel((const uint8_t *) (*src) + dLen, dLen, p, escFirst, nextMask);
-
-        if (escFirst) *pState = YDEC_STATE_EQ; // escape next character
-        else if (nextMask == 1) *pState = YDEC_STATE_CRLF; // next character is '.', where previous two were \r\n
-        else if (nextMask == 2) *pState = YDEC_STATE_CR; // next characters are '\n.', previous is \r
-        else *pState = YDEC_STATE_NONE;
-
-        *src += dLen;
-        len -= dLen;
-        *dest = p;
-    }
-
-    // end alignment
-    if (len)
-        return do_decode_scalar<isRaw, searchEnd>(src, dest, len, pState);
-    /** for debugging: ensure that the SIMD routine doesn't exit early
-    if(len && !searchEnd) {
-        const uint8_t* s = *src;
-        unsigned char* p = *dest;
-        int ended = do_decode_scalar<isRaw, searchEnd>(src, dest, len, pState);
-        if(*src - s > width*2) {
-            // this shouldn't happen, corrupt some data to fail the test
-            while(p < *dest)
-                *p++ = 0;
-        }
-        return ended;
-    }
-    */
-    return YDEC_END_NONE;
+// without backtracking
+template<bool isRaw>
+static inline uint16_t decoder_set_nextMask(const uint8_t* src, unsigned mask) {
+	if(isRaw) {
+		if(src[0] == '.')
+			return mask & 1;
+		if(src[1] == '.')
+			return mask & 2;
+	}
+	return 0;
 }
 
-static inline void decoder_init_lut(uint8_t *eqFixLUT, void *compactLUT) {
-    for (int i = 0; i < 256; i++) {
-        int k = i;
-        int p = 0;
-
-        // fix LUT
-        k = i;
-        p = 0;
-        for (int j = 0; j < 8; j++) {
-            k = i >> j;
-            if (k & 1) {
-                p |= 1 << j;
-                j++;
-            }
-        }
-        eqFixLUT[i] = p;
-
-#ifdef YENC_DEC_USE_THINTABLE
-        uint8_t* res = (uint8_t*)compactLUT + i*8;
-        k = i;
-        p = 0;
-        for(int j=0; j<8; j++) {
-            if(!(k & 1)) {
-                res[p++] = j;
-            }
-            k >>= 1;
-        }
-        for(; p<8; p++)
-            res[p] = 0x80;
-#endif
-    }
-#ifndef YENC_DEC_USE_THINTABLE
-    for (int i = 0; i < 32768; i++) {
-        int k = i;
-        uint8_t *res = (uint8_t *) compactLUT + i * 16;
-        int p = 0;
-
-        for (int j = 0; j < 16; j++) {
-            if (!(k & 1)) {
-                res[p++] = j;
-            }
-            k >>= 1;
-        }
-        for (; p < 16; p++)
-            res[p] = 0x80;
-    }
-#endif
+// resolve invalid sequences of = to deal with cases like '===='
+// bit hack inspired from simdjson: https://youtu.be/wlvKAT7SZIQ?t=33m38s
+template<typename T>
+static inline T fix_eqMask(T mask, T maskShift1) {
+	// isolate the start of each consecutive bit group (e.g. 01011101 -> 01000101)
+	T start = mask & ~maskShift1;
+	
+	// this strategy works by firstly separating groups that start on even/odd bits
+	// generally, it doesn't matter which one (even/odd) we pick, but clearing even groups specifically allows the escFirst bit in maskShift1 to work
+	// (this is because the start of the escFirst group is at index -1, an odd bit, but we can't clear it due to being < 0, so we just retain all odd groups instead)
+	
+	const T even = (T)0x5555555555555555; // every even bit (01010101...)
+	
+	// obtain groups which start on an odd bit (clear groups that start on an even bit, but this leaves an unwanted trailing bit)
+	T oddGroups = mask + (start & even);
+	
+	// clear even bits in odd groups, whilst conversely preserving even bits in even groups
+	// the `& mask` also conveniently gets rid of unwanted trailing bits
+	return (oddGroups ^ even) & mask;
 }
-
diff --git a/src/yencode/decoder_neon.cc b/src/yencode/decoder_neon.cc
index c1bf912..ac325c5 100644
--- a/src/yencode/decoder_neon.cc
+++ b/src/yencode/decoder_neon.cc
@@ -1,12 +1,8 @@
 #include "common.h"
+#include "decoder_common.h"
 
 #ifdef __ARM_NEON
 
-#ifndef __aarch64__
-#define YENC_DEC_USE_THINTABLE 1
-#endif
-#include "decoder_common.h"
-
 
 #if defined(_MSC_VER) && !defined(__clang__)
 # define vld1_u8_align(p, a) vld1_u8_ex(p, a*8)
@@ -31,8 +27,8 @@
 # define vst1q_u8_x2_unaligned vst1q_u8_x2
 #else
 static HEDLEY_ALWAYS_INLINE void vst1q_u8_x2_unaligned(uint8_t* p, uint8x16x2_t data) {
-    vst1q_u8(p, data.val[0]);
-    vst1q_u8(p+16, data.val[1]);
+	vst1q_u8(p, data.val[0]);
+	vst1q_u8(p+16, data.val[1]);
 }
 #endif
 
@@ -44,435 +40,423 @@ static struct { char bytes[16]; } ALIGN_TO(16, compactLUT[32768]);
 # pragma pack()
 #endif
 
-static uint8_t eqFixLUT[256];
-
 
 
 static bool neon_vect_is_nonzero(uint8x16_t v) {
 # ifdef __aarch64__
-    return !!(vget_lane_u64(vreinterpret_u64_u32(vqmovn_u64(vreinterpretq_u64_u8(v))), 0));
+	return !!(vget_lane_u64(vreinterpret_u64_u32(vqmovn_u64(vreinterpretq_u64_u8(v))), 0));
 # else
-    uint32x4_t tmp1 = vreinterpretq_u32_u8(v);
-    uint32x2_t tmp2 = vorr_u32(vget_low_u32(tmp1), vget_high_u32(tmp1));
-    return !!(vget_lane_u32(vpmax_u32(tmp2, tmp2), 0));
+	uint32x4_t tmp1 = vreinterpretq_u32_u8(v);
+	uint32x2_t tmp2 = vorr_u32(vget_low_u32(tmp1), vget_high_u32(tmp1));
+	return !!(vget_lane_u32(vpmax_u32(tmp2, tmp2), 0));
 # endif
 }
 
 
+namespace RapidYenc {
+
 template<bool isRaw, bool searchEnd>
-HEDLEY_ALWAYS_INLINE void do_decode_neon(const uint8_t* HEDLEY_RESTRICT src, long& len, unsigned char* HEDLEY_RESTRICT & p, unsigned char& escFirst, uint16_t& nextMask) {
-    HEDLEY_ASSUME(escFirst == 0 || escFirst == 1);
-    HEDLEY_ASSUME(nextMask == 0 || nextMask == 1 || nextMask == 2);
-    uint8x16_t yencOffset = escFirst ? vmakeq_u8(42+64,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42) : vdupq_n_u8(42);
+HEDLEY_ALWAYS_INLINE void do_decode_neon(const uint8_t* src, long& len, unsigned char*& p, unsigned char& escFirst, uint16_t& nextMask) {
+	HEDLEY_ASSUME(escFirst == 0 || escFirst == 1);
+	HEDLEY_ASSUME(nextMask == 0 || nextMask == 1 || nextMask == 2);
+	uint8x16_t yencOffset = escFirst ? vmakeq_u8(42+64,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42) : vdupq_n_u8(42);
 #ifdef __aarch64__
-    uint8x16_t nextMaskMix = vdupq_n_u8(0);
-    if(nextMask == 1)
-        nextMaskMix = vsetq_lane_u8(1, nextMaskMix, 0);
-    if(nextMask == 2)
-        nextMaskMix = vsetq_lane_u8(2, nextMaskMix, 1);
+	uint8x16_t nextMaskMix = vdupq_n_u8(0);
+	if(nextMask == 1)
+		nextMaskMix = vsetq_lane_u8(1, nextMaskMix, 0);
+	if(nextMask == 2)
+		nextMaskMix = vsetq_lane_u8(2, nextMaskMix, 1);
 #else
-    uint8x16_t lfCompare = vdupq_n_u8('\n');
-    if(isRaw) {
-        if(nextMask == 1)
-            lfCompare = vsetq_lane_u8('.', lfCompare, 0);
-        if(nextMask == 2)
-            lfCompare = vsetq_lane_u8('.', lfCompare, 1);
-    }
+	uint8x16_t lfCompare = vdupq_n_u8('\n');
+	if(isRaw) {
+		if(nextMask == 1)
+			lfCompare = vsetq_lane_u8('.', lfCompare, 0);
+		if(nextMask == 2)
+			lfCompare = vsetq_lane_u8('.', lfCompare, 1);
+	}
 #endif
-    long i;
-    for(i = -len; i; i += sizeof(uint8x16_t)*2) {
-        uint8x16x2_t data = vld1q_u8_x2_align(src+i, 32);
-        uint8x16_t dataA = data.val[0];
-        uint8x16_t dataB = data.val[1];
-
-        // search for special chars
-        uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('=')),
-        cmpEqB = vceqq_u8(dataB, vdupq_n_u8('=')),
+	
+	decoder_set_nextMask<isRaw>(src, len, nextMask);
+	
+	long i;
+	for(i = -len; i; i += sizeof(uint8x16_t)*2) {
+		uint8x16x2_t data = vld1q_u8_x2_align(src+i, 32);
+		uint8x16_t dataA = data.val[0];
+		uint8x16_t dataB = data.val[1];
+		
+		// search for special chars
+		uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('=')),
+		cmpEqB = vceqq_u8(dataB, vdupq_n_u8('=')),
 #ifdef __aarch64__
-        cmpA = vqtbx1q_u8(
-            cmpEqA,
-            //                             \n      \r
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataA
-        ),
-        cmpB = vqtbx1q_u8(
-            cmpEqB,
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataB
-        );
-        if(isRaw) cmpA = vorrq_u8(cmpA, nextMaskMix);
+		cmpA = vqtbx1q_u8(
+			cmpEqA,
+			//                             \n      \r
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataA
+		),
+		cmpB = vqtbx1q_u8(
+			cmpEqB,
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataB
+		);
+		if(isRaw) cmpA = vorrq_u8(cmpA, nextMaskMix);
 #else
-        cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r')),
-        cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r')),
-        cmpA = vorrq_u8(
-            vorrq_u8(
-                cmpCrA,
-                vceqq_u8(dataA, lfCompare)
-            ),
-            cmpEqA
-        ),
-        cmpB = vorrq_u8(
-            vorrq_u8(
-                cmpCrB,
-                vceqq_u8(dataB, vdupq_n_u8('\n'))
-            ),
-            cmpEqB
-        );
+		cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r')),
+		cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r')),
+		cmpA = vorrq_u8(
+			vorrq_u8(
+				cmpCrA,
+				vceqq_u8(dataA, lfCompare)
+			),
+			cmpEqA
+		),
+		cmpB = vorrq_u8(
+			vorrq_u8(
+				cmpCrB,
+				vceqq_u8(dataB, vdupq_n_u8('\n'))
+			),
+			cmpEqB
+		);
 #endif
-
-
+		
+		
 #ifdef __aarch64__
-        if (LIKELIHOOD(0.42 /*guess*/, neon_vect_is_nonzero(vorrq_u8(cmpA, cmpB)))) {
-            cmpA = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-            cmpB = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-            uint8x16_t cmpMerge = vpaddq_u8(cmpA, cmpB);
-            uint8x16_t cmpEqMerge = vpaddq_u8(
-                vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-            );
-
-            uint8x16_t cmpCombined = vpaddq_u8(cmpMerge, cmpEqMerge);
-            cmpCombined = vpaddq_u8(cmpCombined, cmpCombined);
-            uint8x8_t cmpPacked = vget_low_u8(cmpCombined);
-            uint32_t mask = vgetq_lane_u32(vreinterpretq_u32_u8(cmpCombined), 0);
-            uint32_t maskEq = vgetq_lane_u32(vreinterpretq_u32_u8(cmpCombined), 1);
+		if (LIKELIHOOD(0.42 /*guess*/, neon_vect_is_nonzero(vorrq_u8(cmpA, cmpB)))) {
+			cmpA = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+			cmpB = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+			uint8x16_t cmpMerge = vpaddq_u8(cmpA, cmpB);
+			uint8x16_t cmpEqMerge = vpaddq_u8(
+				vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+				vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+			);
+			
+			uint8x16_t cmpCombined = vpaddq_u8(cmpMerge, cmpEqMerge);
+			cmpCombined = vpaddq_u8(cmpCombined, cmpCombined);
+			uint8x8_t cmpPacked = vget_low_u8(cmpCombined);
+			uint32_t mask = vgetq_lane_u32(vreinterpretq_u32_u8(cmpCombined), 0);
+			uint32_t maskEq = vgetq_lane_u32(vreinterpretq_u32_u8(cmpCombined), 1);
 #else
-        cmpA = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-        cmpB = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-        // no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
-        uint8x8_t cmpPacked = vpadd_u8(
-            vpadd_u8(
-                vget_low_u8(cmpA), vget_high_u8(cmpA)
-            ),
-            vpadd_u8(
-                vget_low_u8(cmpB), vget_high_u8(cmpB)
-            )
-        );
-        cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
-        uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
-        if(LIKELIHOOD(0.42, mask != 0)) {
-            uint8x16_t cmpEqMaskedA = vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-            uint8x16_t cmpEqMaskedB = vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-            uint8x8_t cmpEqPacked = vpadd_u8(
-                vpadd_u8(
-                    vget_low_u8(cmpEqMaskedA), vget_high_u8(cmpEqMaskedA)
-                ),
-                vpadd_u8(
-                    vget_low_u8(cmpEqMaskedB), vget_high_u8(cmpEqMaskedB)
-                )
-            );
-            cmpEqPacked = vpadd_u8(cmpEqPacked, cmpEqPacked);
-            uint32_t maskEq = vget_lane_u32(vreinterpret_u32_u8(cmpEqPacked), 0);
+		cmpA = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+		cmpB = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+		// no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
+		uint8x8_t cmpPacked = vpadd_u8(
+			vpadd_u8(
+				vget_low_u8(cmpA), vget_high_u8(cmpA)
+			),
+			vpadd_u8(
+				vget_low_u8(cmpB), vget_high_u8(cmpB)
+			)
+		);
+		cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
+		uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
+		if(LIKELIHOOD(0.42, mask != 0)) {
+			uint8x16_t cmpEqMaskedA = vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+			uint8x16_t cmpEqMaskedB = vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+			uint8x8_t cmpEqPacked = vpadd_u8(
+				vpadd_u8(
+					vget_low_u8(cmpEqMaskedA), vget_high_u8(cmpEqMaskedA)
+				),
+				vpadd_u8(
+					vget_low_u8(cmpEqMaskedB), vget_high_u8(cmpEqMaskedB)
+				)
+			);
+			cmpEqPacked = vpadd_u8(cmpEqPacked, cmpEqPacked);
+			uint32_t maskEq = vget_lane_u32(vreinterpret_u32_u8(cmpEqPacked), 0);
 #endif
-
-            // handle \r\n. sequences
-            // RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
-            if((isRaw || searchEnd) && LIKELIHOOD(0.15, mask != maskEq)) {
-                // vext seems to be a cheap operation on ARM, relative to loads, so only avoid it if there's only one load (isRaw only)
-                uint8x16_t tmpData2, nextData;
-                if(isRaw && !searchEnd) {
-                    tmpData2 = vld1q_u8(src+i + 2 + sizeof(uint8x16_t));
-                } else {
-                    nextData = vld1q_u8_align(src+i + sizeof(uint8x16_t)*2, 16); // only 32-bits needed, but there doesn't appear a nice way to do this via intrinsics: https://stackoverflow.com/questions/46910799/arm-neon-intrinsics-convert-d-64-bit-register-to-low-half-of-q-128-bit-regis
-                    tmpData2 = vextq_u8(dataB, nextData, 2);
-                }
+			
+			// handle \r\n. sequences
+			// RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
+			if((isRaw || searchEnd) && LIKELIHOOD(0.15, mask != maskEq)) {
+				// vext seems to be a cheap operation on ARM, relative to loads, so only avoid it if there's only one load (isRaw only)
+				uint8x16_t tmpData2, nextData;
+				if(isRaw && !searchEnd) {
+					tmpData2 = vld1q_u8(src+i + 2 + sizeof(uint8x16_t));
+				} else {
+					nextData = vld1q_u8_align(src+i + sizeof(uint8x16_t)*2, 16); // only 32-bits needed, but there doesn't appear a nice way to do this via intrinsics: https://stackoverflow.com/questions/46910799/arm-neon-intrinsics-convert-d-64-bit-register-to-low-half-of-q-128-bit-regis
+					tmpData2 = vextq_u8(dataB, nextData, 2);
+				}
 #ifdef __aarch64__
-                uint8x16_t cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r'));
-                uint8x16_t cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r'));
+				uint8x16_t cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r'));
+				uint8x16_t cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r'));
 # define NEXT_DATA(n) vextq_u8(dataB, nextData, n)
 #else
 // on ARMv7, prefer loading over VEXT to avoid holding onto nextData reference; this reduces register spills. Shouldn't be an issue on ARMv8 due to 32x 128-bit registers
 # define NEXT_DATA(n) vld1q_u8(src+i + n+sizeof(uint8x16_t))
 #endif
-                uint8x16_t match2EqA, match2Cr_DotA;
-                uint8x16_t match2EqB, match2Cr_DotB;
-                if(searchEnd) {
-                    match2EqB = vceqq_u8(tmpData2, vdupq_n_u8('='));
-                }
-                if(isRaw) {
-                    match2Cr_DotA = vandq_u8(cmpCrA, vceqq_u8(vextq_u8(dataA, dataB, 2), vdupq_n_u8('.')));
-                    match2Cr_DotB = vandq_u8(cmpCrB, vceqq_u8(tmpData2, vdupq_n_u8('.')));
-                }
-
-                // find patterns of \r_.
-                if(isRaw && LIKELIHOOD(0.001, neon_vect_is_nonzero(
-                    vorrq_u8(match2Cr_DotA, match2Cr_DotB)
-                ))) {
-                    uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
-                    uint8x16_t match1LfB;
-                    if(searchEnd)
-                        match1LfB = vceqq_u8(NEXT_DATA(1), vdupq_n_u8('\n'));
-                    else
-                        match1LfB = vceqq_u8(vld1q_u8(src+i + 1+sizeof(uint8x16_t)), vdupq_n_u8('\n'));
-                    // merge matches of \r_. with those for \n
-                    uint8x16_t match2NlDotA = vandq_u8(match2Cr_DotA, match1LfA);
-                    uint8x16_t match2NlDotB = vandq_u8(match2Cr_DotB, match1LfB);
-                    if(searchEnd) {
-                        uint8x16_t match1NlA = vandq_u8(match1LfA, cmpCrA);
-                        uint8x16_t match1NlB = vandq_u8(match1LfB, cmpCrB);
-
-                        uint8x16_t tmpData3 = NEXT_DATA(3);
-                        uint8x16_t tmpData4 = NEXT_DATA(4);
-                        // match instances of \r\n.\r\n and \r\n.=y
-                        uint8x16_t match3CrB = vceqq_u8(tmpData3, vdupq_n_u8('\r'));
-                        uint8x16_t match4LfB = vceqq_u8(tmpData4, vdupq_n_u8('\n'));
-                        uint8x16_t match4Nl = vbslq_u8(vdupq_n_u8('\r'), // exact VBSL vector doesn't matter, so reuse the '\r' vector
-                            vextq_u8(match1NlA, match1NlB, 3),
-                            vandq_u8(match3CrB, match4LfB)
-                        );
-                        uint8x16_t match4EqY = vbslq_u8(vdupq_n_u8('\r'),
-                            // match =y
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataA, dataB, 4)), vdupq_n_u16(0x793d))),
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(tmpData4), vdupq_n_u16(0x793d)))
-                        );
-
-                        match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
-                        uint8x16_t match3EqY = vbslq_u8(vdupq_n_u8('\r'),
-                            vandq_u8(
-                                vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')),
-                                match2EqA
-                            ), vandq_u8(
-                                vceqq_u8(tmpData3, vdupq_n_u8('y')),
-                                match2EqB
-                            )
-                        );
-                        // merge \r\n and =y matches for tmpData4
-                        uint8x16_t match4End = vorrq_u8(
-                            match4Nl,
-                            vreinterpretq_u8_u16(vsriq_n_u16(vreinterpretq_u16_u8(match4EqY), vreinterpretq_u16_u8(match3EqY), 8))
-                        );
-                        // merge with \r\n.
-                        uint8x16_t match2NlDot = vbslq_u8(vdupq_n_u8('\r'), match2NlDotA, match2NlDotB);
-                        match4End = vandq_u8(match4End, match2NlDot);
-                        // match \r\n=y
-                        uint8x16_t match1Nl = vbslq_u8(vdupq_n_u8('\r'), match1NlA, match1NlB);
-                        uint8x16_t match3End = vandq_u8(match3EqY, match1Nl);
-                        // combine match sequences
-                        if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(match4End, match3End)))) {
-                            // terminator found
-                            // there's probably faster ways to do this, but reverting to scalar code should be good enough
-                            len += i;
-                            break;
-                        }
-                    }
+				uint8x16_t match2EqA, match2Cr_DotA;
+				uint8x16_t match2EqB, match2Cr_DotB;
+				if(searchEnd) {
+					match2EqB = vceqq_u8(tmpData2, vdupq_n_u8('='));
+				}
+				if(isRaw) {
+					match2Cr_DotA = vandq_u8(cmpCrA, vceqq_u8(vextq_u8(dataA, dataB, 2), vdupq_n_u8('.')));
+					match2Cr_DotB = vandq_u8(cmpCrB, vceqq_u8(tmpData2, vdupq_n_u8('.')));
+				}
+				
+				// find patterns of \r_.
+				if(isRaw && LIKELIHOOD(0.001, neon_vect_is_nonzero(
+					vorrq_u8(match2Cr_DotA, match2Cr_DotB)
+				))) {
+					uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
+					uint8x16_t match1LfB;
+					if(searchEnd)
+						match1LfB = vceqq_u8(NEXT_DATA(1), vdupq_n_u8('\n'));
+					else
+						match1LfB = vceqq_u8(vld1q_u8(src+i + 1+sizeof(uint8x16_t)), vdupq_n_u8('\n'));
+					// merge matches of \r_. with those for \n
+					uint8x16_t match2NlDotA = vandq_u8(match2Cr_DotA, match1LfA);
+					uint8x16_t match2NlDotB = vandq_u8(match2Cr_DotB, match1LfB);
+					if(searchEnd) {
+						uint8x16_t match1NlA = vandq_u8(match1LfA, cmpCrA);
+						uint8x16_t match1NlB = vandq_u8(match1LfB, cmpCrB);
+						
+						uint8x16_t tmpData3 = NEXT_DATA(3);
+						uint8x16_t tmpData4 = NEXT_DATA(4);
+						// match instances of \r\n.\r\n and \r\n.=y
+						uint8x16_t match3CrB = vceqq_u8(tmpData3, vdupq_n_u8('\r'));
+						uint8x16_t match4LfB = vceqq_u8(tmpData4, vdupq_n_u8('\n'));
+						uint8x16_t match4Nl = vbslq_u8(vdupq_n_u8('\r'), // exact VBSL vector doesn't matter, so reuse the '\r' vector
+							vextq_u8(match1NlA, match1NlB, 3),
+							vandq_u8(match3CrB, match4LfB)
+						);
+						uint8x16_t match4EqY = vbslq_u8(vdupq_n_u8('\r'),
+							// match =y
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataA, dataB, 4)), vdupq_n_u16(0x793d))),
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(tmpData4), vdupq_n_u16(0x793d)))
+						);
+						
+						match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
+						uint8x16_t match3EqY = vbslq_u8(vdupq_n_u8('\r'),
+							vandq_u8(
+								vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')),
+								match2EqA
+							), vandq_u8(
+								vceqq_u8(tmpData3, vdupq_n_u8('y')),
+								match2EqB
+							)
+						);
+						// merge \r\n and =y matches for tmpData4
+						uint8x16_t match4End = vorrq_u8(
+							match4Nl,
+							vreinterpretq_u8_u16(vsriq_n_u16(vreinterpretq_u16_u8(match4EqY), vreinterpretq_u16_u8(match3EqY), 8))
+						);
+						// merge with \r\n.
+						uint8x16_t match2NlDot = vbslq_u8(vdupq_n_u8('\r'), match2NlDotA, match2NlDotB);
+						match4End = vandq_u8(match4End, match2NlDot);
+						// match \r\n=y
+						uint8x16_t match1Nl = vbslq_u8(vdupq_n_u8('\r'), match1NlA, match1NlB);
+						uint8x16_t match3End = vandq_u8(match3EqY, match1Nl);
+						// combine match sequences
+						if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(match4End, match3End)))) {
+							// terminator found
+							// there's probably faster ways to do this, but reverting to scalar code should be good enough
+							len += i;
+							nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
 #ifdef __aarch64__
-                    uint8x16_t match2NlDotBMasked = vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                    uint8x16_t mergeKillDots = vpaddq_u8(
-                        vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                        match2NlDotBMasked
-                    );
-                    uint8x8_t mergeKillDots2 = vget_low_u8(vpaddq_u8(mergeKillDots, mergeKillDots));
+					uint8x16_t match2NlDotBMasked = vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+					uint8x16_t mergeKillDots = vpaddq_u8(
+						vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+						match2NlDotBMasked
+					);
+					uint8x8_t mergeKillDots2 = vget_low_u8(vpaddq_u8(mergeKillDots, mergeKillDots));
 #else
-                    uint8x16_t match2NlDotMaskedA = vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                    uint8x16_t match2NlDotMaskedB = vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                    uint8x8_t mergeKillDots2 = vpadd_u8(
-                        vpadd_u8(
-                            vget_low_u8(match2NlDotMaskedA), vget_high_u8(match2NlDotMaskedA)
-                        ),
-                        vpadd_u8(
-                            vget_low_u8(match2NlDotMaskedB), vget_high_u8(match2NlDotMaskedB)
-                        )
-                    );
+					uint8x16_t match2NlDotMaskedA = vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+					uint8x16_t match2NlDotMaskedB = vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+					uint8x8_t mergeKillDots2 = vpadd_u8(
+						vpadd_u8(
+							vget_low_u8(match2NlDotMaskedA), vget_high_u8(match2NlDotMaskedA)
+						),
+						vpadd_u8(
+							vget_low_u8(match2NlDotMaskedB), vget_high_u8(match2NlDotMaskedB)
+						)
+					);
 #endif
-                    mergeKillDots2 = vpadd_u8(mergeKillDots2, mergeKillDots2);
-                    uint32x2_t mergeKillDotsShifted = vshl_n_u32(vreinterpret_u32_u8(mergeKillDots2), 2);
-                    mask |= vget_lane_u32(mergeKillDotsShifted, 0);
-                    cmpPacked = vorr_u8(cmpPacked, vreinterpret_u8_u32(mergeKillDotsShifted));
+					mergeKillDots2 = vpadd_u8(mergeKillDots2, mergeKillDots2);
+					uint32x2_t mergeKillDotsShifted = vshl_n_u32(vreinterpret_u32_u8(mergeKillDots2), 2);
+					mask |= vget_lane_u32(mergeKillDotsShifted, 0);
+					cmpPacked = vorr_u8(cmpPacked, vreinterpret_u8_u32(mergeKillDotsShifted));
 #ifdef __aarch64__
-                    nextMaskMix = vextq_u8(match2NlDotB, vdupq_n_u8(0), 14);
+					nextMaskMix = vextq_u8(match2NlDotB, vdupq_n_u8(0), 14);
 #else
-                    lfCompare = vcombine_u8(vbsl_u8(
-                        vext_u8(vget_high_u8(match2NlDotB), vdup_n_u8('\n'), 6),
-                        vdup_n_u8('.'),
-                        vget_high_u8(lfCompare)
-                    ), vget_high_u8(lfCompare));
+					lfCompare = vcombine_u8(vbsl_u8(
+						vext_u8(vget_high_u8(match2NlDotB), vdup_n_u8('\n'), 6),
+						vdup_n_u8('.'),
+						vget_high_u8(lfCompare)
+					), vget_high_u8(lfCompare));
 #endif
-                } else if(searchEnd) {
-                    match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
-                    uint8x16_t match3EqYA = vandq_u8(match2EqA, vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')));
-                    uint8x16_t match3EqYB = vandq_u8(match2EqB, vceqq_u8(NEXT_DATA(3), vdupq_n_u8('y')));
-                    if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
-                        match3EqYA, match3EqYB
-                    )))) {
-                        uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
-                        uint8x16_t match1LfB = vceqq_u8(NEXT_DATA(1), vdupq_n_u8('\n'));
-                        uint8x16_t matchEnd = vorrq_u8(
-                            vandq_u8(match3EqYA, vandq_u8(match1LfA, cmpCrA)),
-                            vandq_u8(match3EqYB, vandq_u8(match1LfB, cmpCrB))
-                        );
-                        if(LIKELIHOOD(0.001, neon_vect_is_nonzero(matchEnd))) {
-                            len += i;
-                            break;
-                        }
-                    }
+				} else if(searchEnd) {
+					match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
+					uint8x16_t match3EqYA = vandq_u8(match2EqA, vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')));
+					uint8x16_t match3EqYB = vandq_u8(match2EqB, vceqq_u8(NEXT_DATA(3), vdupq_n_u8('y')));
+					if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
+						match3EqYA, match3EqYB
+					)))) {
+						uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
+						uint8x16_t match1LfB = vceqq_u8(NEXT_DATA(1), vdupq_n_u8('\n'));
+						uint8x16_t matchEnd = vorrq_u8(
+							vandq_u8(match3EqYA, vandq_u8(match1LfA, cmpCrA)),
+							vandq_u8(match3EqYB, vandq_u8(match1LfB, cmpCrB))
+						);
+						if(LIKELIHOOD(0.001, neon_vect_is_nonzero(matchEnd))) {
+							len += i;
+							nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
 #undef NEXT_DATA
-                    if(isRaw)
+					if(isRaw)
 #ifdef __aarch64__
-                        nextMaskMix = vdupq_n_u8(0);
+						nextMaskMix = vdupq_n_u8(0);
 #else
-                        lfCompare = vcombine_u8(vget_high_u8(lfCompare), vget_high_u8(lfCompare));
+						lfCompare = vcombine_u8(vget_high_u8(lfCompare), vget_high_u8(lfCompare));
 #endif
-                } else if(isRaw) // no \r_. found
+				} else if(isRaw) // no \r_. found
 #ifdef __aarch64__
-                    nextMaskMix = vdupq_n_u8(0);
+					nextMaskMix = vdupq_n_u8(0);
 #else
-                    lfCompare = vcombine_u8(vget_high_u8(lfCompare), vget_high_u8(lfCompare));
+					lfCompare = vcombine_u8(vget_high_u8(lfCompare), vget_high_u8(lfCompare));
 #endif
-            }
-
-            // a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
-            // the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
-            // firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
-            if(LIKELIHOOD(0.0001, (mask & ((maskEq << 1) | escFirst)) != 0)) {
-                uint8_t tmp = eqFixLUT[(maskEq&0xff) & ~escFirst];
-                uint32_t maskEq2 = tmp;
-                for(int j=8; j<32; j+=8) {
-                    tmp = eqFixLUT[((maskEq>>j)&0xff) & ~(tmp>>7)];
-                    maskEq2 |= tmp<<j;
-                }
-                maskEq = maskEq2;
-
-                // next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
-                maskEq = (maskEq<<1) | escFirst;
-                mask &= ~maskEq;
-                escFirst = tmp>>7;
-
-                // unescape chars following `=`
-                uint8x8_t maskEqTemp = vreinterpret_u8_u32(vmov_n_u32(maskEq));
-                cmpPacked = vbic_u8(cmpPacked, maskEqTemp); // `mask &= ~maskEq` in vector form
+			}
+			
+			// a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
+			// the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
+			// firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
+			uint32_t maskEqShift1 = (maskEq << 1) | escFirst;
+			if(LIKELIHOOD(0.0001, (mask & maskEqShift1) != 0)) {
+				maskEq = fix_eqMask<uint32_t>(maskEq, maskEqShift1);
+				
+				unsigned char nextEscFirst = maskEq>>31;
+				// next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
+				maskEq = (maskEq<<1) | escFirst;
+				mask &= ~maskEq;
+				escFirst = nextEscFirst;
+				
+				// unescape chars following `=`
+				uint8x8_t maskEqTemp = vreinterpret_u8_u32(vmov_n_u32(maskEq));
+				cmpPacked = vbic_u8(cmpPacked, maskEqTemp); // `mask &= ~maskEq` in vector form
 #ifdef __aarch64__
-                uint8x16_t vMaskEqA = vqtbl1q_u8(
-                    vcombine_u8(maskEqTemp, vdup_n_u8(0)),
-                    vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
-                );
-                uint8x16_t vMaskEqB = vqtbl1q_u8(
-                    vcombine_u8(maskEqTemp, vdup_n_u8(0)),
-                    vmakeq_u8(2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3)
-                );
+				uint8x16_t vMaskEqA = vqtbl1q_u8(
+					vcombine_u8(maskEqTemp, vdup_n_u8(0)),
+					vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
+				);
+				uint8x16_t vMaskEqB = vqtbl1q_u8(
+					vcombine_u8(maskEqTemp, vdup_n_u8(0)),
+					vmakeq_u8(2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3)
+				);
 #else
-                uint8x16_t vMaskEqA = vcombine_u8(
-                    vdup_lane_u8(maskEqTemp, 0),
-                    vdup_lane_u8(maskEqTemp, 1)
-                );
-                uint8x16_t vMaskEqB = vcombine_u8(
-                    vdup_lane_u8(maskEqTemp, 2),
-                    vdup_lane_u8(maskEqTemp, 3)
-                );
+				uint8x16_t vMaskEqA = vcombine_u8(
+					vdup_lane_u8(maskEqTemp, 0),
+					vdup_lane_u8(maskEqTemp, 1)
+				);
+				uint8x16_t vMaskEqB = vcombine_u8(
+					vdup_lane_u8(maskEqTemp, 2),
+					vdup_lane_u8(maskEqTemp, 3)
+				);
 #endif
-                vMaskEqA = vtstq_u8(vMaskEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                vMaskEqB = vtstq_u8(vMaskEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-
-                dataA = vsubq_u8(
-                    dataA,
-                    vbslq_u8(vMaskEqA, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-                dataB = vsubq_u8(
-                    dataB,
-                    vbslq_u8(vMaskEqB, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-            } else {
-                // no invalid = sequences found - we can cut out some things from above
-                // this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
-                escFirst = (maskEq >> 31);
-
-                dataA = vsubq_u8(
-                    dataA,
-                    vbslq_u8(
-                        vextq_u8(vdupq_n_u8(42), cmpEqA, 15),
-                        vdupq_n_u8(64+42),
-                        yencOffset
-                    )
-                );
-                dataB = vsubq_u8(
-                    dataB,
-                    vbslq_u8(
-                        vextq_u8(cmpEqA, cmpEqB, 15),
-                        vdupq_n_u8(64+42),
-                        vdupq_n_u8(42)
-                    )
-                );
-            }
-            yencOffset = vsetq_lane_u8((escFirst << 6) | 42, yencOffset, 0);
-
-            // all that's left is to 'compress' the data (skip over masked chars)
-            uint32_t counts = 0x08080808 - vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
+				vMaskEqA = vtstq_u8(vMaskEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				vMaskEqB = vtstq_u8(vMaskEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				
+				dataA = vsubq_u8(
+					dataA,
+					vbslq_u8(vMaskEqA, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+				dataB = vsubq_u8(
+					dataB,
+					vbslq_u8(vMaskEqB, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+			} else {
+				// no invalid = sequences found - we can cut out some things from above
+				// this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
+				escFirst = (maskEq >> 31);
+				
+				dataA = vsubq_u8(
+					dataA,
+					vbslq_u8(
+						vextq_u8(vdupq_n_u8(42), cmpEqA, 15),
+						vdupq_n_u8(64+42),
+						yencOffset
+					)
+				);
+				dataB = vsubq_u8(
+					dataB,
+					vbslq_u8(
+						vextq_u8(cmpEqA, cmpEqB, 15),
+						vdupq_n_u8(64+42),
+						vdupq_n_u8(42)
+					)
+				);
+			}
+			yencOffset = vsetq_lane_u8((escFirst << 6) | 42, yencOffset, 0);
+			
+			// all that's left is to 'compress' the data (skip over masked chars)
+			uint32_t counts = 0x08080808 - vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
 #ifdef __aarch64__
-            counts += counts >> 8;
-            vst1q_u8(p, vqtbl1q_u8(
-                dataA,
-                vld1q_u8_align((uint8_t*)(compactLUT + (mask&0x7fff)), 16)
-            ));
-            p += counts & 0xff;
-            mask >>= 16;
-            vst1q_u8(p, vqtbl1q_u8(
-                dataB,
-                vld1q_u8_align((uint8_t*)(compactLUT + (mask&0x7fff)), 16)
-            ));
-            p += (counts>>16) & 0xff;
+			counts += counts >> 8;
+			vst1q_u8(p, vqtbl1q_u8(
+				dataA,
+				vld1q_u8_align((uint8_t*)(compactLUT + (mask&0x7fff)), 16)
+			));
+			p += counts & 0xff;
+			mask >>= 16;
+			vst1q_u8(p, vqtbl1q_u8(
+				dataB,
+				vld1q_u8_align((uint8_t*)(compactLUT + (mask&0x7fff)), 16)
+			));
+			p += (counts>>16) & 0xff;
 #else
-            // lookup compress masks and shuffle
-            vst1_u8(p, vtbl1_u8(
-                vget_low_u8(dataA),
-                vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
-            ));
-            p += counts & 0xff;
-            mask >>= 8;
-            vst1_u8(p, vtbl1_u8(
-                vget_high_u8(dataA),
-                vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
-            ));
-            p += (counts>>8) & 0xff;
-            mask >>= 8;
-            vst1_u8(p, vtbl1_u8(
-                vget_low_u8(dataB),
-                vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
-            ));
-            p += (counts>>16) & 0xff;
-            mask >>= 8;
-            vst1_u8(p, vtbl1_u8(
-                vget_high_u8(dataB),
-                vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
-            ));
-            p += (counts>>24) & 0xff;
-
+			// lookup compress masks and shuffle
+			vst1_u8(p, vtbl1_u8(
+				vget_low_u8(dataA),
+				vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
+			));
+			p += counts & 0xff;
+			mask >>= 8;
+			vst1_u8(p, vtbl1_u8(
+				vget_high_u8(dataA),
+				vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
+			));
+			p += (counts>>8) & 0xff;
+			mask >>= 8;
+			vst1_u8(p, vtbl1_u8(
+				vget_low_u8(dataB),
+				vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
+			));
+			p += (counts>>16) & 0xff;
+			mask >>= 8;
+			vst1_u8(p, vtbl1_u8(
+				vget_high_u8(dataB),
+				vld1_u8_align((uint8_t*)(compactLUT + (mask&0xff)), 8)
+			));
+			p += (counts>>24) & 0xff;
+			
 #endif
-
-        } else {
-            dataA = vsubq_u8(dataA, yencOffset);
-            dataB = vsubq_u8(dataB, vdupq_n_u8(42));
-            vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, dataB));
-            p += sizeof(uint8x16_t)*2;
-            escFirst = 0;
+			
+		} else {
+			dataA = vsubq_u8(dataA, yencOffset);
+			dataB = vsubq_u8(dataB, vdupq_n_u8(42));
+			vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, dataB));
+			p += sizeof(uint8x16_t)*2;
+			escFirst = 0;
 #ifdef __aarch64__
-            yencOffset = vdupq_n_u8(42);
+			yencOffset = vdupq_n_u8(42);
 #else
-            yencOffset = vcombine_u8(vdup_n_u8(42), vget_high_u8(yencOffset));
+			yencOffset = vcombine_u8(vdup_n_u8(42), vget_high_u8(yencOffset));
 #endif
-        }
-    }
-
-    if(isRaw) {
-        if(len != 0) { // have to gone through at least one loop cycle
-            if(src[i-2] == '\r' && src[i-1] == '\n' && src[i] == '.')
-                nextMask = 1;
-            else if(src[i-1] == '\r' && src[i] == '\n' && src[i+1] == '.')
-                nextMask = 2;
-            else
-                nextMask = 0;
-        }
-    } else
-        nextMask = 0;
+		}
+	}
 }
-
-void decoder_set_neon_funcs() {
-    decoder_init_lut(eqFixLUT, compactLUT);
-    _do_decode = &do_decode_simd<false, false, sizeof(uint8x16_t)*2, do_decode_neon<false, false> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(uint8x16_t)*2, do_decode_neon<true, false> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(uint8x16_t)*2, do_decode_neon<true, true> >;
-    _decode_simd_level = 1;
+} // namespace
+
+void RapidYenc::decoder_set_neon_funcs() {
+	decoder_init_lut(compactLUT);
+	_do_decode = &do_decode_simd<false, false, sizeof(uint8x16_t)*2, do_decode_neon<false, false> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(uint8x16_t)*2, do_decode_neon<true, false> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(uint8x16_t)*2, do_decode_neon<true, true> >;
+	_decode_isa = ISA_LEVEL_NEON;
 }
 #else
-
-void decoder_set_neon_funcs() {}
-
+void RapidYenc::decoder_set_neon_funcs() {}
 #endif
diff --git a/src/yencode/decoder_neon64.cc b/src/yencode/decoder_neon64.cc
index d205e10..0ab42a1 100644
--- a/src/yencode/decoder_neon64.cc
+++ b/src/yencode/decoder_neon64.cc
@@ -1,30 +1,28 @@
 #include "common.h"
-
+#include "decoder_common.h"
 #if defined(__ARM_NEON) && defined(__aarch64__)
 
-#include "decoder_common.h"
 
 #pragma pack(16)
 static struct { char bytes[16]; } ALIGN_TO(16, compactLUT[32768]);
 #pragma pack()
 
-static uint8_t eqFixLUT[256];
 
 // AArch64 GCC lacks these functions until 8.5, 9.4 and 10.1 (10.0 unknown)
 #if !defined(__clang__) && !defined(_MSC_VER) && (!defined(__aarch64__) || !(HEDLEY_GCC_VERSION_CHECK(9,4,0) || (!HEDLEY_GCC_VERSION_CHECK(9,0,0) && HEDLEY_GCC_VERSION_CHECK(8,5,0))))
 static HEDLEY_ALWAYS_INLINE uint8x16x4_t _vld1q_u8_x4(const uint8_t* p) {
-    uint8x16x4_t ret;
-    ret.val[0] = vld1q_u8(p);
-    ret.val[1] = vld1q_u8(p+16);
-    ret.val[2] = vld1q_u8(p+32);
-    ret.val[3] = vld1q_u8(p+48);
-    return ret;
+	uint8x16x4_t ret;
+	ret.val[0] = vld1q_u8(p);
+	ret.val[1] = vld1q_u8(p+16);
+	ret.val[2] = vld1q_u8(p+32);
+	ret.val[3] = vld1q_u8(p+48);
+	return ret;
 }
 static HEDLEY_ALWAYS_INLINE void _vst1q_u8_x4(uint8_t* p, uint8x16x4_t data) {
-    vst1q_u8(p, data.val[0]);
-    vst1q_u8(p+16, data.val[1]);
-    vst1q_u8(p+32, data.val[2]);
-    vst1q_u8(p+48, data.val[3]);
+	vst1q_u8(p, data.val[0]);
+	vst1q_u8(p+16, data.val[1]);
+	vst1q_u8(p+32, data.val[2]);
+	vst1q_u8(p+48, data.val[3]);
 }
 #else
 # define _vld1q_u8_x4 vld1q_u8_x4
@@ -33,425 +31,416 @@ static HEDLEY_ALWAYS_INLINE void _vst1q_u8_x4(uint8_t* p, uint8x16x4_t data) {
 
 
 static bool neon_vect_is_nonzero(uint8x16_t v) {
-    return !!(vget_lane_u64(vreinterpret_u64_u32(vqmovn_u64(vreinterpretq_u64_u8(v))), 0));
+	return !!(vget_lane_u64(vreinterpret_u64_u32(vqmovn_u64(vreinterpretq_u64_u8(v))), 0));
 }
 
 static HEDLEY_ALWAYS_INLINE uint8x16_t mergeCompares(uint8x16_t a, uint8x16_t b, uint8x16_t c, uint8x16_t d) {
-    // constant vectors arbitrarily chosen from ones that can be reused; exact ordering of bits doesn't matter, we just need to mix them in
-    return vbslq_u8(
-        vdupq_n_u8('='),
-        vbslq_u8(vdupq_n_u8('y'), a, b),
-        vbslq_u8(vdupq_n_u8('y'), c, d)
-    );
+	// constant vectors arbitrarily chosen from ones that can be reused; exact ordering of bits doesn't matter, we just need to mix them in
+	return vbslq_u8(
+		vdupq_n_u8('='),
+		vbslq_u8(vdupq_n_u8('y'), a, b),
+		vbslq_u8(vdupq_n_u8('y'), c, d)
+	);
 }
 
 
-template<bool isRaw, bool searchEnd>
-HEDLEY_ALWAYS_INLINE void do_decode_neon(const uint8_t* HEDLEY_RESTRICT src, long& len, unsigned char* HEDLEY_RESTRICT & p, unsigned char& escFirst, uint16_t& nextMask) {
-    HEDLEY_ASSUME(escFirst == 0 || escFirst == 1);
-    HEDLEY_ASSUME(nextMask == 0 || nextMask == 1 || nextMask == 2);
-    uint8x16_t nextMaskMix = vdupq_n_u8(0);
-    if(nextMask == 1)
-        nextMaskMix = vsetq_lane_u8(1, nextMaskMix, 0);
-    if(nextMask == 2)
-        nextMaskMix = vsetq_lane_u8(2, nextMaskMix, 1);
-    uint8x16_t yencOffset = escFirst ? vmakeq_u8(42+64,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42) : vdupq_n_u8(42);
-    long i;
-    for(i = -len; i; i += sizeof(uint8x16_t)*4) {
-        uint8x16x4_t data = _vld1q_u8_x4(src+i);
-        uint8x16_t dataA = data.val[0];
-        uint8x16_t dataB = data.val[1];
-        uint8x16_t dataC = data.val[2];
-        uint8x16_t dataD = data.val[3];
-
-        // search for special chars
-        uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('=')),
-        cmpEqB = vceqq_u8(dataB, vdupq_n_u8('=')),
-        cmpEqC = vceqq_u8(dataC, vdupq_n_u8('=')),
-        cmpEqD = vceqq_u8(dataD, vdupq_n_u8('=')),
-        cmpA = vqtbx1q_u8(
-            cmpEqA,
-            //                             \n      \r
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataA
-        ),
-        cmpB = vqtbx1q_u8(
-            cmpEqB,
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataB
-        ),
-        cmpC = vqtbx1q_u8(
-            cmpEqC,
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataC
-        ),
-        cmpD = vqtbx1q_u8(
-            cmpEqD,
-            vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataD
-        );
-        if(isRaw) cmpA = vorrq_u8(cmpA, nextMaskMix);
-
-        if (LIKELIHOOD(0.42 /*guess*/, neon_vect_is_nonzero(vorrq_u8(
-            vorrq_u8(cmpA, cmpB),
-            vorrq_u8(cmpC, cmpD)
-        )))) {
-            uint8x16_t cmpMerge = vpaddq_u8(
-                vpaddq_u8(
-                    vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                    vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-                ),
-                vpaddq_u8(
-                    vandq_u8(cmpC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                    vandq_u8(cmpD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-                )
-            );
-            uint8x16_t cmpEqMerge = vpaddq_u8(
-                vpaddq_u8(
-                    vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                    vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-                ),
-                vpaddq_u8(
-                    vandq_u8(cmpEqC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                    vandq_u8(cmpEqD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-                )
-            );
-
-            uint8x16_t cmpCombined = vpaddq_u8(cmpMerge, cmpEqMerge);
-            uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpCombined), 0);
-            uint64_t maskEq = vgetq_lane_u64(vreinterpretq_u64_u8(cmpCombined), 1);
-
-            // handle \r\n. sequences
-            // RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
-            if((isRaw || searchEnd) && LIKELIHOOD(0.15, mask != maskEq)) {
-                // vext seems to be a cheap operation on ARM, relative to loads, so only avoid it if there's only one load (isRaw only)
-                uint8x16_t tmpData2, nextData;
-                if(isRaw && !searchEnd) {
-                    tmpData2 = vld1q_u8(src+i + 2 + sizeof(uint8x16_t)*3);
-                } else {
-                    nextData = vld1q_u8(src+i + sizeof(uint8x16_t)*4); // only 32-bits needed, but there doesn't appear a nice way to do this via intrinsics: https://stackoverflow.com/questions/46910799/arm-neon-intrinsics-convert-d-64-bit-register-to-low-half-of-q-128-bit-regis
-                    tmpData2 = vextq_u8(dataD, nextData, 2);
-                }
-                uint8x16_t cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r'));
-                uint8x16_t cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r'));
-                uint8x16_t cmpCrC = vceqq_u8(dataC, vdupq_n_u8('\r'));
-                uint8x16_t cmpCrD = vceqq_u8(dataD, vdupq_n_u8('\r'));
-                uint8x16_t match2EqA, match2Cr_DotA;
-                uint8x16_t match2EqB, match2Cr_DotB;
-                uint8x16_t match2EqC, match2Cr_DotC;
-                uint8x16_t match2EqD, match2Cr_DotD;
-                if(searchEnd) {
-                    match2EqD = vceqq_u8(tmpData2, vdupq_n_u8('='));
-                }
-                if(isRaw) {
-                    match2Cr_DotA = vandq_u8(cmpCrA, vceqq_u8(vextq_u8(dataA, dataB, 2), vdupq_n_u8('.')));
-                    match2Cr_DotB = vandq_u8(cmpCrB, vceqq_u8(vextq_u8(dataB, dataC, 2), vdupq_n_u8('.')));
-                    match2Cr_DotC = vandq_u8(cmpCrC, vceqq_u8(vextq_u8(dataC, dataD, 2), vdupq_n_u8('.')));
-                    match2Cr_DotD = vandq_u8(cmpCrD, vceqq_u8(tmpData2, vdupq_n_u8('.')));
-                }
-
-                // find patterns of \r_.
-                if(isRaw && LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
-                    vorrq_u8(match2Cr_DotA, match2Cr_DotB),
-                    vorrq_u8(match2Cr_DotC, match2Cr_DotD)
-                )))) {
-                    uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
-                    uint8x16_t match1LfB = vceqq_u8(vextq_u8(dataB, dataC, 1), vdupq_n_u8('\n'));
-                    uint8x16_t match1LfC = vceqq_u8(vextq_u8(dataC, dataD, 1), vdupq_n_u8('\n'));
-                    uint8x16_t match1LfD;
-                    if(searchEnd)
-                        match1LfD = vceqq_u8(vextq_u8(dataD, nextData, 1), vdupq_n_u8('\n'));
-                    else
-                        match1LfD = vceqq_u8(vld1q_u8(src+i + 1+sizeof(uint8x16_t)*3), vdupq_n_u8('\n'));
-                    // merge matches of \r_. with those for \n
-                    uint8x16_t match2NlDotA = vandq_u8(match2Cr_DotA, match1LfA);
-                    uint8x16_t match2NlDotB = vandq_u8(match2Cr_DotB, match1LfB);
-                    uint8x16_t match2NlDotC = vandq_u8(match2Cr_DotC, match1LfC);
-                    uint8x16_t match2NlDotD = vandq_u8(match2Cr_DotD, match1LfD);
-                    if(searchEnd) {
-                        uint8x16_t match1NlA = vandq_u8(match1LfA, cmpCrA);
-                        uint8x16_t match1NlB = vandq_u8(match1LfB, cmpCrB);
-                        uint8x16_t match1NlC = vandq_u8(match1LfC, cmpCrC);
-                        uint8x16_t match1NlD = vandq_u8(match1LfD, cmpCrD);
-
-                        uint8x16_t tmpData3 = vextq_u8(dataD, nextData, 3);
-                        uint8x16_t tmpData4 = vextq_u8(dataD, nextData, 4);
-                        // match instances of \r\n.\r\n and \r\n.=y
-                        uint8x16_t match3CrD = vceqq_u8(tmpData3, vdupq_n_u8('\r'));
-                        uint8x16_t match4LfD = vceqq_u8(tmpData4, vdupq_n_u8('\n'));
-                        uint8x16_t match4Nl = mergeCompares(
-                            vextq_u8(match1NlA, match1NlB, 3),
-                            vextq_u8(match1NlB, match1NlC, 3),
-                            vextq_u8(match1NlC, match1NlD, 3),
-                            vandq_u8(match3CrD, match4LfD)
-                        );
-                        uint8x16_t match4EqY = mergeCompares(
-                            // match with =y
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataA, dataB, 4)), vdupq_n_u16(0x793d))),
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataB, dataC, 4)), vdupq_n_u16(0x793d))),
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataC, dataD, 4)), vdupq_n_u16(0x793d))),
-                            vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(tmpData4), vdupq_n_u16(0x793d)))
-                        );
-                        match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
-                        match2EqB = vextq_u8(cmpEqB, cmpEqC, 2);
-                        match2EqC = vextq_u8(cmpEqC, cmpEqD, 2);
-                        uint8x16_t match3EqY = mergeCompares(
-                            vandq_u8(
-                                vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')),
-                                match2EqA
-                            ), vandq_u8(
-                                vceqq_u8(vextq_u8(dataB, dataC, 3), vdupq_n_u8('y')),
-                                match2EqB
-                            ), vandq_u8(
-                                vceqq_u8(vextq_u8(dataC, dataD, 3), vdupq_n_u8('y')),
-                                match2EqC
-                            ), vandq_u8(
-                                vceqq_u8(tmpData3, vdupq_n_u8('y')),
-                                match2EqD
-                            )
-                        );
-
-                        // merge \r\n and =y matches for tmpData4
-                        uint8x16_t match4End = vorrq_u8(
-                            match4Nl,
-                            vreinterpretq_u8_u16(vsriq_n_u16(vreinterpretq_u16_u8(match4EqY), vreinterpretq_u16_u8(match3EqY), 8))
-                        );
-                        // merge with \r\n.
-                        uint8x16_t match2NlDot = mergeCompares(match2NlDotA, match2NlDotB, match2NlDotC, match2NlDotD);
-                        match4End = vandq_u8(match4End, match2NlDot);
-                        // match \r\n=y
-                        uint8x16_t match1Nl = mergeCompares(match1NlA, match1NlB, match1NlC, match1NlD);
-                        uint8x16_t match3End = vandq_u8(match3EqY, match1Nl);
-                        // combine match sequences
-                        if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(match4End, match3End)))) {
-                            // terminator found
-                            // there's probably faster ways to do this, but reverting to scalar code should be good enough
-                            len += i;
-                            break;
-                        }
-                    }
-                    uint8x16_t match2NlDotDMasked = vandq_u8(match2NlDotD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                    uint8x16_t mergeKillDots = vpaddq_u8(
-                        vpaddq_u8(
-                            vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                            vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
-                        ),
-                        vpaddq_u8(
-                            vandq_u8(match2NlDotC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
-                            match2NlDotDMasked
-                        )
-                    );
-                    mergeKillDots = vpaddq_u8(mergeKillDots, mergeKillDots);
-                    uint64x2_t mergeKillDotsShifted = vshlq_n_u64(vreinterpretq_u64_u8(mergeKillDots), 2);
-                    mask |= vgetq_lane_u64(mergeKillDotsShifted, 0);
-                    cmpCombined = vorrq_u8(cmpCombined, vreinterpretq_u8_u64(mergeKillDotsShifted));
-                    nextMaskMix = vextq_u8(match2NlDotD, vdupq_n_u8(0), 14);
-                } else if(searchEnd) {
-                    match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
-                    match2EqB = vextq_u8(cmpEqB, cmpEqC, 2);
-                    match2EqC = vextq_u8(cmpEqC, cmpEqD, 2);
+namespace RapidYenc {
 
-                    uint8x16_t match3EqYA = vandq_u8(match2EqA, vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')));
-                    uint8x16_t match3EqYB = vandq_u8(match2EqB, vceqq_u8(vextq_u8(dataB, dataC, 3), vdupq_n_u8('y')));
-                    uint8x16_t match3EqYC = vandq_u8(match2EqC, vceqq_u8(vextq_u8(dataC, dataD, 3), vdupq_n_u8('y')));
-                    uint8x16_t match3EqYD = vandq_u8(match2EqD, vceqq_u8(vextq_u8(dataD, nextData, 3), vdupq_n_u8('y')));
-                    if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
-                        vorrq_u8(match3EqYA, match3EqYB),
-                        vorrq_u8(match3EqYC, match3EqYD)
-                    )))) {
-                        uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
-                        uint8x16_t match1LfB = vceqq_u8(vextq_u8(dataB, dataC, 1), vdupq_n_u8('\n'));
-                        uint8x16_t match1LfC = vceqq_u8(vextq_u8(dataC, dataD, 1), vdupq_n_u8('\n'));
-                        uint8x16_t match1LfD = vceqq_u8(vextq_u8(dataD, nextData, 1), vdupq_n_u8('\n'));
-                        uint8x16_t matchEnd = vorrq_u8(
-                            vorrq_u8(
-                                vandq_u8(match3EqYA, vandq_u8(match1LfA, cmpCrA)),
-                                vandq_u8(match3EqYB, vandq_u8(match1LfB, cmpCrB))
-                            ),
-                            vorrq_u8(
-                                vandq_u8(match3EqYC, vandq_u8(match1LfC, cmpCrC)),
-                                vandq_u8(match3EqYD, vandq_u8(match1LfD, cmpCrD))
-                            )
-                        );
-                        if(LIKELIHOOD(0.001, neon_vect_is_nonzero(matchEnd))) {
-                            len += i;
-                            break;
-                        }
-                    }
-                    if(isRaw)
-                        nextMaskMix = vdupq_n_u8(0);
-                } else if(isRaw) // no \r_. found
-                    nextMaskMix = vdupq_n_u8(0);
-            }
-
-            // a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
-            // the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
-            // firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
-            if(LIKELIHOOD(0.0001, (mask & ((maskEq << 1) | escFirst)) != 0)) {
-                uint8_t tmp = eqFixLUT[(maskEq&0xff) & ~escFirst];
-                uint64_t maskEq2 = tmp;
-                for(int j=8; j<64; j+=8) {
-                    tmp = eqFixLUT[((maskEq>>j)&0xff) & ~(tmp>>7)];
-                    maskEq2 |= ((uint64_t)tmp)<<j;
-                }
-                maskEq = maskEq2;
-
-                // next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
-                maskEq = (maskEq<<1) | escFirst;
-                mask &= ~maskEq;
-                escFirst = tmp>>7;
-
-                // unescape chars following `=`
+template<bool isRaw, bool searchEnd>
+HEDLEY_ALWAYS_INLINE void do_decode_neon(const uint8_t* src, long& len, unsigned char*& p, unsigned char& escFirst, uint16_t& nextMask) {
+	HEDLEY_ASSUME(escFirst == 0 || escFirst == 1);
+	HEDLEY_ASSUME(nextMask == 0 || nextMask == 1 || nextMask == 2);
+	uint8x16_t nextMaskMix = vdupq_n_u8(0);
+	if(nextMask == 1)
+		nextMaskMix = vsetq_lane_u8(1, nextMaskMix, 0);
+	if(nextMask == 2)
+		nextMaskMix = vsetq_lane_u8(2, nextMaskMix, 1);
+	uint8x16_t yencOffset = escFirst ? vmakeq_u8(42+64,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42) : vdupq_n_u8(42);
+	
+	decoder_set_nextMask<isRaw>(src, len, nextMask);
+	
+	long i;
+	for(i = -len; i; i += sizeof(uint8x16_t)*4) {
+		uint8x16x4_t data = _vld1q_u8_x4(src+i);
+		uint8x16_t dataA = data.val[0];
+		uint8x16_t dataB = data.val[1];
+		uint8x16_t dataC = data.val[2];
+		uint8x16_t dataD = data.val[3];
+		
+		// search for special chars
+		uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('=')),
+		cmpEqB = vceqq_u8(dataB, vdupq_n_u8('=')),
+		cmpEqC = vceqq_u8(dataC, vdupq_n_u8('=')),
+		cmpEqD = vceqq_u8(dataD, vdupq_n_u8('=')),
+		cmpA = vqtbx1q_u8(
+			cmpEqA,
+			//                             \n      \r
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataA
+		),
+		cmpB = vqtbx1q_u8(
+			cmpEqB,
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataB
+		),
+		cmpC = vqtbx1q_u8(
+			cmpEqC,
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataC
+		),
+		cmpD = vqtbx1q_u8(
+			cmpEqD,
+			vmakeq_u8(0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataD
+		);
+		if(isRaw) cmpA = vorrq_u8(cmpA, nextMaskMix);
+		
+		if (LIKELIHOOD(0.42 /*guess*/, neon_vect_is_nonzero(vorrq_u8(
+			vorrq_u8(cmpA, cmpB),
+			vorrq_u8(cmpC, cmpD)
+		)))) {
+			uint8x16_t cmpMerge = vpaddq_u8(
+				vpaddq_u8(
+					vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+					vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+				),
+				vpaddq_u8(
+					vandq_u8(cmpC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+					vandq_u8(cmpD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+				)
+			);
+			uint8x16_t cmpEqMerge = vpaddq_u8(
+				vpaddq_u8(
+					vandq_u8(cmpEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+					vandq_u8(cmpEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+				),
+				vpaddq_u8(
+					vandq_u8(cmpEqC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+					vandq_u8(cmpEqD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+				)
+			);
+			
+			uint8x16_t cmpCombined = vpaddq_u8(cmpMerge, cmpEqMerge);
+			uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpCombined), 0);
+			uint64_t maskEq = vgetq_lane_u64(vreinterpretq_u64_u8(cmpCombined), 1);
+			
+			// handle \r\n. sequences
+			// RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
+			if((isRaw || searchEnd) && LIKELIHOOD(0.15, mask != maskEq)) {
+				// vext seems to be a cheap operation on ARM, relative to loads, so only avoid it if there's only one load (isRaw only)
+				uint8x16_t tmpData2, nextData;
+				if(isRaw && !searchEnd) {
+					tmpData2 = vld1q_u8(src+i + 2 + sizeof(uint8x16_t)*3);
+				} else {
+					nextData = vld1q_u8(src+i + sizeof(uint8x16_t)*4); // only 32-bits needed, but there doesn't appear a nice way to do this via intrinsics: https://stackoverflow.com/questions/46910799/arm-neon-intrinsics-convert-d-64-bit-register-to-low-half-of-q-128-bit-regis
+					tmpData2 = vextq_u8(dataD, nextData, 2);
+				}
+				uint8x16_t cmpCrA = vceqq_u8(dataA, vdupq_n_u8('\r'));
+				uint8x16_t cmpCrB = vceqq_u8(dataB, vdupq_n_u8('\r'));
+				uint8x16_t cmpCrC = vceqq_u8(dataC, vdupq_n_u8('\r'));
+				uint8x16_t cmpCrD = vceqq_u8(dataD, vdupq_n_u8('\r'));
+				uint8x16_t match2EqA, match2Cr_DotA;
+				uint8x16_t match2EqB, match2Cr_DotB;
+				uint8x16_t match2EqC, match2Cr_DotC;
+				uint8x16_t match2EqD, match2Cr_DotD;
+				if(searchEnd) {
+					match2EqD = vceqq_u8(tmpData2, vdupq_n_u8('='));
+				}
+				if(isRaw) {
+					match2Cr_DotA = vandq_u8(cmpCrA, vceqq_u8(vextq_u8(dataA, dataB, 2), vdupq_n_u8('.')));
+					match2Cr_DotB = vandq_u8(cmpCrB, vceqq_u8(vextq_u8(dataB, dataC, 2), vdupq_n_u8('.')));
+					match2Cr_DotC = vandq_u8(cmpCrC, vceqq_u8(vextq_u8(dataC, dataD, 2), vdupq_n_u8('.')));
+					match2Cr_DotD = vandq_u8(cmpCrD, vceqq_u8(tmpData2, vdupq_n_u8('.')));
+				}
+				
+				// find patterns of \r_.
+				if(isRaw && LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
+					vorrq_u8(match2Cr_DotA, match2Cr_DotB),
+					vorrq_u8(match2Cr_DotC, match2Cr_DotD)
+				)))) {
+					uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
+					uint8x16_t match1LfB = vceqq_u8(vextq_u8(dataB, dataC, 1), vdupq_n_u8('\n'));
+					uint8x16_t match1LfC = vceqq_u8(vextq_u8(dataC, dataD, 1), vdupq_n_u8('\n'));
+					uint8x16_t match1LfD;
+					if(searchEnd)
+						match1LfD = vceqq_u8(vextq_u8(dataD, nextData, 1), vdupq_n_u8('\n'));
+					else
+						match1LfD = vceqq_u8(vld1q_u8(src+i + 1+sizeof(uint8x16_t)*3), vdupq_n_u8('\n'));
+					// merge matches of \r_. with those for \n
+					uint8x16_t match2NlDotA = vandq_u8(match2Cr_DotA, match1LfA);
+					uint8x16_t match2NlDotB = vandq_u8(match2Cr_DotB, match1LfB);
+					uint8x16_t match2NlDotC = vandq_u8(match2Cr_DotC, match1LfC);
+					uint8x16_t match2NlDotD = vandq_u8(match2Cr_DotD, match1LfD);
+					if(searchEnd) {
+						uint8x16_t match1NlA = vandq_u8(match1LfA, cmpCrA);
+						uint8x16_t match1NlB = vandq_u8(match1LfB, cmpCrB);
+						uint8x16_t match1NlC = vandq_u8(match1LfC, cmpCrC);
+						uint8x16_t match1NlD = vandq_u8(match1LfD, cmpCrD);
+						
+						uint8x16_t tmpData3 = vextq_u8(dataD, nextData, 3);
+						uint8x16_t tmpData4 = vextq_u8(dataD, nextData, 4);
+						// match instances of \r\n.\r\n and \r\n.=y
+						uint8x16_t match3CrD = vceqq_u8(tmpData3, vdupq_n_u8('\r'));
+						uint8x16_t match4LfD = vceqq_u8(tmpData4, vdupq_n_u8('\n'));
+						uint8x16_t match4Nl = mergeCompares(
+							vextq_u8(match1NlA, match1NlB, 3),
+							vextq_u8(match1NlB, match1NlC, 3),
+							vextq_u8(match1NlC, match1NlD, 3),
+							vandq_u8(match3CrD, match4LfD)
+						);
+						uint8x16_t match4EqY = mergeCompares(
+							// match with =y
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataA, dataB, 4)), vdupq_n_u16(0x793d))),
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataB, dataC, 4)), vdupq_n_u16(0x793d))),
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(vextq_u8(dataC, dataD, 4)), vdupq_n_u16(0x793d))),
+							vreinterpretq_u8_u16(vceqq_u16(vreinterpretq_u16_u8(tmpData4), vdupq_n_u16(0x793d)))
+						);
+						match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
+						match2EqB = vextq_u8(cmpEqB, cmpEqC, 2);
+						match2EqC = vextq_u8(cmpEqC, cmpEqD, 2);
+						uint8x16_t match3EqY = mergeCompares(
+							vandq_u8(
+								vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')),
+								match2EqA
+							), vandq_u8(
+								vceqq_u8(vextq_u8(dataB, dataC, 3), vdupq_n_u8('y')),
+								match2EqB
+							), vandq_u8(
+								vceqq_u8(vextq_u8(dataC, dataD, 3), vdupq_n_u8('y')),
+								match2EqC
+							), vandq_u8(
+								vceqq_u8(tmpData3, vdupq_n_u8('y')),
+								match2EqD
+							)
+						);
+						
+						// merge \r\n and =y matches for tmpData4
+						uint8x16_t match4End = vorrq_u8(
+							match4Nl,
+							vreinterpretq_u8_u16(vsriq_n_u16(vreinterpretq_u16_u8(match4EqY), vreinterpretq_u16_u8(match3EqY), 8))
+						);
+						// merge with \r\n.
+						uint8x16_t match2NlDot = mergeCompares(match2NlDotA, match2NlDotB, match2NlDotC, match2NlDotD);
+						match4End = vandq_u8(match4End, match2NlDot);
+						// match \r\n=y
+						uint8x16_t match1Nl = mergeCompares(match1NlA, match1NlB, match1NlC, match1NlD);
+						uint8x16_t match3End = vandq_u8(match3EqY, match1Nl);
+						// combine match sequences
+						if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(match4End, match3End)))) {
+							// terminator found
+							// there's probably faster ways to do this, but reverting to scalar code should be good enough
+							len += i;
+							nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
+					uint8x16_t match2NlDotDMasked = vandq_u8(match2NlDotD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+					uint8x16_t mergeKillDots = vpaddq_u8(
+						vpaddq_u8(
+							vandq_u8(match2NlDotA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+							vandq_u8(match2NlDotB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128))
+						),
+						vpaddq_u8(
+							vandq_u8(match2NlDotC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128)),
+							match2NlDotDMasked
+						)
+					);
+					mergeKillDots = vpaddq_u8(mergeKillDots, mergeKillDots);
+					uint64x2_t mergeKillDotsShifted = vshlq_n_u64(vreinterpretq_u64_u8(mergeKillDots), 2);
+					mask |= vgetq_lane_u64(mergeKillDotsShifted, 0);
+					cmpCombined = vorrq_u8(cmpCombined, vreinterpretq_u8_u64(mergeKillDotsShifted));
+					nextMaskMix = vextq_u8(match2NlDotD, vdupq_n_u8(0), 14);
+				} else if(searchEnd) {
+					match2EqA = vextq_u8(cmpEqA, cmpEqB, 2);
+					match2EqB = vextq_u8(cmpEqB, cmpEqC, 2);
+					match2EqC = vextq_u8(cmpEqC, cmpEqD, 2);
+					
+					uint8x16_t match3EqYA = vandq_u8(match2EqA, vceqq_u8(vextq_u8(dataA, dataB, 3), vdupq_n_u8('y')));
+					uint8x16_t match3EqYB = vandq_u8(match2EqB, vceqq_u8(vextq_u8(dataB, dataC, 3), vdupq_n_u8('y')));
+					uint8x16_t match3EqYC = vandq_u8(match2EqC, vceqq_u8(vextq_u8(dataC, dataD, 3), vdupq_n_u8('y')));
+					uint8x16_t match3EqYD = vandq_u8(match2EqD, vceqq_u8(vextq_u8(dataD, nextData, 3), vdupq_n_u8('y')));
+					if(LIKELIHOOD(0.001, neon_vect_is_nonzero(vorrq_u8(
+						vorrq_u8(match3EqYA, match3EqYB),
+						vorrq_u8(match3EqYC, match3EqYD)
+					)))) {
+						uint8x16_t match1LfA = vceqq_u8(vextq_u8(dataA, dataB, 1), vdupq_n_u8('\n'));
+						uint8x16_t match1LfB = vceqq_u8(vextq_u8(dataB, dataC, 1), vdupq_n_u8('\n'));
+						uint8x16_t match1LfC = vceqq_u8(vextq_u8(dataC, dataD, 1), vdupq_n_u8('\n'));
+						uint8x16_t match1LfD = vceqq_u8(vextq_u8(dataD, nextData, 1), vdupq_n_u8('\n'));
+						uint8x16_t matchEnd = vorrq_u8(
+							vorrq_u8(
+								vandq_u8(match3EqYA, vandq_u8(match1LfA, cmpCrA)),
+								vandq_u8(match3EqYB, vandq_u8(match1LfB, cmpCrB))
+							),
+							vorrq_u8(
+								vandq_u8(match3EqYC, vandq_u8(match1LfC, cmpCrC)),
+								vandq_u8(match3EqYD, vandq_u8(match1LfD, cmpCrD))
+							)
+						);
+						if(LIKELIHOOD(0.001, neon_vect_is_nonzero(matchEnd))) {
+							len += i;
+							nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
+					if(isRaw)
+						nextMaskMix = vdupq_n_u8(0);
+				} else if(isRaw) // no \r_. found
+					nextMaskMix = vdupq_n_u8(0);
+			}
+			
+			// a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
+			// the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
+			// firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
+			uint64_t maskEqShift1 = (maskEq << 1) | escFirst;
+			if(LIKELIHOOD(0.0001, (mask & maskEqShift1) != 0)) {
+				maskEq = fix_eqMask<uint64_t>(maskEq, maskEqShift1);
+				
+				unsigned char nextEscFirst = maskEq>>63;
+				// next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
+				maskEq = (maskEq<<1) | escFirst;
+				mask &= ~maskEq;
+				escFirst = nextEscFirst;
+				
+				// unescape chars following `=`
 #if defined(__GNUC__) && !defined(__clang__)
-                // this seems to stop GCC9 producing slow code, for some reason... TODO: investigate why
-                uint8x8_t _maskEqTemp = vreinterpret_u8_u64(vmov_n_u64(maskEq));
-                uint8x16_t maskEqTemp = vcombine_u8(_maskEqTemp, vdup_n_u8(0));
+				// this seems to stop GCC9 producing slow code, for some reason... TODO: investigate why
+				uint8x8_t _maskEqTemp = vreinterpret_u8_u64(vmov_n_u64(maskEq));
+				uint8x16_t maskEqTemp = vcombine_u8(_maskEqTemp, vdup_n_u8(0));
 #else
-                uint8x16_t maskEqTemp = vreinterpretq_u8_u64(vmovq_n_u64(maskEq));
+				uint8x16_t maskEqTemp = vreinterpretq_u8_u64(vmovq_n_u64(maskEq));
 #endif
-                cmpCombined = vbicq_u8(cmpCombined, maskEqTemp); // `mask &= ~maskEq` in vector form
-
-                uint8x16_t vMaskEqA = vqtbl1q_u8(
-                    maskEqTemp,
-                    vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
-                );
-                maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
-                uint8x16_t vMaskEqB = vqtbl1q_u8(
-                    maskEqTemp,
-                    vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
-                );
-                maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
-                uint8x16_t vMaskEqC = vqtbl1q_u8(
-                    maskEqTemp,
-                    vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
-                );
-                maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
-                uint8x16_t vMaskEqD = vqtbl1q_u8(
-                    maskEqTemp,
-                    vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
-                );
-                vMaskEqA = vtstq_u8(vMaskEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                vMaskEqB = vtstq_u8(vMaskEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                vMaskEqC = vtstq_u8(vMaskEqC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-                vMaskEqD = vtstq_u8(vMaskEqD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-
-                dataA = vsubq_u8(
-                    dataA,
-                    vbslq_u8(vMaskEqA, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-                dataB = vsubq_u8(
-                    dataB,
-                    vbslq_u8(vMaskEqB, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-                dataC = vsubq_u8(
-                    dataC,
-                    vbslq_u8(vMaskEqC, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-                dataD = vsubq_u8(
-                    dataD,
-                    vbslq_u8(vMaskEqD, vdupq_n_u8(64+42), vdupq_n_u8(42))
-                );
-            } else {
-                // no invalid = sequences found - we can cut out some things from above
-                // this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
-                escFirst = (maskEq >> 63);
-
-                dataA = vsubq_u8(
-                    dataA,
-                    vbslq_u8(
-                        vextq_u8(vdupq_n_u8(42), cmpEqA, 15),
-                        vdupq_n_u8(64+42),
-                        yencOffset
-                    )
-                );
-                dataB = vsubq_u8(
-                    dataB,
-                    vbslq_u8(
-                        vextq_u8(cmpEqA, cmpEqB, 15),
-                        vdupq_n_u8(64+42),
-                        vdupq_n_u8(42)
-                    )
-                );
-                dataC = vsubq_u8(
-                    dataC,
-                    vbslq_u8(
-                        vextq_u8(cmpEqB, cmpEqC, 15),
-                        vdupq_n_u8(64+42),
-                        vdupq_n_u8(42)
-                    )
-                );
-                dataD = vsubq_u8(
-                    dataD,
-                    vbslq_u8(
-                        vextq_u8(cmpEqC, cmpEqD, 15),
-                        vdupq_n_u8(64+42),
-                        vdupq_n_u8(42)
-                    )
-                );
-            }
-            yencOffset = vsetq_lane_u8((escFirst << 6) | 42, yencOffset, 0);
-
-            // all that's left is to 'compress' the data (skip over masked chars)
-            uint64_t counts = vget_lane_u64(vreinterpret_u64_u8(vcnt_u8(vget_low_u8(cmpCombined))), 0);
-            counts = 0x0808080808080808ULL - counts;
-            counts += counts>>8;
-
-            vst1q_u8(p, vqtbl1q_u8(
-                dataA,
-                vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
-            ));
-            p += counts & 0xff;
-            mask >>= 16;
-            vst1q_u8(p, vqtbl1q_u8(
-                dataB,
-                vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
-            ));
-            p += (counts>>16) & 0xff;
-            mask >>= 16;
-            vst1q_u8(p, vqtbl1q_u8(
-                dataC,
-                vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
-            ));
-            p += (counts>>32) & 0xff;
-            mask >>= 16;
-            vst1q_u8(p, vqtbl1q_u8(
-                dataD,
-                vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
-            ));
-            p += (counts>>48) & 0xff;
-        } else {
-            dataA = vsubq_u8(dataA, yencOffset);
-            dataB = vsubq_u8(dataB, vdupq_n_u8(42));
-            dataC = vsubq_u8(dataC, vdupq_n_u8(42));
-            dataD = vsubq_u8(dataD, vdupq_n_u8(42));
-            _vst1q_u8_x4(p, vcreate4_u8(dataA, dataB, dataC, dataD));
-            p += sizeof(uint8x16_t)*4;
-            escFirst = 0;
-            yencOffset = vdupq_n_u8(42);
-        }
-    }
-    if(isRaw) {
-        if(len != 0) { // have to gone through at least one loop cycle
-            if(src[i-2] == '\r' && src[i-1] == '\n' && src[i] == '.')
-                nextMask = 1;
-            else if(src[i-1] == '\r' && src[i] == '\n' && src[i+1] == '.')
-                nextMask = 2;
-            else
-                nextMask = 0;
-        }
-    } else
-        nextMask = 0;
+				cmpCombined = vbicq_u8(cmpCombined, maskEqTemp); // `mask &= ~maskEq` in vector form
+				
+				uint8x16_t vMaskEqA = vqtbl1q_u8(
+					maskEqTemp,
+					vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
+				);
+				maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
+				uint8x16_t vMaskEqB = vqtbl1q_u8(
+					maskEqTemp,
+					vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
+				);
+				maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
+				uint8x16_t vMaskEqC = vqtbl1q_u8(
+					maskEqTemp,
+					vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
+				);
+				maskEqTemp = vextq_u8(maskEqTemp, maskEqTemp, 2);
+				uint8x16_t vMaskEqD = vqtbl1q_u8(
+					maskEqTemp,
+					vmakeq_u8(0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1)
+				);
+				vMaskEqA = vtstq_u8(vMaskEqA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				vMaskEqB = vtstq_u8(vMaskEqB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				vMaskEqC = vtstq_u8(vMaskEqC, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				vMaskEqD = vtstq_u8(vMaskEqD, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+				
+				dataA = vsubq_u8(
+					dataA,
+					vbslq_u8(vMaskEqA, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+				dataB = vsubq_u8(
+					dataB,
+					vbslq_u8(vMaskEqB, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+				dataC = vsubq_u8(
+					dataC,
+					vbslq_u8(vMaskEqC, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+				dataD = vsubq_u8(
+					dataD,
+					vbslq_u8(vMaskEqD, vdupq_n_u8(64+42), vdupq_n_u8(42))
+				);
+			} else {
+				// no invalid = sequences found - we can cut out some things from above
+				// this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
+				escFirst = (maskEq >> 63);
+				
+				dataA = vsubq_u8(
+					dataA,
+					vbslq_u8(
+						vextq_u8(vdupq_n_u8(42), cmpEqA, 15),
+						vdupq_n_u8(64+42),
+						yencOffset
+					)
+				);
+				dataB = vsubq_u8(
+					dataB,
+					vbslq_u8(
+						vextq_u8(cmpEqA, cmpEqB, 15),
+						vdupq_n_u8(64+42),
+						vdupq_n_u8(42)
+					)
+				);
+				dataC = vsubq_u8(
+					dataC,
+					vbslq_u8(
+						vextq_u8(cmpEqB, cmpEqC, 15),
+						vdupq_n_u8(64+42),
+						vdupq_n_u8(42)
+					)
+				);
+				dataD = vsubq_u8(
+					dataD,
+					vbslq_u8(
+						vextq_u8(cmpEqC, cmpEqD, 15),
+						vdupq_n_u8(64+42),
+						vdupq_n_u8(42)
+					)
+				);
+			}
+			yencOffset = vsetq_lane_u8((escFirst << 6) | 42, yencOffset, 0);
+			
+			// all that's left is to 'compress' the data (skip over masked chars)
+			uint64_t counts = vget_lane_u64(vreinterpret_u64_u8(vcnt_u8(vget_low_u8(cmpCombined))), 0);
+			counts = 0x0808080808080808ULL - counts;
+			counts += counts>>8;
+			
+			vst1q_u8(p, vqtbl1q_u8(
+				dataA,
+				vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
+			));
+			p += counts & 0xff;
+			mask >>= 16;
+			vst1q_u8(p, vqtbl1q_u8(
+				dataB,
+				vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
+			));
+			p += (counts>>16) & 0xff;
+			mask >>= 16;
+			vst1q_u8(p, vqtbl1q_u8(
+				dataC,
+				vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
+			));
+			p += (counts>>32) & 0xff;
+			mask >>= 16;
+			vst1q_u8(p, vqtbl1q_u8(
+				dataD,
+				vld1q_u8((uint8_t*)(compactLUT + (mask&0x7fff)))
+			));
+			p += (counts>>48) & 0xff;
+		} else {
+			dataA = vsubq_u8(dataA, yencOffset);
+			dataB = vsubq_u8(dataB, vdupq_n_u8(42));
+			dataC = vsubq_u8(dataC, vdupq_n_u8(42));
+			dataD = vsubq_u8(dataD, vdupq_n_u8(42));
+			_vst1q_u8_x4(p, vcreate4_u8(dataA, dataB, dataC, dataD));
+			p += sizeof(uint8x16_t)*4;
+			escFirst = 0;
+			yencOffset = vdupq_n_u8(42);
+		}
+	}
 }
+} // namespace
 
-void decoder_set_neon_funcs() {
-    decoder_init_lut(eqFixLUT, compactLUT);
-    _do_decode = &do_decode_simd<false, false, sizeof(uint8x16_t)*4, do_decode_neon<false, false> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(uint8x16_t)*4, do_decode_neon<true, false> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(uint8x16_t)*4, do_decode_neon<true, true> >;
-    _decode_simd_level = 1;
+void RapidYenc::decoder_set_neon_funcs() {
+	decoder_init_lut(compactLUT);
+	_do_decode = &do_decode_simd<false, false, sizeof(uint8x16_t)*4, do_decode_neon<false, false> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(uint8x16_t)*4, do_decode_neon<true, false> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(uint8x16_t)*4, do_decode_neon<true, true> >;
+	_decode_isa = ISA_LEVEL_NEON;
 }
 #else
-
-void decoder_set_neon_funcs() {}
-
+void RapidYenc::decoder_set_neon_funcs() {}
 #endif
diff --git a/src/yencode/decoder_rvv.cc b/src/yencode/decoder_rvv.cc
new file mode 100644
index 0000000..d2315ff
--- /dev/null
+++ b/src/yencode/decoder_rvv.cc
@@ -0,0 +1,281 @@
+#include "common.h"
+#include "decoder_common.h"
+#ifdef __riscv_vector
+
+
+#ifdef __riscv_v_intrinsic
+# define RV_vmerge_vxm_u8m2 RV(vmerge_vxm_u8m2)
+# define RV_vmerge_vxm_u16m2 RV(vmerge_vxm_u16m2)
+#else
+# define RV_vmerge_vxm_u8m2(v, x, m, vl) RV(vmerge_vxm_u8m2)(m, v, x, vl)
+# define RV_vmerge_vxm_u16m2(v, x, m, vl) RV(vmerge_vxm_u16m2)(m, v, x, vl)
+#endif
+
+#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 12000
+# define RV_VEC_CAST(masksz, vecsz, vec) RV(vreinterpret_v_b##masksz##_u##vecsz##m1)(vec)
+#else
+# define RV_VEC_CAST(masksz, vecsz, vec) *(vuint##vecsz##m1_t*)(&(vec))
+#endif
+
+
+template<int shift>
+static inline vbool4_t mask_lshift(vbool4_t m, unsigned shiftIn, size_t vl) {
+	vuint8m1_t mv = RV_VEC_CAST(4, 8, m);
+	vuint8m1_t mvl = RV(vsll_vx_u8m1)(mv, shift, vl/8);
+	vuint8m1_t mvr = RV(vsrl_vx_u8m1)(mv, 8-shift, vl/8);
+	mvr = RV(vslide1up_vx_u8m1)(mvr, shiftIn, vl/8);
+	
+	return RV(vmor_mm_b4)(
+		RV_MASK_CAST(4, 8, mvl), RV_MASK_CAST(4, 8, mvr), vl
+	);
+}
+template<int shift>
+static inline vbool64_t mask_lshift(vbool64_t m, unsigned shiftIn, size_t vl) {
+	vuint8m1_t mv = RV_VEC_CAST(64, 8, m);
+	vuint8m1_t mvl = RV(vsll_vx_u8m1)(mv, shift, vl/8);
+	vuint8m1_t mvr = RV(vsrl_vx_u8m1)(mv, 8-shift, vl/8);
+	mvr = RV(vslide1up_vx_u8m1)(mvr, shiftIn, vl/8);
+	
+	return RV(vmor_mm_b64)(
+		RV_MASK_CAST(64, 8, mvl), RV_MASK_CAST(64, 8, mvr), vl
+	);
+}
+
+static inline vuint8m2_t set_first_vu8(vuint8m2_t src, uint8_t item, size_t vl) {
+#ifdef __riscv_v_intrinsic
+	return RV(vmv_s_x_u8m2_tu)(src, item, vl);
+#else
+	vuint8m1_t m = RV(vslide1up_vx_u8m1)(RV(vmv_v_x_u8m1)(0, ~0), 1, ~0);
+	return RV_vmerge_vxm_u8m2(src, item, RV_MASK_CAST(4, 8, m), vl);
+#endif
+}
+static inline vuint16m2_t set_first_vu16(vuint16m2_t src, uint16_t item, size_t vl) {
+#ifdef __riscv_v_intrinsic
+	return RV(vmv_s_x_u16m2_tu)(src, item, vl);
+#else
+	vuint16m1_t m = RV(vslide1up_vx_u16m1)(RV(vmv_v_x_u16m1)(0, ~0), 1, ~0);
+	return RV_vmerge_vxm_u16m2(src, item, RV_MASK_CAST(8, 16, m), vl);
+#endif
+}
+
+
+namespace RapidYenc {
+
+template<bool isRaw, bool searchEnd>
+HEDLEY_ALWAYS_INLINE void do_decode_rvv(const uint8_t* src, long& len, unsigned char*& outp, unsigned char& escFirst, uint16_t& nextMask) {
+	HEDLEY_ASSUME(escFirst == 0 || escFirst == 1);
+	HEDLEY_ASSUME(nextMask == 0 || nextMask == 1 || nextMask == 2);
+	
+	size_t vl2 = RV(vsetvlmax_e8m2)();
+	
+	vuint8m2_t yencOffset = RV(vmv_v_x_u8m2)(42, vl2);
+	if(escFirst) yencOffset = set_first_vu8(yencOffset, 42+64, vl2);
+	vuint8m2_t lfCompare = RV(vmv_v_x_u8m2)('\n', vl2);
+	if(nextMask && isRaw) {
+		lfCompare = RV(vreinterpret_v_u16m2_u8m2)(
+			set_first_vu16(RV(vreinterpret_v_u8m2_u16m2)(lfCompare), nextMask == 1 ? 0x0a2e /*".\n"*/ : 0x2e0a /*"\n."*/, vl2/2)
+		);
+	}
+	
+	// mask where only the highest bit is set
+	vbool4_t lastBit = RV(vmseq_vx_u8m2_b4)(
+		RV(vslide1down_vx_u8m2)(RV(vmv_v_x_u8m2)(0, vl2), 1, vl2),
+		1, vl2
+	);
+	
+	decoder_set_nextMask<isRaw>(src, len, nextMask);
+	
+	// TODO: consider exploiting partial vector capability
+	long inpos;
+	for(inpos = -len; inpos; inpos += vl2) {
+		vuint8m2_t data = RV(vle8_v_u8m2)(src + inpos, vl2);
+		
+		// search for special chars
+		vbool4_t cmpEq = RV(vmseq_vx_u8m2_b4)(data, '=', vl2);
+		vbool4_t cmpCr = RV(vmseq_vx_u8m2_b4)(data, '\r', vl2);
+		// note: cmp is always negated (unlike cmpEq/Cr)
+		vbool4_t cmp = RV(vmnor_mm_b4)(
+			RV(vmor_mm_b4)(cmpEq, cmpCr, vl2),
+			isRaw ? RV(vmseq_vv_u8m2_b4)(data, lfCompare, vl2) : RV(vmseq_vx_u8m2_b4)(data, '\n', vl2),
+			vl2
+		);
+		
+		size_t numOutputChars = RV(vcpop_m_b4)(cmp, vl2);
+		
+		if(numOutputChars != vl2) {
+			// dot-unstuffing + end detection
+			if((isRaw || searchEnd) && RV(vcpop_m_b4)(RV(vmxnor_mm_b4)(cmp, cmpEq, vl2), vl2)) {
+				uint32_t nextWord;
+				if(!searchEnd) {
+					memcpy(&nextWord, src + inpos + vl2, 2);
+				} else {
+					memcpy(&nextWord, src + inpos + vl2, 4);
+				}
+				vuint8m2_t nextData2 = RV(vreinterpret_v_u16m2_u8m2)(RV(vslide1down_vx_u16m2)(RV(vreinterpret_v_u8m2_u16m2)(data), nextWord, vl2/2));
+				
+				vbool4_t match2Cr_Dot, match3EqY;
+				vuint8m2_t nextData3;
+				if(isRaw) {
+					match2Cr_Dot = RV(vmand_mm_b4)(cmpCr, RV(vmseq_vx_u8m2_b4)(nextData2, '.', vl2), vl2);
+				}
+				
+				if(searchEnd) {
+					nextData3 = RV(vslide1down_vx_u8m2)(nextData2, nextWord>>16, vl2);
+					match3EqY = RV(vmand_mm_b4)(
+						RV(vmseq_vx_u8m2_b4)(nextData2, '=', vl2),
+						RV(vmseq_vx_u8m2_b4)(nextData3, 'y', vl2),
+						vl2
+					);
+				}
+				
+				// find patterns of \r_.
+				if(isRaw && LIKELIHOOD(0.001, RV(vcpop_m_b4)(match2Cr_Dot, vl2) > 0)) {
+					// find \r\n.
+					vuint8m2_t nextData1 = RV(vslide1down_vx_u8m2)(data, nextWord, vl2);
+					vbool4_t match1Lf = RV(vmseq_vx_u8m2_b4)(nextData1, '\n', vl2);
+					vbool4_t match2NlDot = RV(vmand_mm_b4)(match2Cr_Dot, match1Lf, vl2);
+					
+					if(searchEnd) {
+						vbool4_t match1Nl = RV(vmand_mm_b4)(cmpCr, match1Lf, vl2);
+						
+						vuint8m2_t nextData4 = RV(vreinterpret_v_u32m2_u8m2)(RV(vslide1down_vx_u32m2)(RV(vreinterpret_v_u8m2_u32m2)(data), nextWord, vl2/4));
+						
+						// match instances of \r\n.\r\n and \r\n.=y
+						vbool4_t match4Nl = RV(vmand_mm_b4)(
+							RV(vmseq_vx_u8m2_b4)(nextData3, '\r', vl2),
+							RV(vmseq_vx_u8m2_b4)(nextData4, '\n', vl2),
+							vl2
+						);
+						vbool4_t match4EqY = RV(vmand_mm_b4)(
+							RV(vmseq_vx_u8m2_b4)(nextData3, '=', vl2),
+							RV(vmseq_vx_u8m2_b4)(nextData4, 'y', vl2),
+							vl2
+						);
+						
+						// merge \r\n and =y matches
+						vbool4_t match4End = RV(vmor_mm_b4)(match4Nl, match4EqY, vl2);
+						// merge with \r\n.
+						match4End = RV(vmand_mm_b4)(match4End, match2NlDot, vl2);
+						// merge \r\n=y
+						vbool4_t match3End = RV(vmand_mm_b4)(match1Nl, match3EqY, vl2);
+						
+						vbool4_t matchEnd = RV(vmor_mm_b4)(match4End, match3End, vl2);
+						
+						// combine match sequences
+						if(LIKELIHOOD(0.001, RV(vcpop_m_b4)(matchEnd, vl2) > 0)) {
+							// terminator found
+							len += inpos;
+							nextMask = decoder_set_nextMask<isRaw>(src+inpos, ~RV(vmv_x_s_u8m1_u8)(RV_VEC_CAST(4, 8, cmp)));
+							break;
+						}
+					}
+					
+					// shift match2NlDot by 2
+					cmp = RV(vmandn_mm_b4)(cmp, mask_lshift<2>(match2NlDot, 0, vl2), vl2);
+					numOutputChars = RV(vcpop_m_b4)(cmp, vl2);
+					
+					vuint8mf4_t nextNlDot = RV(vslidedown_vx_u8mf4)(
+#ifndef __riscv_v_intrinsic
+						RV(vmv_v_x_u8mf4)(0, vl2/8),
+#endif
+						RV_VEC_U8MF4_CAST(match2NlDot), vl2/8-1, vl2/8
+					);
+					nextNlDot = RV(vsrl_vx_u8mf4)(nextNlDot, 6, vl2/8);
+					vuint8m1_t nextNlDotVec = RV(vlmul_ext_v_u8mf4_u8m1)(nextNlDot);
+					lfCompare = RV_vmerge_vxm_u8m2(RV(vmv_v_x_u8m2)('\n', vl2), '.', RV_MASK_CAST(4, 8, nextNlDotVec), vl2);
+				} else if(searchEnd) {
+					if(LIKELIHOOD(0.001, RV(vcpop_m_b4)(match3EqY, vl2) != 0)) {
+						vuint8m2_t nextData1 = RV(vslide1down_vx_u8m2)(data, nextWord, vl2);
+						vbool4_t match1Lf = RV(vmseq_vx_u8m2_b4)(nextData1, '\n', vl2);
+						vbool4_t matchEnd = RV(vmand_mm_b4)(RV(vmand_mm_b4)(match3EqY, cmpCr, vl2), match1Lf, vl2);
+						if(LIKELIHOOD(0.001, RV(vcpop_m_b4)(matchEnd, vl2) > 0)) {
+							len += inpos;
+							nextMask = decoder_set_nextMask<isRaw>(src+inpos, ~RV(vmv_x_s_u8m1_u8)(RV_VEC_CAST(4, 8, cmp)));
+							break;
+						}
+					}
+					if(isRaw)
+						lfCompare = RV(vmv_v_x_u8m2)('\n', vl2);
+				} else if(isRaw) // no \r_. found
+					lfCompare = RV(vmv_v_x_u8m2)('\n', vl2);
+			}
+			
+			// the second character in an escape sequence
+			vbool4_t cmpEqShift1 = mask_lshift<1>(cmpEq, escFirst, vl2);
+			
+			// a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
+			// the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
+			// firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
+			if(LIKELIHOOD(0.0001, RV(vcpop_m_b4)(RV(vmandn_mm_b4)(cmpEqShift1, cmp, vl2), vl2) != 0)) {
+				// replicate fix_eqMask, but in vector form
+				vbool4_t groupStart = RV(vmandn_mm_b4)(cmpEq, cmpEqShift1, vl2);
+				vuint8m1_t evenBitsV = RV(vmv_v_x_u8m1)(0x55, vl2);
+				vbool4_t evenBits = RV_MASK_CAST(4, 8, evenBitsV);
+				vbool4_t evenStart = RV(vmand_mm_b4)(groupStart, evenBits, vl2);
+				
+				// compute `cmpEq + evenStart` to obtain oddGroups
+				vbool4_t oddGroups;
+				vuint64m1_t cmpEq64 = RV_VEC_CAST(4, 64, cmpEq);
+				vuint64m1_t evenStart64 = RV_VEC_CAST(4, 64, evenStart);
+				vuint64m1_t oddGroups64;
+				if(vl2 <= 64) {
+					// no loop needed - single 64b add will work
+					oddGroups64 = RV(vadd_vv_u64m1)(cmpEq64, evenStart64, 1);
+				} else {
+					// need to loop whilst the add causes a carry
+					unsigned vl64 = vl2/64;
+					vbool64_t carry = RV(vmadc_vv_u64m1_b64)(cmpEq64, evenStart64, vl64);
+					carry = mask_lshift<1>(carry, 0, vl64);
+					oddGroups64 = RV(vadd_vv_u64m1)(cmpEq64, evenStart64, 1);
+					while(RV(vcpop_m_b64)(carry, vl64)) {
+						vbool64_t nextCarry = RV(vmadc_vx_u64m1_b64)(oddGroups64, 1, vl64);
+						oddGroups64 = RV(vadd_vx_u64m1_mu)(carry, oddGroups64, oddGroups64, 1, vl64);
+						carry = mask_lshift<1>(nextCarry, 0, vl64);
+					}
+				}
+				oddGroups = RV_MASK_CAST(4, 64, oddGroups64);
+				
+				cmpEq = RV(vmand_mm_b4)(RV(vmxor_mm_b4)(oddGroups, evenBits, vl2), cmpEq, vl2);
+				
+				cmpEqShift1 = mask_lshift<1>(cmpEq, escFirst, vl2);
+				cmp = RV(vmor_mm_b4)(cmpEqShift1, cmp, vl2); // ~(~cmp & ~cmpEqShift1)
+				numOutputChars = RV(vcpop_m_b4)(cmp, vl2);
+			}
+			escFirst = RV(vcpop_m_b4)(RV(vmand_mm_b4)(cmpEq, lastBit, vl2), vl2);
+			
+			data = RV(vsub_vv_u8m2)(data, RV_vmerge_vxm_u8m2(yencOffset, 64+42, cmpEqShift1, vl2), vl2);
+			yencOffset = set_first_vu8(yencOffset, 42 | (escFirst<<6), vl2);
+			
+			// all that's left is to remove unwanted chars
+#ifdef __riscv_v_intrinsic
+			data = RV(vcompress_vm_u8m2)(data, cmp, vl2);
+#else
+			data = RV(vcompress_vm_u8m2)(cmp, data, data, vl2);
+#endif
+			RV(vse8_v_u8m2)(outp, data, vl2);
+		} else {
+			data = RV(vsub_vv_u8m2)(data, yencOffset, vl2);
+			RV(vse8_v_u8m2)(outp, data, vl2);
+			// TODO: should these be done at LMUL=1? or, it might not be worth this strategy (e.g. do an additional OR instead), considering the cost of LMUL=2
+			yencOffset = RV(vmv_v_x_u8m2)(42, vl2);
+			if(isRaw) lfCompare = RV(vmv_v_x_u8m2)('\n', vl2);
+			escFirst = 0;
+		}
+		outp += numOutputChars;
+	}
+}
+
+size_t decoder_rvv_width() {
+	return RV(vsetvlmax_e8m2)();
+}
+} // namespace
+
+void RapidYenc::decoder_set_rvv_funcs() {
+	_do_decode = &do_decode_simd<false, false, decoder_rvv_width, do_decode_rvv<false, false> >;
+	_do_decode_raw = &do_decode_simd<true, false, decoder_rvv_width, do_decode_rvv<true, false> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, decoder_rvv_width, do_decode_rvv<true, true> >;
+	_decode_isa = ISA_LEVEL_RVV;
+}
+#else
+void RapidYenc::decoder_set_rvv_funcs() {}
+#endif
diff --git a/src/yencode/decoder_sse2.cc b/src/yencode/decoder_sse2.cc
index 1347e32..829b261 100644
--- a/src/yencode/decoder_sse2.cc
+++ b/src/yencode/decoder_sse2.cc
@@ -1,19 +1,38 @@
 #include "common.h"
 
-#ifdef __SSE2__
 #include "decoder_common.h"
+#ifdef __SSE2__
 #include "decoder_sse_base.h"
 
-void decoder_set_sse2_funcs() {
-    decoder_sse_init();
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSE2> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSE2> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSE2> >;
-    _decode_simd_level = ISA_LEVEL_SSE2;
+void RapidYenc::decoder_sse_init(RapidYenc::SSELookups* HEDLEY_RESTRICT& lookups) {
+	ALIGN_ALLOC(lookups, sizeof(SSELookups), 16);
+	for(int i=0; i<256; i++) {
+		lookups->BitsSetTable256inv[i] = 8 - (
+			(i & 1) + ((i>>1) & 1) + ((i>>2) & 1) + ((i>>3) & 1) + ((i>>4) & 1) + ((i>>5) & 1) + ((i>>6) & 1) + ((i>>7) & 1)
+		);
+		
+		#define _X(n, k) ((((n) & (1<<k)) ? 192ULL : 0ULL) << (k*8))
+		lookups->eqAdd[i] = _X(i, 0) | _X(i, 1) | _X(i, 2) | _X(i, 3) | _X(i, 4) | _X(i, 5) | _X(i, 6) | _X(i, 7);
+		#undef _X
+	}
+	for(int i=0; i<32; i++) {
+		for(int j=0; j<16; j++) {
+			if(i >= 16) // only used for LZCNT
+				lookups->unshufMask[i*16 + j] = ((31-i)>j ? -1 : 0);
+			else // only used for BSR
+				lookups->unshufMask[i*16 + j] = (i>j ? -1 : 0);
+		}
+	}
 }
-#else
-
-void decoder_set_sse2_funcs() {}
 
+void RapidYenc::decoder_set_sse2_funcs() {
+	decoder_sse_init(lookups);
+	decoder_init_lut(lookups->compact);
+	_do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSE2> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSE2> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSE2> >;
+	_decode_isa = ISA_LEVEL_SSE2;
+}
+#else
+void RapidYenc::decoder_set_sse2_funcs() {}
 #endif
diff --git a/src/yencode/decoder_sse_base.h b/src/yencode/decoder_sse_base.h
index 8f3543c..042faf9 100644
--- a/src/yencode/decoder_sse_base.h
+++ b/src/yencode/decoder_sse_base.h
@@ -7,6 +7,13 @@
 # define _mm_shrdi_epi16 _mm128_shrdi_epi16
 #endif
 
+#if defined(__tune_icelake_client__) || defined(__tune_icelake_server__) || defined(__tune_tigerlake__) || defined(__tune_rocketlake__) || defined(__tune_alderlake__) || defined(__tune_sapphirerapids__)
+# define COMPRESS_STORE _mm_mask_compressstoreu_epi8
+#else
+// avoid uCode on Zen4
+# define COMPRESS_STORE(dst, mask, vec) _mm_storeu_si128((__m128i*)(dst), _mm_maskz_compress_epi8(mask, vec))
+#endif
+
 // GCC (ver 6-10(dev)) fails to optimize pure C version of mask testing, but has this intrinsic; Clang >= 7 optimizes C version fine
 #if (defined(__GNUC__) && __GNUC__ >= 7) || (defined(_MSC_VER) && _MSC_VER >= 1924)
 # define KORTEST16(a, b) !_kortestz_mask16_u8((a), (b))
@@ -18,46 +25,30 @@
 # define KOR16(a, b) ((a) | (b))
 #endif
 
-#pragma pack(16)
-static struct {
-    unsigned char BitsSetTable256inv[256];
-    /*align16*/ struct { char bytes[16]; } compact[32768];
-    uint8_t eqFix[256];
-    /*align8*/ uint64_t eqAdd[256];
-    /*align16*/ int8_t unshufMask[32*16];
-} * HEDLEY_RESTRICT lookups;
-#pragma pack()
+namespace RapidYenc {
+	#pragma pack(16)
+	typedef struct {
+		unsigned char BitsSetTable256inv[256];
+		/*align16*/ struct { char bytes[16]; } compact[32768];
+		/*align8*/ uint64_t eqAdd[256];
+		/*align16*/ int8_t unshufMask[32*16];
+	} SSELookups;
+	#pragma pack()
+}
+static RapidYenc::SSELookups* HEDLEY_RESTRICT lookups;
 
 
 static HEDLEY_ALWAYS_INLINE __m128i force_align_read_128(const void* p) {
 #ifdef _MSC_VER
-    // MSVC complains about casting away volatile
-    return *(__m128i *)(p);
+	// MSVC complains about casting away volatile
+	return *(__m128i *)(p);
 #else
-    return *(volatile __m128i *)(p);
+	return *(volatile __m128i *)(p);
 #endif
 }
 
-
-static void decoder_sse_init() {
-    ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
-    for(int i=0; i<256; i++) {
-        lookups->BitsSetTable256inv[i] = 8 - (
-            (i & 1) + ((i>>1) & 1) + ((i>>2) & 1) + ((i>>3) & 1) + ((i>>4) & 1) + ((i>>5) & 1) + ((i>>6) & 1) + ((i>>7) & 1)
-        );
-
-#define _X(n, k) ((((n) & (1<<k)) ? 192ULL : 0ULL) << (k*8))
-        lookups->eqAdd[i] = _X(i, 0) | _X(i, 1) | _X(i, 2) | _X(i, 3) | _X(i, 4) | _X(i, 5) | _X(i, 6) | _X(i, 7);
-#undef _X
-    }
-    for(int i=0; i<32; i++) {
-        for(int j=0; j<16; j++) {
-            if(i >= 16) // only used for LZCNT
-                lookups->unshufMask[i*16 + j] = ((31-i)>j ? -1 : 0);
-            else // only used for BSR
-                lookups->unshufMask[i*16 + j] = (i>j ? -1 : 0);
-        }
-    }
+namespace RapidYenc {
+	void decoder_sse_init(SSELookups* HEDLEY_RESTRICT& lookups); // defined in decoder_sse2.cc
 }
 
 
@@ -66,9 +57,9 @@ static void decoder_sse_init() {
 # include <intrin.h>
 # include <ammintrin.h>
 static HEDLEY_ALWAYS_INLINE unsigned BSR32(unsigned src) {
-    unsigned long result;
-    _BitScanReverse((unsigned long*)&result, src);
-    return result;
+	unsigned long result;
+	_BitScanReverse((unsigned long*)&result, src);
+	return result;
 }
 #elif defined(__GNUC__)
 // have seen Clang not like _bit_scan_reverse
@@ -81,638 +72,628 @@ static HEDLEY_ALWAYS_INLINE unsigned BSR32(unsigned src) {
 
 template<enum YEncDecIsaLevel use_isa>
 static HEDLEY_ALWAYS_INLINE __m128i sse2_compact_vect(uint32_t mask, __m128i data) {
-    while(mask) {
-        unsigned bitIndex;
+	while(mask) {
+		unsigned bitIndex;
 #if defined(__LZCNT__)
-        if(use_isa & ISA_FEATURE_LZCNT) {
-            // lzcnt is always at least as fast as bsr, so prefer it if it's available
-            bitIndex = _lzcnt_u32(mask);
-            mask &= 0x7fffffffU>>bitIndex;
-        } else
+		if(use_isa & ISA_FEATURE_LZCNT) {
+			// lzcnt is always at least as fast as bsr, so prefer it if it's available
+			bitIndex = _lzcnt_u32(mask);
+			mask &= 0x7fffffffU>>bitIndex;
+		} else
 #endif
-        {
-            bitIndex = BSR32(mask);
-            mask ^= 1<<bitIndex;
-        }
-        __m128i mergeMask = _mm_load_si128((__m128i*)lookups->unshufMask + bitIndex);
-        data = _mm_or_si128(
-            _mm_and_si128(mergeMask, data),
-            _mm_andnot_si128(mergeMask, _mm_srli_si128(data, 1))
-        );
-    }
-    return data;
+		{
+			bitIndex = BSR32(mask);
+			mask ^= 1<<bitIndex;
+		}
+		__m128i mergeMask = _mm_load_si128((__m128i*)lookups->unshufMask + bitIndex);
+		data = _mm_or_si128(
+			_mm_and_si128(mergeMask, data),
+			_mm_andnot_si128(mergeMask, _mm_srli_si128(data, 1))
+		);
+	}
+	return data;
 }
 
-template<bool isRaw, bool searchEnd, enum YEncDecIsaLevel use_isa>
-HEDLEY_ALWAYS_INLINE void do_decode_sse(const uint8_t* HEDLEY_RESTRICT src, long& len, unsigned char* HEDLEY_RESTRICT & p, unsigned char& _escFirst, uint16_t& _nextMask) {
-    HEDLEY_ASSUME(_escFirst == 0 || _escFirst == 1);
-    HEDLEY_ASSUME(_nextMask == 0 || _nextMask == 1 || _nextMask == 2);
-    uintptr_t escFirst = _escFirst;
-    __m128i yencOffset = escFirst ? _mm_set_epi8(
-        -42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42-64
-    ) : _mm_set1_epi8(-42);
+namespace RapidYenc {
 
+template<bool isRaw, bool searchEnd, enum YEncDecIsaLevel use_isa>
+HEDLEY_ALWAYS_INLINE void do_decode_sse(const uint8_t* src, long& len, unsigned char*& p, unsigned char& _escFirst, uint16_t& _nextMask) {
+	HEDLEY_ASSUME(_escFirst == 0 || _escFirst == 1);
+	HEDLEY_ASSUME(_nextMask == 0 || _nextMask == 1 || _nextMask == 2);
+	uintptr_t escFirst = _escFirst;
+	__m128i yencOffset = escFirst ? _mm_set_epi8(
+		-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42,-42-64
+	) : _mm_set1_epi8(-42);
+	
 #if defined(__SSSE3__) && !defined(__tune_atom__) && !defined(__tune_slm__) && !defined(__tune_btver1__) && !defined(__tune_btver2__)
-    const bool _USING_FAST_MATCH = (use_isa >= ISA_LEVEL_SSSE3);
+	const bool _USING_FAST_MATCH = (use_isa >= ISA_LEVEL_SSSE3);
 #else
-    const bool _USING_FAST_MATCH = false;
+	const bool _USING_FAST_MATCH = false;
 #endif
 #if defined(__SSE4_1__) && !defined(__tune_slm__) && !defined(__tune_goldmont__) && !defined(__tune_goldmont_plus__) && !defined(__tune_tremont__)
-    const bool _USING_BLEND_ADD = (use_isa >= ISA_LEVEL_SSE41);
+	const bool _USING_BLEND_ADD = (use_isa >= ISA_LEVEL_SSE41);
 #else
-    const bool _USING_BLEND_ADD = false;
+	const bool _USING_BLEND_ADD = false;
 #endif
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
 # if defined(_MSC_VER) && !defined(PLATFORM_AMD64) && !defined(__clang__)
-    const bool useAVX3MaskCmp = false;
+	const bool useAVX3MaskCmp = false;
 # else
-    const bool useAVX3MaskCmp = (use_isa >= ISA_LEVEL_AVX3);
+	const bool useAVX3MaskCmp = (use_isa >= ISA_LEVEL_AVX3);
 # endif
 #endif
-
-    __m128i lfCompare = _mm_set1_epi8('\n');
-    __m128i minMask = _mm_set1_epi8('.');
-    if(_nextMask && isRaw) {
-        if(_USING_FAST_MATCH)
-            minMask = _mm_insert_epi16(minMask, _nextMask == 1 ? 0x2e00 : 0x002e, 0);
-        else
-            lfCompare = _mm_insert_epi16(lfCompare, _nextMask == 1 ? 0x0a2e /*".\n"*/ : 0x2e0a /*"\n."*/, 0);
-    }
-    intptr_t i;
-    for(i = -len; i; i += sizeof(__m128i)*2) {
-        __m128i oDataA = _mm_load_si128((__m128i *)(src+i));
-        __m128i oDataB = _mm_load_si128((__m128i *)(src+i) + 1);
-
-        // search for special chars
-        __m128i cmpEqA, cmpEqB, cmpCrA, cmpCrB;
-        __m128i cmpA, cmpB;
+	
+	__m128i lfCompare = _mm_set1_epi8('\n');
+	__m128i minMask = _mm_set1_epi8('.');
+	if(_nextMask && isRaw) {
+		if(_USING_FAST_MATCH)
+			minMask = _mm_insert_epi16(minMask, _nextMask == 1 ? 0x2e00 : 0x002e, 0);
+		else
+			lfCompare = _mm_insert_epi16(lfCompare, _nextMask == 1 ? 0x0a2e /*".\n"*/ : 0x2e0a /*"\n."*/, 0);
+	}
+	
+	decoder_set_nextMask<isRaw>(src, len, _nextMask); // set this before the loop because we can't check src after it's been overwritten
+	
+	intptr_t i;
+	for(i = -len; i; i += sizeof(__m128i)*2) {
+		__m128i oDataA = _mm_load_si128((__m128i *)(src+i));
+		__m128i oDataB = _mm_load_si128((__m128i *)(src+i) + 1);
+		
+		// search for special chars
+		__m128i cmpEqA, cmpEqB, cmpCrA, cmpCrB;
+		__m128i cmpA, cmpB;
 #if defined(__SSSE3__)
-        if(_USING_FAST_MATCH) {
-            cmpA = _mm_cmpeq_epi8(oDataA, _mm_shuffle_epi8(
-                _mm_set_epi8(-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'),
-                _mm_min_epu8(oDataA, minMask)
-            ));
-            cmpB = _mm_cmpeq_epi8(oDataB, _mm_shuffle_epi8(
-                _mm_set_epi8(-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'),
-                _mm_min_epu8(oDataB, _mm_set1_epi8('.'))
-            ));
-        } else
+		if(_USING_FAST_MATCH) {
+			cmpA = _mm_cmpeq_epi8(oDataA, _mm_shuffle_epi8(
+				_mm_set_epi8(-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'),
+				_mm_min_epu8(oDataA, minMask)
+			));
+			cmpB = _mm_cmpeq_epi8(oDataB, _mm_shuffle_epi8(
+				_mm_set_epi8(-1,'=','\r',-1,-1,'\n',-1,-1,-1,-1,-1,-1,-1,-1,-1,'.'),
+				_mm_min_epu8(oDataB, _mm_set1_epi8('.'))
+			));
+		} else
 #endif
-        {
-            cmpEqA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('='));
-            cmpEqB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('='));
-            cmpCrA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('\r'));
-            cmpCrB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\r'));
-            cmpA = _mm_or_si128(
-                _mm_or_si128(
-                    _mm_cmpeq_epi8(oDataA, lfCompare), cmpCrA
-                ),
-                cmpEqA
-            );
-            cmpB = _mm_or_si128(
-                _mm_or_si128(
-                    _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\n')), cmpCrB
-                ),
-                cmpEqB
-            );
-        }
-
-        __m128i dataA, dataB;
-        if(!_USING_BLEND_ADD)
-            dataA = _mm_add_epi8(oDataA, yencOffset);
-        uint32_t mask = (unsigned)_mm_movemask_epi8(cmpA) | ((unsigned)_mm_movemask_epi8(cmpB) << 16); // not the most accurate mask if we have invalid sequences; we fix this up later
-
-        if (LIKELIHOOD(0.42 /* rough guess */, mask != 0)) {
-            if(_USING_FAST_MATCH) {
-                cmpEqA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('='));
-                cmpEqB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('='));
-            }
-
+		{
+			cmpEqA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('='));
+			cmpEqB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('='));
+			cmpCrA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('\r'));
+			cmpCrB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\r'));
+			cmpA = _mm_or_si128(
+				_mm_or_si128(
+					_mm_cmpeq_epi8(oDataA, lfCompare), cmpCrA
+				),
+				cmpEqA
+			);
+			cmpB = _mm_or_si128(
+				_mm_or_si128(
+					_mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\n')), cmpCrB
+				),
+				cmpEqB
+			);
+		}
+		
+		__m128i dataA, dataB;
+		if(!_USING_BLEND_ADD)
+			dataA = _mm_add_epi8(oDataA, yencOffset);
+		uint32_t mask = (unsigned)_mm_movemask_epi8(cmpA) | ((unsigned)_mm_movemask_epi8(cmpB) << 16); // not the most accurate mask if we have invalid sequences; we fix this up later
+		
+		if (LIKELIHOOD(0.42 /* rough guess */, mask != 0)) {
+			if(_USING_FAST_MATCH) {
+				cmpEqA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('='));
+				cmpEqB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('='));
+			}
+			
 #define LOAD_HALVES(a, b) _mm_castps_si128(_mm_loadh_pi( \
-    _mm_castsi128_ps(_mm_loadl_epi64((__m128i*)(a))), \
-    (__m64*)(b) \
+	_mm_castsi128_ps(_mm_loadl_epi64((__m128i*)(a))), \
+	(__m64*)(b) \
 ))
-
-            // a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
-            // the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
-            // firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
-            uint32_t maskEq = (unsigned)_mm_movemask_epi8(cmpEqA) | ((unsigned)_mm_movemask_epi8(cmpEqB) << 16);
-
-            // handle \r\n. sequences
-            // RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
-            if((isRaw || searchEnd) && LIKELIHOOD(0.25, mask != maskEq)) {
+			
+			// a spec compliant encoder should never generate sequences: ==, =\n and =\r, but we'll handle them to be spec compliant
+			// the yEnc specification requires any character following = to be unescaped, not skipped over, so we'll deal with that
+			// firstly, check for invalid sequences of = (we assume that these are rare, as a spec compliant yEnc encoder should not generate these)
+			uint32_t maskEq = (unsigned)_mm_movemask_epi8(cmpEqA) | ((unsigned)_mm_movemask_epi8(cmpEqB) << 16);
+			
+			// handle \r\n. sequences
+			// RFC3977 requires the first dot on a line to be stripped, due to dot-stuffing
+			if((isRaw || searchEnd) && LIKELIHOOD(0.25, mask != maskEq)) {
 #if 0
-                // for experimentation: prefer shifting data over unaligned loads on CPUs with slow unaligned handling
-                // haven't ever seen this be beneficial though
-                __m128i nextDataB;
-                if(searchEnd && _USING_BLEND_ADD)
-                    nextDataB = _mm_cvtsi32_si128(*(uint32_t*)(src+i+sizeof(__m128i)*2));
+				// for experimentation: prefer shifting data over unaligned loads on CPUs with slow unaligned handling
+				// haven't ever seen this be beneficial though
+				__m128i nextDataB;
+				if(searchEnd && _USING_BLEND_ADD)
+					nextDataB = _mm_cvtsi32_si128(*(uint32_t*)(src+i+sizeof(__m128i)*2));
 # define SHIFT_DATA_A(offs) (searchEnd && _USING_BLEND_ADD ? _mm_alignr_epi8(oDataB, oDataA, offs) : _mm_loadu_si128((__m128i *)(src+i+offs)))
 # define SHIFT_DATA_B(offs) (searchEnd && _USING_BLEND_ADD ? _mm_alignr_epi8(nextDataB, oDataB, offs) : _mm_loadu_si128((__m128i *)(src+i+offs) + 1))
 #else
 # define SHIFT_DATA_A(offs) _mm_loadu_si128((__m128i *)(src+i+offs))
 # define SHIFT_DATA_B(offs) _mm_loadu_si128((__m128i *)(src+i+offs) + 1)
 #endif
-                __m128i tmpData2A = SHIFT_DATA_A(2);
-                __m128i tmpData2B = SHIFT_DATA_B(2);
-                __m128i match2EqA, match2EqB;
-
+				__m128i tmpData2A = SHIFT_DATA_A(2);
+				__m128i tmpData2B = SHIFT_DATA_B(2);
+				__m128i match2EqA, match2EqB;
+				
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                __mmask16 match2EqMaskA, match2EqMaskB;
-                __mmask16 match0CrMaskA, match0CrMaskB;
-                __mmask16 match2CrXDtMaskA, match2CrXDtMaskB;
-                if(useAVX3MaskCmp && searchEnd) {
-                    match2EqMaskA = _mm_cmpeq_epi8_mask(_mm_set1_epi8('='), tmpData2A);
-                    match2EqMaskB = _mm_cmpeq_epi8_mask(_mm_set1_epi8('='), tmpData2B);
-                } else
+				__mmask16 match2EqMaskA, match2EqMaskB;
+				__mmask16 match0CrMaskA, match0CrMaskB;
+				__mmask16 match2CrXDtMaskA, match2CrXDtMaskB;
+				if(useAVX3MaskCmp && searchEnd) {
+					match2EqMaskA = _mm_cmpeq_epi8_mask(_mm_set1_epi8('='), tmpData2A);
+					match2EqMaskB = _mm_cmpeq_epi8_mask(_mm_set1_epi8('='), tmpData2B);
+				} else
 #endif
-                if(searchEnd) {
+				if(searchEnd) {
 #if !defined(__tune_btver1__)
-                    if(use_isa < ISA_LEVEL_SSSE3)
+					if(use_isa < ISA_LEVEL_SSSE3)
 #endif
-                        match2EqA = _mm_cmpeq_epi8(_mm_set1_epi8('='), tmpData2A);
-                    match2EqB = _mm_cmpeq_epi8(_mm_set1_epi8('='), tmpData2B);
-                }
-                int partialKillDotFound;
-                __m128i match2CrXDtA, match2CrXDtB;
-                if(isRaw) {
+						match2EqA = _mm_cmpeq_epi8(_mm_set1_epi8('='), tmpData2A);
+					match2EqB = _mm_cmpeq_epi8(_mm_set1_epi8('='), tmpData2B);
+				}
+				int partialKillDotFound;
+				__m128i match2CrXDtA, match2CrXDtB;
+				if(isRaw) {
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(useAVX3MaskCmp) {
-                        match0CrMaskA = _mm_cmpeq_epi8_mask(oDataA, _mm_set1_epi8('\r'));
-                        match0CrMaskB = _mm_cmpeq_epi8_mask(oDataB, _mm_set1_epi8('\r'));
-                        match2CrXDtMaskA = _mm_mask_cmpeq_epi8_mask(match0CrMaskA, tmpData2A, _mm_set1_epi8('.'));
-                        match2CrXDtMaskB = _mm_mask_cmpeq_epi8_mask(match0CrMaskB, tmpData2B, _mm_set1_epi8('.'));
-                        partialKillDotFound = KORTEST16(match2CrXDtMaskA, match2CrXDtMaskB);
-                    } else
+					if(useAVX3MaskCmp) {
+						match0CrMaskA = _mm_cmpeq_epi8_mask(oDataA, _mm_set1_epi8('\r'));
+						match0CrMaskB = _mm_cmpeq_epi8_mask(oDataB, _mm_set1_epi8('\r'));
+						match2CrXDtMaskA = _mm_mask_cmpeq_epi8_mask(match0CrMaskA, tmpData2A, _mm_set1_epi8('.'));
+						match2CrXDtMaskB = _mm_mask_cmpeq_epi8_mask(match0CrMaskB, tmpData2B, _mm_set1_epi8('.'));
+						partialKillDotFound = KORTEST16(match2CrXDtMaskA, match2CrXDtMaskB);
+					} else
 #endif
-                    {
-                        if(_USING_FAST_MATCH) {
-                            cmpCrA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('\r'));
-                            cmpCrB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\r'));
-                        }
-                        match2CrXDtA = _mm_and_si128(cmpCrA, _mm_cmpeq_epi8(tmpData2A, _mm_set1_epi8('.')));
-                        match2CrXDtB = _mm_and_si128(cmpCrB, _mm_cmpeq_epi8(tmpData2B, _mm_set1_epi8('.')));
-                        partialKillDotFound = _mm_movemask_epi8(_mm_or_si128(match2CrXDtA, match2CrXDtB));
-                    }
-                }
-
-                if(isRaw && LIKELIHOOD(0.001, partialKillDotFound)) {
-                    __m128i match2NlDotA, match1NlA;
-                    __m128i match2NlDotB, match1NlB;
-                    // merge matches for \r\n.
+					{
+						if(_USING_FAST_MATCH) {
+							cmpCrA = _mm_cmpeq_epi8(oDataA, _mm_set1_epi8('\r'));
+							cmpCrB = _mm_cmpeq_epi8(oDataB, _mm_set1_epi8('\r'));
+						}
+						match2CrXDtA = _mm_and_si128(cmpCrA, _mm_cmpeq_epi8(tmpData2A, _mm_set1_epi8('.')));
+						match2CrXDtB = _mm_and_si128(cmpCrB, _mm_cmpeq_epi8(tmpData2B, _mm_set1_epi8('.')));
+						partialKillDotFound = _mm_movemask_epi8(_mm_or_si128(match2CrXDtA, match2CrXDtB));
+					}
+				}
+				
+				if(isRaw && LIKELIHOOD(0.001, partialKillDotFound)) {
+					__m128i match2NlDotA, match1NlA;
+					__m128i match2NlDotB, match1NlB;
+					// merge matches for \r\n.
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    __mmask16 match1NlMaskA, match1NlMaskB;
-                    __mmask16 match2NlDotMaskA, match2NlDotMaskB;
-                    if(useAVX3MaskCmp) {
-                        match1NlMaskA = _mm_mask_cmpeq_epi8_mask(
-                            match0CrMaskA,
-                            _mm_set1_epi8('\n'),
-                            SHIFT_DATA_A(1)
-                        );
-                        match1NlMaskB = _mm_mask_cmpeq_epi8_mask(
-                            match0CrMaskB,
-                            _mm_set1_epi8('\n'),
-                            SHIFT_DATA_B(1)
-                        );
-                        match2NlDotMaskA = KAND16(match2CrXDtMaskA, match1NlMaskA);
-                        match2NlDotMaskB = KAND16(match2CrXDtMaskB, match1NlMaskB);
-                    } else
+					__mmask16 match1NlMaskA, match1NlMaskB;
+					__mmask16 match2NlDotMaskA, match2NlDotMaskB;
+					if(useAVX3MaskCmp) {
+						match1NlMaskA = _mm_mask_cmpeq_epi8_mask(
+							match0CrMaskA,
+							_mm_set1_epi8('\n'),
+							SHIFT_DATA_A(1)
+						);
+						match1NlMaskB = _mm_mask_cmpeq_epi8_mask(
+							match0CrMaskB,
+							_mm_set1_epi8('\n'),
+							SHIFT_DATA_B(1)
+						);
+						match2NlDotMaskA = KAND16(match2CrXDtMaskA, match1NlMaskA);
+						match2NlDotMaskB = KAND16(match2CrXDtMaskB, match1NlMaskB);
+					} else
 #endif
-                    {
-                        __m128i match1LfA = _mm_cmpeq_epi8(_mm_set1_epi8('\n'), SHIFT_DATA_A(1));
-                        __m128i match1LfB = _mm_cmpeq_epi8(_mm_set1_epi8('\n'), SHIFT_DATA_B(1));
-
-                        // always recompute cmpCr to avoid register spills above
-                        cmpCrA = _mm_cmpeq_epi8(force_align_read_128(src+i), _mm_set1_epi8('\r'));
-                        cmpCrB = _mm_cmpeq_epi8(force_align_read_128(src+i + sizeof(__m128i)), _mm_set1_epi8('\r'));
-                        match1NlA = _mm_and_si128(match1LfA, cmpCrA);
-                        match1NlB = _mm_and_si128(match1LfB, cmpCrB);
-                        match2NlDotA = _mm_and_si128(match2CrXDtA, match1NlA);
-                        match2NlDotB = _mm_and_si128(match2CrXDtB, match1NlB);
-                    }
-                    if(searchEnd) {
-                        __m128i tmpData3A = SHIFT_DATA_A(3);
-                        __m128i tmpData3B = SHIFT_DATA_B(3);
-                        __m128i tmpData4A = SHIFT_DATA_A(4);
-                        __m128i tmpData4B = SHIFT_DATA_B(4);
-                        // match instances of \r\n.\r\n and \r\n.=y
-                        // TODO: consider doing a PALIGNR using match1Nl for match4NlA
-                        __m128i match3CrA = _mm_cmpeq_epi8(_mm_set1_epi8('\r'), tmpData3A);
-                        __m128i match3CrB = _mm_cmpeq_epi8(_mm_set1_epi8('\r'), tmpData3B);
-                        __m128i match4LfA = _mm_cmpeq_epi8(tmpData4A, _mm_set1_epi8('\n'));
-                        __m128i match4LfB = _mm_cmpeq_epi8(tmpData4B, _mm_set1_epi8('\n'));
-                        __m128i match4EqYA = _mm_cmpeq_epi16(tmpData4A, _mm_set1_epi16(0x793d)); // =y
-                        __m128i match4EqYB = _mm_cmpeq_epi16(tmpData4B, _mm_set1_epi16(0x793d)); // =y
-
-                        int matchEnd;
+					{
+						__m128i match1LfA = _mm_cmpeq_epi8(_mm_set1_epi8('\n'), SHIFT_DATA_A(1));
+						__m128i match1LfB = _mm_cmpeq_epi8(_mm_set1_epi8('\n'), SHIFT_DATA_B(1));
+						
+						// always recompute cmpCr to avoid register spills above
+						cmpCrA = _mm_cmpeq_epi8(force_align_read_128(src+i), _mm_set1_epi8('\r'));
+						cmpCrB = _mm_cmpeq_epi8(force_align_read_128(src+i + sizeof(__m128i)), _mm_set1_epi8('\r'));
+						match1NlA = _mm_and_si128(match1LfA, cmpCrA);
+						match1NlB = _mm_and_si128(match1LfB, cmpCrB);
+						match2NlDotA = _mm_and_si128(match2CrXDtA, match1NlA);
+						match2NlDotB = _mm_and_si128(match2CrXDtB, match1NlB);
+					}
+					if(searchEnd) {
+						__m128i tmpData3A = SHIFT_DATA_A(3);
+						__m128i tmpData3B = SHIFT_DATA_B(3);
+						__m128i tmpData4A = SHIFT_DATA_A(4);
+						__m128i tmpData4B = SHIFT_DATA_B(4);
+						// match instances of \r\n.\r\n and \r\n.=y
+						// TODO: consider doing a PALIGNR using match1Nl for match4NlA
+						__m128i match3CrA = _mm_cmpeq_epi8(_mm_set1_epi8('\r'), tmpData3A);
+						__m128i match3CrB = _mm_cmpeq_epi8(_mm_set1_epi8('\r'), tmpData3B);
+						__m128i match4LfA = _mm_cmpeq_epi8(tmpData4A, _mm_set1_epi8('\n'));
+						__m128i match4LfB = _mm_cmpeq_epi8(tmpData4B, _mm_set1_epi8('\n'));
+						__m128i match4EqYA = _mm_cmpeq_epi16(tmpData4A, _mm_set1_epi16(0x793d)); // =y
+						__m128i match4EqYB = _mm_cmpeq_epi16(tmpData4B, _mm_set1_epi16(0x793d)); // =y
+						
+						int matchEnd;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                        if(useAVX3MaskCmp) {
-                            __mmask16 match3EqYMaskA = _mm_mask_cmpeq_epi8_mask(
-                                match2EqMaskA, _mm_set1_epi8('y'), tmpData3A
-                            );
-                            __mmask16 match3EqYMaskB = _mm_mask_cmpeq_epi8_mask(
-                                match2EqMaskB, _mm_set1_epi8('y'), tmpData3B
-                            );
-                            __m128i match34EqYA, match34EqYB;
+						if(useAVX3MaskCmp) {
+							__mmask16 match3EqYMaskA = _mm_mask_cmpeq_epi8_mask(
+								match2EqMaskA, _mm_set1_epi8('y'), tmpData3A
+							);
+							__mmask16 match3EqYMaskB = _mm_mask_cmpeq_epi8_mask(
+								match2EqMaskB, _mm_set1_epi8('y'), tmpData3B
+							);
+							__m128i match34EqYA, match34EqYB;
 # ifdef __AVX512VBMI2__
-                            if(use_isa >= ISA_LEVEL_VBMI2) {
-                                match34EqYA = _mm_shrdi_epi16(_mm_movm_epi8(match3EqYMaskA), match4EqYA, 8);
-                                match34EqYB = _mm_shrdi_epi16(_mm_movm_epi8(match3EqYMaskB), match4EqYB, 8);
-                            } else
+							if(use_isa >= ISA_LEVEL_VBMI2) {
+								match34EqYA = _mm_shrdi_epi16(_mm_movm_epi8(match3EqYMaskA), match4EqYA, 8);
+								match34EqYB = _mm_shrdi_epi16(_mm_movm_epi8(match3EqYMaskB), match4EqYB, 8);
+							} else
 # endif
-                            {
-                                // (match4EqY & 0xff00) | (match3EqY >> 8)
-                                match34EqYA = _mm_mask_blend_epi8(match3EqYMaskA>>1, _mm_and_si128(match4EqYA, _mm_set1_epi16(-0xff)), _mm_set1_epi8(-1));
-                                match34EqYB = _mm_mask_blend_epi8(match3EqYMaskB>>1, _mm_and_si128(match4EqYB, _mm_set1_epi16(-0xff)), _mm_set1_epi8(-1));
-                            }
-                            // merge \r\n and =y matches for tmpData4
-                            __m128i match4EndA = _mm_ternarylogic_epi32(match34EqYA, match3CrA, match4LfA, 0xF8); // (match3Cr & match4Lf) | match34EqY
-                            __m128i match4EndB = _mm_ternarylogic_epi32(match34EqYB, match3CrB, match4LfB, 0xF8);
-                            // merge with \r\n. and combine
-                            matchEnd = KORTEST16(
-                                KOR16(
-                                    _mm_mask_test_epi8_mask(match2NlDotMaskA, match4EndA, match4EndA),
-                                    KAND16(match3EqYMaskA, match1NlMaskA)
-                                ),
-                                KOR16(
-                                    _mm_mask_test_epi8_mask(match2NlDotMaskB, match4EndB, match4EndB),
-                                    KAND16(match3EqYMaskB, match1NlMaskB)
-                                )
-                            );
-                        } else
+							{
+								// (match4EqY & 0xff00) | (match3EqY >> 8)
+								match34EqYA = _mm_mask_blend_epi8(match3EqYMaskA>>1, _mm_and_si128(match4EqYA, _mm_set1_epi16(-0xff)), _mm_set1_epi8(-1));
+								match34EqYB = _mm_mask_blend_epi8(match3EqYMaskB>>1, _mm_and_si128(match4EqYB, _mm_set1_epi16(-0xff)), _mm_set1_epi8(-1));
+							}
+							// merge \r\n and =y matches for tmpData4
+							__m128i match4EndA = _mm_ternarylogic_epi32(match34EqYA, match3CrA, match4LfA, 0xF8); // (match3Cr & match4Lf) | match34EqY
+							__m128i match4EndB = _mm_ternarylogic_epi32(match34EqYB, match3CrB, match4LfB, 0xF8);
+							// merge with \r\n. and combine
+							matchEnd = KORTEST16(
+								KOR16(
+									_mm_mask_test_epi8_mask(match2NlDotMaskA, match4EndA, match4EndA),
+									KAND16(match3EqYMaskA, match1NlMaskA)
+								),
+								KOR16(
+									_mm_mask_test_epi8_mask(match2NlDotMaskB, match4EndB, match4EndB),
+									KAND16(match3EqYMaskB, match1NlMaskB)
+								)
+							);
+						} else
 #endif
-                        {
+						{
 #if defined(__SSSE3__) && !defined(__tune_btver1__)
-                            if(use_isa >= ISA_LEVEL_SSSE3)
-                                match2EqA = _mm_alignr_epi8(cmpEqB, cmpEqA, 2);
+							if(use_isa >= ISA_LEVEL_SSSE3)
+								match2EqA = _mm_alignr_epi8(cmpEqB, cmpEqA, 2);
 #endif
-                            __m128i match3EqYA = _mm_and_si128(match2EqA, _mm_cmpeq_epi8(_mm_set1_epi8('y'), tmpData3A));
-                            __m128i match3EqYB = _mm_and_si128(match2EqB, _mm_cmpeq_epi8(_mm_set1_epi8('y'), tmpData3B));
-                            match4EqYA = _mm_slli_epi16(match4EqYA, 8); // TODO: also consider using PBLENDVB here with shifted match3EqY instead
-                            match4EqYB = _mm_slli_epi16(match4EqYB, 8);
-                            // merge \r\n and =y matches for tmpData4
-                            __m128i match4EndA = _mm_or_si128(
-                                _mm_and_si128(match3CrA, match4LfA),
-                                _mm_or_si128(match4EqYA, _mm_srli_epi16(match3EqYA, 8)) // _mm_srli_si128 by 1 also works
-                            );
-                            __m128i match4EndB = _mm_or_si128(
-                                _mm_and_si128(match3CrB, match4LfB),
-                                _mm_or_si128(match4EqYB, _mm_srli_epi16(match3EqYB, 8)) // _mm_srli_si128 by 1 also works
-                            );
-                            // merge with \r\n.
-                            match4EndA = _mm_and_si128(match4EndA, match2NlDotA);
-                            match4EndB = _mm_and_si128(match4EndB, match2NlDotB);
-                            // match \r\n=y
-                            __m128i match3EndA = _mm_and_si128(match3EqYA, match1NlA);
-                            __m128i match3EndB = _mm_and_si128(match3EqYB, match1NlB);
-                            // combine match sequences
-                            matchEnd = _mm_movemask_epi8(_mm_or_si128(
-                                _mm_or_si128(match4EndA, match3EndA),
-                                _mm_or_si128(match4EndB, match3EndB)
-                            ));
-                        }
-
-                        if(LIKELIHOOD(0.001, matchEnd)) {
-                            // terminator found
-                            // there's probably faster ways to do this, but reverting to scalar code should be good enough
-                            len += (long)i;
-                            break;
-                        }
-                    }
+							__m128i match3EqYA = _mm_and_si128(match2EqA, _mm_cmpeq_epi8(_mm_set1_epi8('y'), tmpData3A));
+							__m128i match3EqYB = _mm_and_si128(match2EqB, _mm_cmpeq_epi8(_mm_set1_epi8('y'), tmpData3B));
+							match4EqYA = _mm_slli_epi16(match4EqYA, 8); // TODO: also consider using PBLENDVB here with shifted match3EqY instead
+							match4EqYB = _mm_slli_epi16(match4EqYB, 8);
+							// merge \r\n and =y matches for tmpData4
+							__m128i match4EndA = _mm_or_si128(
+								_mm_and_si128(match3CrA, match4LfA),
+								_mm_or_si128(match4EqYA, _mm_srli_epi16(match3EqYA, 8)) // _mm_srli_si128 by 1 also works
+							);
+							__m128i match4EndB = _mm_or_si128(
+								_mm_and_si128(match3CrB, match4LfB),
+								_mm_or_si128(match4EqYB, _mm_srli_epi16(match3EqYB, 8)) // _mm_srli_si128 by 1 also works
+							);
+							// merge with \r\n.
+							match4EndA = _mm_and_si128(match4EndA, match2NlDotA);
+							match4EndB = _mm_and_si128(match4EndB, match2NlDotB);
+							// match \r\n=y
+							__m128i match3EndA = _mm_and_si128(match3EqYA, match1NlA);
+							__m128i match3EndB = _mm_and_si128(match3EqYB, match1NlB);
+							// combine match sequences
+							matchEnd = _mm_movemask_epi8(_mm_or_si128(
+								_mm_or_si128(match4EndA, match3EndA),
+								_mm_or_si128(match4EndB, match3EndB)
+							));
+						}
+						
+						if(LIKELIHOOD(0.001, matchEnd)) {
+							// terminator found
+							// there's probably faster ways to do this, but reverting to scalar code should be good enough
+							len += (long)i;
+							_nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(useAVX3MaskCmp) {
-                        mask |= match2NlDotMaskA << 2;
-                        mask |= (match2NlDotMaskB << 18) & 0xffffffff;
-                        minMask = _mm_maskz_mov_epi8(~(match2NlDotMaskB>>14), _mm_set1_epi8('.'));
-                    } else
+					if(useAVX3MaskCmp) {
+						mask |= match2NlDotMaskA << 2;
+						mask |= (match2NlDotMaskB << 18) & 0xffffffff;
+						minMask = _mm_maskz_mov_epi8(~(match2NlDotMaskB>>14), _mm_set1_epi8('.'));
+					} else
 #endif
-                    {
-                        mask |= (_mm_movemask_epi8(match2NlDotA) << 2);
-                        mask |= (_mm_movemask_epi8(match2NlDotB) << 18) & 0xffffffff;
-                        match2NlDotB = _mm_srli_si128(match2NlDotB, 14);
-                        if(_USING_FAST_MATCH)
-                            minMask = _mm_subs_epu8(_mm_set1_epi8('.'), match2NlDotB);
-                        else
-                            // this bitiwse trick works because '.'|'\n' == '.'
-                            lfCompare = _mm_or_si128(
-                                _mm_and_si128(match2NlDotB, _mm_set1_epi8('.')),
-                                _mm_set1_epi8('\n')
-                            );
-                    }
-                }
-                else if(searchEnd) {
-                    bool partialEndFound;
-                    __m128i match3EqYA, match3EqYB;
+					{
+						mask |= (_mm_movemask_epi8(match2NlDotA) << 2);
+						mask |= (_mm_movemask_epi8(match2NlDotB) << 18) & 0xffffffff;
+						match2NlDotB = _mm_srli_si128(match2NlDotB, 14);
+						if(_USING_FAST_MATCH)
+							minMask = _mm_subs_epu8(_mm_set1_epi8('.'), match2NlDotB);
+						else
+							// this bitiwse trick works because '.'|'\n' == '.'
+							lfCompare = _mm_or_si128(
+								_mm_and_si128(match2NlDotB, _mm_set1_epi8('.')),
+								_mm_set1_epi8('\n')
+							);
+					}
+				}
+				else if(searchEnd) {
+					bool partialEndFound;
+					__m128i match3EqYA, match3EqYB;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                    __mmask16 match3EqYMaskA, match3EqYMaskB;
-                    if(useAVX3MaskCmp) {
-                        match3EqYMaskA = _mm_mask_cmpeq_epi8_mask(
-                            match2EqMaskA,
-                            _mm_set1_epi8('y'),
-                            SHIFT_DATA_A(3)
-                        );
-                        match3EqYMaskB = _mm_mask_cmpeq_epi8_mask(
-                            match2EqMaskB,
-                            _mm_set1_epi8('y'),
-                            SHIFT_DATA_B(3)
-                        );
-                        partialEndFound = KORTEST16(match3EqYMaskA, match3EqYMaskB);
-                    } else
+					__mmask16 match3EqYMaskA, match3EqYMaskB;
+					if(useAVX3MaskCmp) {
+						match3EqYMaskA = _mm_mask_cmpeq_epi8_mask(
+							match2EqMaskA,
+							_mm_set1_epi8('y'),
+							SHIFT_DATA_A(3)
+						);
+						match3EqYMaskB = _mm_mask_cmpeq_epi8_mask(
+							match2EqMaskB,
+							_mm_set1_epi8('y'),
+							SHIFT_DATA_B(3)
+						);
+						partialEndFound = KORTEST16(match3EqYMaskA, match3EqYMaskB);
+					} else
 #endif
-                    {
-                        __m128i match3YA = _mm_cmpeq_epi8(
-                            _mm_set1_epi8('y'),
-                            SHIFT_DATA_A(3)
-                        );
-                        __m128i match3YB = _mm_cmpeq_epi8(
-                            _mm_set1_epi8('y'),
-                            SHIFT_DATA_B(3)
-                        );
+					{
+						__m128i match3YA = _mm_cmpeq_epi8(
+							_mm_set1_epi8('y'),
+							SHIFT_DATA_A(3)
+						);
+						__m128i match3YB = _mm_cmpeq_epi8(
+							_mm_set1_epi8('y'),
+							SHIFT_DATA_B(3)
+						);
 #if defined(__SSSE3__) && !defined(__tune_btver1__)
-                        if(use_isa >= ISA_LEVEL_SSSE3)
-                            match2EqA = _mm_alignr_epi8(cmpEqB, cmpEqA, 2);
+						if(use_isa >= ISA_LEVEL_SSSE3)
+							match2EqA = _mm_alignr_epi8(cmpEqB, cmpEqA, 2);
 #endif
-                        match3EqYA = _mm_and_si128(match2EqA, match3YA);
-                        match3EqYB = _mm_and_si128(match2EqB, match3YB);
-                        partialEndFound = _mm_movemask_epi8(_mm_or_si128(match3EqYA, match3EqYB));
-                    }
-                    if(LIKELIHOOD(0.001, partialEndFound)) {
-                        // if the rare case of '=y' is found, do a more precise check
-                        bool endFound;
-
+						match3EqYA = _mm_and_si128(match2EqA, match3YA);
+						match3EqYB = _mm_and_si128(match2EqB, match3YB);
+						partialEndFound = _mm_movemask_epi8(_mm_or_si128(match3EqYA, match3EqYB));
+					}
+					if(LIKELIHOOD(0.001, partialEndFound)) {
+						// if the rare case of '=y' is found, do a more precise check
+						bool endFound;
+						
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                        if(useAVX3MaskCmp) {
-                            __mmask16 match3LfEqYMaskA = _mm_mask_cmpeq_epi8_mask(
-                                match3EqYMaskA,
-                                _mm_set1_epi8('\n'),
-                                SHIFT_DATA_A(1)
-                            );
-                            __mmask16 match3LfEqYMaskB = _mm_mask_cmpeq_epi8_mask(
-                                match3EqYMaskB,
-                                _mm_set1_epi8('\n'),
-                                SHIFT_DATA_B(1)
-                            );
-
-                            endFound = KORTEST16(
-                                _mm_mask_cmpeq_epi8_mask(match3LfEqYMaskA, oDataA, _mm_set1_epi8('\r')),
-                                _mm_mask_cmpeq_epi8_mask(match3LfEqYMaskB, oDataB, _mm_set1_epi8('\r'))
-                            );
-                        } else
+						if(useAVX3MaskCmp) {
+							__mmask16 match3LfEqYMaskA = _mm_mask_cmpeq_epi8_mask(
+								match3EqYMaskA,
+								_mm_set1_epi8('\n'),
+								SHIFT_DATA_A(1)
+							);
+							__mmask16 match3LfEqYMaskB = _mm_mask_cmpeq_epi8_mask(
+								match3EqYMaskB,
+								_mm_set1_epi8('\n'),
+								SHIFT_DATA_B(1)
+							);
+							
+							endFound = KORTEST16(
+								_mm_mask_cmpeq_epi8_mask(match3LfEqYMaskA, oDataA, _mm_set1_epi8('\r')),
+								_mm_mask_cmpeq_epi8_mask(match3LfEqYMaskB, oDataB, _mm_set1_epi8('\r'))
+							);
+						} else
 #endif
-                        {
-                            // always recompute cmpCr to avoid register spills above
-                            cmpCrA = _mm_cmpeq_epi8(force_align_read_128(src+i), _mm_set1_epi8('\r'));
-                            cmpCrB = _mm_cmpeq_epi8(force_align_read_128(src+i + sizeof(__m128i)), _mm_set1_epi8('\r'));
-                            __m128i match1LfA = _mm_cmpeq_epi8(
-                                _mm_set1_epi8('\n'),
-                                SHIFT_DATA_A(1)
-                            );
-                            __m128i match1LfB = _mm_cmpeq_epi8(
-                                _mm_set1_epi8('\n'),
-                                SHIFT_DATA_B(1)
-                            );
-                            endFound = _mm_movemask_epi8(_mm_or_si128(
-                                _mm_and_si128(
-                                    match3EqYA,
-                                    _mm_and_si128(match1LfA, cmpCrA)
-                                ),
-                                _mm_and_si128(
-                                    match3EqYB,
-                                    _mm_and_si128(match1LfB, cmpCrB)
-                                )
-                            ));
-                        }
-
-                        if(endFound) {
-                            len += (long)i;
-                            break;
-                        }
-                    }
-                    if(isRaw) {
-                        if(_USING_FAST_MATCH)
-                            minMask = _mm_set1_epi8('.');
-                        else
-                            lfCompare = _mm_set1_epi8('\n');
-                    }
-                }
-                else if(isRaw) { // no \r_. found
-                    if(_USING_FAST_MATCH)
-                        minMask = _mm_set1_epi8('.');
-                    else
-                        lfCompare = _mm_set1_epi8('\n');
-                }
-            }
+						{
+							// always recompute cmpCr to avoid register spills above
+							cmpCrA = _mm_cmpeq_epi8(force_align_read_128(src+i), _mm_set1_epi8('\r'));
+							cmpCrB = _mm_cmpeq_epi8(force_align_read_128(src+i + sizeof(__m128i)), _mm_set1_epi8('\r'));
+							__m128i match1LfA = _mm_cmpeq_epi8(
+								_mm_set1_epi8('\n'),
+								SHIFT_DATA_A(1)
+							);
+							__m128i match1LfB = _mm_cmpeq_epi8(
+								_mm_set1_epi8('\n'),
+								SHIFT_DATA_B(1)
+							);
+							endFound = _mm_movemask_epi8(_mm_or_si128(
+								_mm_and_si128(
+									match3EqYA,
+									_mm_and_si128(match1LfA, cmpCrA)
+								),
+								_mm_and_si128(
+									match3EqYB,
+									_mm_and_si128(match1LfB, cmpCrB)
+								)
+							));
+						}
+						
+						if(endFound) {
+							len += (long)i;
+							_nextMask = decoder_set_nextMask<isRaw>(src+i, mask);
+							break;
+						}
+					}
+					if(isRaw) {
+						if(_USING_FAST_MATCH)
+							minMask = _mm_set1_epi8('.');
+						else
+							lfCompare = _mm_set1_epi8('\n');
+					}
+				}
+				else if(isRaw) { // no \r_. found
+					if(_USING_FAST_MATCH)
+						minMask = _mm_set1_epi8('.');
+					else
+						lfCompare = _mm_set1_epi8('\n');
+				}
+			}
 #undef SHIFT_DATA_A
 #undef SHIFT_DATA_B
-
-            if(!_USING_BLEND_ADD)
-                dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
-
-            if(LIKELIHOOD(0.0001, (mask & ((maskEq << 1) + escFirst)) != 0)) {
-                // resolve invalid sequences of = to deal with cases like '===='
-                unsigned tmp = lookups->eqFix[(maskEq&0xff) & ~escFirst];
-                uint32_t maskEq2 = tmp;
-                for(int j=8; j<32; j+=8) {
-                    tmp = lookups->eqFix[((maskEq>>j)&0xff) & ~(tmp>>7)];
-                    maskEq2 |= tmp<<j;
-                }
-                maskEq = maskEq2;
-
-                mask &= ~escFirst;
-                escFirst = (maskEq >> 31);
-                // next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
-                maskEq <<= 1;
-                mask &= ~maskEq;
-
-                if(_USING_BLEND_ADD) {
-                    dataA = _mm_add_epi8(oDataA, yencOffset);
-                    dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
-                }
-
-                // unescape chars following `=`
+			
+			if(!_USING_BLEND_ADD)
+				dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
+			
+			uint32_t maskEqShift1 = (maskEq << 1) + escFirst;
+			if(LIKELIHOOD(0.0001, (mask & maskEqShift1) != 0)) {
+				maskEq = fix_eqMask<uint32_t>(maskEq, maskEqShift1);
+				mask &= ~escFirst;
+				escFirst = maskEq >> 31;
+				// next, eliminate anything following a `=` from the special char mask; this eliminates cases of `=\r` so that they aren't removed
+				maskEq <<= 1;
+				mask &= ~maskEq;
+				
+				if(_USING_BLEND_ADD) {
+					dataA = _mm_add_epi8(oDataA, yencOffset);
+					dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
+				}
+				
+				// unescape chars following `=`
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
-                    // GCC < 7 seems to generate rubbish assembly for this
-                    dataA = _mm_mask_add_epi8(
-                        dataA,
-                        (__mmask16)maskEq,
-                        dataA,
-                        _mm_set1_epi8(-64)
-                    );
-                    dataB = _mm_mask_add_epi8(
-                        dataB,
-                        (__mmask16)(maskEq>>16),
-                        dataB,
-                        _mm_set1_epi8(-64)
-                    );
-                } else
+				if(use_isa >= ISA_LEVEL_AVX3) {
+					// GCC < 7 seems to generate rubbish assembly for this
+					dataA = _mm_mask_add_epi8(
+						dataA,
+						(__mmask16)maskEq,
+						dataA,
+						_mm_set1_epi8(-64)
+					);
+					dataB = _mm_mask_add_epi8(
+						dataB,
+						(__mmask16)(maskEq>>16),
+						dataB,
+						_mm_set1_epi8(-64)
+					);
+				} else
 #endif
-                {
-                    dataA = _mm_add_epi8(
-                        dataA,
-                        LOAD_HALVES(
-                            lookups->eqAdd + (maskEq&0xff),
-                            lookups->eqAdd + ((maskEq>>8)&0xff)
-                        )
-                    );
-                    maskEq >>= 16;
-                    dataB = _mm_add_epi8(
-                        dataB,
-                        LOAD_HALVES(
-                            lookups->eqAdd + (maskEq&0xff),
-                            lookups->eqAdd + ((maskEq>>8)&0xff)
-                        )
-                    );
-
-                    yencOffset = _mm_xor_si128(_mm_set1_epi8(-42),
-                        _mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
-                    );
-                }
-            } else {
-                // no invalid = sequences found - we can cut out some things from above
-                // this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
-                escFirst = (maskEq >> 31);
-
+				{
+					dataA = _mm_add_epi8(
+						dataA,
+						LOAD_HALVES(
+							lookups->eqAdd + (maskEq&0xff),
+							lookups->eqAdd + ((maskEq>>8)&0xff)
+						)
+					);
+					maskEq >>= 16;
+					dataB = _mm_add_epi8(
+						dataB,
+						LOAD_HALVES(
+							lookups->eqAdd + (maskEq&0xff),
+							lookups->eqAdd + ((maskEq>>8)&0xff)
+						)
+					);
+					
+					yencOffset = _mm_xor_si128(_mm_set1_epi8(-42), 
+						_mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
+					);
+				}
+			} else {
+				// no invalid = sequences found - we can cut out some things from above
+				// this code path is a shortened version of above; it's here because it's faster, and what we'll be dealing with most of the time
+				escFirst = (maskEq >> 31);
+				
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
-                    dataA = _mm_add_epi8(
-                        oDataA,
-                        _mm_ternarylogic_epi32(
-                            _mm_slli_si128(cmpEqA, 1), yencOffset, _mm_set1_epi8(-42-64), 0xac
-                        )
-                    );
-                    dataB = _mm_add_epi8(
-                        oDataB,
-                        _mm_ternarylogic_epi32(
-                            _mm_alignr_epi8(cmpEqB, cmpEqA, 15), _mm_set1_epi8(-42), _mm_set1_epi8(-42-64), 0xac
-                        )
-                    );
-                } else
+				if(use_isa >= ISA_LEVEL_AVX3) {
+					dataA = _mm_add_epi8(
+						oDataA,
+						_mm_ternarylogic_epi32(
+							_mm_slli_si128(cmpEqA, 1), yencOffset, _mm_set1_epi8(-42-64), 0xac
+						)
+					);
+					dataB = _mm_add_epi8(
+						oDataB,
+						_mm_ternarylogic_epi32(
+							_mm_alignr_epi8(cmpEqB, cmpEqA, 15), _mm_set1_epi8(-42), _mm_set1_epi8(-42-64), 0xac
+						)
+					);
+				} else
 #endif
 #if defined(__SSE4_1__)
-                if(_USING_BLEND_ADD) {
-                    /* // the following strategy seems more ideal, however, both GCC and Clang go bonkers over it and spill more registers
-                    cmpEqA = _mm_blendv_epi8(_mm_set1_epi8(-42), _mm_set1_epi8(-42-64), cmpEqA);
-                    cmpEqB = _mm_blendv_epi8(_mm_set1_epi8(-42), _mm_set1_epi8(-42-64), cmpEqB);
-                    dataB = _mm_add_epi8(oDataB, _mm_alignr_epi8(cmpEqB, cmpEqA, 15));
-                    dataA = _mm_add_epi8(oDataA, _mm_and_si128(
-                        _mm_alignr_epi8(cmpEqA, _mm_set1_epi8(-42), 15),
-                        yencOffset
-                    ));
-                    yencOffset = _mm_alignr_epi8(_mm_set1_epi8(-42), cmpEqB, 15);
-                    */
-
-                    dataA = _mm_add_epi8(
-                        oDataA,
-                        _mm_blendv_epi8(
-                            yencOffset, _mm_set1_epi8(-42-64), _mm_slli_si128(cmpEqA, 1)
-                        )
-                    );
-                    dataB = _mm_add_epi8(
-                        oDataB,
-                        _mm_blendv_epi8(
-                            _mm_set1_epi8(-42), _mm_set1_epi8(-42-64), _mm_alignr_epi8(cmpEqB, cmpEqA, 15)
-                        )
-                    );
-                    yencOffset = _mm_xor_si128(_mm_set1_epi8(-42),
-                        _mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
-                    );
-                } else
+				if(_USING_BLEND_ADD) {
+					/* // the following strategy seems more ideal, however, both GCC and Clang go bonkers over it and spill more registers
+					cmpEqA = _mm_blendv_epi8(_mm_set1_epi8(-42), _mm_set1_epi8(-42-64), cmpEqA);
+					cmpEqB = _mm_blendv_epi8(_mm_set1_epi8(-42), _mm_set1_epi8(-42-64), cmpEqB);
+					dataB = _mm_add_epi8(oDataB, _mm_alignr_epi8(cmpEqB, cmpEqA, 15));
+					dataA = _mm_add_epi8(oDataA, _mm_and_si128(
+						_mm_alignr_epi8(cmpEqA, _mm_set1_epi8(-42), 15),
+						yencOffset
+					));
+					yencOffset = _mm_alignr_epi8(_mm_set1_epi8(-42), cmpEqB, 15);
+					*/
+					
+					dataA = _mm_add_epi8(
+						oDataA,
+						_mm_blendv_epi8(
+							yencOffset, _mm_set1_epi8(-42-64), _mm_slli_si128(cmpEqA, 1)
+						)
+					);
+					dataB = _mm_add_epi8(
+						oDataB,
+						_mm_blendv_epi8(
+							_mm_set1_epi8(-42), _mm_set1_epi8(-42-64), _mm_alignr_epi8(cmpEqB, cmpEqA, 15)
+						)
+					);
+					yencOffset = _mm_xor_si128(_mm_set1_epi8(-42), 
+						_mm_slli_epi16(_mm_cvtsi32_si128((int)escFirst), 6)
+					);
+				} else
 #endif
-                {
-                    cmpEqA = _mm_and_si128(cmpEqA, _mm_set1_epi8(-64));
-                    cmpEqB = _mm_and_si128(cmpEqB, _mm_set1_epi8(-64));
-                    yencOffset = _mm_add_epi8(_mm_set1_epi8(-42), _mm_srli_si128(cmpEqB, 15));
+				{
+					cmpEqA = _mm_and_si128(cmpEqA, _mm_set1_epi8(-64));
+					cmpEqB = _mm_and_si128(cmpEqB, _mm_set1_epi8(-64));
+					yencOffset = _mm_add_epi8(_mm_set1_epi8(-42), _mm_srli_si128(cmpEqB, 15));
 #if defined(__SSSE3__) && !defined(__tune_btver1__)
-                    if(use_isa >= ISA_LEVEL_SSSE3)
-                        cmpEqB = _mm_alignr_epi8(cmpEqB, cmpEqA, 15);
-                    else
+					if(use_isa >= ISA_LEVEL_SSSE3)
+						cmpEqB = _mm_alignr_epi8(cmpEqB, cmpEqA, 15);
+					else
 #endif
-                        cmpEqB = _mm_or_si128(
-                            _mm_slli_si128(cmpEqB, 1),
-                            _mm_srli_si128(cmpEqA, 15)
-                        );
-                    cmpEqA = _mm_slli_si128(cmpEqA, 1);
-                    dataA = _mm_add_epi8(dataA, cmpEqA);
-                    dataB = _mm_add_epi8(dataB, cmpEqB);
-                }
-            }
-            // subtract 64 from first element if escFirst == 1
+						cmpEqB = _mm_or_si128(
+							_mm_slli_si128(cmpEqB, 1),
+							_mm_srli_si128(cmpEqA, 15)
+						);
+					cmpEqA = _mm_slli_si128(cmpEqA, 1);
+					dataA = _mm_add_epi8(dataA, cmpEqA);
+					dataB = _mm_add_epi8(dataB, cmpEqB);
+				}
+			}
+			// subtract 64 from first element if escFirst == 1
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_AVX3) {
-                yencOffset = _mm_mask_add_epi8(_mm_set1_epi8(-42), (__mmask16)escFirst, _mm_set1_epi8(-42), _mm_set1_epi8(-64));
-            }
+			if(use_isa >= ISA_LEVEL_AVX3) {
+				yencOffset = _mm_mask_add_epi8(_mm_set1_epi8(-42), (__mmask16)escFirst, _mm_set1_epi8(-42), _mm_set1_epi8(-64));
+			}
 #endif
-
-            // all that's left is to 'compress' the data (skip over masked chars)
+			
+			// all that's left is to 'compress' the data (skip over masked chars)
 #ifdef __SSSE3__
-            if(use_isa >= ISA_LEVEL_SSSE3) {
+			if(use_isa >= ISA_LEVEL_SSSE3) {
 # if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__POPCNT__)
-                if(use_isa >= ISA_LEVEL_VBMI2) {
-                    _mm_mask_compressstoreu_epi8(p, KNOT16(mask), dataA);
-                    p -= popcnt32(mask & 0xffff);
-                    _mm_mask_compressstoreu_epi8(p+XMM_SIZE, KNOT16(mask>>16), dataB);
-                    p -= popcnt32(mask>>16);
-                    p += XMM_SIZE*2;
-                } else
+				if(use_isa >= ISA_LEVEL_VBMI2) {
+					COMPRESS_STORE(p, KNOT16(mask), dataA);
+					p -= popcnt32(mask & 0xffff);
+					COMPRESS_STORE(p+XMM_SIZE, KNOT16(mask>>16), dataB);
+					p -= popcnt32(mask>>16);
+					p += XMM_SIZE*2;
+				} else
 # endif
-                {
-
-                    dataA = _mm_shuffle_epi8(dataA, _mm_load_si128((__m128i*)(lookups->compact + (mask&0x7fff))));
-                    STOREU_XMM(p, dataA);
-
-                    dataB = _mm_shuffle_epi8(dataB, _mm_load_si128((__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0))));
-
+				{
+					
+					dataA = _mm_shuffle_epi8(dataA, _mm_load_si128((__m128i*)(lookups->compact + (mask&0x7fff))));
+					STOREU_XMM(p, dataA);
+					
+					dataB = _mm_shuffle_epi8(dataB, _mm_load_si128((__m128i*)((char*)lookups->compact + ((mask >> 12) & 0x7fff0))));
+					
 # if defined(__POPCNT__) && !defined(__tune_btver1__)
-                    if(use_isa & ISA_FEATURE_POPCNT) {
-                        p -= popcnt32(mask & 0xffff);
-                        STOREU_XMM(p+XMM_SIZE, dataB);
-                        p -= popcnt32(mask & 0xffff0000);
-                        p += XMM_SIZE*2;
-                    } else
+					if(use_isa & ISA_FEATURE_POPCNT) {
+						p -= popcnt32(mask & 0xffff);
+						STOREU_XMM(p+XMM_SIZE, dataB);
+						p -= popcnt32(mask & 0xffff0000);
+						p += XMM_SIZE*2;
+					} else
 # endif
-                    {
-                        p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
-                        STOREU_XMM(p, dataB);
-                        mask >>= 16;
-                        p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
-                    }
-                }
-            } else
+					{
+						p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
+						STOREU_XMM(p, dataB);
+						mask >>= 16;
+						p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
+					}
+				}
+			} else
 #endif
-            {
-                dataA = sse2_compact_vect<use_isa>(mask & 0xffff, dataA);
-                STOREU_XMM(p, dataA);
-                p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
-                mask >>= 16;
-                dataB = sse2_compact_vect<use_isa>(mask, dataB);
-                STOREU_XMM(p, dataB);
-                p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
-            }
+			{
+				dataA = sse2_compact_vect<use_isa>(mask & 0xffff, dataA);
+				STOREU_XMM(p, dataA);
+				p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
+				mask >>= 16;
+				dataB = sse2_compact_vect<use_isa>(mask, dataB);
+				STOREU_XMM(p, dataB);
+				p += lookups->BitsSetTable256inv[mask & 0xff] + lookups->BitsSetTable256inv[(mask >> 8) & 0xff];
+			}
 #undef LOAD_HALVES
-        } else {
-            if(_USING_BLEND_ADD)
-                dataA = _mm_add_epi8(oDataA, yencOffset);
-            dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
-
-            STOREU_XMM(p, dataA);
-            STOREU_XMM(p+XMM_SIZE, dataB);
-            p += XMM_SIZE*2;
-            escFirst = 0;
-            yencOffset = _mm_set1_epi8(-42);
-        }
-    }
-    _escFirst = (unsigned char)escFirst;
-    if(isRaw) {
-        if(len != 0) { // have to gone through at least one loop cycle
-            if(src[i-2] == '\r' && src[i-1] == '\n' && src[i] == '.')
-                _nextMask = 1;
-            else if(src[i-1] == '\r' && src[i] == '\n' && src[i+1] == '.')
-                _nextMask = 2;
-            else
-                _nextMask = 0;
-        }
-    } else
-        _nextMask = 0;
+		} else {
+			if(_USING_BLEND_ADD)
+				dataA = _mm_add_epi8(oDataA, yencOffset);
+			dataB = _mm_add_epi8(oDataB, _mm_set1_epi8(-42));
+			
+			STOREU_XMM(p, dataA);
+			STOREU_XMM(p+XMM_SIZE, dataB);
+			p += XMM_SIZE*2;
+			escFirst = 0;
+			yencOffset = _mm_set1_epi8(-42);
+		}
+	}
+	_escFirst = (unsigned char)escFirst;
 }
+} // namespace
 #endif
diff --git a/src/yencode/decoder_ssse3.cc b/src/yencode/decoder_ssse3.cc
index e43a74a..25fd671 100644
--- a/src/yencode/decoder_ssse3.cc
+++ b/src/yencode/decoder_ssse3.cc
@@ -1,22 +1,18 @@
 #include "common.h"
 
-#ifdef __SSSE3__
 #include "decoder_common.h"
+#ifdef __SSSE3__
 #include "decoder_sse_base.h"
-void decoder_set_ssse3_funcs() {
-    decoder_sse_init();
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSSE3> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSSE3> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSSE3> >;
-    _decode_simd_level = ISA_LEVEL_SSSE3;
+void RapidYenc::decoder_set_ssse3_funcs() {
+	decoder_sse_init(lookups);
+	decoder_init_lut(lookups->compact);
+	_do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_SSSE3> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_SSSE3> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_SSSE3> >;
+	_decode_isa = ISA_LEVEL_SSSE3;
 }
 #else
-
-void decoder_set_sse2_funcs();
-
-void decoder_set_ssse3_funcs() {
-    decoder_set_sse2_funcs();
+void RapidYenc::decoder_set_ssse3_funcs() {
+	decoder_set_sse2_funcs();
 }
-
 #endif
diff --git a/src/yencode/decoder_vbmi2.cc b/src/yencode/decoder_vbmi2.cc
index 1977102..53682ad 100644
--- a/src/yencode/decoder_vbmi2.cc
+++ b/src/yencode/decoder_vbmi2.cc
@@ -1,34 +1,32 @@
 #include "common.h"
+# include "decoder_common.h"
+
+#if !defined(__EVEX512__) && (defined(__AVX10_1__) || defined(__EVEX256__)) && defined(__AVX512VL__) && defined(__AVX512VBMI2__) && defined(__AVX512BW__)
+const bool RapidYenc::decoder_has_avx10 = true;
+#else
+const bool RapidYenc::decoder_has_avx10 = false;
+#endif
 
 #if defined(__AVX512VL__) && defined(__AVX512VBMI2__) && defined(__AVX512BW__)
-# include "decoder_common.h"
 # ifndef YENC_DISABLE_AVX256
 #  include "decoder_avx2_base.h"
-void decoder_set_vbmi2_funcs() {
-    ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
-    // TODO: consider removing compact LUT
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_LEVEL_VBMI2> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_LEVEL_VBMI2> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_LEVEL_VBMI2> >;
-    _decode_simd_level = ISA_LEVEL_VBMI2;
+void RapidYenc::decoder_set_vbmi2_funcs() {
+	_do_decode = &do_decode_simd<false, false, sizeof(__m256i)*2, do_decode_avx2<false, false, ISA_LEVEL_VBMI2> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m256i)*2, do_decode_avx2<true, false, ISA_LEVEL_VBMI2> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m256i)*2, do_decode_avx2<true, true, ISA_LEVEL_VBMI2> >;
+	_decode_isa = ISA_LEVEL_VBMI2;
 }
 # else
 #  include "decoder_sse_base.h"
-void decoder_set_vbmi2_funcs() {
-    decoder_sse_init();
-    decoder_init_lut(lookups->eqFix, lookups->compact);
-    _do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_VBMI2> >;
-    _do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_VBMI2> >;
-    _do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_VBMI2> >;
-    _decode_simd_level = ISA_LEVEL_VBMI2;
+void RapidYenc::decoder_set_vbmi2_funcs() {
+	_do_decode = &do_decode_simd<false, false, sizeof(__m128i)*2, do_decode_sse<false, false, ISA_LEVEL_VBMI2> >;
+	_do_decode_raw = &do_decode_simd<true, false, sizeof(__m128i)*2, do_decode_sse<true, false, ISA_LEVEL_VBMI2> >;
+	_do_decode_end_raw = &do_decode_simd<true, true, sizeof(__m128i)*2, do_decode_sse<true, true, ISA_LEVEL_VBMI2> >;
+	_decode_isa = ISA_LEVEL_VBMI2;
 }
 # endif
 #else
-
-void decoder_set_avx2_funcs();
-
-void decoder_set_vbmi2_funcs() {
-    decoder_set_avx2_funcs();
+void RapidYenc::decoder_set_vbmi2_funcs() {
+	decoder_set_avx2_funcs();
 }
 #endif
diff --git a/src/yencode/encoder.cc b/src/yencode/encoder.cc
index 329b3b4..b0642fb 100644
--- a/src/yencode/encoder.cc
+++ b/src/yencode/encoder.cc
@@ -2,181 +2,197 @@
 #include "encoder_common.h"
 #include "encoder.h"
 
-size_t do_encode_generic(int line_size, int *colOffset, const unsigned char *HEDLEY_RESTRICT src,
-                         unsigned char *HEDLEY_RESTRICT dest, size_t len, int doEnd) {
-    unsigned char *es = (unsigned char *) src + len;
-    unsigned char *p = dest; // destination pointer
-    long i = -(long) len; // input position
-    unsigned char c, escaped; // input character; escaped input character
-    int col = *colOffset;
 
-    if (col == 0) {
-        c = es[i++];
-        if (escapedLUT[c]) {
-            memcpy(p, &escapedLUT[c], sizeof(uint16_t));
-            p += 2;
-            col = 2;
-        } else {
-            *(p++) = c + 42;
-            col = 1;
-        }
-    }
-    while (i < 0) {
-        // main line
-        unsigned char *sp = NULL;
-        while (i < -1 - 8 && line_size - col - 1 > 8) {
-            // 8 cycle unrolled version
-            sp = p;
-#define DO_THING(n) \
-                c = es[i+n], escaped = escapeLUT[c]; \
-                if (escaped) \
-                    *(p++) = escaped; \
-                else { \
-                    memcpy(p, &escapedLUT[c], sizeof(uint16_t)); \
-                    p += 2; \
-                }
-            DO_THING(0);
-            DO_THING(1);
-            DO_THING(2);
-            DO_THING(3);
-            DO_THING(4);
-            DO_THING(5);
-            DO_THING(6);
-            DO_THING(7);
-
-            i += 8;
-            col += (int) (p - sp);
-        }
-        if (sp && col >= line_size - 1) {
-            // TODO: consider revert optimisation from SIMD code
-            // we overflowed - need to revert and use slower method :(
-            col -= (int) (p - sp);
-            p = sp;
-            i -= 8;
-        }
-        // handle remaining chars
-        while (col < line_size - 1) {
-            c = es[i++], escaped = escapeLUT[c];
-            if (escaped) {
-                *(p++) = escaped;
-                col++;
-            } else {
-                memcpy(p, &escapedLUT[c], sizeof(uint16_t));
-                p += 2;
-                col += 2;
-            }
-            /* experimental branchless version
-            *p = '=';
-            c = (es[i++] + 42) & 0xFF;
-            int cond = (c=='\0' || c=='=' || c=='\r' || c=='\n');
-            *(p+cond) = c + (cond << 6);
-            p += 1+cond;
-            col += 1+cond;
-            */
-            if (i >= 0) goto end;
-        }
-
-        // last line char
-        if (col <
-            line_size) { // this can only be false if the last character was an escape sequence (or line_size is horribly small), in which case, we don't need to handle space/tab cases
-            c = es[i++];
-            if (escapedLUT[c] && c != '.' - 42) {
-                memcpy(p, &escapedLUT[c], sizeof(uint16_t));
-                p += 2;
-            } else {
-                *(p++) = c + 42;
-            }
-        }
-
-        if (i >= 0) break;
-
-        c = es[i++];
-        if (escapedLUT[c]) {
-            uint32_t w = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t) escapedLUT[c]);
-            memcpy(p, &w, sizeof(w));
-            p += 4;
-            col = 2;
-        } else {
-            // another option may be to just write the EOL and let the first char be handled by the faster methods above, but it appears that writing the extra byte here is generally faster...
-            uint32_t w = UINT32_PACK('\r', '\n', (uint32_t)(c + 42), 0);
-            memcpy(p, &w, sizeof(w));
-            p += 3;
-            col = 1;
-        }
-    }
-
-    end:
-    if (doEnd) {
-        // special case: if the last character is a space/tab, it needs to be escaped as it's the final character on the line
-        unsigned char lc = *(p - 1);
-        if (lc == '\t' || lc == ' ') {
-            *(p - 1) = '=';
-            *p = lc + 64;
-            p++;
-            col++;
-        }
-    }
-    *colOffset = col;
-    return p - dest;
+// lookup tables for scalar processing
+#define _B1(n) _B(n), _B(n+1), _B(n+2), _B(n+3)
+#define _B2(n) _B1(n), _B1(n+4), _B1(n+8), _B1(n+12)
+#define _B3(n) _B2(n), _B2(n+16), _B2(n+32), _B2(n+48)
+#define _BX _B3(0), _B3(64), _B3(128), _B3(192)
+
+const unsigned char RapidYenc::escapeLUT[256] = { // whether or not the character is critical
+#define _B(n) ((n == 214 || n == '\r'+214 || n == '\n'+214 || n == '='-42) ? 0 : (n+42) & 0xff)
+	_BX
+#undef _B
+};
+const uint16_t RapidYenc::escapedLUT[256] = { // escaped sequences for characters that need escaping
+#define _B(n) ((n == 214 || n == 214+'\r' || n == 214+'\n' || n == '='-42 || n == 214+'\t' || n == 214+' ' || n == '.'-42) ? UINT16_PACK('=', ((n+42+64)&0xff)) : 0)
+	_BX
+#undef _B
+};
+
+#undef _B1
+#undef _B2
+#undef _B3
+#undef _BX
+
+
+
+size_t RapidYenc::do_encode_generic(int line_size, int* colOffset, const unsigned char* HEDLEY_RESTRICT src, unsigned char* HEDLEY_RESTRICT dest, size_t len, int doEnd) {
+	unsigned char* es = (unsigned char*)src + len;
+	unsigned char *p = dest; // destination pointer
+	long i = -(long)len; // input position
+	unsigned char c, escaped; // input character; escaped input character
+	int col = *colOffset;
+	
+	if (col == 0) {
+		c = es[i++];
+		if (RapidYenc::escapedLUT[c]) {
+			memcpy(p, &RapidYenc::escapedLUT[c], sizeof(uint16_t));
+			p += 2;
+			col = 2;
+		} else {
+			*(p++) = c + 42;
+			col = 1;
+		}
+	}
+	while(i < 0) {
+		// main line
+		unsigned char* sp = NULL;
+		while (i < -1-8 && line_size-col-1 > 8) {
+			// 8 cycle unrolled version
+			sp = p;
+			#define DO_THING(n) \
+				c = es[i+n], escaped = RapidYenc::escapeLUT[c]; \
+				if (escaped) \
+					*(p++) = escaped; \
+				else { \
+					memcpy(p, &RapidYenc::escapedLUT[c], sizeof(uint16_t)); \
+					p += 2; \
+				}
+			DO_THING(0);
+			DO_THING(1);
+			DO_THING(2);
+			DO_THING(3);
+			DO_THING(4);
+			DO_THING(5);
+			DO_THING(6);
+			DO_THING(7);
+			
+			i += 8;
+			col += (int)(p - sp);
+		}
+		if(sp && col >= line_size-1) {
+			// TODO: consider revert optimisation from SIMD code
+			// we overflowed - need to revert and use slower method :(
+			col -= (int)(p - sp);
+			p = sp;
+			i -= 8;
+		}
+		// handle remaining chars
+		while(col < line_size-1) {
+			c = es[i++], escaped = RapidYenc::escapeLUT[c];
+			if (escaped) {
+				*(p++) = escaped;
+				col++;
+			}
+			else {
+				memcpy(p, &RapidYenc::escapedLUT[c], sizeof(uint16_t));
+				p += 2;
+				col += 2;
+			}
+			/* experimental branchless version 
+			*p = '=';
+			c = (es[i++] + 42) & 0xFF;
+			int cond = (c=='\0' || c=='=' || c=='\r' || c=='\n');
+			*(p+cond) = c + (cond << 6);
+			p += 1+cond;
+			col += 1+cond;
+			*/
+			if (i >= 0) goto end;
+		}
+		
+		// last line char
+		if(col < line_size) { // this can only be false if the last character was an escape sequence (or line_size is horribly small), in which case, we don't need to handle space/tab cases
+			c = es[i++];
+			if (RapidYenc::escapedLUT[c] && c != '.'-42) {
+				memcpy(p, &RapidYenc::escapedLUT[c], sizeof(uint16_t));
+				p += 2;
+			} else {
+				*(p++) = c + 42;
+			}
+		}
+		
+		if (i >= 0) break;
+		
+		c = es[i++];
+		if (RapidYenc::escapedLUT[c]) {
+			uint32_t w = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)RapidYenc::escapedLUT[c]);
+			memcpy(p, &w, sizeof(w));
+			p += 4;
+			col = 2;
+		} else {
+			// another option may be to just write the EOL and let the first char be handled by the faster methods above, but it appears that writing the extra byte here is generally faster...
+			uint32_t w = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+			memcpy(p, &w, sizeof(w));
+			p += 3;
+			col = 1;
+		}
+	}
+	
+	end:
+	if(doEnd) {
+		// special case: if the last character is a space/tab, it needs to be escaped as it's the final character on the line
+		unsigned char lc = *(p-1);
+		if(lc == '\t' || lc == ' ') {
+			*(p-1) = '=';
+			*p = lc+64;
+			p++;
+			col++;
+		}
+	}
+	*colOffset = col;
+	return p - dest;
 }
 
 
-extern "C" {
-size_t (*_do_encode)(int, int *, const unsigned char * HEDLEY_RESTRICT, unsigned char * HEDLEY_RESTRICT, size_t,
-                     int) = &do_encode_generic;
+namespace RapidYenc {
+	size_t (*_do_encode)(int, int*, const unsigned char* HEDLEY_RESTRICT, unsigned char* HEDLEY_RESTRICT, size_t, int) = &do_encode_generic;
+	int _encode_isa = ISA_GENERIC;
 }
 
-void encoder_sse2_init();
-
-void encoder_ssse3_init();
-
-void encoder_avx_init();
-
-void encoder_avx2_init();
-
-void encoder_vbmi2_init();
-
-void encoder_neon_init();
-
-#if defined(PLATFORM_X86) && defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE != 0
+#if defined(PLATFORM_X86) && defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE!=0
 # if defined(__AVX2__) && !defined(YENC_DISABLE_AVX256)
 #  include "encoder_avx_base.h"
 static inline void encoder_native_init() {
-    _do_encode = &do_encode_simd< do_encode_avx2<ISA_NATIVE> >;
-    encoder_avx2_lut<ISA_NATIVE>();
+	RapidYenc::_do_encode = &do_encode_simd< RapidYenc::do_encode_avx2<ISA_NATIVE> >;
+	encoder_avx2_lut<ISA_NATIVE>();
+	RapidYenc::_encode_isa = ISA_NATIVE;
 }
 # else
 #  include "encoder_sse_base.h"
 static inline void encoder_native_init() {
-    _do_encode = &do_encode_simd< do_encode_sse<ISA_NATIVE> >;
-    encoder_sse_lut<ISA_NATIVE>();
+	RapidYenc::_do_encode = &do_encode_simd< RapidYenc::do_encode_sse<ISA_NATIVE> >;
+	encoder_sse_lut<ISA_NATIVE>();
+	RapidYenc::_encode_isa = ISA_NATIVE;
 }
 # endif
 #endif
 
 
-void encoder_init() {
+void RapidYenc::encoder_init() {
 #ifdef PLATFORM_X86
 # if defined(YENC_BUILD_NATIVE) && YENC_BUILD_NATIVE!=0
-    encoder_native_init();
+	encoder_native_init();
 # else
-    int use_isa = cpu_supports_isa();
-    if(use_isa >= ISA_LEVEL_VBMI2) {
-        encoder_vbmi2_init();
-    } else if(use_isa >= ISA_LEVEL_AVX2) {
-        encoder_avx2_init();
-    } else if(use_isa >= ISA_LEVEL_AVX) {
-        encoder_avx_init();
-    } else if(use_isa >= ISA_LEVEL_SSSE3) {
-        encoder_ssse3_init();
-    } else {
-        encoder_sse2_init();
-    }
+	int use_isa = cpu_supports_isa();
+	if(use_isa >= ISA_LEVEL_VBMI2 && (encoder_has_avx10 || (use_isa & ISA_FEATURE_EVEX512)))
+		encoder_vbmi2_init();
+	else if(use_isa >= ISA_LEVEL_AVX2)
+		encoder_avx2_init();
+	else if(use_isa >= ISA_LEVEL_AVX)
+		encoder_avx_init();
+	else if(use_isa >= ISA_LEVEL_SSSE3)
+		encoder_ssse3_init();
+	else
+		encoder_sse2_init();
 # endif
 #endif
 #ifdef PLATFORM_ARM
-    if(cpu_supports_neon()) {
-        encoder_neon_init();
-    }
+	if(cpu_supports_neon())
+		encoder_neon_init();
+#endif
+#ifdef __riscv
+	if(cpu_supports_rvv())
+		encoder_rvv_init();
 #endif
 }
diff --git a/src/yencode/encoder.h b/src/yencode/encoder.h
index 4301fd8..2862a91 100644
--- a/src/yencode/encoder.h
+++ b/src/yencode/encoder.h
@@ -1,22 +1,23 @@
 #ifndef __YENC_ENCODER_H
 #define __YENC_ENCODER_H
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
 #include "hedley.h"
 
-extern size_t
-(*_do_encode)(int, int *, const unsigned char * HEDLEY_RESTRICT, unsigned char * HEDLEY_RESTRICT, size_t, int);
+namespace RapidYenc {
+
 
-#define do_encode (*_do_encode)
 
+extern size_t (*_do_encode)(int, int*, const unsigned char* HEDLEY_RESTRICT, unsigned char* HEDLEY_RESTRICT, size_t, int);
+extern int _encode_isa;
+static inline size_t encode(int line_size, int* colOffset, const void* HEDLEY_RESTRICT src, void* HEDLEY_RESTRICT dest, size_t len, int doEnd) {
+	return (*_do_encode)(line_size, colOffset, (const unsigned char* HEDLEY_RESTRICT)src, (unsigned char*)dest, len, doEnd);
+}
 void encoder_init();
+static inline int encode_isa_level() {
+	return _encode_isa;
+}
+
 
 
-#ifdef __cplusplus
 }
 #endif
-#endif
diff --git a/src/yencode/encoder_avx.cc b/src/yencode/encoder_avx.cc
index 0750477..929c953 100644
--- a/src/yencode/encoder_avx.cc
+++ b/src/yencode/encoder_avx.cc
@@ -1,19 +1,17 @@
 #include "common.h"
+#include "encoder_common.h"
 
 #if defined(__AVX__) && defined(__POPCNT__)
 #include "encoder_sse_base.h"
 
-void encoder_avx_init() {
-    _do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSE4_POPCNT> >;
-    encoder_sse_lut<ISA_LEVEL_SSE4_POPCNT>();
+void RapidYenc::encoder_avx_init() {
+	_do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSE4_POPCNT> >;
+	encoder_sse_lut<ISA_LEVEL_SSE4_POPCNT>();
+	_encode_isa = ISA_LEVEL_AVX;
 }
 #else
-
-void encoder_ssse3_init();
-
-void encoder_avx_init() {
-    encoder_ssse3_init();
+void RapidYenc::encoder_avx_init() {
+	encoder_ssse3_init();
 }
-
 #endif
 
diff --git a/src/yencode/encoder_avx2.cc b/src/yencode/encoder_avx2.cc
index 109ae60..10b9e30 100644
--- a/src/yencode/encoder_avx2.cc
+++ b/src/yencode/encoder_avx2.cc
@@ -1,19 +1,17 @@
 #include "common.h"
+#include "encoder_common.h"
 
 #if defined(__AVX2__) && !defined(YENC_DISABLE_AVX256)
 #include "encoder_avx_base.h"
 
-void encoder_avx2_init() {
-    _do_encode = &do_encode_simd< do_encode_avx2<ISA_LEVEL_AVX2> >;
-    encoder_avx2_lut<ISA_LEVEL_AVX2>();
+void RapidYenc::encoder_avx2_init() {
+	_do_encode = &do_encode_simd< do_encode_avx2<ISA_LEVEL_AVX2> >;
+	encoder_avx2_lut<ISA_LEVEL_AVX2>();
+	_encode_isa = ISA_LEVEL_AVX2;
 }
 #else
-
-void encoder_avx_init();
-
-void encoder_avx2_init() {
-    encoder_avx_init();
+void RapidYenc::encoder_avx2_init() {
+	encoder_avx_init();
 }
-
 #endif
 
diff --git a/src/yencode/encoder_avx_base.h b/src/yencode/encoder_avx_base.h
index 6bf460a..d663f92 100644
--- a/src/yencode/encoder_avx_base.h
+++ b/src/yencode/encoder_avx_base.h
@@ -1,6 +1,5 @@
 // can't seem to make this worth it
 #include "common.h"
-
 #ifdef __AVX2__
 
 #include "encoder.h"
@@ -15,551 +14,562 @@
 
 #pragma pack(16)
 static struct {
-    uint32_t eolLastChar[256];
-    /*align32*/ __m256i shufExpand[65536]; // huge 2MB table
-    /*align32*/ int8_t expandMergemix[33*2*32]; // not used in AVX3
+	uint32_t eolLastChar[256];
+	/*align32*/ __m256i shufExpand[65536]; // huge 2MB table
+	/*align32*/ int8_t expandMergemix[33*2*32]; // not used in AVX3
 } * HEDLEY_RESTRICT lookupsAVX2;
 static struct {
-    uint32_t eolLastChar[256];
-    uint32_t expand[65536]; // biggish 256KB table (but still smaller than the 2MB table)
+	uint32_t eolLastChar[256];
+	uint32_t expand[65536]; // biggish 256KB table (but still smaller than the 2MB table)
 } * HEDLEY_RESTRICT lookupsVBMI2;
 #pragma pack()
 
 static inline void fill_eolLastChar(uint32_t* table) {
-    for(int n=0; n<256; n++) {
-        table[n] = ((n == 214+'\t' || n == 214+' ' || n == 214+'\0' || n == 214+'\n' || n == 214+'\r' || n == '='-42) ? (((n+42+64)&0xff)<<8)+0x0a0d003d : ((n+42)&0xff)+0x0a0d00);
-    }
+	for(int n=0; n<256; n++) {
+		table[n] = ((n == 214+'\t' || n == 214+' ' || n == 214+'\0' || n == 214+'\n' || n == 214+'\r' || n == '='-42) ? (((n+42+64)&0xff)<<8)+0x0a0d003d : ((n+42)&0xff)+0x0a0d00);
+	}
 }
 
 template<enum YEncDecIsaLevel use_isa>
 static void encoder_avx2_lut() {
-    if(use_isa >= ISA_LEVEL_VBMI2) {
-        ALIGN_ALLOC(lookupsVBMI2, sizeof(*lookupsVBMI2), 32);
-        fill_eolLastChar(lookupsVBMI2->eolLastChar);
-        for(int i=0; i<65536; i++) {
-            int k = i;
-            uint32_t expand = 0;
-            int p = 0;
-            for(int j=0; j<16; j++) {
-                if(k & 1) {
-                    p++;
-                }
-                expand |= 1<<(j+p);
-                k >>= 1;
-            }
-            lookupsVBMI2->expand[i] = expand;
-        }
-    } else {
-        ALIGN_ALLOC(lookupsAVX2, sizeof(*lookupsAVX2), 32);
-        fill_eolLastChar(lookupsAVX2->eolLastChar);
-        for(int i=0; i<65536; i++) {
-            int k = i;
-            uint8_t* res = (uint8_t*)(lookupsAVX2->shufExpand + i);
-            int p = 0;
-            for(int j=0; j<16; j++) {
-                if(k & 1) {
-                    res[j+p] = 0xff;
-                    p++;
-                }
-                res[j+p] = j;
-                k >>= 1;
-            }
-            for(; p<16; p++)
-                res[16+p] = 0x40; // arbitrary value (top bit cannot be set)
-        }
-        for(int i=0; i<33; i++) {
-            int n = (i == 32 ? 32 : 31-i);
-            for(int j=0; j<32; j++) {
-                lookupsAVX2->expandMergemix[i*64 + j] = (n>=j ? -1 : 0);
-                lookupsAVX2->expandMergemix[i*64 + j + 32] = ('='*(n==j) + 64*(n==j-1) + 42*(n!=j));
-            }
-        }
-    }
+	if(use_isa >= ISA_LEVEL_VBMI2) {
+		ALIGN_ALLOC(lookupsVBMI2, sizeof(*lookupsVBMI2), 32);
+		fill_eolLastChar(lookupsVBMI2->eolLastChar);
+		for(int i=0; i<65536; i++) {
+			int k = i;
+			uint32_t expand = 0;
+			int p = 0;
+			for(int j=0; j<16; j++) {
+				if(k & 1) {
+					p++;
+				}
+				expand |= 1<<(j+p);
+				k >>= 1;
+			}
+			lookupsVBMI2->expand[i] = expand;
+		}
+	} else {
+		ALIGN_ALLOC(lookupsAVX2, sizeof(*lookupsAVX2), 32);
+		fill_eolLastChar(lookupsAVX2->eolLastChar);
+		for(int i=0; i<65536; i++) {
+			int k = i;
+			uint8_t* res = (uint8_t*)(lookupsAVX2->shufExpand + i);
+			int p = 0;
+			for(int j=0; j<16; j++) {
+				if(k & 1) {
+					res[j+p] = 0xff;
+					p++;
+				}
+				res[j+p] = j;
+				k >>= 1;
+			}
+			for(; p<16; p++)
+				res[16+p] = 0x40; // arbitrary value (top bit cannot be set)
+		}
+		for(int i=0; i<33; i++) {
+			int n = (i == 32 ? 32 : 31-i);
+			for(int j=0; j<32; j++) {
+				lookupsAVX2->expandMergemix[i*64 + j] = (n>=j ? -1 : 0);
+				lookupsAVX2->expandMergemix[i*64 + j + 32] = ('='*(n==j) + 64*(n==j-1) + 42*(n!=j));
+			}
+		}
+	}
 }
 
+namespace RapidYenc {
+
 template<enum YEncDecIsaLevel use_isa>
 HEDLEY_ALWAYS_INLINE void do_encode_avx2(int line_size, int* colOffset, const uint8_t* HEDLEY_RESTRICT srcEnd, uint8_t* HEDLEY_RESTRICT& dest, size_t& len) {
-    // offset position to enable simpler loop condition checking
-    const int INPUT_OFFSET = YMM_SIZE*4 + 1 -1; // -1 to change <= to <
-    if(len <= INPUT_OFFSET || line_size < 16) return;
-
-    uint8_t *p = dest; // destination pointer
-    intptr_t i = -(intptr_t)len; // input position
-    intptr_t lineSizeOffset = -line_size +1; // -1 because we want to stop one char before the end to handle the last char differently
-    intptr_t col = *colOffset + lineSizeOffset;
-
-    i += INPUT_OFFSET;
-    const uint8_t* es = srcEnd - INPUT_OFFSET;
-
+	// offset position to enable simpler loop condition checking
+	const int INPUT_OFFSET = YMM_SIZE*4 + 1 -1; // -1 to change <= to <
+	if(len <= INPUT_OFFSET || line_size < 16) return;
+	
+	uint8_t *p = dest; // destination pointer
+	intptr_t i = -(intptr_t)len; // input position
+	intptr_t lineSizeOffset = -line_size +1; // -1 because we want to stop one char before the end to handle the last char differently
+	intptr_t col = *colOffset + lineSizeOffset;
+	
+	i += INPUT_OFFSET;
+	const uint8_t* es = srcEnd - INPUT_OFFSET;
+	
 #if !defined(__tune_bdver4__) && !defined(__tune_znver1__)
-    // always process at least one byte to prevent underflow when doing a read with -1 offset
-    if(col < 0 && col != -line_size+1) {
-        // not the first/last character of a line
-        uint8_t c = es[i++];
-        if(HEDLEY_UNLIKELY(c == 214 || c == '\n'+214 || c == '\r'+214 || c == '='-42)) {
-            *(uint16_t*)p = 0x6a3d + (((uint16_t)c) << 8);
-            p += 2;
-            col += 2;
-        } else {
-            *p++ = c+42;
-            col++;
-        }
-    }
+	// always process at least one byte to prevent underflow when doing a read with -1 offset
+	if(col < 0 && col != -line_size+1) {
+		// not the first/last character of a line
+		uint8_t c = es[i++];
+		if(HEDLEY_UNLIKELY(c == 214 || c == '\n'+214 || c == '\r'+214 || c == '='-42)) {
+			*(uint16_t*)p = 0x6a3d + (((uint16_t)c) << 8);
+			p += 2;
+			col += 2;
+		} else {
+			*p++ = c+42;
+			col++;
+		}
+	}
 #endif
-
-    if(HEDLEY_UNLIKELY(col >= 0)) {
-        uint8_t c = es[i++];
-        if(col == 0) {
-            // last char
-            uint32_t eolChar = (use_isa >= ISA_LEVEL_VBMI2 ? lookupsVBMI2->eolLastChar[c] : lookupsAVX2->eolLastChar[c]);
-            *(uint32_t*)p = eolChar;
-            p += 3 + (uintptr_t)(eolChar>>27);
-            col = -line_size+1;
-        } else {
-            // line overflowed, insert a newline
-            if (LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
-                *(uint32_t*)p = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
-                p += 4;
-                col = 2-line_size + 1;
-            } else {
-                *(uint32_t*)p = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
-                p += 3;
-                col = 2-line_size;
-            }
-        }
-    }
-    if (HEDLEY_LIKELY(col == -line_size+1)) {
-        // first char of the line
-        uint8_t c = es[i++];
-        if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
-            *(uint16_t*)p = escapedLUT[c];
-            p += 2;
-            col += 2;
-        } else {
-            *(p++) = c + 42;
-            col += 1;
-        }
-    }
-    do {
-        __m256i dataA = _mm256_loadu_si256((__m256i *)(es + i));
-        __m256i dataB = _mm256_loadu_si256((__m256i *)(es + i) + 1);
-        i += YMM_SIZE*2;
-        // search for special chars
-        __m256i cmpA = _mm256_cmpeq_epi8(
-            _mm256_shuffle_epi8(_mm256_set_epi8(
-                '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
-                '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-            ), _mm256_abs_epi8(dataA)),
-            dataA
-        );
-        __m256i cmpB = _mm256_cmpeq_epi8(
-            _mm256_shuffle_epi8(_mm256_set_epi8(
-                '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
-                '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-            ), _mm256_abs_epi8(dataB)),
-            dataB
-        );
-
+	
+	if(HEDLEY_UNLIKELY(col >= 0)) {
+		uint8_t c = es[i++];
+		if(col == 0) {
+			// last char
+			uint32_t eolChar = (use_isa >= ISA_LEVEL_VBMI2 ? lookupsVBMI2->eolLastChar[c] : lookupsAVX2->eolLastChar[c]);
+			*(uint32_t*)p = eolChar;
+			p += 3 + (uintptr_t)(eolChar>>27);
+			col = -line_size+1;
+		} else {
+			// line overflowed, insert a newline
+			if (LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
+				*(uint32_t*)p = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
+				p += 4;
+				col = 2-line_size + 1;
+			} else {
+				*(uint32_t*)p = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+				p += 3;
+				col = 2-line_size;
+			}
+		}
+	}
+	if (HEDLEY_LIKELY(col == -line_size+1)) {
+		// first char of the line
+		uint8_t c = es[i++];
+		if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
+			*(uint16_t*)p = escapedLUT[c];
+			p += 2;
+			col += 2;
+		} else {
+			*(p++) = c + 42;
+			col += 1;
+		}
+	}
+	do {
+		__m256i dataA = _mm256_loadu_si256((__m256i *)(es + i));
+		__m256i dataB = _mm256_loadu_si256((__m256i *)(es + i) + 1);
+		i += YMM_SIZE*2;
+		// search for special chars
+		__m256i cmpA = _mm256_cmpeq_epi8(
+			_mm256_shuffle_epi8(_mm256_set_epi8(
+				'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
+				'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+			), _mm256_abs_epi8(dataA)),
+			dataA
+		);
+		__m256i cmpB = _mm256_cmpeq_epi8(
+			_mm256_shuffle_epi8(_mm256_set_epi8(
+				'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
+				'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+			), _mm256_abs_epi8(dataB)),
+			dataB
+		);
+		
 #if defined(__AVX512VL__)
-        if(use_isa >= ISA_LEVEL_AVX3) {
-            dataA = _mm256_add_epi8(dataA, _mm256_set1_epi8(42));
-            dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
-            dataB = _mm256_add_epi8(dataB, _mm256_set1_epi8(42));
-            dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
-        }
+		if(use_isa >= ISA_LEVEL_AVX3) {
+			dataA = _mm256_add_epi8(dataA, _mm256_set1_epi8(42));
+			dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
+			dataB = _mm256_add_epi8(dataB, _mm256_set1_epi8(42));
+			dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
+		}
 #endif
-
-        uint32_t maskA = (uint32_t)_mm256_movemask_epi8(cmpA);
-        uint32_t maskB = (uint32_t)_mm256_movemask_epi8(cmpB);
-        unsigned int maskBitsA = popcnt32(maskA);
-        unsigned int maskBitsB = popcnt32(maskB);
-        unsigned int outputBytesA = maskBitsA + YMM_SIZE;
-        unsigned int bitIndexA, bitIndexB;
-        if (LIKELIHOOD(0.170, (maskBitsA|maskBitsB) > 1)) {
-            _encode_loop_branch_slow:
-            unsigned int m1 = maskA & 0xffff, m3 = maskB & 0xffff;
-            unsigned int m2, m4;
-            __m256i data1A, data2A;
-            __m256i data1B, data2B;
-            __m256i shuf1A, shuf1B; // not set in VBMI2 path
-            __m256i shuf2A, shuf2B; // not set in VBMI2 path
-
+		
+		uint32_t maskA = (uint32_t)_mm256_movemask_epi8(cmpA);
+		uint32_t maskB = (uint32_t)_mm256_movemask_epi8(cmpB);
+		unsigned int maskBitsA = popcnt32(maskA);
+		unsigned int maskBitsB = popcnt32(maskB);
+		unsigned int outputBytesA = maskBitsA + YMM_SIZE;
+		unsigned int bitIndexA, bitIndexB;
+		if (LIKELIHOOD(0.170, (maskBitsA|maskBitsB) > 1)) {
+			_encode_loop_branch_slow:
+			unsigned int m1 = maskA & 0xffff, m3 = maskB & 0xffff;
+			unsigned int m2, m4;
+			__m256i data1A, data2A;
+			__m256i data1B, data2B;
+			__m256i shuf1A, shuf1B; // not set in VBMI2 path
+			__m256i shuf2A, shuf2B; // not set in VBMI2 path
+			
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_VBMI2) {
-                m2 = maskA >> 16;
-                m4 = maskB >> 16;
-
-                /* alternative no-LUT strategy
-                uint64_t expandMaskA = ~_pdep_u64(~maskA, 0x5555555555555555); // expand bits, with bits set
-                expandMaskA = _pext_u64(expandMaskA^0x5555555555555555, expandMaskA);
-                */
-
-                data1A = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m1), dataA);
-                data2A = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m2), _mm256_castsi128_si256(
-                    _mm256_extracti128_si256(dataA, 1)
-                ));
-                data1B = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m3), dataB);
-                data2B = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m4), _mm256_castsi128_si256(
-                    _mm256_extracti128_si256(dataB, 1)
-                ));
-            } else
+			if(use_isa >= ISA_LEVEL_VBMI2) {
+				m2 = maskA >> 16;
+				m4 = maskB >> 16;
+				
+				/* alternative no-LUT strategy
+				uint64_t expandMaskA = ~_pdep_u64(~maskA, 0x5555555555555555); // expand bits, with bits set
+				expandMaskA = _pext_u64(expandMaskA^0x5555555555555555, expandMaskA);
+				*/
+				
+				data1A = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m1), dataA);
+				data2A = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m2), _mm256_castsi128_si256(
+					_mm256_extracti128_si256(dataA, 1)
+				));
+				data1B = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m3), dataB);
+				data2B = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KLOAD32(lookupsVBMI2->expand, m4), _mm256_castsi128_si256(
+					_mm256_extracti128_si256(dataB, 1)
+				));
+			} else
 #endif
-            {
-                if(use_isa < ISA_LEVEL_AVX3) {
-                    dataA = _mm256_add_epi8(dataA, _mm256_blendv_epi8(_mm256_set1_epi8(42), _mm256_set1_epi8(42+64), cmpA));
-                    dataB = _mm256_add_epi8(dataB, _mm256_blendv_epi8(_mm256_set1_epi8(42), _mm256_set1_epi8(42+64), cmpB));
-                }
-
-                m2 = (maskA >> 11) & 0x1fffe0;
-                m4 = (maskB >> 11) & 0x1fffe0;
-
-                // duplicate halves
-                data1A = _mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1);
-                data1B = _mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1);
-#if defined(__tune_znver2__) || defined(__tune_znver3__)
-                data2A = _mm256_permute2x128_si256(dataA, dataA, 0x11);
-                data2B = _mm256_permute2x128_si256(dataB, dataB, 0x11);
+			{
+				if(use_isa < ISA_LEVEL_AVX3) {
+					dataA = _mm256_add_epi8(dataA, _mm256_blendv_epi8(_mm256_set1_epi8(42), _mm256_set1_epi8(42+64), cmpA));
+					dataB = _mm256_add_epi8(dataB, _mm256_blendv_epi8(_mm256_set1_epi8(42), _mm256_set1_epi8(42+64), cmpB));
+				}
+				
+				m2 = (maskA >> 11) & 0x1fffe0;
+				m4 = (maskB >> 11) & 0x1fffe0;
+				
+				// duplicate halves
+				data1A = _mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1);
+				data1B = _mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1);
+#if defined(__tune_znver2__) || defined(__tune_znver3__) || defined(__tune_znver4__)
+				data2A = _mm256_permute2x128_si256(dataA, dataA, 0x11);
+				data2B = _mm256_permute2x128_si256(dataB, dataB, 0x11);
 #else
-                data2A = _mm256_permute4x64_epi64(dataA, 0xee);
-                data2B = _mm256_permute4x64_epi64(dataB, 0xee);
+				data2A = _mm256_permute4x64_epi64(dataA, 0xee);
+				data2B = _mm256_permute4x64_epi64(dataB, 0xee);
 #endif
-
-                shuf1A = _mm256_load_si256(lookupsAVX2->shufExpand + m1);
-                shuf2A = _mm256_load_si256((__m256i*)((char*)(lookupsAVX2->shufExpand) + m2));
-                shuf1B = _mm256_load_si256(lookupsAVX2->shufExpand + m3);
-                shuf2B = _mm256_load_si256((__m256i*)((char*)(lookupsAVX2->shufExpand) + m4));
-
-                // expand
-                data1A = _mm256_shuffle_epi8(data1A, shuf1A);
-                data2A = _mm256_shuffle_epi8(data2A, shuf2A);
-                data1B = _mm256_shuffle_epi8(data1B, shuf1B);
-                data2B = _mm256_shuffle_epi8(data2B, shuf2B);
-                // add in '='
-                data1A = _mm256_blendv_epi8(data1A, _mm256_set1_epi8('='), shuf1A);
-                data2A = _mm256_blendv_epi8(data2A, _mm256_set1_epi8('='), shuf2A);
-                data1B = _mm256_blendv_epi8(data1B, _mm256_set1_epi8('='), shuf1B);
-                data2B = _mm256_blendv_epi8(data2B, _mm256_set1_epi8('='), shuf2B);
-            }
-
-            unsigned int shuf1Len = popcnt32(m1) + 16;
-            unsigned int shuf3Len = popcnt32(m3) + 16;
-            _mm256_storeu_si256((__m256i*)p, data1A);
-            _mm256_storeu_si256((__m256i*)(p + shuf1Len), data2A);
-            _mm256_storeu_si256((__m256i*)(p + outputBytesA), data1B);
-            _mm256_storeu_si256((__m256i*)(p + outputBytesA + shuf3Len), data2B);
-            unsigned int outputBytes = YMM_SIZE + outputBytesA + maskBitsB;
-            p += outputBytes;
-            col += outputBytes;
-
-            if(col >= 0) {
-                // we overflowed - find correct position to revert back to
-                // this is perhaps sub-optimal on 32-bit, but who still uses that with AVX2?
-                uint64_t eqMask;
-                int shiftAmt = (int)(maskBitsB + YMM_SIZE -1 - col);
-                if(HEDLEY_UNLIKELY(shiftAmt < 0)) {
-                    uint32_t eqMask1, eqMask2;
+				
+				shuf1A = _mm256_load_si256(lookupsAVX2->shufExpand + m1);
+				shuf2A = _mm256_load_si256((__m256i*)((char*)(lookupsAVX2->shufExpand) + m2));
+				shuf1B = _mm256_load_si256(lookupsAVX2->shufExpand + m3);
+				shuf2B = _mm256_load_si256((__m256i*)((char*)(lookupsAVX2->shufExpand) + m4));
+				
+				// expand
+				data1A = _mm256_shuffle_epi8(data1A, shuf1A);
+				data2A = _mm256_shuffle_epi8(data2A, shuf2A);
+				data1B = _mm256_shuffle_epi8(data1B, shuf1B);
+				data2B = _mm256_shuffle_epi8(data2B, shuf2B);
+				// add in '='
+				data1A = _mm256_blendv_epi8(data1A, _mm256_set1_epi8('='), shuf1A);
+				data2A = _mm256_blendv_epi8(data2A, _mm256_set1_epi8('='), shuf2A);
+				data1B = _mm256_blendv_epi8(data1B, _mm256_set1_epi8('='), shuf1B);
+				data2B = _mm256_blendv_epi8(data2B, _mm256_set1_epi8('='), shuf2B);
+			}
+			
+			unsigned int shuf1Len = popcnt32(m1) + 16;
+			unsigned int shuf3Len = popcnt32(m3) + 16;
+			_mm256_storeu_si256((__m256i*)p, data1A);
+			_mm256_storeu_si256((__m256i*)(p + shuf1Len), data2A);
+			_mm256_storeu_si256((__m256i*)(p + outputBytesA), data1B);
+			_mm256_storeu_si256((__m256i*)(p + outputBytesA + shuf3Len), data2B);
+			unsigned int outputBytes = YMM_SIZE + outputBytesA + maskBitsB;
+			p += outputBytes;
+			col += outputBytes;
+			
+			if(col >= 0) {
+				// we overflowed - find correct position to revert back to
+				// this is perhaps sub-optimal on 32-bit, but who still uses that with AVX2?
+				uint64_t eqMask;
+				int shiftAmt = (int)(maskBitsB + YMM_SIZE -1 - col);
+				if(HEDLEY_UNLIKELY(shiftAmt < 0)) {
+					uint32_t eqMask1, eqMask2;
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(use_isa >= ISA_LEVEL_VBMI2) {
-                        eqMask1 = lookupsVBMI2->expand[m1];
-                        eqMask2 = lookupsVBMI2->expand[m2];
-                    } else
+					if(use_isa >= ISA_LEVEL_VBMI2) {
+						eqMask1 = lookupsVBMI2->expand[m1];
+						eqMask2 = lookupsVBMI2->expand[m2];
+					} else
 #endif
-                    {
-                        eqMask1 = (uint32_t)_mm256_movemask_epi8(shuf1A);
-                        eqMask2 = (uint32_t)_mm256_movemask_epi8(shuf2A);
-                    }
-                    eqMask = eqMask1 | ((uint64_t)eqMask2 << shuf1Len);
-                    if(use_isa < ISA_LEVEL_VBMI2)
-                        i += (uintptr_t)maskBitsB;
-                    else
-                        i -= YMM_SIZE;
-                    shiftAmt += outputBytesA;
-                } else {
-                    uint32_t eqMask3, eqMask4;
+					{
+						eqMask1 = (uint32_t)_mm256_movemask_epi8(shuf1A);
+						eqMask2 = (uint32_t)_mm256_movemask_epi8(shuf2A);
+					}
+					eqMask = eqMask1 | ((uint64_t)eqMask2 << shuf1Len);
+					if(use_isa < ISA_LEVEL_VBMI2)
+						i += (uintptr_t)maskBitsB;
+					else
+						i -= YMM_SIZE;
+					shiftAmt += outputBytesA;
+				} else {
+					uint32_t eqMask3, eqMask4;
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(use_isa >= ISA_LEVEL_VBMI2) {
-                        eqMask3 = lookupsVBMI2->expand[m3];
-                        eqMask4 = lookupsVBMI2->expand[m4];
-                    } else
+					if(use_isa >= ISA_LEVEL_VBMI2) {
+						eqMask3 = lookupsVBMI2->expand[m3];
+						eqMask4 = lookupsVBMI2->expand[m4];
+					} else
 #endif
-                    {
-                        eqMask3 = (uint32_t)_mm256_movemask_epi8(shuf1B);
-                        eqMask4 = (uint32_t)_mm256_movemask_epi8(shuf2B);
-                    }
-                    eqMask = eqMask3 | ((uint64_t)eqMask4 << shuf3Len);
-                }
-
+					{
+						eqMask3 = (uint32_t)_mm256_movemask_epi8(shuf1B);
+						eqMask4 = (uint32_t)_mm256_movemask_epi8(shuf2B);
+					}
+					eqMask = eqMask3 | ((uint64_t)eqMask4 << shuf3Len);
+				}
+				
 #if defined(__GNUC__) && defined(PLATFORM_AMD64)
-                if(use_isa >= ISA_LEVEL_VBMI2) {
-                    asm(
-                        "shrq $1, %[eqMask] \n"
-                        "shrq %%cl, %[eqMask] \n"
-                        "adcq %q[col], %q[p] \n"
-                        : [eqMask]"+r"(eqMask), [p]"+r"(p)
-                        : "c"(shiftAmt), [col]"r"(~col)
-                    );
-                    i -= _mm_popcnt_u64(eqMask);
-                } else
+				if(use_isa >= ISA_LEVEL_VBMI2) {
+					__asm__(
+						"shrq $1, %[eqMask] \n"
+						"shrq %%cl, %[eqMask] \n"
+						"adcq %q[col], %q[p] \n"
+						: [eqMask]"+r"(eqMask), [p]"+r"(p)
+						: "c"(shiftAmt), [col]"r"(~col)
+					);
+					i -= _mm_popcnt_u64(eqMask);
+				} else
 #endif
-                {
-                    eqMask >>= shiftAmt;
-                    unsigned int bitCount;
+				{
+					eqMask >>= shiftAmt;
+					unsigned int bitCount;
 #ifdef PLATFORM_AMD64
-                    bitCount = (unsigned int)_mm_popcnt_u64(eqMask);
+					bitCount = (unsigned int)_mm_popcnt_u64(eqMask);
 #else
-                    bitCount = popcnt32(eqMask & 0xffffffff) + popcnt32(eqMask >> 32);
+					bitCount = popcnt32(eqMask & 0xffffffff) + popcnt32(eqMask >> 32);
 #endif
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__AVX512BW__)
-                    if(use_isa >= ISA_LEVEL_VBMI2) {
-                        i -= bitCount;
-                        p -= col;
-                        if(LIKELIHOOD(0.98, (eqMask & 1) != 1))
-                            p--;
-                        else
-                            i++;
-                    } else
+					if(use_isa >= ISA_LEVEL_VBMI2) {
+						i -= bitCount;
+						p -= col;
+						if(LIKELIHOOD(0.98, (eqMask & 1) != 1))
+							p--;
+						else
+							i++;
+					} else
 #endif
-                    {
-                        i += bitCount;
-                        unsigned int revert = (unsigned int)(col + (eqMask & 1));
-                        p -= revert;
-                        i -= revert;
-                    }
-                }
-                goto _encode_eol_handle_pre;
-            }
-        } else {
-            //_encode_loop_branch_fast:
-            maskBitsB += YMM_SIZE;
+					{
+						i += bitCount;
+						unsigned int revert = (unsigned int)(col + (eqMask & 1));
+						p -= revert;
+						i -= revert;
+					}
+				}
+				goto _encode_eol_handle_pre;
+			}
+		} else {
+			//_encode_loop_branch_fast:
+			maskBitsB += YMM_SIZE;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_AVX3) {
+			if(use_isa >= ISA_LEVEL_AVX3) {
 # if defined(__AVX512VBMI2__)
-                if(use_isa >= ISA_LEVEL_VBMI2) {
-                    _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataA);
-                    dataA = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskA), dataA);
-                    _mm256_storeu_si256((__m256i*)p, dataA);
-                    p += outputBytesA;
-
-                    _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataB);
-                    dataB = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskB), dataB);
-                    _mm256_storeu_si256((__m256i*)p, dataB);
-                    p += maskBitsB;
-                } else
+				if(use_isa >= ISA_LEVEL_VBMI2) {
+					__m128i dataTop = _mm256_extracti128_si256(dataA, 1);
+					dataA = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskA), dataA);
+					_mm256_storeu_si256((__m256i*)p, dataA);
+					p[32] = _mm_extract_epi8(dataTop, 15);
+					p += outputBytesA;
+					
+					dataTop = _mm256_extracti128_si256(dataB, 1);
+					dataB = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskB), dataB);
+					_mm256_storeu_si256((__m256i*)p, dataB);
+					p[32] = _mm_extract_epi8(dataTop, 15);
+					p += maskBitsB;
+				} else
 # endif
-                {
-                    _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataA);
-                    dataA = _mm256_mask_alignr_epi8(dataA, (uint32_t)(-(int32_t)maskA), dataA, _mm256_permute4x64_epi64(dataA, _MM_SHUFFLE(1,0,3,2)), 15);
-                    dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
-                    _mm256_storeu_si256((__m256i*)p, dataA);
-                    p += outputBytesA;
-
-                    _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataB);
-                    dataB = _mm256_mask_alignr_epi8(dataB, (uint32_t)(-(int32_t)maskB), dataB, _mm256_permute4x64_epi64(dataB, _MM_SHUFFLE(1,0,3,2)), 15);
-                    dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8('='), 0xb8);
-                    _mm256_storeu_si256((__m256i*)p, dataB);
-                    p += maskBitsB;
-                }
-            } else
+				{
+					__m256i dataSwapped = _mm256_permute4x64_epi64(dataA, _MM_SHUFFLE(1,0,3,2));
+					dataA = _mm256_mask_alignr_epi8(dataA, (uint32_t)(-(int32_t)maskA), dataA, dataSwapped, 15);
+					dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
+					_mm256_storeu_si256((__m256i*)p, dataA);
+					p[32] = _mm_extract_epi8(_mm256_castsi256_si128(dataSwapped), 15);
+					p += outputBytesA;
+					
+					dataSwapped = _mm256_permute4x64_epi64(dataB, _MM_SHUFFLE(1,0,3,2));
+					dataB = _mm256_mask_alignr_epi8(dataB, (uint32_t)(-(int32_t)maskB), dataB, dataSwapped, 15);
+					dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8('='), 0xb8);
+					_mm256_storeu_si256((__m256i*)p, dataB);
+					p[32] = _mm_extract_epi8(_mm256_castsi256_si128(dataSwapped), 15);
+					p += maskBitsB;
+				}
+			} else
 #endif
-            {
-                bitIndexA = _lzcnt_u32(maskA);
-                bitIndexB = _lzcnt_u32(maskB);
-                __m256i mergeMaskA = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE));
-                __m256i mergeMaskB = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE));
-
+			{
+				bitIndexA = _lzcnt_u32(maskA);
+				bitIndexB = _lzcnt_u32(maskB);
+				__m256i mergeMaskA = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE));
+				__m256i mergeMaskB = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE));
+				
 #if defined(__tune_bdver4__) || defined(__tune_znver1__)
-                // avoid slower 32-byte crossing loads on Zen1
-                __m256i dataAShifted = _mm256_alignr_epi8(
-                    dataA,
-                    _mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1),
-                    15
-                );
-                __m256i dataBShifted = _mm256_alignr_epi8(
-                    dataB,
-                    _mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1),
-                    15
-                );
+				// avoid slower 32-byte crossing loads on Zen1
+				__m256i dataAShifted = _mm256_alignr_epi8(
+					dataA,
+					_mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1),
+					15
+				);
+				__m256i dataBShifted = _mm256_alignr_epi8(
+					dataB,
+					_mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1),
+					15
+				);
 #else
-                __m256i dataAShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE*2 - 1));
-                __m256i dataBShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE - 1));
+				__m256i dataAShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE*2 - 1));
+				__m256i dataBShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE - 1));
 #endif
-                dataA = _mm256_andnot_si256(cmpA, dataA); // clear space for '=' char
-                dataA = _mm256_blendv_epi8(dataAShifted, dataA, mergeMaskA);
-                dataA = _mm256_add_epi8(dataA, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE) + 1));
-                _mm256_storeu_si256((__m256i*)p, dataA);
-                p[YMM_SIZE] = es[i-1-YMM_SIZE] + 42 + (64 & (maskA>>(YMM_SIZE-1-6)));
-                p += outputBytesA;
-
-                dataB = _mm256_andnot_si256(cmpB, dataB);
-                dataB = _mm256_blendv_epi8(dataBShifted, dataB, mergeMaskB);
-                dataB = _mm256_add_epi8(dataB, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE) + 1));
-                _mm256_storeu_si256((__m256i*)p, dataB);
-                p[YMM_SIZE] = es[i-1] + 42 + (64 & (maskB>>(YMM_SIZE-1-6)));
-                p += maskBitsB;
-            }
-            col += outputBytesA + maskBitsB;
-
-            if(col >= 0) {
-                _encode_loop_branch_fast_eol:
-                if(HEDLEY_UNLIKELY(col > (intptr_t)maskBitsB)) {
-                    if(use_isa >= ISA_LEVEL_AVX3)
-                        bitIndexA = _lzcnt_u32(maskA);
-                    bitIndexA += 1 + maskBitsB;
-
-                    i += maskBitsB - YMM_SIZE;
-                    if(HEDLEY_UNLIKELY(col == (intptr_t)bitIndexA)) {
-                        // this is an escape character, so line will need to overflow
-                        p--;
-                    } else {
-                        i += (col > (intptr_t)bitIndexA);
-                    }
-                } else {
-                    if(use_isa >= ISA_LEVEL_AVX3)
-                        bitIndexB = _lzcnt_u32(maskB);
-                    bitIndexB++;
-
-                    if(HEDLEY_UNLIKELY(col == (intptr_t)bitIndexB)) {
-                        p--;
-                    } else {
-                        i += (col > (intptr_t)bitIndexB);
-                    }
-                }
-                i -= col;
-                p -= col;
-
-                _encode_eol_handle_pre:
-                uint32_t eolChar = (use_isa >= ISA_LEVEL_VBMI2 ? lookupsVBMI2->eolLastChar[es[i]] : lookupsAVX2->eolLastChar[es[i]]);
-                *(uint32_t*)p = eolChar;
-                p += 3 + (uintptr_t)(eolChar>>27);
-                col = lineSizeOffset;
-
-                if(HEDLEY_UNLIKELY(i >= 0)) { // this isn't really a proper check - it's only needed to support short lines; basically, if the line is too short, `i` never gets checked, so we need one somewhere
-                    i++;
-                    break;
-                }
-
-                dataA = _mm256_loadu_si256((__m256i *)(es + i + 1));
-                dataB = _mm256_loadu_si256((__m256i *)(es + i + 1) + 1);
-                i += YMM_SIZE*2 + 1;
-                // search for special chars
-                cmpA = _mm256_cmpeq_epi8(
-                    _mm256_shuffle_epi8(_mm256_set_epi8(
-                        '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
-                        '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                    ), _mm256_adds_epi8(
-                        _mm256_abs_epi8(dataA), _mm256_set_epi64x(0, 0, 0, 88)
-                    )),
-                    dataA
-                );
-                cmpB = _mm256_cmpeq_epi8(
-                    _mm256_shuffle_epi8(_mm256_set_epi8(
-                        '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
-                        '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                    ), _mm256_abs_epi8(dataB)),
-                    dataB
-                );
-
-                // duplicate some code from above to reduce jumping a little
+				dataA = _mm256_andnot_si256(cmpA, dataA); // clear space for '=' char
+				dataA = _mm256_blendv_epi8(dataAShifted, dataA, mergeMaskA);
+				dataA = _mm256_add_epi8(dataA, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE) + 1));
+				_mm256_storeu_si256((__m256i*)p, dataA);
+				p[YMM_SIZE] = es[i-1-YMM_SIZE] + 42 + (64 & (maskA>>(YMM_SIZE-1-6)));
+				p += outputBytesA;
+				
+				dataB = _mm256_andnot_si256(cmpB, dataB);
+				dataB = _mm256_blendv_epi8(dataBShifted, dataB, mergeMaskB);
+				dataB = _mm256_add_epi8(dataB, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE) + 1));
+				_mm256_storeu_si256((__m256i*)p, dataB);
+				p[YMM_SIZE] = es[i-1] + 42 + (64 & (maskB>>(YMM_SIZE-1-6)));
+				p += maskBitsB;
+			}
+			col += outputBytesA + maskBitsB;
+			
+			if(col >= 0) {
+				_encode_loop_branch_fast_eol:
+				if(HEDLEY_UNLIKELY(col > (intptr_t)maskBitsB)) {
+					if(use_isa >= ISA_LEVEL_AVX3)
+						bitIndexA = _lzcnt_u32(maskA);
+					bitIndexA += 1 + maskBitsB;
+					
+					i += maskBitsB - YMM_SIZE;
+					if(HEDLEY_UNLIKELY(col == (intptr_t)bitIndexA)) {
+						// this is an escape character, so line will need to overflow
+						p--;
+					} else {
+						i += (col > (intptr_t)bitIndexA);
+					}
+				} else {
+					if(use_isa >= ISA_LEVEL_AVX3)
+						bitIndexB = _lzcnt_u32(maskB);
+					bitIndexB++;
+					
+					if(HEDLEY_UNLIKELY(col == (intptr_t)bitIndexB)) {
+						p--;
+					} else {
+						i += (col > (intptr_t)bitIndexB);
+					}
+				}
+				i -= col;
+				p -= col;
+				
+				_encode_eol_handle_pre:
+				uint32_t eolChar = (use_isa >= ISA_LEVEL_VBMI2 ? lookupsVBMI2->eolLastChar[es[i]] : lookupsAVX2->eolLastChar[es[i]]);
+				*(uint32_t*)p = eolChar;
+				p += 3 + (uintptr_t)(eolChar>>27);
+				col = lineSizeOffset;
+				
+				if(HEDLEY_UNLIKELY(i >= 0)) { // this isn't really a proper check - it's only needed to support short lines; basically, if the line is too short, `i` never gets checked, so we need one somewhere
+					i++;
+					break;
+				}
+				
+				dataA = _mm256_loadu_si256((__m256i *)(es + i + 1));
+				dataB = _mm256_loadu_si256((__m256i *)(es + i + 1) + 1);
+				i += YMM_SIZE*2 + 1;
+				// search for special chars
+				cmpA = _mm256_cmpeq_epi8(
+					_mm256_shuffle_epi8(_mm256_set_epi8(
+						'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
+						'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+					), _mm256_adds_epi8(
+						_mm256_abs_epi8(dataA), _mm256_set_epi64x(0, 0, 0, 88)
+					)),
+					dataA
+				);
+				cmpB = _mm256_cmpeq_epi8(
+					_mm256_shuffle_epi8(_mm256_set_epi8(
+						'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42,
+						'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+					), _mm256_abs_epi8(dataB)),
+					dataB
+				);
+				
+				// duplicate some code from above to reduce jumping a little
 #if defined(__AVX512VL__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
-                    dataA = _mm256_add_epi8(dataA, _mm256_set1_epi8(42));
-                    dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
-                    dataB = _mm256_add_epi8(dataB, _mm256_set1_epi8(42));
-                    dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
-                }
+				if(use_isa >= ISA_LEVEL_AVX3) {
+					dataA = _mm256_add_epi8(dataA, _mm256_set1_epi8(42));
+					dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
+					dataB = _mm256_add_epi8(dataB, _mm256_set1_epi8(42));
+					dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8(64), 0xf8); // data | (cmp & 64)
+				}
 #endif
-
-                maskA = (uint32_t)_mm256_movemask_epi8(cmpA);
-                maskB = (uint32_t)_mm256_movemask_epi8(cmpB);
-                maskBitsA = popcnt32(maskA);
-                maskBitsB = popcnt32(maskB);
-                outputBytesA = maskBitsA + YMM_SIZE;
-                if (LIKELIHOOD(0.170, (maskBitsA|maskBitsB) > 1))
-                    goto _encode_loop_branch_slow;
-
-
-                //goto _encode_loop_branch_fast;
-                // duplicating the code, instead of using the goto above, seems to fix a performance regression in GCC
-                maskBitsB += YMM_SIZE;
+				
+				maskA = (uint32_t)_mm256_movemask_epi8(cmpA);
+				maskB = (uint32_t)_mm256_movemask_epi8(cmpB);
+				maskBitsA = popcnt32(maskA);
+				maskBitsB = popcnt32(maskB);
+				outputBytesA = maskBitsA + YMM_SIZE;
+				if (LIKELIHOOD(0.170, (maskBitsA|maskBitsB) > 1))
+					goto _encode_loop_branch_slow;
+				
+				
+				//goto _encode_loop_branch_fast;
+				// duplicating the code, instead of using the goto above, seems to fix a performance regression in GCC
+				maskBitsB += YMM_SIZE;
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-                if(use_isa >= ISA_LEVEL_AVX3) {
+				if(use_isa >= ISA_LEVEL_AVX3) {
 # if defined(__AVX512VBMI2__)
-                    if(use_isa >= ISA_LEVEL_VBMI2) {
-                        _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataA);
-                        dataA = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskA), dataA);
-                        _mm256_storeu_si256((__m256i*)p, dataA);
-                        p += outputBytesA;
-
-                        _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataB);
-                        dataB = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskB), dataB);
-                        _mm256_storeu_si256((__m256i*)p, dataB);
-                        p += maskBitsB;
-                    } else
+					if(use_isa >= ISA_LEVEL_VBMI2) {
+						__m128i dataTop = _mm256_extracti128_si256(dataA, 1);
+						dataA = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskA), dataA);
+						_mm256_storeu_si256((__m256i*)p, dataA);
+						p[32] = _mm_extract_epi8(dataTop, 15);
+						p += outputBytesA;
+						
+						dataTop = _mm256_extracti128_si256(dataB, 1);
+						dataB = _mm256_mask_expand_epi8(_mm256_set1_epi8('='), KNOT32(maskB), dataB);
+						_mm256_storeu_si256((__m256i*)p, dataB);
+						p[32] = _mm_extract_epi8(dataTop, 15);
+						p += maskBitsB;
+					} else
 # endif
-                    {
-                        _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataA);
-                        dataA = _mm256_mask_alignr_epi8(dataA, (uint32_t)(-(int32_t)maskA), dataA, _mm256_permute4x64_epi64(dataA, _MM_SHUFFLE(1,0,3,2)), 15);
-                        dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
-                        _mm256_storeu_si256((__m256i*)p, dataA);
-                        p += outputBytesA;
-
-                        _mm256_mask_storeu_epi8(p+1, 1UL<<31, dataB);
-                        dataB = _mm256_mask_alignr_epi8(dataB, (uint32_t)(-(int32_t)maskB), dataB, _mm256_permute4x64_epi64(dataB, _MM_SHUFFLE(1,0,3,2)), 15);
-                        dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8('='), 0xb8);
-                        _mm256_storeu_si256((__m256i*)p, dataB);
-                        p += maskBitsB;
-                    }
-                } else
+					{
+						__m256i dataSwapped = _mm256_permute4x64_epi64(dataA, _MM_SHUFFLE(1,0,3,2));
+						dataA = _mm256_mask_alignr_epi8(dataA, (uint32_t)(-(int32_t)maskA), dataA, dataSwapped, 15);
+						dataA = _mm256_ternarylogic_epi32(dataA, cmpA, _mm256_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
+						_mm256_storeu_si256((__m256i*)p, dataA);
+						p[32] = _mm_extract_epi8(_mm256_castsi256_si128(dataSwapped), 15);
+						p += outputBytesA;
+						
+						dataSwapped = _mm256_permute4x64_epi64(dataB, _MM_SHUFFLE(1,0,3,2));
+						dataB = _mm256_mask_alignr_epi8(dataB, (uint32_t)(-(int32_t)maskB), dataB, dataSwapped, 15);
+						dataB = _mm256_ternarylogic_epi32(dataB, cmpB, _mm256_set1_epi8('='), 0xb8);
+						_mm256_storeu_si256((__m256i*)p, dataB);
+						p[32] = _mm_extract_epi8(_mm256_castsi256_si128(dataSwapped), 15);
+						p += maskBitsB;
+					}
+				} else
 #endif
-                {
-                    bitIndexA = _lzcnt_u32(maskA);
-                    bitIndexB = _lzcnt_u32(maskB);
-                    __m256i mergeMaskA = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE));
-                    __m256i mergeMaskB = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE));
-
+				{
+					bitIndexA = _lzcnt_u32(maskA);
+					bitIndexB = _lzcnt_u32(maskB);
+					__m256i mergeMaskA = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE));
+					__m256i mergeMaskB = _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE));
+					
 #if defined(__tune_bdver4__) || defined(__tune_znver1__)
-                    // avoid slower 32-byte crossing loads on Zen1
-                    __m256i dataAShifted = _mm256_alignr_epi8(
-                        dataA,
-                        _mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1),
-                        15
-                    );
-                    __m256i dataBShifted = _mm256_alignr_epi8(
-                        dataB,
-                        _mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1),
-                        15
-                    );
+					// avoid slower 32-byte crossing loads on Zen1
+					__m256i dataAShifted = _mm256_alignr_epi8(
+						dataA,
+						_mm256_inserti128_si256(dataA, _mm256_castsi256_si128(dataA), 1),
+						15
+					);
+					__m256i dataBShifted = _mm256_alignr_epi8(
+						dataB,
+						_mm256_inserti128_si256(dataB, _mm256_castsi256_si128(dataB), 1),
+						15
+					);
 #else
-                    __m256i dataAShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE*2 - 1));
-                    __m256i dataBShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE - 1));
+					__m256i dataAShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE*2 - 1));
+					__m256i dataBShifted = _mm256_loadu_si256((__m256i *)(es + i - YMM_SIZE - 1));
 #endif
-                    dataA = _mm256_andnot_si256(cmpA, dataA); // clear space for '=' char
-                    dataA = _mm256_blendv_epi8(dataAShifted, dataA, mergeMaskA);
-                    dataA = _mm256_add_epi8(dataA, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE) + 1));
-                    _mm256_storeu_si256((__m256i*)p, dataA);
-                    p[YMM_SIZE] = es[i-1-YMM_SIZE] + 42 + (64 & (maskA>>(YMM_SIZE-1-6)));
-                    p += outputBytesA;
-
-                    dataB = _mm256_andnot_si256(cmpB, dataB);
-                    dataB = _mm256_blendv_epi8(dataBShifted, dataB, mergeMaskB);
-                    dataB = _mm256_add_epi8(dataB, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE) + 1));
-                    _mm256_storeu_si256((__m256i*)p, dataB);
-                    p[YMM_SIZE] = es[i-1] + 42 + (64 & (maskB>>(YMM_SIZE-1-6)));
-                    p += maskBitsB;
-                }
-                col += outputBytesA + maskBitsB;
-
-                if(col >= 0)
-                    goto _encode_loop_branch_fast_eol;
-            }
-        }
-    } while(i < 0);
-
-    _mm256_zeroupper();
-
-    *colOffset = (int)(col + line_size -1);
-    dest = p;
-    len = -(i - INPUT_OFFSET);
+					dataA = _mm256_andnot_si256(cmpA, dataA); // clear space for '=' char
+					dataA = _mm256_blendv_epi8(dataAShifted, dataA, mergeMaskA);
+					dataA = _mm256_add_epi8(dataA, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexA*2*YMM_SIZE) + 1));
+					_mm256_storeu_si256((__m256i*)p, dataA);
+					p[YMM_SIZE] = es[i-1-YMM_SIZE] + 42 + (64 & (maskA>>(YMM_SIZE-1-6)));
+					p += outputBytesA;
+					
+					dataB = _mm256_andnot_si256(cmpB, dataB);
+					dataB = _mm256_blendv_epi8(dataBShifted, dataB, mergeMaskB);
+					dataB = _mm256_add_epi8(dataB, _mm256_load_si256((const __m256i*)(lookupsAVX2->expandMergemix + bitIndexB*2*YMM_SIZE) + 1));
+					_mm256_storeu_si256((__m256i*)p, dataB);
+					p[YMM_SIZE] = es[i-1] + 42 + (64 & (maskB>>(YMM_SIZE-1-6)));
+					p += maskBitsB;
+				}
+				col += outputBytesA + maskBitsB;
+				
+				if(col >= 0)
+					goto _encode_loop_branch_fast_eol;
+			}
+		}
+	} while(i < 0);
+	
+	_mm256_zeroupper();
+	
+	*colOffset = (int)(col + line_size -1);
+	dest = p;
+	len = -(i - INPUT_OFFSET);
 }
+} // namespace
 
 #endif
diff --git a/src/yencode/encoder_common.h b/src/yencode/encoder_common.h
index 65bdeba..8e4975c 100644
--- a/src/yencode/encoder_common.h
+++ b/src/yencode/encoder_common.h
@@ -1,113 +1,103 @@
 #ifndef __YENC_ENCODER_COMMON
 #define __YENC_ENCODER_COMMON
 
-// lookup tables for scalar processing
-#define _B1(n) _B(n), _B(n+1), _B(n+2), _B(n+3)
-#define _B2(n) _B1(n), _B1(n+4), _B1(n+8), _B1(n+12)
-#define _B3(n) _B2(n), _B2(n+16), _B2(n+32), _B2(n+48)
-#define _BX _B3(0), _B3(64), _B3(128), _B3(192)
-
-static const unsigned char escapeLUT[256] = { // whether or not the character is critical
-#define _B(n) ((n == 214 || n == '\r'+214 || n == '\n'+214 || n == '='-42) ? 0 : (n+42) & 0xff)
-        _BX
-#undef _B
-};
-static const uint16_t escapedLUT[256] = { // escaped sequences for characters that need escaping
-#define _B(n) ((n == 214 || n == 214+'\r' || n == 214+'\n' || n == '='-42 || n == 214+'\t' || n == 214+' ' || n == '.'-42) ? UINT16_PACK('=', ((n+42+64)&0xff)) : 0)
-        _BX
-#undef _B
-};
-
-#undef _B1
-#undef _B2
-#undef _B3
-#undef _BX
-
-
-size_t do_encode_generic(int line_size, int *colOffset, const unsigned char *HEDLEY_RESTRICT src,
-                         unsigned char *HEDLEY_RESTRICT dest, size_t len, int doEnd);
-
-template<void(&kernel)(int, int *, const uint8_t *HEDLEY_RESTRICT, uint8_t *HEDLEY_RESTRICT &, size_t &)>
-
-static size_t
-do_encode_simd(int line_size, int *colOffset, const uint8_t *HEDLEY_RESTRICT src, uint8_t *HEDLEY_RESTRICT dest,
-               size_t len, int doEnd) {
-    if (len < 1) return 0;
-    if (line_size < 12) { // short lines probably not worth processing in a SIMD way
-        // we assume at least the first and last char exist in the line, and since the first char could be escaped, and SIMD encoder assumes at least one non-first/last char, assumption means that line size has to be >= 4
-        return do_encode_generic(line_size, colOffset, src, dest, len, doEnd);
-    }
-
-    const uint8_t *es = src + len;
-    uint8_t *p = dest;
-
-    if (*colOffset < 0) *colOffset = 0; // sanity check
-
-    kernel(line_size, colOffset, es, p, len);
+namespace RapidYenc {
+	void encoder_sse2_init();
+	void encoder_ssse3_init();
+	void encoder_avx_init();
+	void encoder_avx2_init();
+	void encoder_vbmi2_init();
+	extern const bool encoder_has_avx10;
+	void encoder_neon_init();
+	void encoder_rvv_init();
+	
+	// lookup tables for scalar processing
+	extern const unsigned char escapeLUT[256];
+	extern const uint16_t escapedLUT[256];
+	
+	size_t do_encode_generic(int line_size, int* colOffset, const unsigned char* HEDLEY_RESTRICT src, unsigned char* HEDLEY_RESTRICT dest, size_t len, int doEnd);
+}
 
-    // scalar loop to process remaining
-    long i = -(long) len;
-    if (*colOffset == 0 && i < 0) {
-        uint8_t c = es[i++];
-        if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
-            memcpy(p, escapedLUT + c, 2);
-            p += 2;
-            *colOffset = 2;
-        } else {
-            *(p++) = c + 42;
-            *colOffset = 1;
-        }
-    }
-    while (i < 0) {
-        uint8_t c = es[i++];
-        if (*colOffset < line_size - 1) {
-            if (!escapeLUT[c]) {
-                p[0] = '=';
-                p[1] = c + 42 + 64;
-                p += 2;
-                (*colOffset) += 2;
-            } else {
-                *(p++) = escapeLUT[c];
-                (*colOffset) += 1;
-            }
-        } else {
-            if (*colOffset < line_size) {
-                if (escapedLUT[c] && c != '.' - 42) {
-                    memcpy(p, escapedLUT + c, 2);
-                    p += 2;
-                } else {
-                    *(p++) = c + 42;
-                }
-                if (i == 0) break;
-                c = es[i++];
-            }
 
-            // handle EOL
-            if (escapedLUT[c]) {
-                uint32_t w = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t) escapedLUT[c]);
-                memcpy(p, &w, sizeof(w));
-                p += 4;
-                *colOffset = 2;
-            } else {
-                uint32_t w = UINT32_PACK('\r', '\n', (uint32_t)(c + 42), 0);
-                memcpy(p, &w, sizeof(w));
-                p += 3;
-                *colOffset = 1;
-            }
-        }
-    }
 
-    if (doEnd) {
-        // special case: if the last character is a space/tab, it needs to be escaped as it's the final character on the line
-        unsigned char lc = *(p - 1);
-        if (lc == '\t' || lc == ' ') {
-            p[-1] = '=';
-            *p = lc + 64;
-            p++;
-            (*colOffset)++;
-        }
-    }
-    return p - dest;
+template<void(&kernel)(int, int*, const uint8_t* HEDLEY_RESTRICT, uint8_t* HEDLEY_RESTRICT&, size_t&)>
+static size_t do_encode_simd(int line_size, int* colOffset, const unsigned char* HEDLEY_RESTRICT src, unsigned char* HEDLEY_RESTRICT dest, size_t len, int doEnd) {
+	if(len < 1) return 0;
+	if(line_size < 12) { // short lines probably not worth processing in a SIMD way
+		// we assume at least the first and last char exist in the line, and since the first char could be escaped, and SIMD encoder assumes at least one non-first/last char, assumption means that line size has to be >= 4
+		return RapidYenc::do_encode_generic(line_size, colOffset, src, dest, len, doEnd);
+	}
+	
+	const uint8_t* es = src + len;
+	uint8_t* p = dest;
+	
+	if(*colOffset < 0) *colOffset = 0; // sanity check
+	
+	kernel(line_size, colOffset, es, p, len);
+	
+	// scalar loop to process remaining
+	long i = -(long)len;
+	if(*colOffset == 0 && i < 0) {
+		uint8_t c = es[i++];
+		if (LIKELIHOOD(0.0273, RapidYenc::escapedLUT[c] != 0)) {
+			memcpy(p, RapidYenc::escapedLUT + c, 2);
+			p += 2;
+			*colOffset = 2;
+		} else {
+			*(p++) = c + 42;
+			*colOffset = 1;
+		}
+	}
+	while(i < 0) {
+		uint8_t c = es[i++];
+		if(*colOffset < line_size-1) {
+			if(!RapidYenc::escapeLUT[c]) {
+				p[0] = '=';
+				p[1] = c+42+64;
+				p += 2;
+				(*colOffset) += 2;
+			} else {
+				*(p++) = RapidYenc::escapeLUT[c];
+				(*colOffset) += 1;
+			}
+		} else {
+			if(*colOffset < line_size) {
+				if (RapidYenc::escapedLUT[c] && c != '.'-42) {
+					memcpy(p, RapidYenc::escapedLUT + c, 2);
+					p += 2;
+				} else {
+					*(p++) = c + 42;
+				}
+				if(i == 0) break;
+				c = es[i++];
+			}
+			
+			// handle EOL
+			if (RapidYenc::escapedLUT[c]) {
+				uint32_t w = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)RapidYenc::escapedLUT[c]);
+				memcpy(p, &w, sizeof(w));
+				p += 4;
+				*colOffset = 2;
+			} else {
+				uint32_t w = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+				memcpy(p, &w, sizeof(w));
+				p += 3;
+				*colOffset = 1;
+			}
+		}
+	}
+	
+	if(doEnd) {
+		// special case: if the last character is a space/tab, it needs to be escaped as it's the final character on the line
+		unsigned char lc = *(p-1);
+		if(lc == '\t' || lc == ' ') {
+			p[-1] = '=';
+			*p = lc+64;
+			p++;
+			(*colOffset)++;
+		}
+	}
+	return p - dest;
 }
 
 #endif /* __YENC_ENCODER_COMMON */
diff --git a/src/yencode/encoder_neon.cc b/src/yencode/encoder_neon.cc
index 786f3b9..e3faab2 100644
--- a/src/yencode/encoder_neon.cc
+++ b/src/yencode/encoder_neon.cc
@@ -1,549 +1,550 @@
 #include "common.h"
+#include "encoder_common.h"
 
 #ifdef __ARM_NEON
 #include "encoder.h"
-#include "encoder_common.h"
 
 // Clang wrongly assumes alignment on vst1q_u8_x2, and ARMv7 GCC doesn't support the function, so effectively, it can only be used in ARMv8 compilers
 #if defined(__aarch64__) && (defined(__clang__) || HEDLEY_GCC_VERSION_CHECK(8,5,0))
 # define vst1q_u8_x2_unaligned vst1q_u8_x2
 #else
 static HEDLEY_ALWAYS_INLINE void vst1q_u8_x2_unaligned(uint8_t* p, uint8x16x2_t data) {
-    vst1q_u8(p, data.val[0]);
-    vst1q_u8(p+16, data.val[1]);
+	vst1q_u8(p, data.val[0]);
+	vst1q_u8(p+16, data.val[1]);
 }
 #endif
 
 
-static uint8x16_t ALIGN_TO(16, shufLUT[256]);
-static uint16_t expandLUT[256];
-
-static HEDLEY_ALWAYS_INLINE void encode_eol_handle_pre(const uint8_t* HEDLEY_RESTRICT es, long& i, uint8_t*& p, long& col, long lineSizeOffset) {
-    uint8x16_t oDataA = vld1q_u8(es + i);
-    uint8x16_t oDataB = vld1q_u8(es + i + sizeof(uint8x16_t));
-    uint8x16_t dataA = oDataA;
-    uint8x16_t dataB = oDataB;
+// ARM's CLZ instruction at native bit-width
 #ifdef __aarch64__
-    uint8x16_t cmpA = vreinterpretq_u8_s8(vqtbx2q_s8(
-        vdupq_n_s8('='-42),
-        vcreate2_s8(vmakeq_s8('\0'-42,-128,-128,'\0'-42,'\t'-42,'\n'-42,'\r'-42,'\t'-42,'\n'-42,'\r'-42,-128,-128,'\0'-42,-128,-128,-128), vmakeq_s8(' '-42,'\n'-42,'\r'-42,' '-42,-128,-128,-128,-128,-128,-128,'.'-42,-128,-128,-128,'='-42,-128)),
-        vreinterpretq_u8_s8(vhaddq_s8(vreinterpretq_s8_u8(dataA), vmakeq_s8(42,48,66,66, 66,66,66,66, 66,66,66,66, 66,66,66,66)))
-    ));
-    cmpA = vceqq_u8(cmpA, dataA);
-
-    dataB = vaddq_u8(oDataB, vdupq_n_u8(42));
-    uint8x16_t cmpB = vqtbx1q_u8(
-        vceqq_u8(oDataB, vdupq_n_u8('='-42)),
-        //         \0                    \n      \r
-        vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-        dataB
-    );
-    dataA = vaddq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(64+42), vdupq_n_u8(42)));
-    dataB = vorrq_u8(dataB, vandq_u8(cmpB, vdupq_n_u8(64)));
+static HEDLEY_ALWAYS_INLINE int clz_n(uint64_t v) {
+# ifdef _MSC_VER
+	long r;
+	// does this work?
+	if(_BitScanReverse64((unsigned long*)&r, v))
+		r ^= 63;
+	else
+		r = 64;
+	return r;
+# else
+#  if defined(__clang__) || HEDLEY_GCC_VERSION_CHECK(11,0,0)
+	// this pattern is only detected on GCC >= 11 (Clang 9 seems to as well, unsure about earlier versions)
+	// - note: return type must be 'int'; GCC fails to optimise this if type is 'long'
+	// GCC <= 10 doesn't optimize around the '0 = undefined behaviour', so not needed there
+	if(v == 0) return 64;
+#  endif
+	return __builtin_clzll(v);
+# endif
+}
 #else
-    uint8x16_t cmpA = vorrq_u8(
-        vorrq_u8(
-            vceqq_u8(oDataA, vdupq_n_u8(-42)),
-            vceqq_u8(oDataA, vdupq_n_u8('='-42))
-        ),
-        vorrq_u8(
-            vceqq_u8(oDataA, vdupq_n_u8('\r'-42)),
-            vceqq_u8(oDataA, vdupq_n_u8('\n'-42))
-        )
-    );
-    uint8x16_t cmpB = vorrq_u8(
-        vorrq_u8(
-            vceqq_u8(oDataB, vdupq_n_u8(-42)),
-            vceqq_u8(oDataB, vdupq_n_u8('='-42))
-        ),
-        vorrq_u8(
-            vceqq_u8(oDataB, vdupq_n_u8('\r'-42)),
-            vceqq_u8(oDataB, vdupq_n_u8('\n'-42))
-        )
-    );
-
-    // dup low 2 bytes & compare
-    uint8x8_t firstTwoChars = vreinterpret_u8_u16(vdup_lane_u16(vreinterpret_u16_u8(vget_low_u8(oDataA)), 0));
-    uint8x8_t cmpNl = vceq_u8(firstTwoChars, vmake_u8(
-        ' '+214,' '+214,'\t'+214,'\t'+214,'\r'+214,'.'-42,'='-42,'='-42
-    ));
-    // use padd to merge comparisons
-    uint16x4_t cmpNl2 = vreinterpret_u16_u8(cmpNl);
-    cmpNl2 = vpadd_u16(cmpNl2, vdup_n_u16(0));
-    cmpNl2 = vpadd_u16(cmpNl2, vdup_n_u16(0));
-    cmpA = vcombine_u8(
-        vorr_u8(vget_low_u8(cmpA), vreinterpret_u8_u16(cmpNl2)),
-        vget_high_u8(cmpA)
-    );
-    dataA = vsubq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
-    dataB = vsubq_u8(dataB, vbslq_u8(cmpB, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
+static HEDLEY_ALWAYS_INLINE int clz_n(uint32_t v) {
+# ifdef __GNUC__
+#  if defined(__clang__) || HEDLEY_GCC_VERSION_CHECK(7,0,0)
+	// as with AArch64 version above, only insert this check if compiler can optimise it away
+	if(v == 0) return 32;
+#  endif
+	return __builtin_clz(v);
+# elif defined(_MSC_VER)
+	return _arm_clz(v);
+# else
+	return __clz(v); // ARM compiler?
+# endif
+}
 #endif
 
+static uint8x16_t ALIGN_TO(16, shufLUT[256]);
+static uint16_t expandLUT[256];
 
-    uint8x16_t cmpAMasked = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-    uint8x16_t cmpBMasked = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+static HEDLEY_ALWAYS_INLINE void encode_eol_handle_pre(const uint8_t* HEDLEY_RESTRICT es, long& i, uint8_t*& p, long& col, long lineSizeOffset) {
+	uint8x16_t oDataA = vld1q_u8(es + i);
+	uint8x16_t oDataB = vld1q_u8(es + i + sizeof(uint8x16_t));
+	uint8x16_t dataA = oDataA;
+	uint8x16_t dataB = oDataB;
 #ifdef __aarch64__
-    uint8x16_t cmpMerge = vpaddq_u8(cmpAMasked, cmpBMasked);
-    cmpMerge = vpaddq_u8(cmpMerge, cmpMerge);
-    uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpMerge), 0);
-
-    // write out first char + newline
-    uint32_t firstChar = vgetq_lane_u8(dataA, 0);
-    if(LIKELIHOOD(0.0234, mask & 1)) {
-        firstChar <<= 8;
-        firstChar |= 0x0a0d003d;
-        memcpy(p, &firstChar, sizeof(firstChar));
-        p += 4;
-        mask ^= 1;
-        cmpMerge = vbicq_u8(cmpMerge, vmakeq_u8(1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0));
-    } else {
-        firstChar |= 0x0a0d00;
-        memcpy(p, &firstChar, sizeof(firstChar));
-        p += 3;
-    }
-
-    if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
-        mask |= mask >> 8;
-        uint8x8_t cmpPacked = vpadd_u8(vget_low_u8(cmpMerge), vget_low_u8(cmpMerge));
-        uint8_t m1 = (mask & 0xff);
-        uint8_t m2 = ((mask >> 16) & 0xff);
-        uint8_t m3 = ((mask >> 32) & 0xff);
-        uint8_t m4 = ((mask >> 48) & 0xff);
+	uint8x16_t cmpA = vreinterpretq_u8_s8(vqtbx2q_s8(
+		vdupq_n_s8('='-42),
+		vcreate2_s8(vmakeq_s8('\0'-42,-128,-128,'\0'-42,'\t'-42,'\n'-42,'\r'-42,'\t'-42,'\n'-42,'\r'-42,-128,-128,'\0'-42,-128,-128,-128), vmakeq_s8(' '-42,'\n'-42,'\r'-42,' '-42,-128,-128,-128,-128,-128,-128,'.'-42,-128,-128,-128,'='-42,-128)),
+		vreinterpretq_u8_s8(vhaddq_s8(vreinterpretq_s8_u8(dataA), vmakeq_s8(42,48,66,66, 66,66,66,66, 66,66,66,66, 66,66,66,66)))
+	));
+	cmpA = vceqq_u8(cmpA, dataA);
+	
+	dataB = vaddq_u8(oDataB, vdupq_n_u8(42));
+	uint8x16_t cmpB = vqtbx1q_u8(
+		vceqq_u8(oDataB, vdupq_n_u8('='-42)),
+		//         \0                    \n      \r
+		vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+		dataB
+	);
+	dataA = vaddq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(64+42), vdupq_n_u8(42)));
+	dataB = vorrq_u8(dataB, vandq_u8(cmpB, vdupq_n_u8(64)));
 #else
-    // no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
-    uint8x8_t cmpPacked = vpadd_u8(
-        vpadd_u8(
-            vget_low_u8(cmpAMasked), vget_high_u8(cmpAMasked)
-        ),
-        vpadd_u8(
-            vget_low_u8(cmpBMasked), vget_high_u8(cmpBMasked)
-        )
-    );
-    cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
-    uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
-
-    // write out first char + newline
-    uint32_t firstChar = vgetq_lane_u8(dataA, 0);
-    if(LIKELIHOOD(0.0234, mask & 1)) {
-        firstChar <<= 8;
-        firstChar |= 0x0a0d003d;
-        memcpy(p, &firstChar, sizeof(firstChar));
-        p += 4;
-        mask ^= 1;
-        cmpPacked = vbic_u8(cmpPacked, vmake_u8(1,0,0,0, 0,0,0,0));
-    } else {
-        firstChar |= 0x0a0d00;
-        memcpy(p, &firstChar, sizeof(firstChar));
-        p += 3;
-    }
-
-    if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
-        uint8_t m1 = (mask & 0xff);
-        uint8_t m2 = ((mask >> 8) & 0xff);
-        uint8_t m3 = ((mask >> 16) & 0xff);
-        uint8_t m4 = ((mask >> 24) & 0xff);
+	uint8x16_t cmpA = vorrq_u8(
+		vorrq_u8(
+			vceqq_u8(oDataA, vdupq_n_u8(-42)),
+			vceqq_u8(oDataA, vdupq_n_u8('='-42))
+		),
+		vorrq_u8(
+			vceqq_u8(oDataA, vdupq_n_u8('\r'-42)),
+			vceqq_u8(oDataA, vdupq_n_u8('\n'-42))
+		)
+	);
+	uint8x16_t cmpB = vorrq_u8(
+		vorrq_u8(
+			vceqq_u8(oDataB, vdupq_n_u8(-42)),
+			vceqq_u8(oDataB, vdupq_n_u8('='-42))
+		),
+		vorrq_u8(
+			vceqq_u8(oDataB, vdupq_n_u8('\r'-42)),
+			vceqq_u8(oDataB, vdupq_n_u8('\n'-42))
+		)
+	);
+	
+	// dup low 2 bytes & compare
+	uint8x8_t firstTwoChars = vreinterpret_u8_u16(vdup_lane_u16(vreinterpret_u16_u8(vget_low_u8(oDataA)), 0));
+	uint8x8_t cmpNl = vceq_u8(firstTwoChars, vmake_u8(
+		' '+214,' '+214,'\t'+214,'\t'+214,'\r'+214,'.'-42,'='-42,'='-42
+	));
+	// use padd to merge comparisons
+	uint16x4_t cmpNl2 = vreinterpret_u16_u8(cmpNl);
+	cmpNl2 = vpadd_u16(cmpNl2, vdup_n_u16(0));
+	cmpNl2 = vpadd_u16(cmpNl2, vdup_n_u16(0));
+	cmpA = vcombine_u8(
+		vorr_u8(vget_low_u8(cmpA), vreinterpret_u8_u16(cmpNl2)),
+		vget_high_u8(cmpA)
+	);
+	dataA = vsubq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
+	dataB = vsubq_u8(dataB, vbslq_u8(cmpB, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
 #endif
-
-        // perform lookup for shuffle mask
-        uint8x16_t shuf1 = vld1q_u8((uint8_t*)(shufLUT + m1));
-        uint8x16_t shuf2 = vld1q_u8((uint8_t*)(shufLUT + m2));
-        uint8x16_t shuf3 = vld1q_u8((uint8_t*)(shufLUT + m3));
-        uint8x16_t shuf4 = vld1q_u8((uint8_t*)(shufLUT + m4));
+	
+	
+	uint8x16_t cmpAMasked = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+	uint8x16_t cmpBMasked = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
 #ifdef __aarch64__
-        uint8x16_t data1A = vqtbx1q_u8(shuf1, dataA, shuf1);
-        uint8x16_t data2A = vqtbx1q_u8(shuf2, vextq_u8(dataA, dataA, 8), shuf2);
-        uint8x16_t data1B = vqtbx1q_u8(shuf3, dataB, shuf3);
-        uint8x16_t data2B = vqtbx1q_u8(shuf4, vextq_u8(dataB, dataB, 8), shuf4);
+	uint8x16_t cmpMerge = vpaddq_u8(cmpAMasked, cmpBMasked);
+	cmpMerge = vpaddq_u8(cmpMerge, cmpMerge);
+	uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpMerge), 0);
+	
+	// write out first char + newline
+	uint32_t firstChar = vgetq_lane_u8(dataA, 0);
+	if(LIKELIHOOD(0.0234, mask & 1)) {
+		firstChar <<= 8;
+		firstChar |= 0x0a0d003d;
+		memcpy(p, &firstChar, sizeof(firstChar));
+		p += 4;
+		mask ^= 1;
+		cmpMerge = vbicq_u8(cmpMerge, vmakeq_u8(1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0));
+	} else {
+		firstChar |= 0x0a0d00;
+		memcpy(p, &firstChar, sizeof(firstChar));
+		p += 3;
+	}
+	
+	if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
+		mask |= mask >> 8;
+		uint8x8_t cmpPacked = vpadd_u8(vget_low_u8(cmpMerge), vget_low_u8(cmpMerge));
+		uint8_t m1 = (mask & 0xff);
+		uint8_t m2 = ((mask >> 16) & 0xff);
+		uint8_t m3 = ((mask >> 32) & 0xff);
+		uint8_t m4 = ((mask >> 48) & 0xff);
 #else
-        uint8x8_t shuf1l = vget_low_u8(shuf1);
-        uint8x8_t shuf1h = vget_high_u8(shuf1);
-        uint8x8_t shuf2l = vget_low_u8(shuf2);
-        uint8x8_t shuf2h = vget_high_u8(shuf2);
-        uint8x8_t shuf3l = vget_low_u8(shuf3);
-        uint8x8_t shuf3h = vget_high_u8(shuf3);
-        uint8x8_t shuf4l = vget_low_u8(shuf4);
-        uint8x8_t shuf4h = vget_high_u8(shuf4);
-        uint8x16_t data1A = vcombine_u8(vtbx1_u8(shuf1l, vget_low_u8(dataA), shuf1l),
-                                        vtbx1_u8(shuf1h, vget_low_u8(dataA), shuf1h));
-        uint8x16_t data2A = vcombine_u8(vtbx1_u8(shuf2l, vget_high_u8(dataA), shuf2l),
-                                        vtbx1_u8(shuf2h, vget_high_u8(dataA), shuf2h));
-        uint8x16_t data1B = vcombine_u8(vtbx1_u8(shuf3l, vget_low_u8(dataB), shuf3l),
-                                        vtbx1_u8(shuf3h, vget_low_u8(dataB), shuf3h));
-        uint8x16_t data2B = vcombine_u8(vtbx1_u8(shuf4l, vget_high_u8(dataB), shuf4l),
-                                        vtbx1_u8(shuf4h, vget_high_u8(dataB), shuf4h));
+	// no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
+	uint8x8_t cmpPacked = vpadd_u8(
+		vpadd_u8(
+			vget_low_u8(cmpAMasked), vget_high_u8(cmpAMasked)
+		),
+		vpadd_u8(
+			vget_low_u8(cmpBMasked), vget_high_u8(cmpBMasked)
+		)
+	);
+	cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
+	uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
+	
+	// write out first char + newline
+	uint32_t firstChar = vgetq_lane_u8(dataA, 0);
+	if(LIKELIHOOD(0.0234, mask & 1)) {
+		firstChar <<= 8;
+		firstChar |= 0x0a0d003d;
+		memcpy(p, &firstChar, sizeof(firstChar));
+		p += 4;
+		mask ^= 1;
+		cmpPacked = vbic_u8(cmpPacked, vmake_u8(1,0,0,0, 0,0,0,0));
+	} else {
+		firstChar |= 0x0a0d00;
+		memcpy(p, &firstChar, sizeof(firstChar));
+		p += 3;
+	}
+	
+	if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
+		uint8_t m1 = (mask & 0xff);
+		uint8_t m2 = ((mask >> 8) & 0xff);
+		uint8_t m3 = ((mask >> 16) & 0xff);
+		uint8_t m4 = ((mask >> 24) & 0xff);
 #endif
-        data1A = vextq_u8(data1A, data1A, 1); // shift out processed byte (last char of line)
-
-        uint32_t counts = vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
-        counts += 0x08080807;
-
-        unsigned char shuf1Len = counts & 0xff;
-        unsigned char shuf2Len = (counts>>8) & 0xff;
-        unsigned char shuf3Len = (counts>>16) & 0xff;
-        unsigned char shuf4Len = (counts>>24) & 0xff;
-        uint32_t shufTotalLen = counts * 0x1010101;
-        shufTotalLen >>= 24;
-
-        vst1q_u8(p, data1A);
-        p += shuf1Len;
-        vst1q_u8(p, data2A);
-        p += shuf2Len;
-        vst1q_u8(p, data1B);
-        p += shuf3Len;
-        vst1q_u8(p, data2B);
-        p += shuf4Len;
-        col = shufTotalLen+1 + lineSizeOffset-32;
-    } else {
-        // shuffle stuff up
+		
+		// perform lookup for shuffle mask
+		uint8x16_t shuf1 = vld1q_u8((uint8_t*)(shufLUT + m1));
+		uint8x16_t shuf2 = vld1q_u8((uint8_t*)(shufLUT + m2));
+		uint8x16_t shuf3 = vld1q_u8((uint8_t*)(shufLUT + m3));
+		uint8x16_t shuf4 = vld1q_u8((uint8_t*)(shufLUT + m4));
 #ifdef __aarch64__
-# ifdef _MSC_VER
-        long bitIndex;
-        if(_BitScanReverse64((unsigned long*)&bitIndex, mask))
-            bitIndex ^= 63;
-        else
-            bitIndex = 64;
-# else
-        long bitIndex = __builtin_clzll(mask);
-# endif
+		uint8x16_t data1A = vqtbx1q_u8(shuf1, dataA, shuf1);
+		uint8x16_t data2A = vqtbx1q_u8(shuf2, vextq_u8(dataA, dataA, 8), shuf2);
+		uint8x16_t data1B = vqtbx1q_u8(shuf3, dataB, shuf3);
+		uint8x16_t data2B = vqtbx1q_u8(shuf4, vextq_u8(dataB, dataB, 8), shuf4);
 #else
-# ifdef __GNUC__
-        long bitIndex = __builtin_clz(mask); // TODO: is the 'undefined if 0' case problematic here?
-# elif defined(_MSC_VER)
-        long bitIndex = _arm_clz(mask);
-# else
-        long bitIndex = __clz(mask); // ARM compiler?
-# endif
+		uint8x8_t shuf1l = vget_low_u8(shuf1);
+		uint8x8_t shuf1h = vget_high_u8(shuf1);
+		uint8x8_t shuf2l = vget_low_u8(shuf2);
+		uint8x8_t shuf2h = vget_high_u8(shuf2);
+		uint8x8_t shuf3l = vget_low_u8(shuf3);
+		uint8x8_t shuf3h = vget_high_u8(shuf3);
+		uint8x8_t shuf4l = vget_low_u8(shuf4);
+		uint8x8_t shuf4h = vget_high_u8(shuf4);
+		uint8x16_t data1A = vcombine_u8(vtbx1_u8(shuf1l, vget_low_u8(dataA), shuf1l),
+		                                vtbx1_u8(shuf1h, vget_low_u8(dataA), shuf1h));
+		uint8x16_t data2A = vcombine_u8(vtbx1_u8(shuf2l, vget_high_u8(dataA), shuf2l),
+		                                vtbx1_u8(shuf2h, vget_high_u8(dataA), shuf2h));
+		uint8x16_t data1B = vcombine_u8(vtbx1_u8(shuf3l, vget_low_u8(dataB), shuf3l),
+		                                vtbx1_u8(shuf3h, vget_low_u8(dataB), shuf3h));
+		uint8x16_t data2B = vcombine_u8(vtbx1_u8(shuf4l, vget_high_u8(dataB), shuf4l),
+		                                vtbx1_u8(shuf4h, vget_high_u8(dataB), shuf4h));
 #endif
-
-        uint8x16_t vClz = vdupq_n_u8(bitIndex & ~(sizeof(mask)*8));
+		data1A = vextq_u8(data1A, data1A, 1); // shift out processed byte (last char of line)
+		
+		uint32_t counts = vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
+		counts += 0x08080807;
+		
+		unsigned char shuf1Len = counts & 0xff;
+		unsigned char shuf2Len = (counts>>8) & 0xff;
+		unsigned char shuf3Len = (counts>>16) & 0xff;
+		unsigned char shuf4Len = (counts>>24) & 0xff;
+		uint32_t shufTotalLen = counts * 0x1010101;
+		shufTotalLen >>= 24;
+		
+		vst1q_u8(p, data1A);
+		p += shuf1Len;
+		vst1q_u8(p, data2A);
+		p += shuf2Len;
+		vst1q_u8(p, data1B);
+		p += shuf3Len;
+		vst1q_u8(p, data2B);
+		p += shuf4Len;
+		col = shufTotalLen+1 + lineSizeOffset-32;
+	} else {
+		// shuffle stuff up
+		long bitIndex = clz_n(mask);
+		uint8x16_t vClz = vdupq_n_u8(bitIndex & ~(sizeof(mask)*8));
 #ifdef __aarch64__
-        uint8x16_t blendA = vcgtq_u8(vmakeq_u8(63,62,61,60,51,50,49,48,47,46,45,44,35,34,33,32), vClz);
-        uint8x16_t blendB = vcgtq_u8(vmakeq_u8(31,30,29,28,19,18,17,16,15,14,13,12, 3, 2, 1, 0), vClz);
+		uint8x16_t blendA = vcgtq_u8(vmakeq_u8(63,62,61,60,51,50,49,48,47,46,45,44,35,34,33,32), vClz);
+		uint8x16_t blendB = vcgtq_u8(vmakeq_u8(31,30,29,28,19,18,17,16,15,14,13,12, 3, 2, 1, 0), vClz);
 #else
-        uint8x16_t blendA = vcgtq_u8(vmakeq_u8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16), vClz);
-        uint8x16_t blendB = vcgtq_u8(vmakeq_u8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), vClz);
+		uint8x16_t blendA = vcgtq_u8(vmakeq_u8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16), vClz);
+		uint8x16_t blendB = vcgtq_u8(vmakeq_u8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), vClz);
 #endif
-        uint8x16_t dataAShifted = vbslq_u8(cmpA, vdupq_n_u8('='), dataA);
-        uint8x16_t dataBShifted = vbslq_u8(cmpB, vdupq_n_u8('='), dataB);
-        dataAShifted = vextq_u8(dataAShifted, dataBShifted, 1);
-        dataBShifted = vextq_u8(dataBShifted, dataBShifted, 1);
-        dataA = vbslq_u8(blendA, dataAShifted, dataA);
-        dataB = vbslq_u8(blendB, dataBShifted, dataB);
-
-        vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, dataB));
-        p += sizeof(uint8x16_t)*2 - 1;
-        p += (mask != 0);
-        col = lineSizeOffset + (mask != 0);
-    }
-
-    i += sizeof(uint8x16_t)*2;
-    // TODO: check col >= 0 if we want to support short lines
+		uint8x16_t dataAShifted = vbslq_u8(cmpA, vdupq_n_u8('='), dataA);
+		uint8x16_t dataBShifted = vbslq_u8(cmpB, vdupq_n_u8('='), dataB);
+		dataAShifted = vextq_u8(dataAShifted, dataBShifted, 1);
+		dataBShifted = vextq_u8(dataBShifted, dataBShifted, 1);
+		dataA = vbslq_u8(blendA, dataAShifted, dataA);
+		dataB = vbslq_u8(blendB, dataBShifted, dataB);
+		
+		vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, dataB));
+		p += sizeof(uint8x16_t)*2 - 1;
+		p += (mask != 0);
+		col = lineSizeOffset + (mask != 0);
+	}
+	
+	i += sizeof(uint8x16_t)*2;
+	// TODO: check col >= 0 if we want to support short lines
 }
 
 
-HEDLEY_ALWAYS_INLINE void do_encode_neon(int line_size, int* colOffset, const uint8_t* HEDLEY_RESTRICT srcEnd, uint8_t* HEDLEY_RESTRICT& dest, size_t& len) {
-    // offset position to enable simpler loop condition checking
-    const int INPUT_OFFSET = sizeof(uint8x16_t)*4 -1; // extra chars for EOL handling, -1 to change <= to <
-    if(len <= INPUT_OFFSET || line_size < (int)sizeof(uint8x16_t)*4) return;
-
-    uint8_t *p = dest; // destination pointer
-    long i = -(long)len; // input position
-    long lineSizeOffset = -line_size +32; // line size plus vector length
-    long col = *colOffset - line_size +1;
-
-    i += INPUT_OFFSET;
-    const uint8_t* es = srcEnd - INPUT_OFFSET;
-
-    if (HEDLEY_LIKELY(col == -line_size+1)) {
-        uint8_t c = es[i++];
-        if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
-            memcpy(p, escapedLUT + c, 2);
-            p += 2;
-            col += 2;
-        } else {
-            *(p++) = c + 42;
-            col += 1;
-        }
-    }
-    if(HEDLEY_UNLIKELY(col >= 0)) {
-        if(col == 0)
-            encode_eol_handle_pre(es, i, p, col, lineSizeOffset);
-        else {
-            uint8_t c = es[i++];
-            if (LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
-                uint32_t v = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
-                memcpy(p, &v, sizeof(v));
-                p += 4;
-                col = 2-line_size + 1;
-            } else {
-                uint32_t v = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
-                memcpy(p, &v, sizeof(v));
-                p += 3;
-                col = 2-line_size;
-            }
-        }
-    }
-    while(i < 0) {
-        // for unaligned loads, separate loads seem to be faster than vld1q_u8_x2 on Cortex A53; unsure if this applies elsewhere
-        uint8x16_t dataA = vld1q_u8(es + i);
-        uint8x16_t dataB = vld1q_u8(es + i + sizeof(uint8x16_t));
-        i += sizeof(uint8x16_t)*2;
-        // search for special chars
-#ifdef __aarch64__
-        uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('='-42));
-        uint8x16_t cmpEqB = vceqq_u8(dataB, vdupq_n_u8('='-42));
-        dataA = vaddq_u8(dataA, vdupq_n_u8(42));
-        dataB = vaddq_u8(dataB, vdupq_n_u8(42));
-        uint8x16_t cmpA = vqtbx1q_u8(
-            cmpEqA,
-            //         \0                    \n      \r
-            vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataA
-        );
-        uint8x16_t cmpB = vqtbx1q_u8(
-            cmpEqB,
-            //         \0                    \n      \r
-            vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
-            dataB
-        );
-
-        dataA = vorrq_u8(dataA, vandq_u8(cmpA, vdupq_n_u8(64)));
-        dataB = vorrq_u8(dataB, vandq_u8(cmpB, vdupq_n_u8(64)));
-#else
-        // the ARMv8 strategy may be worth it here with 2x vtbx2's, but both GCC-9 and Clang-9 generate poor assembly for it, so it performs worse than the following
-        uint8x16_t cmpA = vorrq_u8(
-            vorrq_u8(
-                vceqq_u8(dataA, vdupq_n_u8(-42)),
-                vceqq_u8(dataA, vdupq_n_u8('='-42))
-            ),
-            vorrq_u8(
-                vceqq_u8(dataA, vdupq_n_u8('\r'-42)),
-                vceqq_u8(dataA, vdupq_n_u8('\n'-42))
-            )
-        );
-        uint8x16_t cmpB = vorrq_u8(
-            vorrq_u8(
-                vceqq_u8(dataB, vdupq_n_u8(-42)),
-                vceqq_u8(dataB, vdupq_n_u8('='-42))
-            ),
-            vorrq_u8(
-                vceqq_u8(dataB, vdupq_n_u8('\r'-42)),
-                vceqq_u8(dataB, vdupq_n_u8('\n'-42))
-            )
-        );
-
-        dataA = vsubq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
-        dataB = vsubq_u8(dataB, vbslq_u8(cmpB, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
-#endif
-
+namespace RapidYenc {
 
-        long bitIndex; // prevent compiler whining
-        uint8x16_t cmpAMasked = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
-        uint8x16_t cmpBMasked = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+HEDLEY_ALWAYS_INLINE void do_encode_neon(int line_size, int* colOffset, const uint8_t* HEDLEY_RESTRICT srcEnd, uint8_t* HEDLEY_RESTRICT& dest, size_t& len) {
+	// offset position to enable simpler loop condition checking
+	const int INPUT_OFFSET = sizeof(uint8x16_t)*4 -1; // extra chars for EOL handling, -1 to change <= to <
+	if(len <= INPUT_OFFSET || line_size < (int)sizeof(uint8x16_t)*4) return;
+	
+	uint8_t *p = dest; // destination pointer
+	long i = -(long)len; // input position
+	long lineSizeOffset = -line_size +32; // line size plus vector length
+	long col = *colOffset - line_size +1;
+	
+	i += INPUT_OFFSET;
+	const uint8_t* es = srcEnd - INPUT_OFFSET;
+	
+	if (HEDLEY_LIKELY(col == -line_size+1)) {
+		uint8_t c = es[i++];
+		if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
+			memcpy(p, escapedLUT + c, 2);
+			p += 2;
+			col += 2;
+		} else {
+			*(p++) = c + 42;
+			col += 1;
+		}
+	}
+	if(HEDLEY_UNLIKELY(col >= 0)) {
+		if(col == 0)
+			encode_eol_handle_pre(es, i, p, col, lineSizeOffset);
+		else {
+			uint8_t c = es[i++];
+			if (LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
+				uint32_t v = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
+				memcpy(p, &v, sizeof(v));
+				p += 4;
+				col = 2-line_size + 1;
+			} else {
+				uint32_t v = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+				memcpy(p, &v, sizeof(v));
+				p += 3;
+				col = 2-line_size;
+			}
+		}
+	}
+	while(i < 0) {
+		// for unaligned loads, separate loads seem to be faster than vld1q_u8_x2 on Cortex A53; unsure if this applies elsewhere
+		uint8x16_t dataA = vld1q_u8(es + i);
+		uint8x16_t dataB = vld1q_u8(es + i + sizeof(uint8x16_t));
+		i += sizeof(uint8x16_t)*2;
+		// search for special chars
 #ifdef __aarch64__
-        uint8x16_t cmpMerge = vpaddq_u8(cmpAMasked, cmpBMasked);
-        cmpMerge = vpaddq_u8(cmpMerge, cmpMerge);
-        uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpMerge), 0);
-        if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
-            mask |= mask >> 8;
-            uint8x8_t cmpPacked = vpadd_u8(vget_low_u8(cmpMerge), vget_low_u8(cmpMerge));
-            uint8_t m1 = (mask & 0xff);
-            uint8_t m2 = ((mask >> 16) & 0xff);
-            uint8_t m3 = ((mask >> 32) & 0xff);
-            uint8_t m4 = ((mask >> 48) & 0xff);
+		uint8x16_t cmpEqA = vceqq_u8(dataA, vdupq_n_u8('='-42));
+		uint8x16_t cmpEqB = vceqq_u8(dataB, vdupq_n_u8('='-42));
+		dataA = vaddq_u8(dataA, vdupq_n_u8(42));
+		dataB = vaddq_u8(dataB, vdupq_n_u8(42));
+		uint8x16_t cmpA = vqtbx1q_u8(
+			cmpEqA,
+			//         \0                    \n      \r
+			vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataA
+		);
+		uint8x16_t cmpB = vqtbx1q_u8(
+			cmpEqB,
+			//         \0                    \n      \r
+			vmakeq_u8(255,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0),
+			dataB
+		);
+		
+		dataA = vorrq_u8(dataA, vandq_u8(cmpA, vdupq_n_u8(64)));
+		dataB = vorrq_u8(dataB, vandq_u8(cmpB, vdupq_n_u8(64)));
 #else
-        // no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
-        uint8x8_t cmpPacked = vpadd_u8(
-            vpadd_u8(
-                vget_low_u8(cmpAMasked), vget_high_u8(cmpAMasked)
-            ),
-            vpadd_u8(
-                vget_low_u8(cmpBMasked), vget_high_u8(cmpBMasked)
-            )
-        );
-        cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
-        uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
-        if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
-            uint8_t m1 = (mask & 0xff);
-            uint8_t m2 = ((mask >> 8) & 0xff);
-            uint8_t m3 = ((mask >> 16) & 0xff);
-            uint8_t m4 = ((mask >> 24) & 0xff);
+		// the ARMv8 strategy may be worth it here with 2x vtbx2's, but both GCC-9 and Clang-9 generate poor assembly for it, so it performs worse than the following
+		uint8x16_t cmpA = vorrq_u8(
+			vorrq_u8(
+				vceqq_u8(dataA, vdupq_n_u8(-42)),
+				vceqq_u8(dataA, vdupq_n_u8('='-42))
+			),
+			vorrq_u8(
+				vceqq_u8(dataA, vdupq_n_u8('\r'-42)),
+				vceqq_u8(dataA, vdupq_n_u8('\n'-42))
+			)
+		);
+		uint8x16_t cmpB = vorrq_u8(
+			vorrq_u8(
+				vceqq_u8(dataB, vdupq_n_u8(-42)),
+				vceqq_u8(dataB, vdupq_n_u8('='-42))
+			),
+			vorrq_u8(
+				vceqq_u8(dataB, vdupq_n_u8('\r'-42)),
+				vceqq_u8(dataB, vdupq_n_u8('\n'-42))
+			)
+		);
+		
+		dataA = vsubq_u8(dataA, vbslq_u8(cmpA, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
+		dataB = vsubq_u8(dataB, vbslq_u8(cmpB, vdupq_n_u8(-64-42), vdupq_n_u8(-42)));
 #endif
-
-            // perform lookup for shuffle mask
-            uint8x16_t shuf1 = vld1q_u8((uint8_t*)(shufLUT + m1));
-            uint8x16_t shuf2 = vld1q_u8((uint8_t*)(shufLUT + m2));
-            uint8x16_t shuf3 = vld1q_u8((uint8_t*)(shufLUT + m3));
-            uint8x16_t shuf4 = vld1q_u8((uint8_t*)(shufLUT + m4));
-
-            // expand halves
+		
+		
+		long bitIndex; // prevent compiler whining
+		uint8x16_t cmpAMasked = vandq_u8(cmpA, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
+		uint8x16_t cmpBMasked = vandq_u8(cmpB, vmakeq_u8(1,2,4,8,16,32,64,128, 1,2,4,8,16,32,64,128));
 #ifdef __aarch64__
-            uint8x16_t data1A = vqtbx1q_u8(shuf1, dataA, shuf1);
-            uint8x16_t data2A = vqtbx1q_u8(shuf2, vextq_u8(dataA, dataA, 8), shuf2);
-            uint8x16_t data1B = vqtbx1q_u8(shuf3, dataB, shuf3);
-            uint8x16_t data2B = vqtbx1q_u8(shuf4, vextq_u8(dataB, dataB, 8), shuf4);
+		uint8x16_t cmpMerge = vpaddq_u8(cmpAMasked, cmpBMasked);
+		cmpMerge = vpaddq_u8(cmpMerge, cmpMerge);
+		uint64_t mask = vgetq_lane_u64(vreinterpretq_u64_u8(cmpMerge), 0);
+		if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
+			mask |= mask >> 8;
+			uint8x8_t cmpPacked = vpadd_u8(vget_low_u8(cmpMerge), vget_low_u8(cmpMerge));
+			uint8_t m1 = (mask & 0xff);
+			uint8_t m2 = ((mask >> 16) & 0xff);
+			uint8_t m3 = ((mask >> 32) & 0xff);
+			uint8_t m4 = ((mask >> 48) & 0xff);
 #else
-            uint8x8_t shuf1l = vget_low_u8(shuf1);
-            uint8x8_t shuf1h = vget_high_u8(shuf1);
-            uint8x8_t shuf2l = vget_low_u8(shuf2);
-            uint8x8_t shuf2h = vget_high_u8(shuf2);
-            uint8x8_t shuf3l = vget_low_u8(shuf3);
-            uint8x8_t shuf3h = vget_high_u8(shuf3);
-            uint8x8_t shuf4l = vget_low_u8(shuf4);
-            uint8x8_t shuf4h = vget_high_u8(shuf4);
-            uint8x16_t data1A = vcombine_u8(vtbx1_u8(shuf1l, vget_low_u8(dataA), shuf1l),
-                                            vtbx1_u8(shuf1h, vget_low_u8(dataA), shuf1h));
-            uint8x16_t data2A = vcombine_u8(vtbx1_u8(shuf2l, vget_high_u8(dataA), shuf2l),
-                                            vtbx1_u8(shuf2h, vget_high_u8(dataA), shuf2h));
-            uint8x16_t data1B = vcombine_u8(vtbx1_u8(shuf3l, vget_low_u8(dataB), shuf3l),
-                                            vtbx1_u8(shuf3h, vget_low_u8(dataB), shuf3h));
-            uint8x16_t data2B = vcombine_u8(vtbx1_u8(shuf4l, vget_high_u8(dataB), shuf4l),
-                                            vtbx1_u8(shuf4h, vget_high_u8(dataB), shuf4h));
+		// no vpaddq_u8 in ARMv7, so need extra 64-bit VPADD
+		uint8x8_t cmpPacked = vpadd_u8(
+			vpadd_u8(
+				vget_low_u8(cmpAMasked), vget_high_u8(cmpAMasked)
+			),
+			vpadd_u8(
+				vget_low_u8(cmpBMasked), vget_high_u8(cmpBMasked)
+			)
+		);
+		cmpPacked = vpadd_u8(cmpPacked, cmpPacked);
+		uint32_t mask = vget_lane_u32(vreinterpret_u32_u8(cmpPacked), 0);
+		if(LIKELIHOOD(0.09, (mask & (mask-1)) != 0)) {
+			uint8_t m1 = (mask & 0xff);
+			uint8_t m2 = ((mask >> 8) & 0xff);
+			uint8_t m3 = ((mask >> 16) & 0xff);
+			uint8_t m4 = ((mask >> 24) & 0xff);
 #endif
-
-            // store out
-            uint32_t counts = vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
-            counts += 0x08080808;
-
-            unsigned char shuf1Len = counts & 0xff;
-            unsigned char shuf2Len = (counts>>8) & 0xff;
-            unsigned char shuf3Len = (counts>>16) & 0xff;
-            unsigned char shuf4Len = (counts>>24) & 0xff;
-            uint32_t shufTotalLen = counts * 0x1010101;
-            shufTotalLen >>= 24;
-
-            vst1q_u8(p, data1A);
-            p += shuf1Len;
-            vst1q_u8(p, data2A);
-            p += shuf2Len;
-            vst1q_u8(p, data1B);
-            p += shuf3Len;
-            vst1q_u8(p, data2B);
-            p += shuf4Len;
-            col += shufTotalLen;
-
-            if(LIKELIHOOD(0.3, col >= 0)) {
-                // we overflowed - find correct position to revert back to
-                long revert = col;
-                long len2ndHalf = shuf3Len+shuf4Len;
-                long shiftAmt = len2ndHalf - col -1;
-                uint32_t eqMaskHalf;
-                if(HEDLEY_UNLIKELY(shiftAmt < 0)) {
-                    eqMaskHalf = (expandLUT[m2] << shuf1Len) | expandLUT[m1];
-                    eqMaskHalf >>= shufTotalLen - col -1;
-                    i += len2ndHalf - 16;
-                } else {
-                    eqMaskHalf = (expandLUT[m4] << shuf3Len) | expandLUT[m3];
-                    eqMaskHalf >>= shiftAmt;
-                }
-                revert += eqMaskHalf & 1;
-
-                // count bits in eqMask
-                uint8x8_t vCnt = vcnt_u8(vreinterpret_u8_u32(vmov_n_u32(eqMaskHalf)));
-                uint32_t cnt = vget_lane_u32(vreinterpret_u32_u8(vCnt), 0);
-                cnt *= 0x1010101;
-                i += cnt >> 24;
-
-                p -= revert;
-                i -= revert;
-                goto _encode_eol_handle_pre;
-            }
-        } else {
-            {
+			
+			// perform lookup for shuffle mask
+			uint8x16_t shuf1 = vld1q_u8((uint8_t*)(shufLUT + m1));
+			uint8x16_t shuf2 = vld1q_u8((uint8_t*)(shufLUT + m2));
+			uint8x16_t shuf3 = vld1q_u8((uint8_t*)(shufLUT + m3));
+			uint8x16_t shuf4 = vld1q_u8((uint8_t*)(shufLUT + m4));
+			
+			// expand halves
 #ifdef __aarch64__
-# ifdef _MSC_VER
-                // does this work?
-                if(_BitScanReverse64((unsigned long*)&bitIndex, mask))
-                    bitIndex ^= 63;
-                else
-                    bitIndex = 64;
-# else
-                bitIndex = __builtin_clzll(mask); // TODO: is the 'undefined if 0' case problematic here?
-# endif
+			uint8x16_t data1A = vqtbx1q_u8(shuf1, dataA, shuf1);
+			uint8x16_t data2A = vqtbx1q_u8(shuf2, vextq_u8(dataA, dataA, 8), shuf2);
+			uint8x16_t data1B = vqtbx1q_u8(shuf3, dataB, shuf3);
+			uint8x16_t data2B = vqtbx1q_u8(shuf4, vextq_u8(dataB, dataB, 8), shuf4);
 #else
-# ifdef __GNUC__
-                bitIndex = __builtin_clz(mask);
-# elif defined(_MSC_VER)
-                bitIndex = _arm_clz(mask);
-# else
-                bitIndex = __clz(mask); // ARM compiler?
-# endif
+			uint8x8_t shuf1l = vget_low_u8(shuf1);
+			uint8x8_t shuf1h = vget_high_u8(shuf1);
+			uint8x8_t shuf2l = vget_low_u8(shuf2);
+			uint8x8_t shuf2h = vget_high_u8(shuf2);
+			uint8x8_t shuf3l = vget_low_u8(shuf3);
+			uint8x8_t shuf3h = vget_high_u8(shuf3);
+			uint8x8_t shuf4l = vget_low_u8(shuf4);
+			uint8x8_t shuf4h = vget_high_u8(shuf4);
+			uint8x16_t data1A = vcombine_u8(vtbx1_u8(shuf1l, vget_low_u8(dataA), shuf1l),
+			                                vtbx1_u8(shuf1h, vget_low_u8(dataA), shuf1h));
+			uint8x16_t data2A = vcombine_u8(vtbx1_u8(shuf2l, vget_high_u8(dataA), shuf2l),
+			                                vtbx1_u8(shuf2h, vget_high_u8(dataA), shuf2h));
+			uint8x16_t data1B = vcombine_u8(vtbx1_u8(shuf3l, vget_low_u8(dataB), shuf3l),
+			                                vtbx1_u8(shuf3h, vget_low_u8(dataB), shuf3h));
+			uint8x16_t data2B = vcombine_u8(vtbx1_u8(shuf4l, vget_high_u8(dataB), shuf4l),
+			                                vtbx1_u8(shuf4h, vget_high_u8(dataB), shuf4h));
 #endif
-
-                uint8x16_t vClz = vdupq_n_u8(bitIndex & ~(sizeof(mask)*8));
+			
+			// store out
+			uint32_t counts = vget_lane_u32(vreinterpret_u32_u8(vcnt_u8(cmpPacked)), 0);
+			counts += 0x08080808;
+			
+			unsigned char shuf1Len = counts & 0xff;
+			unsigned char shuf2Len = (counts>>8) & 0xff;
+			unsigned char shuf3Len = (counts>>16) & 0xff;
+			unsigned char shuf4Len = (counts>>24) & 0xff;
+			uint32_t shufTotalLen = counts * 0x1010101;
+			shufTotalLen >>= 24;
+			
+			vst1q_u8(p, data1A);
+			p += shuf1Len;
+			vst1q_u8(p, data2A);
+			p += shuf2Len;
+			vst1q_u8(p, data1B);
+			p += shuf3Len;
+			vst1q_u8(p, data2B);
+			p += shuf4Len;
+			col += shufTotalLen;
+			
+			if(LIKELIHOOD(0.3, col >= 0)) {
+				// we overflowed - find correct position to revert back to
+				long revert = col;
+				long len2ndHalf = shuf3Len+shuf4Len;
+				long shiftAmt = len2ndHalf - col -1;
+				uint32_t eqMaskHalf;
+				if(HEDLEY_UNLIKELY(shiftAmt < 0)) {
+					eqMaskHalf = (expandLUT[m2] << shuf1Len) | expandLUT[m1];
+					eqMaskHalf >>= shufTotalLen - col -1;
+					i += len2ndHalf - 16;
+				} else {
+					eqMaskHalf = (expandLUT[m4] << shuf3Len) | expandLUT[m3];
+					eqMaskHalf >>= shiftAmt;
+				}
+				revert += eqMaskHalf & 1;
+				
+				// count bits in eqMask
+				uint8x8_t vCnt = vcnt_u8(vreinterpret_u8_u32(vmov_n_u32(eqMaskHalf)));
+				uint32_t cnt = vget_lane_u32(vreinterpret_u32_u8(vCnt), 0);
+				cnt *= 0x1010101;
+				i += cnt >> 24;
+				
+				p -= revert;
+				i -= revert;
+				goto _encode_eol_handle_pre;
+			}
+		} else {
+			{
+				bitIndex = clz_n(mask);
+				uint8x16_t vClz = vdupq_n_u8(bitIndex & ~(sizeof(mask)*8));
 #ifdef __aarch64__
-                uint8x16_t blendA = vcgeq_u8(vmakeq_u8(63,62,61,60,51,50,49,48,47,46,45,44,35,34,33,32), vClz);
-                uint8x16_t blendB = vcgeq_u8(vmakeq_u8(31,30,29,28,19,18,17,16,15,14,13,12, 3, 2, 1, 0), vClz);
+				uint8x16_t blendA = vcgeq_u8(vmakeq_u8(63,62,61,60,51,50,49,48,47,46,45,44,35,34,33,32), vClz);
+				uint8x16_t blendB = vcgeq_u8(vmakeq_u8(31,30,29,28,19,18,17,16,15,14,13,12, 3, 2, 1, 0), vClz);
 #else
-                uint8x16_t blendA = vcgeq_u8(vmakeq_u8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16), vClz);
-                uint8x16_t blendB = vcgeq_u8(vmakeq_u8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), vClz);
+				uint8x16_t blendA = vcgeq_u8(vmakeq_u8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16), vClz);
+				uint8x16_t blendB = vcgeq_u8(vmakeq_u8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), vClz);
 #endif
-                uint8x16_t dataAShifted = vextq_u8(dataA, dataA, 15);
-                uint8x16_t dataBShifted = vextq_u8(dataA, dataB, 15);
-                dataA = vbslq_u8(cmpA, vdupq_n_u8('='), dataA);
-                uint8x16_t outDataB = vbslq_u8(cmpB, vdupq_n_u8('='), dataB);
-                dataA = vbslq_u8(blendA, dataA, dataAShifted);
-                outDataB = vbslq_u8(blendB, outDataB, dataBShifted);
-
-                vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, outDataB));
-                p += sizeof(uint8x16_t)*2;
-                // write last byte
-                *p = vgetq_lane_u8(dataB, 15);
-                p += (mask != 0);
-                col += (mask != 0) + sizeof(uint8x16_t)*2;
-            }
-
-            if(HEDLEY_UNLIKELY(col >= 0)) {
+				uint8x16_t dataAShifted = vextq_u8(dataA, dataA, 15);
+				uint8x16_t dataBShifted = vextq_u8(dataA, dataB, 15);
+				dataA = vbslq_u8(cmpA, vdupq_n_u8('='), dataA);
+				uint8x16_t outDataB = vbslq_u8(cmpB, vdupq_n_u8('='), dataB);
+				dataA = vbslq_u8(blendA, dataA, dataAShifted);
+				outDataB = vbslq_u8(blendB, outDataB, dataBShifted);
+				
+				vst1q_u8_x2_unaligned(p, vcreate2_u8(dataA, outDataB));
+				p += sizeof(uint8x16_t)*2;
+				// write last byte
+				*p = vgetq_lane_u8(dataB, 15);
+				p += (mask != 0);
+				col += (mask != 0) + sizeof(uint8x16_t)*2;
+			}
+			
+			if(HEDLEY_UNLIKELY(col >= 0)) {
 #ifdef __aarch64__
-                // fixup bitIndex
-                bitIndex -= ((bitIndex+4)>>4)<<3;
+				// fixup bitIndex
+				bitIndex -= ((bitIndex+4)>>4)<<3;
 #endif
-                bitIndex = bitIndex +1;
-                if(HEDLEY_UNLIKELY(col == bitIndex)) {
-                    // this is an escape character, so line will need to overflow
-                    p--;
-                } else {
-                    i += (col > bitIndex);
-                }
-                p -= col;
-                i -= col;
-
-                _encode_eol_handle_pre:
-                encode_eol_handle_pre(es, i, p, col, lineSizeOffset);
-            }
-        }
-    }
-
-    *colOffset = col + line_size -1;
-    dest = p;
-    len = -(i - INPUT_OFFSET);
+				bitIndex = bitIndex +1;
+				if(HEDLEY_UNLIKELY(col == bitIndex)) {
+					// this is an escape character, so line will need to overflow
+					p--;
+				} else {
+					i += (col > bitIndex);
+				}
+				p -= col;
+				i -= col;
+				
+				_encode_eol_handle_pre:
+				encode_eol_handle_pre(es, i, p, col, lineSizeOffset);
+			}
+		}
+	}
+	
+	*colOffset = col + line_size -1;
+	dest = p;
+	len = -(i - INPUT_OFFSET);
 }
-
-void encoder_neon_init() {
-    _do_encode = &do_encode_simd<do_encode_neon>;
-    // generate shuf LUT
-    for(int i=0; i<256; i++) {
-        int k = i;
-        uint16_t expand = 0;
-        uint8_t* res = (uint8_t*)(shufLUT + i);
-        int p = 0;
-        for(int j=0; j<8; j++) {
-            if(k & 1) {
-                res[j+p] = '=';
-                expand |= 1<<(j+p);
-                p++;
-            }
-            res[j+p] = j;
-            k >>= 1;
-        }
-        for(; p<8; p++)
-            res[8+p] = 8+p +0x80; // +0x80 => 0 discarded entries; has no effect other than to ease debugging
-
-        expandLUT[i] = expand;
-    }
+} // namespace
+
+void RapidYenc::encoder_neon_init() {
+	_do_encode = &do_encode_simd<do_encode_neon>;
+	_encode_isa = ISA_LEVEL_NEON;
+	// generate shuf LUT
+	for(int i=0; i<256; i++) {
+		int k = i;
+		uint16_t expand = 0;
+		uint8_t* res = (uint8_t*)(shufLUT + i);
+		int p = 0;
+		for(int j=0; j<8; j++) {
+			if(k & 1) {
+				res[j+p] = '=';
+				expand |= 1<<(j+p);
+				p++;
+			}
+			res[j+p] = j;
+			k >>= 1;
+		}
+		for(; p<8; p++)
+			res[8+p] = 8+p +0x80; // +0x80 => 0 discarded entries; has no effect other than to ease debugging
+		
+		expandLUT[i] = expand;
+	}
 }
 #else
-
-void encoder_neon_init() {}
-
+void RapidYenc::encoder_neon_init() {}
 #endif /* defined(__ARM_NEON) */
diff --git a/src/yencode/encoder_rvv.cc b/src/yencode/encoder_rvv.cc
new file mode 100644
index 0000000..8ad7877
--- /dev/null
+++ b/src/yencode/encoder_rvv.cc
@@ -0,0 +1,207 @@
+#include "common.h"
+#include "encoder_common.h"
+
+#ifdef __riscv_vector
+#include "encoder.h"
+
+
+static HEDLEY_ALWAYS_INLINE void encode_eol_handle_pre(const uint8_t* HEDLEY_RESTRICT _src, long& inpos, uint8_t*& outp, long& col, long lineSizeOffset) {
+	// TODO: vectorize
+	uint8_t c = _src[inpos++];
+	if(HEDLEY_UNLIKELY(RapidYenc::escapedLUT[c] && c != '.'-42)) {
+		memcpy(outp, &RapidYenc::escapedLUT[c], sizeof(uint16_t));
+		outp += 2;
+	} else {
+		*(outp++) = c + 42;
+	}
+	
+	c = _src[inpos++];
+	if(LIKELIHOOD(0.0273, RapidYenc::escapedLUT[c]!=0)) {
+		uint32_t w = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)RapidYenc::escapedLUT[c]);
+		memcpy(outp, &w, sizeof(w));
+		outp += 4;
+		col = lineSizeOffset + 2;
+	} else {
+		uint32_t w = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+		memcpy(outp, &w, sizeof(w));
+		outp += 3;
+		col = lineSizeOffset + 1;
+	}
+}
+
+namespace RapidYenc {
+
+HEDLEY_ALWAYS_INLINE void do_encode_rvv(int line_size, int* colOffset, const uint8_t* HEDLEY_RESTRICT srcEnd, uint8_t* HEDLEY_RESTRICT& dest, size_t& len) {
+	size_t vl2 = RV(vsetvlmax_e8m2)(); // TODO: limit to line length
+	// TODO: have a LMUL=1 variant if line_size < vl
+	
+	// offset position to enable simpler loop condition checking
+	const int INPUT_OFFSET = vl2*2 -1; // extra chars for EOL handling, -1 to change <= to <
+	if((intptr_t)len <= INPUT_OFFSET || line_size < (int)vl2*2) return;
+	
+	uint8_t *outp = dest;
+	long inpos = -(long)len;
+	long lineSizeOffset = -line_size +1;
+	long col = *colOffset - line_size +1;
+	
+	inpos += INPUT_OFFSET;
+	const uint8_t* _src = srcEnd - INPUT_OFFSET;
+	
+	if (HEDLEY_LIKELY(col == -line_size+1)) {
+		uint8_t c = _src[inpos++];
+		if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
+			memcpy(outp, escapedLUT + c, 2);
+			outp += 2;
+			col += 2;
+		} else {
+			*(outp++) = c + 42;
+			col += 1;
+		}
+	}
+	if(HEDLEY_UNLIKELY(col >= 0)) {
+		if(col == 0)
+			encode_eol_handle_pre(_src, inpos, outp, col, lineSizeOffset);
+		else {
+			uint8_t c = _src[inpos++];
+			if(LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
+				uint32_t v = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
+				memcpy(outp, &v, sizeof(v));
+				outp += 4;
+				col = 2-line_size + 1;
+			} else {
+				uint32_t v = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+				memcpy(outp, &v, sizeof(v));
+				outp += 3;
+				col = 2-line_size;
+			}
+		}
+	}
+	
+	// vector constants
+	const vuint8mf2_t ALT_SHIFT = RV(vreinterpret_v_u16mf2_u8mf2)(RV(vmv_v_x_u16mf2)(4, vl2));
+	const uint8_t _MASK_EXPAND[] = {0xAA, 0xAB, 0xAE, 0xAF, 0xBA, 0xBB, 0xBE, 0xBF, 0xEA, 0xEB, 0xEE, 0xEF, 0xFA, 0xFB, 0xFE, 0xFF};
+	const vuint8m1_t MASK_EXPAND = RV(vle8_v_u8m1)(_MASK_EXPAND, 16);
+	
+	
+	// TODO: consider exploiting partial vector capability
+	while(inpos < 0) {
+		vuint8m2_t data = RV(vle8_v_u8m2)(_src + inpos, vl2);
+		inpos += vl2;
+		
+		// search for special chars
+		// TODO: vrgather strat
+		
+		vuint8m2_t tmpData = RV(vsub_vx_u8m2)(data, -42, vl2);
+		vbool4_t cmp = RV(vmor_mm_b4)(
+			RV(vmor_mm_b4)(
+				RV(vmseq_vx_u8m2_b4)(data, -42, vl2),
+				RV(vmseq_vx_u8m2_b4)(tmpData, '=', vl2),
+				vl2
+			),
+			RV(vmor_mm_b4)(
+				RV(vmseq_vx_u8m2_b4)(data, '\r'-42, vl2),
+				RV(vmseq_vx_u8m2_b4)(data, '\n'-42, vl2),
+				vl2
+			),
+			vl2
+		);
+		
+#ifdef __riscv_v_intrinsic
+		data = RV(vor_vx_u8m2_mu)(cmp, tmpData, tmpData, 64, vl2);
+#else
+		data = RV(vor_vx_u8m2_m)(cmp, tmpData, tmpData, 64, vl2);
+#endif
+		
+		int idx;
+		size_t count = RV(vcpop_m_b4)(cmp, vl2);
+		if(count > 1) {
+			// widen mask: 4b->8b
+			vuint8mf4_t vcmp = RV_VEC_U8MF4_CAST(cmp);
+			// TODO: use vwsll instead if available
+			//    -  is clmul useful here?
+			vuint8mf2_t xcmp = RV(vreinterpret_v_u16mf2_u8mf2)(RV(vwmulu_vx_u16mf2)(vcmp, 16, vl2));
+			xcmp = RV(vsrl_vv_u8mf2)(xcmp, ALT_SHIFT, vl2);
+			
+			// expand mask by inserting '1' between each bit (0000abcd -> 1a1b1c1d)
+			vuint8m1_t xcmpTmp = RV(vrgather_vv_u8m1)(MASK_EXPAND, RV(vlmul_ext_v_u8mf2_u8m1)(xcmp), vl2);
+			vbool2_t cmpmask = RV_MASK_CAST(2, 8, xcmpTmp);
+			
+			// expand data and insert =
+			// TODO: use vwsll instead if available
+			vuint16m4_t data2 = RV(vzext_vf2_u16m4)(data, vl2);
+			data2 = RV(vsll_vx_u16m4)(data2, 8, vl2);
+			data2 = RV(vor_vx_u16m4)(data2, '=', vl2);
+			
+			// prune unneeded =
+			vuint8m4_t dataTmp = RV(vreinterpret_v_u16m4_u8m4)(data2);
+			vuint8m4_t final_data = RV(vcompress_vm_u8m4)(
+#ifdef __riscv_v_intrinsic
+				dataTmp, cmpmask, vl2*2
+#else
+				cmpmask, dataTmp, dataTmp, vl2*2
+#endif
+			);
+			
+			RV(vse8_v_u8m4)(outp, final_data, vl2*2);
+			outp += vl2 + count;
+			col += vl2 + count;
+			
+			if(col >= 0) {
+				// we overflowed - find correct position to revert back to
+				// TODO: stick with u8 type for vlmax <= 2048 (need to check if ok if vlmax == 2048)
+				//   - considering that it's rare for colWidth > 128, maybe just don't support vectors that long
+				vuint16m8_t xidx = RV(viota_m_u16m8)(cmpmask, vl2*2);
+				vbool2_t discardmask = RV(vmsgeu_vx_u16m8_b2)(xidx, vl2 + count - col, vl2*2);
+				long idx_revert = RV(vcpop_m_b2)(discardmask, vl2*2);
+				
+				outp -= col + (idx_revert & 1);
+				inpos -= ((idx_revert+1) >> 1);
+				
+				goto _encode_eol_handle_pre;
+			}
+		} else {
+			// 0 or 1 special characters
+			{
+				vbool4_t mask = RV(vmsbf_m_b4)(cmp, vl2);
+				// TODO: is it better to shuffle this into two stores, instead of three?
+				RV(vse8_v_u8m2_m)(mask, outp, data, vl2);
+				idx = RV(vcpop_m_b4)(mask, vl2);
+				outp[idx] = '=';
+				RV(vse8_v_u8m2_m)(RV(vmnot_m_b4)(mask, vl2), outp+1, data, vl2);
+				
+				outp += vl2 + count;
+				col += vl2 + count;
+			}
+			
+			if(col >= 0) {
+				if(count > 0) {
+					idx = vl2 - idx;
+					if(HEDLEY_UNLIKELY(col == idx)) {
+						// this is an escape character, so line will need to overflow
+						outp--;
+					} else {
+						inpos += (col > idx);
+					}
+				}
+				outp -= col;
+				inpos -= col;
+				
+				_encode_eol_handle_pre:
+				encode_eol_handle_pre(_src, inpos, outp, col, lineSizeOffset);
+			}
+		}
+	}
+	
+	*colOffset = col + line_size -1;
+	dest = outp;
+	len = -(inpos - INPUT_OFFSET);
+}
+} // namespace
+
+void RapidYenc::encoder_rvv_init() {
+	_do_encode = &do_encode_simd<do_encode_rvv>;
+	_encode_isa = ISA_LEVEL_RVV;
+}
+#else
+void RapidYenc::encoder_rvv_init() {}
+#endif /* defined(__riscv_vector) */
diff --git a/src/yencode/encoder_sse2.cc b/src/yencode/encoder_sse2.cc
index c3aa824..015150f 100644
--- a/src/yencode/encoder_sse2.cc
+++ b/src/yencode/encoder_sse2.cc
@@ -1,15 +1,15 @@
 #include "common.h"
+#include "encoder_common.h"
 
 #ifdef __SSE2__
 #include "encoder_sse_base.h"
 
-void encoder_sse2_init() {
-    _do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSE2> >;
-    encoder_sse_lut<ISA_LEVEL_SSE2>();
+void RapidYenc::encoder_sse2_init() {
+	_do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSE2> >;
+	encoder_sse_lut<ISA_LEVEL_SSE2>();
+	_encode_isa = ISA_LEVEL_SSE2;
 }
 #else
-
-void encoder_sse2_init() {}
-
+void RapidYenc::encoder_sse2_init() {}
 #endif
 
diff --git a/src/yencode/encoder_sse_base.h b/src/yencode/encoder_sse_base.h
index 1858713..f6c18b4 100644
--- a/src/yencode/encoder_sse_base.h
+++ b/src/yencode/encoder_sse_base.h
@@ -16,73 +16,67 @@
 
 #pragma pack(16)
 static struct {
-    /*align16*/ struct {
-        __m128i shuf, mix;
-    } shufMix[256];
-    unsigned char BitsSetTable256plus8[256];
-    uint32_t eolLastChar[256];
-    uint16_t eolFirstMask[256];
-    uint16_t expandMask[256];
-    /*align16*/ int8_t expandMaskmix[33 * 2 * 32];
-    /*align16*/ int8_t expandShufmaskmix[33 * 2 * 32];
-} *HEDLEY_RESTRICT lookups;
+	/*align16*/ struct { __m128i shuf, mix; } shufMix[256];
+	unsigned char BitsSetTable256plus8[256];
+	uint32_t eolLastChar[256];
+	uint16_t eolFirstMask[256];
+	uint16_t expandMask[256];
+	/*align16*/ int8_t expandMaskmix[33*2*32];
+	/*align16*/ int8_t expandShufmaskmix[33*2*32];
+} * HEDLEY_RESTRICT lookups;
 #pragma pack()
 
 template<enum YEncDecIsaLevel use_isa>
 static void encoder_sse_lut() {
-    ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
-    for (int i = 0; i < 256; i++) {
-        int k = i;
-        uint8_t *res = (uint8_t * )(&(lookups->shufMix[i].shuf));
-        uint16_t expand = 0;
-        int p = 0;
-        for (int j = 0; j < 8; j++) {
-            if (k & 1) {
-                res[j + p] = 0xf0 + j;
-                p++;
-            }
-            expand |= 1 << (j + p);
-            res[j + p] = j;
-            k >>= 1;
-        }
-        for (; p < 8; p++)
-            res[8 + p] = 8 + p +
-                         0x40; // +0x40 is an arbitrary value to make debugging slightly easier?  the top bit cannot be set
-
-        lookups->expandMask[i] = expand;
-
-        // calculate add mask for mixing escape chars in
-        __m128i shuf = _mm_load_si128((__m128i *) res);
-        __m128i maskEsc = _mm_cmpeq_epi8(_mm_and_si128(shuf, _mm_set1_epi8(-16)), _mm_set1_epi8(-16)); // -16 == 0xf0
-        __m128i addMask = _mm_and_si128(_mm_slli_si128(maskEsc, 1), _mm_set1_epi8(64));
-        addMask = _mm_or_si128(addMask, _mm_and_si128(maskEsc, _mm_set1_epi8('=' - 42)));
-        addMask = _mm_add_epi8(addMask, _mm_set1_epi8(42));
-
-        _mm_store_si128(&(lookups->shufMix[i].mix), addMask);
-
-
-        lookups->eolLastChar[i] = ((i == 214 + '\t' || i == 214 + ' ' || i == 214 + '\0' || i == 214 + '\n' ||
-                                    i == 214 + '\r' || i == '=' - 42) ? (((i + 42 + 64) & 0xff) << 8) + 0x0a0d003d :
-                                   ((i + 42) & 0xff) + 0x0a0d00);
-        lookups->eolFirstMask[i] = ((i == 214 + '\t' || i == 214 + ' ' || i == '.' - 42) ? 1 : 0);
-
-        lookups->BitsSetTable256plus8[i] = 8 + (
-                (i & 1) + ((i >> 1) & 1) + ((i >> 2) & 1) + ((i >> 3) & 1) + ((i >> 4) & 1) + ((i >> 5) & 1) +
-                ((i >> 6) & 1) + ((i >> 7) & 1)
-        );
-    }
-    for (int i = 0; i < 33; i++) {
-        int n = (use_isa & ISA_FEATURE_LZCNT) ? (i == 32 ? 32 : 31 - i) : (i == 0 ? 32 : i - 1);
-        for (int j = 0; j < 32; j++) {
-            lookups->expandMaskmix[i * 64 + j] = (n > j ? -1 : 0);
-            if (j > 15) // mask part
-                lookups->expandShufmaskmix[i * 64 + j] = (n >= j ? -1 : 0);
-            else // shuffle part
-                lookups->expandShufmaskmix[i * 64 + j] = (n == j ? -1 : (j - (n < j)));
-            lookups->expandMaskmix[i * 64 + j + 32] = (n == j ? '=' : 42 + 64 * (n == j - 1));
-            lookups->expandShufmaskmix[i * 64 + j + 32] = (n == j ? '=' : 42 + 64 * (n == j - 1));
-        }
-    }
+	ALIGN_ALLOC(lookups, sizeof(*lookups), 16);
+	for(int i=0; i<256; i++) {
+		int k = i;
+		uint8_t* res = (uint8_t*)(&(lookups->shufMix[i].shuf));
+		uint16_t expand = 0;
+		int p = 0;
+		for(int j=0; j<8; j++) {
+			if(k & 1) {
+				res[j+p] = 0xf0 + j;
+				p++;
+			}
+			expand |= 1<<(j+p);
+			res[j+p] = j;
+			k >>= 1;
+		}
+		for(; p<8; p++)
+			res[8+p] = 8+p +0x40; // +0x40 is an arbitrary value to make debugging slightly easier?  the top bit cannot be set
+		
+		lookups->expandMask[i] = expand;
+		
+		// calculate add mask for mixing escape chars in
+		__m128i shuf = _mm_load_si128((__m128i*)res);
+		__m128i maskEsc = _mm_cmpeq_epi8(_mm_and_si128(shuf, _mm_set1_epi8(-16)), _mm_set1_epi8(-16)); // -16 == 0xf0
+		__m128i addMask = _mm_and_si128(_mm_slli_si128(maskEsc, 1), _mm_set1_epi8(64));
+		addMask = _mm_or_si128(addMask, _mm_and_si128(maskEsc, _mm_set1_epi8('='-42)));
+		addMask = _mm_add_epi8(addMask, _mm_set1_epi8(42));
+		
+		_mm_store_si128(&(lookups->shufMix[i].mix), addMask);
+		
+		
+		lookups->eolLastChar[i] = ((i == 214+'\t' || i == 214+' ' || i == 214+'\0' || i == 214+'\n' || i == 214+'\r' || i == '='-42) ? (((i+42+64)&0xff)<<8)+0x0a0d003d : ((i+42)&0xff)+0x0a0d00);
+		lookups->eolFirstMask[i] = ((i == 214+'\t' || i == 214+' ' || i == '.'-42) ? 1 : 0);
+		
+		lookups->BitsSetTable256plus8[i] = 8 + (
+			(i & 1) + ((i>>1) & 1) + ((i>>2) & 1) + ((i>>3) & 1) + ((i>>4) & 1) + ((i>>5) & 1) + ((i>>6) & 1) + ((i>>7) & 1)
+		);
+	}
+	for(int i=0; i<33; i++) {
+		int n = (use_isa & ISA_FEATURE_LZCNT) ? (i == 32 ? 32 : 31-i) : (i == 0 ? 32 : i-1);
+		for(int j=0; j<32; j++) {
+			lookups->expandMaskmix[i*64 + j] = (n>j ? -1 : 0);
+			if(j > 15) // mask part
+				lookups->expandShufmaskmix[i*64 + j] = (n>=j ? -1 : 0);
+			else // shuffle part
+				lookups->expandShufmaskmix[i*64 + j] = (n==j ? -1 : (j-(n<j)));
+			lookups->expandMaskmix[i*64 + j + 32] = (n==j ? '=' : 42+64*(n==j-1));
+			lookups->expandShufmaskmix[i*64 + j + 32] = (n==j ? '=' : 42+64*(n==j-1));
+		}
+	}
 }
 
 
@@ -91,646 +85,641 @@ static void encoder_sse_lut() {
 # include <intrin.h>
 # include <ammintrin.h>
 static HEDLEY_ALWAYS_INLINE unsigned BSR32(unsigned src) {
-    unsigned long result;
-    _BitScanReverse((unsigned long*)&result, src);
-    return result;
+	unsigned long result;
+	_BitScanReverse((unsigned long*)&result, src);
+	return result;
 }
 #elif defined(__GNUC__)
 // have seen Clang not like _bit_scan_reverse
 # include <x86intrin.h> // for lzcnt
 # define BSR32(src) (31^__builtin_clz(src))
 #else
-
 # include <x86intrin.h>
-
 # define BSR32 _bit_scan_reverse
 #endif
 
 template<enum YEncDecIsaLevel use_isa>
 static HEDLEY_ALWAYS_INLINE __m128i sse2_expand_bytes(unsigned mask, __m128i data) {
-    while (mask) {
-        // get highest bit
-        unsigned bitIndex;
-        __m128i mergeMask;
+	while(mask) {
+		// get highest bit
+		unsigned bitIndex;
+		__m128i mergeMask;
 #if defined(__LZCNT__)
-        if(use_isa & ISA_FEATURE_LZCNT) {
-            bitIndex = _lzcnt_u32(mask);
-            mergeMask = _mm_load_si128((const __m128i*)lookups->expandMaskmix + bitIndex*4);
-            mask &= 0x7fffffffU>>bitIndex;
-        } else
+		if(use_isa & ISA_FEATURE_LZCNT) {
+			bitIndex = _lzcnt_u32(mask);
+			mergeMask = _mm_load_si128((const __m128i*)lookups->expandMaskmix + bitIndex*4);
+			mask &= 0x7fffffffU>>bitIndex;
+		} else
 #endif
-        {
-            // TODO: consider LUT for when BSR is slow
-            bitIndex = BSR32(mask);
-            mergeMask = _mm_load_si128((const __m128i *) lookups->expandMaskmix + (bitIndex + 1) * 4);
-            mask ^= 1 << bitIndex;
-        }
-        // perform expansion
-        data = _mm_or_si128(
-                _mm_and_si128(mergeMask, data),
-                _mm_slli_si128(_mm_andnot_si128(mergeMask, data), 1)
-        );
-    }
-    return data;
+		{
+			// TODO: consider LUT for when BSR is slow
+			bitIndex = BSR32(mask);
+			mergeMask = _mm_load_si128((const __m128i*)lookups->expandMaskmix + (bitIndex+1)*4);
+			mask ^= 1<<bitIndex;
+		}
+		// perform expansion
+		data = _mm_or_si128(
+			_mm_and_si128(mergeMask, data),
+			_mm_slli_si128(_mm_andnot_si128(mergeMask, data), 1)
+		);
+	}
+	return data;
 }
 
 template<enum YEncDecIsaLevel use_isa>
-static HEDLEY_ALWAYS_INLINE uintptr_t
-sse2_expand_store_vector(__m128i data, unsigned int mask, unsigned maskP1, unsigned maskP2, uint8_t *p,
-                         unsigned int &shufLenP1, unsigned int &shufLenP2) {
-    // TODO: consider 1 bit shortcut (slightly tricky with needing bit counts though)
-    if (mask) {
-        __m128i dataA = sse2_expand_bytes<use_isa>(maskP1, data);
-        __m128i dataB = sse2_expand_bytes<use_isa>(maskP2, _mm_srli_si128(data, 8));
-        dataA = _mm_add_epi8(dataA, _mm_load_si128(&(lookups->shufMix[maskP1].mix)));
-        dataB = _mm_add_epi8(dataB, _mm_load_si128(&(lookups->shufMix[maskP2].mix)));
-        shufLenP1 = lookups->BitsSetTable256plus8[maskP1];
-        shufLenP2 = shufLenP1 + lookups->BitsSetTable256plus8[maskP2];
-        STOREU_XMM(p, dataA);
-        STOREU_XMM(p + shufLenP1, dataB);
-        return shufLenP2;
-    } else {
-        STOREU_XMM(p, _mm_sub_epi8(data, _mm_set1_epi8(-42)));
-        shufLenP1 = 8;
-        shufLenP2 = 16;
-        return XMM_SIZE;
-    }
+static HEDLEY_ALWAYS_INLINE uintptr_t sse2_expand_store_vector(__m128i data, unsigned int mask, unsigned maskP1, unsigned maskP2, uint8_t* p, unsigned int& shufLenP1, unsigned int& shufLenP2) {
+	// TODO: consider 1 bit shortcut (slightly tricky with needing bit counts though)
+	if(mask) {
+		__m128i dataA = sse2_expand_bytes<use_isa>(maskP1, data);
+		__m128i dataB = sse2_expand_bytes<use_isa>(maskP2, _mm_srli_si128(data, 8));
+		dataA = _mm_add_epi8(dataA, _mm_load_si128(&(lookups->shufMix[maskP1].mix)));
+		dataB = _mm_add_epi8(dataB, _mm_load_si128(&(lookups->shufMix[maskP2].mix)));
+		shufLenP1 = lookups->BitsSetTable256plus8[maskP1];
+		shufLenP2 = shufLenP1 + lookups->BitsSetTable256plus8[maskP2];
+		STOREU_XMM(p, dataA);
+		STOREU_XMM(p+shufLenP1, dataB);
+		return shufLenP2;
+	} else {
+		STOREU_XMM(p, _mm_sub_epi8(data, _mm_set1_epi8(-42)));
+		shufLenP1 = 8;
+		shufLenP2 = 16;
+		return XMM_SIZE;
+	}
 }
 
+namespace RapidYenc {
 
 template<enum YEncDecIsaLevel use_isa>
-HEDLEY_ALWAYS_INLINE void
-do_encode_sse(int line_size, int *colOffset, const uint8_t *HEDLEY_RESTRICT srcEnd, uint8_t *HEDLEY_RESTRICT &dest,
-              size_t &len) {
-    // offset position to enable simpler loop condition checking
-    const int INPUT_OFFSET = XMM_SIZE * 4 + 1 - 1; // EOL handling reads an additional byte, -1 to change <= to <
-    if (len <= INPUT_OFFSET || line_size < XMM_SIZE) return;
-
-    // slower CPUs prefer to branch as mispredict penalty is probably small relative to general execution
+HEDLEY_ALWAYS_INLINE void do_encode_sse(int line_size, int* colOffset, const uint8_t* HEDLEY_RESTRICT srcEnd, uint8_t* HEDLEY_RESTRICT& dest, size_t& len) {
+	// offset position to enable simpler loop condition checking
+	const int INPUT_OFFSET = XMM_SIZE*4+1 -1; // EOL handling reads an additional byte, -1 to change <= to <
+	if(len <= INPUT_OFFSET || line_size < XMM_SIZE) return;
+	
+	// slower CPUs prefer to branch as mispredict penalty is probably small relative to general execution
 #if defined(__tune_atom__) || defined(__tune_slm__) || defined(__tune_btver1__) || defined(__tune_btver2__)
-    const bool _PREFER_BRANCHING = true;
+	const bool _PREFER_BRANCHING = true;
 #else
-    const bool _PREFER_BRANCHING = (use_isa < ISA_LEVEL_SSSE3);
+	const bool _PREFER_BRANCHING = (use_isa < ISA_LEVEL_SSSE3);
 #endif
-
-    uint8_t *p = dest; // destination pointer
-    intptr_t i = -(intptr_t) len; // input position
-    intptr_t lineSizeOffset = -line_size + 1;
-    //intptr_t col = *colOffset - line_size +1; // for some reason, this causes GCC-8 to spill an extra register, causing the main loop to run ~5% slower, so use the alternative version below
-    intptr_t col = *colOffset + lineSizeOffset;
-
-    i += INPUT_OFFSET;
-    const uint8_t *es = srcEnd - INPUT_OFFSET;
-
-    if (HEDLEY_UNLIKELY(col >= 0)) {
-        uint8_t c = es[i++];
-        if (col == 0) {
-            uint32_t eolChar = lookups->eolLastChar[c];
-            *(uint32_t *) p = eolChar;
-            p += 3 + (eolChar >> 27);
-            col = -line_size + 1;
-        } else {
-            if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
-                *(uint32_t *) p = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t) escapedLUT[c]);
-                p += 4;
-                col = 2 - line_size + 1;
-            } else {
-                *(uint32_t *) p = UINT32_PACK('\r', '\n', (uint32_t)(c + 42), 0);
-                p += 3;
-                col = 2 - line_size;
-            }
-        }
-    }
-    if (HEDLEY_LIKELY(col == -line_size + 1)) {
-        uint8_t c = es[i++];
-        if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
-            *(uint16_t *) p = escapedLUT[c];
-            p += 2;
-            col += 2;
-        } else {
-            *(p++) = c + 42;
-            col += 1;
-        }
-    }
-    do {
-        __m128i dataA = _mm_loadu_si128((__m128i * )(es + i)); // probably not worth the effort to align
-        __m128i dataB = _mm_loadu_si128((__m128i * )(es + i) + 1);
-
-        i += XMM_SIZE * 2;
-        // search for special chars
-        __m128i cmpA, cmpB;
+	
+	uint8_t *p = dest; // destination pointer
+	intptr_t i = -(intptr_t)len; // input position
+	intptr_t lineSizeOffset = -line_size +1;
+	//intptr_t col = *colOffset - line_size +1; // for some reason, this causes GCC-8 to spill an extra register, causing the main loop to run ~5% slower, so use the alternative version below
+	intptr_t col = *colOffset + lineSizeOffset;
+	
+	i += INPUT_OFFSET;
+	const uint8_t* es = srcEnd - INPUT_OFFSET;
+	
+	if(HEDLEY_UNLIKELY(col >= 0)) {
+		uint8_t c = es[i++];
+		if(col == 0) {
+			uint32_t eolChar = lookups->eolLastChar[c];
+			*(uint32_t*)p = eolChar;
+			p += 3 + (eolChar>>27);
+			col = -line_size+1;
+		} else {
+			if (LIKELIHOOD(0.0273, escapedLUT[c]!=0)) {
+				*(uint32_t*)p = UINT32_16_PACK(UINT16_PACK('\r', '\n'), (uint32_t)escapedLUT[c]);
+				p += 4;
+				col = 2-line_size + 1;
+			} else {
+				*(uint32_t*)p = UINT32_PACK('\r', '\n', (uint32_t)(c+42), 0);
+				p += 3;
+				col = 2-line_size;
+			}
+		}
+	}
+	if (HEDLEY_LIKELY(col == -line_size+1)) {
+		uint8_t c = es[i++];
+		if (LIKELIHOOD(0.0273, escapedLUT[c] != 0)) {
+			*(uint16_t*)p = escapedLUT[c];
+			p += 2;
+			col += 2;
+		} else {
+			*(p++) = c + 42;
+			col += 1;
+		}
+	}
+	do {
+		__m128i dataA = _mm_loadu_si128((__m128i *)(es + i)); // probably not worth the effort to align
+		__m128i dataB = _mm_loadu_si128((__m128i *)(es + i) +1);
+		
+		i += XMM_SIZE*2;
+		// search for special chars
+		__m128i cmpA, cmpB;
 #if defined(__SSSE3__) && !defined(__tune_atom__) && !defined(__tune_slm__) && !defined(__tune_btver1__)
-        if(use_isa >= ISA_LEVEL_SSSE3) {
-            cmpA = _mm_cmpeq_epi8(
-                _mm_shuffle_epi8(_mm_set_epi8(
-                    '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                ), _mm_abs_epi8(dataA)),
-                dataA
-            );
-        } else
+		if(use_isa >= ISA_LEVEL_SSSE3) {
+			cmpA = _mm_cmpeq_epi8(
+				_mm_shuffle_epi8(_mm_set_epi8(
+					'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+				), _mm_abs_epi8(dataA)),
+				dataA
+			);
+		} else
 #endif
-        {
-            cmpA = _mm_or_si128(
-                    _mm_or_si128(
-                            _mm_cmpeq_epi8(dataA, _mm_set1_epi8(-42)),
-                            _mm_cmpeq_epi8(dataA, _mm_set1_epi8('\n' - 42))
-                    ),
-                    _mm_or_si128(
-                            _mm_cmpeq_epi8(dataA, _mm_set1_epi8('=' - 42)),
-                            _mm_cmpeq_epi8(dataA, _mm_set1_epi8('\r' - 42))
-                    )
-            );
-        }
-
-        _encode_loop_branchA:
-        unsigned int maskA = _mm_movemask_epi8(cmpA);
-        _encode_loop_branchB:
-
+		{
+			cmpA = _mm_or_si128(
+				_mm_or_si128(
+					_mm_cmpeq_epi8(dataA, _mm_set1_epi8(-42)),
+					_mm_cmpeq_epi8(dataA, _mm_set1_epi8('\n'-42))
+				),
+				_mm_or_si128(
+					_mm_cmpeq_epi8(dataA, _mm_set1_epi8('='-42)),
+					_mm_cmpeq_epi8(dataA, _mm_set1_epi8('\r'-42))
+				)
+			);
+		}
+		
+		_encode_loop_branchA:
+		unsigned int maskA = _mm_movemask_epi8(cmpA);
+		_encode_loop_branchB:
+		
 #if defined(__SSSE3__) && !defined(__tune_atom__) && !defined(__tune_slm__) && !defined(__tune_btver1__)
-        if(use_isa >= ISA_LEVEL_SSSE3) {
-            cmpB = _mm_cmpeq_epi8(
-                _mm_shuffle_epi8(_mm_set_epi8(
-                    '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                ), _mm_abs_epi8(dataB)),
-                dataB
-            );
-        } else
+		if(use_isa >= ISA_LEVEL_SSSE3) {
+			cmpB = _mm_cmpeq_epi8(
+				_mm_shuffle_epi8(_mm_set_epi8(
+					'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+				), _mm_abs_epi8(dataB)),
+				dataB
+			);
+		} else
 #endif
-        {
-            cmpB = _mm_or_si128(
-                    _mm_or_si128(
-                            _mm_cmpeq_epi8(dataB, _mm_set1_epi8(-42)),
-                            _mm_cmpeq_epi8(dataB, _mm_set1_epi8('\n' - 42))
-                    ),
-                    _mm_or_si128(
-                            _mm_cmpeq_epi8(dataB, _mm_set1_epi8('=' - 42)),
-                            _mm_cmpeq_epi8(dataB, _mm_set1_epi8('\r' - 42))
-                    )
-            );
-        }
-        unsigned int maskB = _mm_movemask_epi8(cmpB);
-
-        uint32_t mask = (maskB << 16) | maskA;
-        intptr_t bitIndex; // because you can't goto past variable declarations...
-        intptr_t maskBits, outputBytes;
-
-        bool manyBitsSet;
+		{
+			cmpB = _mm_or_si128(
+				_mm_or_si128(
+					_mm_cmpeq_epi8(dataB, _mm_set1_epi8(-42)),
+					_mm_cmpeq_epi8(dataB, _mm_set1_epi8('\n'-42))
+				),
+				_mm_or_si128(
+					_mm_cmpeq_epi8(dataB, _mm_set1_epi8('='-42)),
+					_mm_cmpeq_epi8(dataB, _mm_set1_epi8('\r'-42))
+				)
+			);
+		}
+		unsigned int maskB = _mm_movemask_epi8(cmpB);
+		
+		uint32_t mask = (maskB<<16) | maskA;
+		intptr_t bitIndex; // because you can't goto past variable declarations...
+		intptr_t maskBits, outputBytes;
+		
+		bool manyBitsSet;
 #if defined(__POPCNT__) && !defined(__tune_btver1__)
-        if(use_isa & ISA_FEATURE_POPCNT) {
-            maskBits = popcnt32(mask);
-            outputBytes = maskBits + XMM_SIZE*2;
-            manyBitsSet = maskBits > 1;
-        } else
+		if(use_isa & ISA_FEATURE_POPCNT) {
+			maskBits = popcnt32(mask);
+			outputBytes = maskBits + XMM_SIZE*2;
+			manyBitsSet = maskBits > 1;
+		} else
 #endif
-        {
-            manyBitsSet = (mask & (mask - 1)) != 0;
-        }
-
-        if (LIKELIHOOD(0.089, manyBitsSet)) {
-            _encode_loop_branch_slow:
-            unsigned m1 = maskA & 0xFF;
-            unsigned m2 = maskA >> 8;
-            unsigned m3 = maskB & 0xFF;
-            unsigned m4 = maskB >> 8;
-            unsigned int shuf1Len, shuf2Len, shuf3Len;
-            __m128i shuf1A, shuf1B, shuf2A, shuf2B; // only used for SSSE3 path
-            __m128i data1A, data1B, data2A, data2B;
-
+		{
+			manyBitsSet = (mask & (mask-1)) != 0;
+		}
+		
+		if (LIKELIHOOD(0.089, manyBitsSet)) {
+			_encode_loop_branch_slow:
+			unsigned m1 = maskA & 0xFF;
+			unsigned m2 = maskA >> 8;
+			unsigned m3 = maskB & 0xFF;
+			unsigned m4 = maskB >> 8;
+			unsigned int shuf1Len, shuf2Len, shuf3Len;
+			__m128i shuf1A, shuf1B, shuf2A, shuf2B; // only used for SSSE3 path
+			__m128i data1A, data1B, data2A, data2B;
+			
 #if defined(__AVX512VBMI2__) && defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_VBMI2) {
-                // do +42 and +64 to data
-                dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
-                dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8(64), 0xf8); // data | (cmp & 64)
-                dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
-                dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8(64), 0xf8);
-
-                /* alternative no-LUT 64-bit only version
-                 * LUT generally seems to be faster though
-                //uint64_t expandMask = _pdep_u64(mask, 0x5555555555555555); // expand bits
-                //expandMask = ~_pext_u64(expandMask, expandMask|~0x5555555555555555);
-                uint64_t expandMask = ~_pdep_u64(~mask, 0x5555555555555555); // expand bits, with bits set
-                expandMask = _pext_u64(expandMask^0x5555555555555555, expandMask);
-                data2A = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>16, _mm_srli_si128(dataA, 8));
-                data1A = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask    , dataA);
-                data2B = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>48, _mm_srli_si128(dataB, 8));
-                data1B = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>32, dataB);
-                */
-                data2A = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m2), _mm_srli_si128(dataA, 8));
-                data1A = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m1), dataA);
-                data2B = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m4), _mm_srli_si128(dataB, 8));
-                data1B = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m3), dataB);
-            } else
+			if(use_isa >= ISA_LEVEL_VBMI2) {
+				// do +42 and +64 to data
+				dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
+				dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8(64), 0xf8); // data | (cmp & 64)
+				dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
+				dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8(64), 0xf8);
+				
+				/* alternative no-LUT 64-bit only version
+				 * LUT generally seems to be faster though
+				//uint64_t expandMask = _pdep_u64(mask, 0x5555555555555555); // expand bits
+				//expandMask = ~_pext_u64(expandMask, expandMask|~0x5555555555555555);
+				uint64_t expandMask = ~_pdep_u64(~mask, 0x5555555555555555); // expand bits, with bits set
+				expandMask = _pext_u64(expandMask^0x5555555555555555, expandMask);
+				data2A = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>16, _mm_srli_si128(dataA, 8));
+				data1A = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask    , dataA);
+				data2B = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>48, _mm_srli_si128(dataB, 8));
+				data1B = _mm_mask_expand_epi8(_mm_set1_epi8('='), expandMask>>32, dataB);
+				*/
+				data2A = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m2), _mm_srli_si128(dataA, 8));
+				data1A = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m1), dataA);
+				data2B = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m4), _mm_srli_si128(dataB, 8));
+				data1B = _mm_mask_expand_epi8(_mm_set1_epi8('='), KLOAD16(lookups->expandMask, m3), dataB);
+			} else
 #endif
 #ifdef __SSSE3__
-            if(use_isa >= ISA_LEVEL_SSSE3) {
-                // perform lookup for shuffle mask
-                shuf1A = _mm_load_si128(&(lookups->shufMix[m1].shuf));
-                shuf2A = _mm_load_si128(&(lookups->shufMix[m2].shuf));
-                shuf1B = _mm_load_si128(&(lookups->shufMix[m3].shuf));
-                shuf2B = _mm_load_si128(&(lookups->shufMix[m4].shuf));
-
-                // second mask processes on second half, so add to the offsets
-                shuf2A = _mm_or_si128(shuf2A, _mm_set1_epi8(8));
-                shuf2B = _mm_or_si128(shuf2B, _mm_set1_epi8(8));
-
-                // expand halves
-                data2A = _mm_shuffle_epi8(dataA, shuf2A);
-                data1A = _mm_shuffle_epi8(dataA, shuf1A);
-                data2B = _mm_shuffle_epi8(dataB, shuf2B);
-                data1B = _mm_shuffle_epi8(dataB, shuf1B);
-
-                // add in escaped chars
-                data1A = _mm_add_epi8(data1A, _mm_load_si128(&(lookups->shufMix[m1].mix)));
-                data2A = _mm_add_epi8(data2A, _mm_load_si128(&(lookups->shufMix[m2].mix)));
-                data1B = _mm_add_epi8(data1B, _mm_load_si128(&(lookups->shufMix[m3].mix)));
-                data2B = _mm_add_epi8(data2B, _mm_load_si128(&(lookups->shufMix[m4].mix)));
-            } else
+			if(use_isa >= ISA_LEVEL_SSSE3) {
+				// perform lookup for shuffle mask
+				shuf1A = _mm_load_si128(&(lookups->shufMix[m1].shuf));
+				shuf2A = _mm_load_si128(&(lookups->shufMix[m2].shuf));
+				shuf1B = _mm_load_si128(&(lookups->shufMix[m3].shuf));
+				shuf2B = _mm_load_si128(&(lookups->shufMix[m4].shuf));
+				
+				// second mask processes on second half, so add to the offsets
+				shuf2A = _mm_or_si128(shuf2A, _mm_set1_epi8(8));
+				shuf2B = _mm_or_si128(shuf2B, _mm_set1_epi8(8));
+				
+				// expand halves
+				data2A = _mm_shuffle_epi8(dataA, shuf2A);
+				data1A = _mm_shuffle_epi8(dataA, shuf1A);
+				data2B = _mm_shuffle_epi8(dataB, shuf2B);
+				data1B = _mm_shuffle_epi8(dataB, shuf1B);
+				
+				// add in escaped chars
+				data1A = _mm_add_epi8(data1A, _mm_load_si128(&(lookups->shufMix[m1].mix)));
+				data2A = _mm_add_epi8(data2A, _mm_load_si128(&(lookups->shufMix[m2].mix)));
+				data1B = _mm_add_epi8(data1B, _mm_load_si128(&(lookups->shufMix[m3].mix)));
+				data2B = _mm_add_epi8(data2B, _mm_load_si128(&(lookups->shufMix[m4].mix)));
+			} else
 #endif
-            {
-                p += sse2_expand_store_vector<use_isa>(dataA, maskA, m1, m2, p, shuf1Len, shuf2Len);
-                unsigned int shuf4Len;
-                p += sse2_expand_store_vector<use_isa>(dataB, maskB, m3, m4, p, shuf3Len, shuf4Len);
-                shuf3Len += shuf2Len;
+			{
+				p += sse2_expand_store_vector<use_isa>(dataA, maskA, m1, m2, p, shuf1Len, shuf2Len);
+				unsigned int shuf4Len;
+				p += sse2_expand_store_vector<use_isa>(dataB, maskB, m3, m4, p, shuf3Len, shuf4Len);
+				shuf3Len += shuf2Len;
 #if !defined(__tune_btver1__)
-                if (!(use_isa & ISA_FEATURE_POPCNT))
+				if(!(use_isa & ISA_FEATURE_POPCNT))
 #endif
-                    outputBytes = shuf2Len + shuf4Len;
-            }
-
-            if (use_isa >= ISA_LEVEL_SSSE3) {
-                // store out
+					outputBytes = shuf2Len + shuf4Len;
+			}
+			
+			if(use_isa >= ISA_LEVEL_SSSE3) {
+				// store out
 #if defined(__POPCNT__) && !defined(__tune_btver1__)
-                if(use_isa & ISA_FEATURE_POPCNT) {
-                    shuf2Len = popcnt32(maskA) + 16;
-# if defined(__tune_znver3__) || defined(__tune_znver2__) || defined(__tune_znver1__) || defined(__tune_btver2__)
-                    shuf1Len = popcnt32(m1) + 8;
-                    shuf3Len = popcnt32(m3) + shuf2Len + 8;
+				if(use_isa & ISA_FEATURE_POPCNT) {
+					shuf2Len = popcnt32(maskA) + 16;
+# if defined(__tune_znver4__) || defined(__tune_znver3__) || defined(__tune_znver2__) || defined(__tune_znver1__) || defined(__tune_btver2__)
+					shuf1Len = popcnt32(m1) + 8;
+					shuf3Len = popcnt32(m3) + shuf2Len + 8;
 # else
-                    shuf1Len = lookups->BitsSetTable256plus8[m1];
-                    shuf3Len = lookups->BitsSetTable256plus8[m3] + shuf2Len;
+					shuf1Len = lookups->BitsSetTable256plus8[m1];
+					shuf3Len = lookups->BitsSetTable256plus8[m3] + shuf2Len;
 # endif
-                } else
+				} else
 #endif
-                {
-                    shuf1Len = lookups->BitsSetTable256plus8[m1];
-                    shuf2Len = shuf1Len + lookups->BitsSetTable256plus8[m2];
-                    shuf3Len = shuf2Len + lookups->BitsSetTable256plus8[m3];
-                    outputBytes = shuf3Len + lookups->BitsSetTable256plus8[m4];
-                }
-                STOREU_XMM(p, data1A);
-                STOREU_XMM(p + shuf1Len, data2A);
-                STOREU_XMM(p + shuf2Len, data1B);
-                STOREU_XMM(p + shuf3Len, data2B);
-                p += outputBytes;
-            }
-            col += outputBytes;
-
-            if (LIKELIHOOD(0.3 /*guess, using 128b lines*/, col >= 0)) {
-                uintptr_t bitCount;
-                intptr_t shiftAmt = (outputBytes - shuf2Len) - col - 1;
-                uint32_t eqMask;
-                if (HEDLEY_UNLIKELY(shiftAmt < 0)) {
-                    shiftAmt += shuf2Len;
-                    i -= 16;
-                    if (use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
-                        eqMask =
-                                ((uint32_t) lookups->expandMask[m2] << shuf1Len)
-                                | (uint32_t) lookups->expandMask[m1];
-                    } else {
-                        eqMask =
-                                ((uint32_t) _mm_movemask_epi8(shuf2A) << shuf1Len)
-                                | (uint32_t) _mm_movemask_epi8(shuf1A);
-                        i += outputBytes - shuf2Len;
-                    }
-                } else {
-                    if (use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
-                        eqMask =
-                                ((uint32_t) lookups->expandMask[m4] << (shuf3Len - shuf2Len))
-                                | (uint32_t) lookups->expandMask[m3];
-                    } else {
-                        eqMask =
-                                ((uint32_t) _mm_movemask_epi8(shuf2B) << (shuf3Len - shuf2Len))
-                                | (uint32_t) _mm_movemask_epi8(shuf1B);
-                    }
-                }
-
-                if (use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
+				{
+					shuf1Len = lookups->BitsSetTable256plus8[m1];
+					shuf2Len = shuf1Len + lookups->BitsSetTable256plus8[m2];
+					shuf3Len = shuf2Len + lookups->BitsSetTable256plus8[m3];
+					outputBytes = shuf3Len + lookups->BitsSetTable256plus8[m4];
+				}
+				STOREU_XMM(p, data1A);
+				STOREU_XMM(p+shuf1Len, data2A);
+				STOREU_XMM(p+shuf2Len, data1B);
+				STOREU_XMM(p+shuf3Len, data2B);
+				p += outputBytes;
+			}
+			col += outputBytes;
+			
+			if(LIKELIHOOD(0.3 /*guess, using 128b lines*/, col >= 0)) {
+				uintptr_t bitCount;
+				intptr_t shiftAmt = (outputBytes - shuf2Len) - col -1;
+				uint32_t eqMask;
+				if(HEDLEY_UNLIKELY(shiftAmt < 0)) {
+					shiftAmt += shuf2Len;
+					i -= 16;
+					if(use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
+						eqMask =
+						  ((uint32_t)lookups->expandMask[m2] << shuf1Len)
+						  | (uint32_t)lookups->expandMask[m1];
+					} else {
+						eqMask =
+						  ((uint32_t)_mm_movemask_epi8(shuf2A) << shuf1Len)
+						  | (uint32_t)_mm_movemask_epi8(shuf1A);
+						i += outputBytes - shuf2Len;
+					}
+				} else {
+					if(use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
+						eqMask =
+						  ((uint32_t)lookups->expandMask[m4] << (shuf3Len-shuf2Len))
+						  | (uint32_t)lookups->expandMask[m3];
+					} else {
+						eqMask =
+						  ((uint32_t)_mm_movemask_epi8(shuf2B) << (shuf3Len-shuf2Len))
+						  | (uint32_t)_mm_movemask_epi8(shuf1B);
+					}
+				}
+				
+				if(use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
 #if defined(__GNUC__)
-                    // be careful to avoid partial flag stalls on Intel P6 CPUs (SHR+ADC will likely stall)
+					// be careful to avoid partial flag stalls on Intel P6 CPUs (SHR+ADC will likely stall)
 # if !(defined(__tune_amdfam10__) || defined(__tune_k8__))
-                    if(use_isa >= ISA_LEVEL_VBMI2)
+					if(use_isa >= ISA_LEVEL_VBMI2)
 # endif
-                    {
-                        asm(
-                            "shrl $1, %[eqMask] \n"
-                            "shrl %%cl, %[eqMask] \n" // TODO: can use shrq to avoid above shift?
+					{
+						__asm__(
+							"shrl $1, %[eqMask] \n"
+							"shrl %%cl, %[eqMask] \n" // TODO: can use shrq to avoid above shift?
 # if defined(PLATFORM_AMD64) && !defined(__ILP32__)
-                            "adcq %q[col], %q[p] \n"
+							"adcq %q[col], %q[p] \n"
 # else
-                            "adcl %[col], %[p] \n"
+							"adcl %[col], %[p] \n"
 # endif
-                            : [eqMask]"+r"(eqMask), [p]"+r"(p)
-                            : "c"(shiftAmt), [col]"r"(~col)
-                        );
-                    }
+							: [eqMask]"+r"(eqMask), [p]"+r"(p)
+							: "c"(shiftAmt), [col]"r"(~col)
+						);
+					}
 # if !(defined(__tune_amdfam10__) || defined(__tune_k8__))
-                    else
+					else
 # else
-                    if(0)
+					if(0)
 # endif
 #endif
-                    {
-                        eqMask >>= shiftAmt;
-                        p -= col;
-                        if (LIKELIHOOD(0.98, (eqMask & 1) != 1))
-                            p--;
-                        else
-                            i++;
-                    }
-                } else {
-                    eqMask >>= shiftAmt;
-                    col += eqMask & 1; // revert if escape char
-                }
-
+					{
+						eqMask >>= shiftAmt;
+						p -= col;
+						if(LIKELIHOOD(0.98, (eqMask & 1) != 1))
+							p--;
+						else
+							i++;
+					}
+				} else {
+					eqMask >>= shiftAmt;
+					col += eqMask & 1; // revert if escape char
+				}
+				
 #if defined(__POPCNT__)
-                if(use_isa & ISA_FEATURE_POPCNT) {
-                    bitCount = popcnt32(eqMask);
-                } else
+				if(use_isa & ISA_FEATURE_POPCNT) {
+					bitCount = popcnt32(eqMask);
+				} else
 #endif
-                {
-                    unsigned char cnt = lookups->BitsSetTable256plus8[eqMask & 0xff];
-                    cnt += lookups->BitsSetTable256plus8[(eqMask >> 8) & 0xff];
-                    cnt += lookups->BitsSetTable256plus8[(eqMask >> 16) & 0xff];
-                    cnt += lookups->BitsSetTable256plus8[(eqMask >> 24) & 0xff];
-                    bitCount = (uintptr_t) cnt - 32;
-                }
-
-                if (use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
-                    i -= bitCount;
-                    goto _encode_eol_handle_pre;
-                } else {
-                    i += bitCount;
-                    goto _encode_eol_handle_pre_adjust;
-                }
-            }
-        } else {
-            if (_PREFER_BRANCHING && LIKELIHOOD(0.663, !mask)) {
-                _encode_loop_branch_fast_noesc:
-                dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
-                dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
-                STOREU_XMM(p, dataA);
-                STOREU_XMM(p + XMM_SIZE, dataB);
-                p += XMM_SIZE * 2;
-                col += XMM_SIZE * 2;
-                if (LIKELIHOOD(0.15, col >= 0))
-                    goto _encode_eol_handle_pre_adjust;
-                continue;
-            }
-            // shortcut for common case of only 1 bit set
-            _encode_loop_branch_fast_1ch:
+				{
+					unsigned char cnt = lookups->BitsSetTable256plus8[eqMask & 0xff];
+					cnt += lookups->BitsSetTable256plus8[(eqMask>>8) & 0xff];
+					cnt += lookups->BitsSetTable256plus8[(eqMask>>16) & 0xff];
+					cnt += lookups->BitsSetTable256plus8[(eqMask>>24) & 0xff];
+					bitCount = (uintptr_t)cnt - 32;
+				}
+				
+				if(use_isa >= ISA_LEVEL_VBMI2 || use_isa < ISA_LEVEL_SSSE3) {
+					i -= bitCount;
+					goto _encode_eol_handle_pre;
+				} else {
+					i += bitCount;
+					goto _encode_eol_handle_pre_adjust;
+				}
+			}
+		} else {
+			if(_PREFER_BRANCHING && LIKELIHOOD(0.663, !mask)) {
+				_encode_loop_branch_fast_noesc:
+				dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
+				dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
+				STOREU_XMM(p, dataA);
+				STOREU_XMM(p+XMM_SIZE, dataB);
+				p += XMM_SIZE*2;
+				col += XMM_SIZE*2;
+				if(LIKELIHOOD(0.15, col >= 0))
+					goto _encode_eol_handle_pre_adjust;
+				continue;
+			}
+			// shortcut for common case of only 1 bit set
+			_encode_loop_branch_fast_1ch:
 #if defined(__AVX512VL__) && defined(__AVX512BW__)
-            if(use_isa >= ISA_LEVEL_AVX3) {
-                dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
-                dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8(64), 0xf8); // data | (cmp & 64)
-                dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
-                dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8(64), 0xf8);
-
-                // store last char
-                _mm_mask_storeu_epi8(p+XMM_SIZE+1, 1<<15, dataB);
-
-                uint32_t blendMask = (uint32_t)(-(int32_t)mask);
-                dataB = _mm_mask_alignr_epi8(dataB, blendMask>>16, dataB, dataA, 15);
-                dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
-
+			if(use_isa >= ISA_LEVEL_AVX3) {
+				dataA = _mm_sub_epi8(dataA, _mm_set1_epi8(-42));
+				dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8(64), 0xf8); // data | (cmp & 64)
+				dataB = _mm_sub_epi8(dataB, _mm_set1_epi8(-42));
+				dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8(64), 0xf8);
+				
+				// store last char
+				p[XMM_SIZE*2] = _mm_extract_epi8(dataB, 15);
+				
+				uint32_t blendMask = (uint32_t)(-(int32_t)mask);
+				dataB = _mm_mask_alignr_epi8(dataB, blendMask>>16, dataB, dataA, 15);
+				dataB = _mm_ternarylogic_epi32(dataB, cmpB, _mm_set1_epi8('='), 0xb8); // (data & ~cmp) | (cmp & '=')
+				
 # if defined(__AVX512VBMI2__)
-                if(use_isa >= ISA_LEVEL_VBMI2)
-                    dataA = _mm_mask_expand_epi8(_mm_set1_epi8('='), ~mask, dataA);
-                else
+				if(use_isa >= ISA_LEVEL_VBMI2)
+					dataA = _mm_mask_expand_epi8(_mm_set1_epi8('='), ~mask, dataA);
+				else
 # endif
-                {
-                    dataA = _mm_mask_alignr_epi8(dataA, blendMask, dataA, dataA, 15); // there's no masked shift, so use ALIGNR instead
-                    dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8('='), 0xb8);
-                }
-            } else
+				{
+					dataA = _mm_mask_alignr_epi8(dataA, blendMask, dataA, dataA, 15); // there's no masked shift, so use ALIGNR instead
+					dataA = _mm_ternarylogic_epi32(dataA, cmpA, _mm_set1_epi8('='), 0xb8);
+				}
+			} else
 #endif
-            {
-
+			{
+				
 #if !defined(__tune_btver1__)
-                if (!(use_isa & ISA_FEATURE_POPCNT))
+				if(!(use_isa & ISA_FEATURE_POPCNT))
 #endif
-                    maskBits = (mask != 0);
-                if (_PREFER_BRANCHING) maskBits = 1;
+					maskBits = (mask != 0);
+				if(_PREFER_BRANCHING) maskBits = 1;
 #if !defined(__tune_btver1__)
-                if (!(use_isa & ISA_FEATURE_POPCNT))
+				if(!(use_isa & ISA_FEATURE_POPCNT))
 #endif
-                    outputBytes = XMM_SIZE * 2 + maskBits;
-
+					outputBytes = XMM_SIZE*2 + maskBits;
+				
 #if defined(__LZCNT__)
-                if(use_isa & ISA_FEATURE_LZCNT)
-                    bitIndex = _lzcnt_u32(mask);
-                else
+				if(use_isa & ISA_FEATURE_LZCNT)
+					bitIndex = _lzcnt_u32(mask);
+				else
 #endif
-                {
-                    bitIndex = BSR32(mask);
-                    bitIndex |= maskBits - 1; // if(mask == 0) bitIndex = -1;
-                }
-                const __m128i *entries;
-
+				{
+					bitIndex = BSR32(mask);
+					bitIndex |= maskBits-1; // if(mask == 0) bitIndex = -1;
+				}
+				const __m128i* entries;
+				
 #if defined(__SSSE3__) && !defined(__tune_atom__) && !defined(__tune_slm__) && !defined(__tune_btver1__)
-                if(use_isa >= ISA_LEVEL_SSSE3) {
-                    entries = (const __m128i*)lookups->expandShufmaskmix;
-                    if(!(use_isa & ISA_FEATURE_LZCNT))
-                        entries += 4;
-                    entries += bitIndex*4;
-
-                    __m128i shufMaskA = _mm_load_si128(entries+0);
-                    __m128i mergeMaskB = _mm_load_si128(entries+1);
-                    __m128i dataBShifted = _mm_alignr_epi8(dataB, dataA, 15);
-                    dataB = _mm_andnot_si128(cmpB, dataB);
-
-                    dataA = _mm_shuffle_epi8(dataA, shufMaskA);
-
+				if(use_isa >= ISA_LEVEL_SSSE3) {
+					entries = (const __m128i*)lookups->expandShufmaskmix;
+					if(!(use_isa & ISA_FEATURE_LZCNT))
+						entries += 4;
+					entries += bitIndex*4;
+					
+					__m128i shufMaskA = _mm_load_si128(entries+0);
+					__m128i mergeMaskB = _mm_load_si128(entries+1);
+					__m128i dataBShifted = _mm_alignr_epi8(dataB, dataA, 15);
+					dataB = _mm_andnot_si128(cmpB, dataB);
+					
+					dataA = _mm_shuffle_epi8(dataA, shufMaskA);
+					
 # if defined(__SSE4_1__) && !defined(__tune_slm__) && !defined(__tune_goldmont__) && !defined(__tune_goldmont_plus__) && !defined(__tune_tremont__)
-                    if(use_isa >= ISA_LEVEL_SSE41) {
-                        dataB = _mm_blendv_epi8(dataBShifted, dataB, mergeMaskB);
-                    } else
+					if(use_isa >= ISA_LEVEL_SSE41) {
+						dataB = _mm_blendv_epi8(dataBShifted, dataB, mergeMaskB);
+					} else
 # endif
-                    {
-                        dataB = _mm_or_si128(
-                            _mm_and_si128(mergeMaskB, dataB),
-                            _mm_andnot_si128(mergeMaskB, dataBShifted)
-                        );
-                    }
-                } else
+					{
+						dataB = _mm_or_si128(
+							_mm_and_si128(mergeMaskB, dataB),
+							_mm_andnot_si128(mergeMaskB, dataBShifted)
+						);
+					}
+				} else
 #endif
-                {
-
-                    entries = (const __m128i *) lookups->expandMaskmix;
-                    if (!(use_isa & ISA_FEATURE_LZCNT))
-                        entries += 4;
-                    entries += bitIndex * 4;
-
-                    __m128i mergeMaskA = _mm_load_si128(entries + 0);
-                    __m128i mergeMaskB = _mm_load_si128(entries + 1);
-                    // TODO: consider deferring mask operation? (does require an extra ANDN but may help with L1 latency)
-                    __m128i dataAMasked = _mm_andnot_si128(mergeMaskA, dataA);
-                    __m128i dataBMasked = _mm_andnot_si128(mergeMaskB, dataB);
-                    __m128i dataAShifted = _mm_slli_si128(dataAMasked, 1);
-                    __m128i dataBShifted;
-
+				{
+					
+					entries = (const __m128i*)lookups->expandMaskmix;
+					if(!(use_isa & ISA_FEATURE_LZCNT))
+						entries += 4;
+					entries += bitIndex*4;
+					
+					__m128i mergeMaskA = _mm_load_si128(entries+0);
+					__m128i mergeMaskB = _mm_load_si128(entries+1);
+					// TODO: consider deferring mask operation? (does require an extra ANDN but may help with L1 latency)
+					__m128i dataAMasked = _mm_andnot_si128(mergeMaskA, dataA);
+					__m128i dataBMasked = _mm_andnot_si128(mergeMaskB, dataB);
+					__m128i dataAShifted = _mm_slli_si128(dataAMasked, 1);
+					__m128i dataBShifted;
+					
 #if defined(__SSSE3__) && !defined(__tune_btver1__)
-                    if(use_isa >= ISA_LEVEL_SSSE3)
-                        dataBShifted = _mm_alignr_epi8(dataBMasked, dataAMasked, 15);
-                    else
+					if(use_isa >= ISA_LEVEL_SSSE3)
+						dataBShifted = _mm_alignr_epi8(dataBMasked, dataAMasked, 15);
+					else
 #endif
-                    dataBShifted = _mm_or_si128(
-                            _mm_slli_si128(dataBMasked, 1),
-                            _mm_srli_si128(dataAMasked, 15)
-                    );
-
-                    // alternatively `_mm_xor_si128(dataAMasked, dataA)` if compiler wants to load mergeMask* again
-                    dataB = _mm_or_si128(
-                            _mm_and_si128(mergeMaskB, dataB), dataBShifted
-                    );
-                    dataA = _mm_or_si128(
-                            _mm_and_si128(mergeMaskA, dataA), dataAShifted
-                    );
-                }
-                // add escape chars
-                dataA = _mm_add_epi8(dataA, _mm_load_si128(entries + 2));
-                dataB = _mm_add_epi8(dataB, _mm_load_si128(entries + 3));
-
-                // store final char
-                p[XMM_SIZE * 2] = es[i - 1] + 42 + (64 & (mask >> (XMM_SIZE * 2 - 1 - 6)));
-            }
-
-            // store main part
-            STOREU_XMM(p, dataA);
-            STOREU_XMM(p + XMM_SIZE, dataB);
-
-            p += outputBytes;
-            col += outputBytes;
-
-            if (LIKELIHOOD(0.3, col >= 0)) {
+						dataBShifted = _mm_or_si128(
+							_mm_slli_si128(dataBMasked, 1),
+							_mm_srli_si128(dataAMasked, 15)
+						);
+					
+					// alternatively `_mm_xor_si128(dataAMasked, dataA)` if compiler wants to load mergeMask* again
+					dataB = _mm_or_si128(
+						_mm_and_si128(mergeMaskB, dataB), dataBShifted
+					);
+					dataA = _mm_or_si128(
+						_mm_and_si128(mergeMaskA, dataA), dataAShifted
+					);
+				}
+				// add escape chars
+				dataA = _mm_add_epi8(dataA, _mm_load_si128(entries+2));
+				dataB = _mm_add_epi8(dataB, _mm_load_si128(entries+3));
+				
+				// store final char
+				p[XMM_SIZE*2] = es[i-1] + 42 + (64 & (mask>>(XMM_SIZE*2-1-6)));
+			}
+			
+			// store main part
+			STOREU_XMM(p, dataA);
+			STOREU_XMM(p+XMM_SIZE, dataB);
+			
+			p += outputBytes;
+			col += outputBytes;
+			
+			if(LIKELIHOOD(0.3, col >= 0)) {
 #if defined(__AVX512VL__)
-                if(use_isa >= ISA_LEVEL_AVX3)
-                    bitIndex = _lzcnt_u32(mask) +1;
-                else
+				if(use_isa >= ISA_LEVEL_AVX3)
+					bitIndex = _lzcnt_u32(mask) +1;
+				else
 #endif
-                if (use_isa & ISA_FEATURE_LZCNT)
-                    bitIndex = bitIndex + 1;
-                else
-                    bitIndex = 31 - bitIndex + 1;
-                if (HEDLEY_UNLIKELY(col == bitIndex)) {
-                    // this is an escape character, so line will need to overflow
-                    p--;
-                } else {
-                    i += (col > bitIndex);
-                }
-                _encode_eol_handle_pre_adjust:
-                p -= col;
-                i -= col;
-
-                _encode_eol_handle_pre:
-                uint32_t eolChar = lookups->eolLastChar[es[i]];
-                *(uint32_t *) p = eolChar;
-                p += 3 + (eolChar >> 27);
-                col = lineSizeOffset;
-
-                if (HEDLEY_UNLIKELY(i >=
-                                    0)) { // this isn't really a proper check - it's only needed to support short lines; basically, if the line is too short, `i` never gets checked, so we need one somewhere
-                    i++;
-                    break;
-                }
-
-                dataA = _mm_loadu_si128((__m128i * )(es + i + 1));
-                dataB = _mm_loadu_si128((__m128i * )(es + i + 1) + 1);
-                // search for special chars (EOL)
+				if(use_isa & ISA_FEATURE_LZCNT)
+					bitIndex = bitIndex +1;
+				else
+					bitIndex = 31-bitIndex +1;
+				if(HEDLEY_UNLIKELY(col == bitIndex)) {
+					// this is an escape character, so line will need to overflow
+					p--;
+				} else {
+					i += (col > bitIndex);
+				}
+				_encode_eol_handle_pre_adjust:
+				p -= col;
+				i -= col;
+				
+				_encode_eol_handle_pre:
+				uint32_t eolChar = lookups->eolLastChar[es[i]];
+				*(uint32_t*)p = eolChar;
+				p += 3 + (eolChar>>27);
+				col = lineSizeOffset;
+				
+				if(HEDLEY_UNLIKELY(i >= 0)) { // this isn't really a proper check - it's only needed to support short lines; basically, if the line is too short, `i` never gets checked, so we need one somewhere
+					i++;
+					break;
+				}
+				
+				dataA = _mm_loadu_si128((__m128i *)(es + i + 1));
+				dataB = _mm_loadu_si128((__m128i *)(es + i + 1) + 1);
+				// search for special chars (EOL)
 #if defined(__SSSE3__) && !defined(__tune_atom__) && !defined(__tune_slm__) && !defined(__tune_btver1__)
-                if(use_isa >= ISA_LEVEL_SSSE3) {
-                    cmpA = _mm_cmpeq_epi8(
-                        _mm_shuffle_epi8(_mm_set_epi8(
-                            '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                        ), _mm_adds_epi8(
-                            _mm_abs_epi8(dataA), _mm_cvtsi32_si128(88)
-                        )),
-                        dataA
-                    );
-                    i += XMM_SIZE*2 + 1;
+				if(use_isa >= ISA_LEVEL_SSSE3) {
+					cmpA = _mm_cmpeq_epi8(
+						_mm_shuffle_epi8(_mm_set_epi8(
+							'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+						), _mm_adds_epi8(
+							_mm_abs_epi8(dataA), _mm_cvtsi32_si128(88)
+						)),
+						dataA
+					);
+					i += XMM_SIZE*2 + 1;
 # if defined(__GNUC__) && !defined(__clang__)
-                    // GCC seems to have trouble keeping track of variable usage and spills many of them if we goto after declarations; Clang9 seems to be fine, or if _PREFER_BRANCHING is used
-                    if(!_PREFER_BRANCHING)
-                        goto _encode_loop_branchA;
+					// GCC seems to have trouble keeping track of variable usage and spills many of them if we goto after declarations; Clang9 seems to be fine, or if _PREFER_BRANCHING is used
+					if(!_PREFER_BRANCHING)
+						goto _encode_loop_branchA;
 # endif
-                    maskA = _mm_movemask_epi8(cmpA);
-                    cmpB = _mm_cmpeq_epi8(
-                        _mm_shuffle_epi8(_mm_set_epi8(
-                            '\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
-                        ), _mm_abs_epi8(dataB)),
-                        dataB
-                    );
-                } else
+					maskA = _mm_movemask_epi8(cmpA);
+					cmpB = _mm_cmpeq_epi8(
+						_mm_shuffle_epi8(_mm_set_epi8(
+							'\0'-42,-42,'\r'-42,'.'-42,'='-42,'\0'-42,'\t'-42,'\n'-42,-42,-42,'\r'-42,-42,'='-42,' '-42,-42,'\n'-42
+						), _mm_abs_epi8(dataB)),
+						dataB
+					);
+				} else
 #endif
-                {
-                    cmpA = _mm_or_si128(
-                            _mm_or_si128(
-                                    _mm_cmpeq_epi8(dataA, _mm_set1_epi8(-42)),
-                                    _mm_cmpeq_epi8(dataA, _mm_set1_epi8('\n' - 42))
-                            ),
-                            _mm_or_si128(
-                                    _mm_cmpeq_epi8(dataA, _mm_set1_epi8('=' - 42)),
-                                    _mm_cmpeq_epi8(dataA, _mm_set1_epi8('\r' - 42))
-                            )
-                    );
-                    maskA = _mm_movemask_epi8(cmpA);
-                    maskA |= lookups->eolFirstMask[es[i + 1]];
-                    i += XMM_SIZE * 2 + 1;
+				{
+					cmpA = _mm_or_si128(
+						_mm_or_si128(
+							_mm_cmpeq_epi8(dataA, _mm_set1_epi8(-42)),
+							_mm_cmpeq_epi8(dataA, _mm_set1_epi8('\n'-42))
+						),
+						_mm_or_si128(
+							_mm_cmpeq_epi8(dataA, _mm_set1_epi8('='-42)),
+							_mm_cmpeq_epi8(dataA, _mm_set1_epi8('\r'-42))
+						)
+					);
+					maskA = _mm_movemask_epi8(cmpA);
+					maskA |= lookups->eolFirstMask[es[i+1]];
+					i += XMM_SIZE*2 + 1;
 #if defined(__GNUC__) && !defined(__clang__)
-                    if(!_PREFER_BRANCHING)
-                        goto _encode_loop_branchB;
+					if(!_PREFER_BRANCHING)
+						goto _encode_loop_branchB;
 #endif
-                    cmpB = _mm_or_si128(
-                            _mm_or_si128(
-                                    _mm_cmpeq_epi8(dataB, _mm_set1_epi8(-42)),
-                                    _mm_cmpeq_epi8(dataB, _mm_set1_epi8('\n' - 42))
-                            ),
-                            _mm_or_si128(
-                                    _mm_cmpeq_epi8(dataB, _mm_set1_epi8('=' - 42)),
-                                    _mm_cmpeq_epi8(dataB, _mm_set1_epi8('\r' - 42))
-                            )
-                    );
-                }
-                maskB = _mm_movemask_epi8(cmpB);
-
-                mask = (maskB << 16) | maskA;
-                bool manyBitsSet; // don't retain this across loop cycles
+					cmpB = _mm_or_si128(
+						_mm_or_si128(
+							_mm_cmpeq_epi8(dataB, _mm_set1_epi8(-42)),
+							_mm_cmpeq_epi8(dataB, _mm_set1_epi8('\n'-42))
+						),
+						_mm_or_si128(
+							_mm_cmpeq_epi8(dataB, _mm_set1_epi8('='-42)),
+							_mm_cmpeq_epi8(dataB, _mm_set1_epi8('\r'-42))
+						)
+					);
+				}
+				maskB = _mm_movemask_epi8(cmpB);
+				
+				mask = (maskB<<16) | maskA;
+				bool manyBitsSet; // don't retain this across loop cycles
 #if defined(__POPCNT__) && !defined(__tune_btver1__)
-                if(use_isa & ISA_FEATURE_POPCNT) {
-                    maskBits = popcnt32(mask);
-                    outputBytes = maskBits + XMM_SIZE*2;
-                    manyBitsSet = maskBits > 1;
-                } else
+				if(use_isa & ISA_FEATURE_POPCNT) {
+					maskBits = popcnt32(mask);
+					outputBytes = maskBits + XMM_SIZE*2;
+					manyBitsSet = maskBits > 1;
+				} else
 #endif
-                {
-                    manyBitsSet = (mask & (mask - 1)) != 0;
-                }
-
-                if (LIKELIHOOD(0.089, manyBitsSet))
-                    goto _encode_loop_branch_slow;
-                if (_PREFER_BRANCHING && LIKELIHOOD(0.663, !mask))
-                    goto _encode_loop_branch_fast_noesc;
-                goto _encode_loop_branch_fast_1ch;
-                if (0) { // silence unused label warnings
-                    goto _encode_loop_branchA;
-                    goto _encode_loop_branchB;
-                }
-            }
-
-        }
-    } while (i < 0);
-
-    *colOffset = (int) (col + line_size - 1);
-    dest = p;
-    len = -(i - INPUT_OFFSET);
+				{
+					manyBitsSet = (mask & (mask-1)) != 0;
+				}
+				
+				if (LIKELIHOOD(0.089, manyBitsSet))
+					goto _encode_loop_branch_slow;
+				if(_PREFER_BRANCHING && LIKELIHOOD(0.663, !mask))
+					goto _encode_loop_branch_fast_noesc;
+				goto _encode_loop_branch_fast_1ch;
+				if(0) { // silence unused label warnings
+					goto _encode_loop_branchA;
+					goto _encode_loop_branchB;
+				}
+			}
+			
+		}
+	} while(i < 0);
+	
+	*colOffset = (int)(col + line_size -1);
+	dest = p;
+	len = -(i - INPUT_OFFSET);
 }
+} // namespace
 
diff --git a/src/yencode/encoder_ssse3.cc b/src/yencode/encoder_ssse3.cc
index 45fe2d6..9ca27ea 100644
--- a/src/yencode/encoder_ssse3.cc
+++ b/src/yencode/encoder_ssse3.cc
@@ -1,21 +1,19 @@
 #include "common.h"
+#include "encoder_common.h"
 
 // slightly faster version which improves the worst case scenario significantly; since worst case doesn't happen often, overall speedup is relatively minor
 // requires PSHUFB (SSSE3) instruction, but will use POPCNT (SSE4.2 (or AMD's ABM, but Phenom doesn't support SSSE3 so doesn't matter)) if available (these only seem to give minor speedups, so considered optional)
 #ifdef __SSSE3__
 #include "encoder_sse_base.h"
 
-void encoder_ssse3_init() {
-    _do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSSE3> >;
-    encoder_sse_lut<ISA_LEVEL_SSSE3>();
+void RapidYenc::encoder_ssse3_init() {
+	_do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_SSSE3> >;
+	encoder_sse_lut<ISA_LEVEL_SSSE3>();
+	_encode_isa = ISA_LEVEL_SSSE3;
 }
 #else
-
-void encoder_sse2_init();
-
-void encoder_ssse3_init() {
-    encoder_sse2_init();
+void RapidYenc::encoder_ssse3_init() {
+	encoder_sse2_init();
 }
-
 #endif
 
diff --git a/src/yencode/encoder_vbmi2.cc b/src/yencode/encoder_vbmi2.cc
index 8dddfd0..9ae279d 100644
--- a/src/yencode/encoder_vbmi2.cc
+++ b/src/yencode/encoder_vbmi2.cc
@@ -1,25 +1,31 @@
 #include "common.h"
+#include "encoder_common.h"
+
+#if !defined(__EVEX512__) && (defined(__AVX10_1__) || defined(__EVEX256__)) && defined(__AVX512VL__) && defined(__AVX512VBMI2__) && defined(__AVX512BW__)
+const bool RapidYenc::encoder_has_avx10 = true;
+#else
+const bool RapidYenc::encoder_has_avx10 = false;
+#endif
 
 #if defined(__AVX512VL__) && defined(__AVX512VBMI2__) && defined(__AVX512BW__)
 # ifndef YENC_DISABLE_AVX256
 #  include "encoder_avx_base.h"
 
-void encoder_vbmi2_init() {
-    _do_encode = &do_encode_simd< do_encode_avx2<ISA_LEVEL_VBMI2> >;
-    encoder_avx2_lut<ISA_LEVEL_VBMI2>();
+void RapidYenc::encoder_vbmi2_init() {
+	_do_encode = &do_encode_simd< do_encode_avx2<ISA_LEVEL_VBMI2> >;
+	encoder_avx2_lut<ISA_LEVEL_VBMI2>();
+	_encode_isa = ISA_LEVEL_VBMI2;
 }
 # else
 #  include "encoder_sse_base.h"
-void encoder_vbmi2_init() {
-    _do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_VBMI2> >;
-    encoder_sse_lut<ISA_LEVEL_VBMI2>();
+void RapidYenc::encoder_vbmi2_init() {
+	_do_encode = &do_encode_simd< do_encode_sse<ISA_LEVEL_VBMI2> >;
+	encoder_sse_lut<ISA_LEVEL_VBMI2>();
+	_encode_isa = ISA_LEVEL_VBMI2;
 }
 # endif
 #else
-
-void encoder_avx2_init();
-
-void encoder_vbmi2_init() {
-    encoder_avx2_init();
+void RapidYenc::encoder_vbmi2_init() {
+	encoder_avx2_init();
 }
 #endif
diff --git a/src/yencode/hedley.h b/src/yencode/hedley.h
index 9a7b335..8a713e6 100644
--- a/src/yencode/hedley.h
+++ b/src/yencode/hedley.h
@@ -10,11 +10,11 @@
  * SPDX-License-Identifier: CC0-1.0
  */
 
-#if !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < 12)
+#if !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < 15)
 #if defined(HEDLEY_VERSION)
 #  undef HEDLEY_VERSION
 #endif
-#define HEDLEY_VERSION 12
+#define HEDLEY_VERSION 15
 
 #if defined(HEDLEY_STRINGIFY_EX)
 #  undef HEDLEY_STRINGIFY_EX
@@ -29,17 +29,27 @@
 #if defined(HEDLEY_CONCAT_EX)
 #  undef HEDLEY_CONCAT_EX
 #endif
-#define HEDLEY_CONCAT_EX(a, b) a##b
+#define HEDLEY_CONCAT_EX(a,b) a##b
 
 #if defined(HEDLEY_CONCAT)
 #  undef HEDLEY_CONCAT
 #endif
-#define HEDLEY_CONCAT(a, b) HEDLEY_CONCAT_EX(a,b)
+#define HEDLEY_CONCAT(a,b) HEDLEY_CONCAT_EX(a,b)
+
+#if defined(HEDLEY_CONCAT3_EX)
+#  undef HEDLEY_CONCAT3_EX
+#endif
+#define HEDLEY_CONCAT3_EX(a,b,c) a##b##c
+
+#if defined(HEDLEY_CONCAT3)
+#  undef HEDLEY_CONCAT3
+#endif
+#define HEDLEY_CONCAT3(a,b,c) HEDLEY_CONCAT3_EX(a,b,c)
 
 #if defined(HEDLEY_VERSION_ENCODE)
 #  undef HEDLEY_VERSION_ENCODE
 #endif
-#define HEDLEY_VERSION_ENCODE(major, minor, revision) (((major) * 1000000) + ((minor) * 1000) + (revision))
+#define HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))
 
 #if defined(HEDLEY_VERSION_DECODE_MAJOR)
 #  undef HEDLEY_VERSION_DECODE_MAJOR
@@ -71,25 +81,25 @@
 #if defined(HEDLEY_GNUC_VERSION)
 #  define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (HEDLEY_GNUC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_GNUC_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_MSVC_VERSION)
 #  undef HEDLEY_MSVC_VERSION
 #endif
-#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000)
+#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL)
 #  define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)
-#elif defined(_MSC_FULL_VER)
+#elif defined(_MSC_FULL_VER) && !defined(__ICL)
 #  define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)
-#elif defined(_MSC_VER)
+#elif defined(_MSC_VER) && !defined(__ICL)
 #  define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)
 #endif
 
 #if defined(HEDLEY_MSVC_VERSION_CHECK)
 #  undef HEDLEY_MSVC_VERSION_CHECK
 #endif
-#if !defined(_MSC_VER)
-#  define HEDLEY_MSVC_VERSION_CHECK(major, minor, patch) (0)
+#if !defined(HEDLEY_MSVC_VERSION)
+#  define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)
 #elif defined(_MSC_VER) && (_MSC_VER >= 1400)
 #  define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))
 #elif defined(_MSC_VER) && (_MSC_VER >= 1200)
@@ -101,9 +111,9 @@
 #if defined(HEDLEY_INTEL_VERSION)
 #  undef HEDLEY_INTEL_VERSION
 #endif
-#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE)
+#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL)
 #  define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)
-#elif defined(__INTEL_COMPILER)
+#elif defined(__INTEL_COMPILER) && !defined(__ICL)
 #  define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)
 #endif
 
@@ -113,7 +123,23 @@
 #if defined(HEDLEY_INTEL_VERSION)
 #  define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (HEDLEY_INTEL_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_INTEL_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(HEDLEY_INTEL_CL_VERSION)
+#  undef HEDLEY_INTEL_CL_VERSION
+#endif
+#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL)
+#  define HEDLEY_INTEL_CL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0)
+#endif
+
+#if defined(HEDLEY_INTEL_CL_VERSION_CHECK)
+#  undef HEDLEY_INTEL_CL_VERSION_CHECK
+#endif
+#if defined(HEDLEY_INTEL_CL_VERSION)
+#  define HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (HEDLEY_INTEL_CL_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+#  define HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_PGI_VERSION)
@@ -129,7 +155,7 @@
 #if defined(HEDLEY_PGI_VERSION)
 #  define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (HEDLEY_PGI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_PGI_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_SUNPRO_VERSION)
@@ -151,7 +177,7 @@
 #if defined(HEDLEY_SUNPRO_VERSION)
 #  define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (HEDLEY_SUNPRO_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_SUNPRO_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_EMSCRIPTEN_VERSION)
@@ -167,7 +193,7 @@
 #if defined(HEDLEY_EMSCRIPTEN_VERSION)
 #  define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (HEDLEY_EMSCRIPTEN_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_ARM_VERSION)
@@ -185,7 +211,7 @@
 #if defined(HEDLEY_ARM_VERSION)
 #  define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (HEDLEY_ARM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_ARM_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_IBM_VERSION)
@@ -205,7 +231,7 @@
 #if defined(HEDLEY_IBM_VERSION)
 #  define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (HEDLEY_IBM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_IBM_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_VERSION)
@@ -213,11 +239,11 @@
 #endif
 #if \
     defined(__TI_COMPILER_VERSION__) && \
-    (\
+    ( \
       defined(__TMS470__) || defined(__TI_ARM__) || \
       defined(__MSP430__) || \
       defined(__TMS320C2000__) \
-)
+    )
 #  if (__TI_COMPILER_VERSION__ >= 16000000)
 #    define HEDLEY_TI_VERSION HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))
 #  endif
@@ -229,7 +255,7 @@
 #if defined(HEDLEY_TI_VERSION)
 #  define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_CL2000_VERSION)
@@ -245,7 +271,7 @@
 #if defined(HEDLEY_TI_CL2000_VERSION)
 #  define HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_CL2000_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_CL2000_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_CL430_VERSION)
@@ -261,7 +287,7 @@
 #if defined(HEDLEY_TI_CL430_VERSION)
 #  define HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_CL430_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_CL430_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_ARMCL_VERSION)
@@ -277,7 +303,7 @@
 #if defined(HEDLEY_TI_ARMCL_VERSION)
 #  define HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_ARMCL_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_ARMCL_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_CL6X_VERSION)
@@ -293,7 +319,7 @@
 #if defined(HEDLEY_TI_CL6X_VERSION)
 #  define HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_CL6X_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_CL6X_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_CL7X_VERSION)
@@ -309,7 +335,7 @@
 #if defined(HEDLEY_TI_CL7X_VERSION)
 #  define HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_CL7X_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_CL7X_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TI_CLPRU_VERSION)
@@ -325,7 +351,7 @@
 #if defined(HEDLEY_TI_CLPRU_VERSION)
 #  define HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_CLPRU_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TI_CLPRU_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_CRAY_VERSION)
@@ -345,7 +371,7 @@
 #if defined(HEDLEY_CRAY_VERSION)
 #  define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (HEDLEY_CRAY_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_CRAY_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_IAR_VERSION)
@@ -355,7 +381,7 @@
 #  if __VER__ > 1000
 #    define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))
 #  else
-#    define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0)
+#    define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0)
 #  endif
 #endif
 
@@ -365,7 +391,7 @@
 #if defined(HEDLEY_IAR_VERSION)
 #  define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (HEDLEY_IAR_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_IAR_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_TINYC_VERSION)
@@ -381,7 +407,7 @@
 #if defined(HEDLEY_TINYC_VERSION)
 #  define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (HEDLEY_TINYC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_TINYC_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_DMC_VERSION)
@@ -397,7 +423,7 @@
 #if defined(HEDLEY_DMC_VERSION)
 #  define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (HEDLEY_DMC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_DMC_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_COMPCERT_VERSION)
@@ -413,7 +439,7 @@
 #if defined(HEDLEY_COMPCERT_VERSION)
 #  define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (HEDLEY_COMPCERT_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_COMPCERT_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_PELLES_VERSION)
@@ -429,7 +455,23 @@
 #if defined(HEDLEY_PELLES_VERSION)
 #  define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (HEDLEY_PELLES_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_PELLES_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)
+#endif
+
+#if defined(HEDLEY_MCST_LCC_VERSION)
+#  undef HEDLEY_MCST_LCC_VERSION
+#endif
+#if defined(__LCC__) && defined(__LCC_MINOR__)
+#  define HEDLEY_MCST_LCC_VERSION HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__)
+#endif
+
+#if defined(HEDLEY_MCST_LCC_VERSION_CHECK)
+#  undef HEDLEY_MCST_LCC_VERSION_CHECK
+#endif
+#if defined(HEDLEY_MCST_LCC_VERSION)
+#  define HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (HEDLEY_MCST_LCC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
+#else
+#  define HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_GCC_VERSION)
@@ -441,6 +483,7 @@
   !defined(HEDLEY_INTEL_VERSION) && \
   !defined(HEDLEY_PGI_VERSION) && \
   !defined(HEDLEY_ARM_VERSION) && \
+  !defined(HEDLEY_CRAY_VERSION) && \
   !defined(HEDLEY_TI_VERSION) && \
   !defined(HEDLEY_TI_ARMCL_VERSION) && \
   !defined(HEDLEY_TI_CL430_VERSION) && \
@@ -448,7 +491,8 @@
   !defined(HEDLEY_TI_CL6X_VERSION) && \
   !defined(HEDLEY_TI_CL7X_VERSION) && \
   !defined(HEDLEY_TI_CLPRU_VERSION) && \
-  !defined(__COMPCERT__)
+  !defined(__COMPCERT__) && \
+  !defined(HEDLEY_MCST_LCC_VERSION)
 #  define HEDLEY_GCC_VERSION HEDLEY_GNUC_VERSION
 #endif
 
@@ -458,13 +502,17 @@
 #if defined(HEDLEY_GCC_VERSION)
 #  define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (HEDLEY_GCC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch))
 #else
-#  define HEDLEY_GCC_VERSION_CHECK(major, minor, patch) (0)
+#  define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)
 #endif
 
 #if defined(HEDLEY_HAS_ATTRIBUTE)
 #  undef HEDLEY_HAS_ATTRIBUTE
 #endif
-#if defined(__has_attribute)
+#if \
+  defined(__has_attribute) && \
+  ( \
+    (!defined(HEDLEY_IAR_VERSION) || HEDLEY_IAR_VERSION_CHECK(8,5,9)) \
+  )
 #  define HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)
 #else
 #  define HEDLEY_HAS_ATTRIBUTE(attribute) (0)
@@ -474,18 +522,18 @@
 #  undef HEDLEY_GNUC_HAS_ATTRIBUTE
 #endif
 #if defined(__has_attribute)
-#  define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute)
+#  define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_HAS_ATTRIBUTE(attribute)
 #else
-#  define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_ATTRIBUTE)
 #  undef HEDLEY_GCC_HAS_ATTRIBUTE
 #endif
 #if defined(__has_attribute)
-#  define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute)
+#  define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_HAS_ATTRIBUTE(attribute)
 #else
-#  define HEDLEY_GCC_HAS_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_CPP_ATTRIBUTE)
@@ -494,7 +542,7 @@
 #if \
   defined(__has_cpp_attribute) && \
   defined(__cplusplus) && \
-  (!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5, 15, 0))
+  (!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5,15,0))
 #  define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)
 #else
 #  define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)
@@ -504,12 +552,12 @@
 #  undef HEDLEY_HAS_CPP_ATTRIBUTE_NS
 #endif
 #if !defined(__cplusplus) || !defined(__has_cpp_attribute)
-#  define HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns, attribute) (0)
+#  define HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)
 #elif \
   !defined(HEDLEY_PGI_VERSION) && \
   !defined(HEDLEY_IAR_VERSION) && \
-  (!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5, 15, 0)) && \
-  (!defined(HEDLEY_MSVC_VERSION) || HEDLEY_MSVC_VERSION_CHECK(19, 20, 0))
+  (!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \
+  (!defined(HEDLEY_MSVC_VERSION) || HEDLEY_MSVC_VERSION_CHECK(19,20,0))
 #  define HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute)
 #else
 #  define HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)
@@ -521,7 +569,7 @@
 #if defined(__has_cpp_attribute) && defined(__cplusplus)
 #  define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
 #else
-#  define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_CPP_ATTRIBUTE)
@@ -530,7 +578,7 @@
 #if defined(__has_cpp_attribute) && defined(__cplusplus)
 #  define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)
 #else
-#  define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_BUILTIN)
@@ -548,7 +596,7 @@
 #if defined(__has_builtin)
 #  define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
 #else
-#  define HEDLEY_GNUC_HAS_BUILTIN(builtin, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_BUILTIN)
@@ -557,7 +605,7 @@
 #if defined(__has_builtin)
 #  define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)
 #else
-#  define HEDLEY_GCC_HAS_BUILTIN(builtin, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_FEATURE)
@@ -575,7 +623,7 @@
 #if defined(__has_feature)
 #  define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
 #else
-#  define HEDLEY_GNUC_HAS_FEATURE(feature, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_FEATURE)
@@ -584,7 +632,7 @@
 #if defined(__has_feature)
 #  define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)
 #else
-#  define HEDLEY_GCC_HAS_FEATURE(feature, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_EXTENSION)
@@ -602,7 +650,7 @@
 #if defined(__has_extension)
 #  define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
 #else
-#  define HEDLEY_GNUC_HAS_EXTENSION(extension, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_EXTENSION)
@@ -611,7 +659,7 @@
 #if defined(__has_extension)
 #  define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)
 #else
-#  define HEDLEY_GCC_HAS_EXTENSION(extension, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_DECLSPEC_ATTRIBUTE)
@@ -629,7 +677,7 @@
 #if defined(__has_declspec_attribute)
 #  define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
 #else
-#  define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)
@@ -638,7 +686,7 @@
 #if defined(__has_declspec_attribute)
 #  define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)
 #else
-#  define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_HAS_WARNING)
@@ -656,7 +704,7 @@
 #if defined(__has_warning)
 #  define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
 #else
-#  define HEDLEY_GNUC_HAS_WARNING(warning, major, minor, patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_GCC_HAS_WARNING)
@@ -665,7 +713,73 @@
 #if defined(__has_warning)
 #  define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)
 #else
-#  define HEDLEY_GCC_HAS_WARNING(warning, major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#endif
+
+#if \
+  (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
+  defined(__clang__) || \
+  HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
+  HEDLEY_PGI_VERSION_CHECK(18,4,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \
+  HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \
+  (HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))
+#  define HEDLEY_PRAGMA(value) _Pragma(#value)
+#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
+#  define HEDLEY_PRAGMA(value) __pragma(value)
+#else
+#  define HEDLEY_PRAGMA(value)
+#endif
+
+#if defined(HEDLEY_DIAGNOSTIC_PUSH)
+#  undef HEDLEY_DIAGNOSTIC_PUSH
+#endif
+#if defined(HEDLEY_DIAGNOSTIC_POP)
+#  undef HEDLEY_DIAGNOSTIC_POP
+#endif
+#if defined(__clang__)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop")
+#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
+#elif HEDLEY_GCC_VERSION_CHECK(4,6,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))
+#  define HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))
+#elif HEDLEY_ARM_VERSION_CHECK(5,6,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("push")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("pop")
+#elif \
+    HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+    HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+    HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \
+    HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \
+    HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+    HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop")
+#elif HEDLEY_PELLES_VERSION_CHECK(2,90,0)
+#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
+#  define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
+#else
+#  define HEDLEY_DIAGNOSTIC_PUSH
+#  define HEDLEY_DIAGNOSTIC_POP
 #endif
 
 /* HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for
@@ -676,12 +790,22 @@
 #if defined(__cplusplus)
 #  if HEDLEY_HAS_WARNING("-Wc++98-compat")
 #    if HEDLEY_HAS_WARNING("-Wc++17-extensions")
-#      define HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
-         HEDLEY_DIAGNOSTIC_PUSH \
-         _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
-         _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \
-         xpr \
-         HEDLEY_DIAGNOSTIC_POP
+#      if HEDLEY_HAS_WARNING("-Wc++1z-extensions")
+#        define HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
+           HEDLEY_DIAGNOSTIC_PUSH \
+           _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
+           _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \
+           _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \
+           xpr \
+           HEDLEY_DIAGNOSTIC_POP
+#      else
+#        define HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
+           HEDLEY_DIAGNOSTIC_PUSH \
+           _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \
+           _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \
+           xpr \
+           HEDLEY_DIAGNOSTIC_POP
+#      endif
 #    else
 #      define HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \
          HEDLEY_DIAGNOSTIC_PUSH \
@@ -702,8 +826,8 @@
 #  define HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr))
 #elif \
   HEDLEY_HAS_WARNING("-Wcast-qual") || \
-  HEDLEY_GCC_VERSION_CHECK(4, 6, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,6,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \
       HEDLEY_DIAGNOSTIC_PUSH \
       HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \
@@ -746,7 +870,7 @@
 #    define HEDLEY_CPP_CAST(T, expr) \
        HEDLEY_DIAGNOSTIC_PUSH \
        _Pragma("diag_suppress=Pe137") \
-       HEDLEY_DIAGNOSTIC_POP \
+       HEDLEY_DIAGNOSTIC_POP
 #  else
 #    define HEDLEY_CPP_CAST(T, expr) ((T) (expr))
 #  endif
@@ -754,103 +878,45 @@
 #  define HEDLEY_CPP_CAST(T, expr) (expr)
 #endif
 
-#if \
-  (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
-  defined(__clang__) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_IAR_VERSION_CHECK(8, 0, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(18, 4, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(4, 7, 0) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(2, 0, 1) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 1, 0) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 0, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) || \
-  HEDLEY_CRAY_VERSION_CHECK(5, 0, 0) || \
-  HEDLEY_TINYC_VERSION_CHECK(0, 9, 17) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(8, 0, 0) || \
-  (HEDLEY_IBM_VERSION_CHECK(10, 1, 0) && defined(__C99_PRAGMA_OPERATOR))
-#  define HEDLEY_PRAGMA(value) _Pragma(#value)
-#elif HEDLEY_MSVC_VERSION_CHECK(15, 0, 0)
-#  define HEDLEY_PRAGMA(value) __pragma(value)
-#else
-#  define HEDLEY_PRAGMA(value)
-#endif
-
-#if defined(HEDLEY_DIAGNOSTIC_PUSH)
-#  undef HEDLEY_DIAGNOSTIC_PUSH
-#endif
-#if defined(HEDLEY_DIAGNOSTIC_POP)
-#  undef HEDLEY_DIAGNOSTIC_POP
-#endif
-#if defined(__clang__)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop")
-#elif HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
-#elif HEDLEY_GCC_VERSION_CHECK(4, 6, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
-#elif HEDLEY_MSVC_VERSION_CHECK(15, 0, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))
-#  define HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))
-#elif HEDLEY_ARM_VERSION_CHECK(5, 6, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("push")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("pop")
-#elif \
-    HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-    HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-    HEDLEY_TI_CL430_VERSION_CHECK(4, 4, 0) || \
-    HEDLEY_TI_CL6X_VERSION_CHECK(8, 1, 0) || \
-    HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-    HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop")
-#elif HEDLEY_PELLES_VERSION_CHECK(2, 90, 0)
-#  define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)")
-#  define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)")
-#else
-#  define HEDLEY_DIAGNOSTIC_PUSH
-#  define HEDLEY_DIAGNOSTIC_POP
-#endif
-
 #if defined(HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)
 #  undef HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
 #endif
 #if HEDLEY_HAS_WARNING("-Wdeprecated-declarations")
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
-#elif HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)")
-#elif HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
+#elif HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786))
+#elif HEDLEY_PGI_VERSION_CHECK(20,7,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445")
+#elif HEDLEY_PGI_VERSION_CHECK(17,10,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444")
-#elif HEDLEY_GCC_VERSION_CHECK(4, 3, 0)
+#elif HEDLEY_GCC_VERSION_CHECK(4,3,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
-#elif HEDLEY_MSVC_VERSION_CHECK(15, 0, 0)
+#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))
+#elif HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444")
 #elif \
-    HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-    (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-    HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-    (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-    HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-    (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-    HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-    (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-    HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-    HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-    HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+    HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+    (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+    HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+    (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+    HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+    (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+    HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+    (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+    HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+    HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+    HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718")
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 13, 0) && !defined(__cplusplus)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)")
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 13, 0) && defined(__cplusplus)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)")
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215")
-#elif HEDLEY_PELLES_VERSION_CHECK(2, 90, 0)
+#elif HEDLEY_PELLES_VERSION_CHECK(2,90,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)")
 #else
 #  define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
@@ -861,24 +927,28 @@
 #endif
 #if HEDLEY_HAS_WARNING("-Wunknown-pragmas")
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"")
-#elif HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)")
-#elif HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
+#elif HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161))
+#elif HEDLEY_PGI_VERSION_CHECK(17,10,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675")
-#elif HEDLEY_GCC_VERSION_CHECK(4, 3, 0)
+#elif HEDLEY_GCC_VERSION_CHECK(4,3,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"")
-#elif HEDLEY_MSVC_VERSION_CHECK(15, 0, 0)
+#elif HEDLEY_MSVC_VERSION_CHECK(15,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))
 #elif \
-    HEDLEY_TI_VERSION_CHECK(16, 9, 0) || \
-    HEDLEY_TI_CL6X_VERSION_CHECK(8, 0, 0) || \
-    HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-    HEDLEY_TI_CLPRU_VERSION_CHECK(2, 3, 0)
+    HEDLEY_TI_VERSION_CHECK(16,9,0) || \
+    HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \
+    HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+    HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163")
-#elif HEDLEY_TI_CL6X_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163")
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161")
+#elif HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161")
 #else
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS
 #endif
@@ -888,23 +958,29 @@
 #endif
 #if HEDLEY_HAS_WARNING("-Wunknown-attributes")
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"")
-#elif HEDLEY_GCC_VERSION_CHECK(4, 6, 0)
+#elif HEDLEY_GCC_VERSION_CHECK(4,6,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
-#elif HEDLEY_INTEL_VERSION_CHECK(17, 0, 0)
+#elif HEDLEY_INTEL_VERSION_CHECK(17,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)")
-#elif HEDLEY_MSVC_VERSION_CHECK(19, 0, 0)
+#elif HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292))
+#elif HEDLEY_MSVC_VERSION_CHECK(19,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030))
-#elif HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
+#elif HEDLEY_PGI_VERSION_CHECK(20,7,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098")
+#elif HEDLEY_PGI_VERSION_CHECK(17,10,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097")
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 14, 0) && defined(__cplusplus)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)")
 #elif \
-    HEDLEY_TI_VERSION_CHECK(18, 1, 0) || \
-    HEDLEY_TI_CL6X_VERSION_CHECK(8, 3, 0) || \
-    HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0)
+    HEDLEY_TI_VERSION_CHECK(18,1,0) || \
+    HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \
+    HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173")
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097")
+#elif HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097")
 #else
 #  define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES
 #endif
@@ -914,63 +990,84 @@
 #endif
 #if HEDLEY_HAS_WARNING("-Wcast-qual")
 #  define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
-#elif HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+#elif HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)")
-#elif HEDLEY_GCC_VERSION_CHECK(3, 0, 0)
+#elif HEDLEY_GCC_VERSION_CHECK(3,0,0)
 #  define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
 #else
 #  define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL
 #endif
 
+#if defined(HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION)
+#  undef HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION
+#endif
+#if HEDLEY_HAS_WARNING("-Wunused-function")
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"")
+#elif HEDLEY_GCC_VERSION_CHECK(3,4,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"")
+#elif HEDLEY_MSVC_VERSION_CHECK(1,0,0)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505))
+#elif HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142")
+#else
+#  define HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION
+#endif
+
 #if defined(HEDLEY_DEPRECATED)
 #  undef HEDLEY_DEPRECATED
 #endif
 #if defined(HEDLEY_DEPRECATED_FOR)
 #  undef HEDLEY_DEPRECATED_FOR
 #endif
-#if defined(__cplusplus) && (__cplusplus >= 201402L)
-#  define HEDLEY_DEPRECATED(since) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]])
-#  define HEDLEY_DEPRECATED_FOR(since, replacement) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]])
+#if \
+  HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
+#  define HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since))
+#  define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement))
 #elif \
-  HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 5, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(5, 6, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 13, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(17, 10, 0) || \
-  HEDLEY_TI_VERSION_CHECK(18, 1, 0) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(18, 1, 0) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(8, 3, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 3, 0)
+  (HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(HEDLEY_IAR_VERSION)) || \
+  HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \
+  HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+  HEDLEY_TI_VERSION_CHECK(18,1,0) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since)))
 #  define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement)))
+#elif defined(__cplusplus) && (__cplusplus >= 201402L)
+#  define HEDLEY_DEPRECATED(since) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]])
+#  define HEDLEY_DEPRECATED_FOR(since, replacement) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]])
 #elif \
   HEDLEY_HAS_ATTRIBUTE(deprecated) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+  HEDLEY_IAR_VERSION_CHECK(8,10,0)
 #  define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))
 #  define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))
-#elif HEDLEY_MSVC_VERSION_CHECK(14, 0, 0)
-#  define HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since))
-#  define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement))
 #elif \
-  HEDLEY_MSVC_VERSION_CHECK(13, 10, 0) || \
-  HEDLEY_PELLES_VERSION_CHECK(6, 50, 0)
+  HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+  HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_DEPRECATED(since) __declspec(deprecated)
 #  define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_DEPRECATED(since) _Pragma("deprecated")
 #  define HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated")
 #else
@@ -983,8 +1080,9 @@
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(warning) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 3, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,3,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since)))
 #else
 #  define HEDLEY_UNAVAILABLE(available_since)
@@ -996,31 +1094,32 @@
 #if defined(HEDLEY_WARN_UNUSED_RESULT_MSG)
 #  undef HEDLEY_WARN_UNUSED_RESULT_MSG
 #endif
-#if (HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)
+#if \
+  HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \
+  HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  (HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
+  HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
+#  define HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+#  define HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))
+#elif (HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)
 #  define HEDLEY_WARN_UNUSED_RESULT HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
 #  define HEDLEY_WARN_UNUSED_RESULT_MSG(msg) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]])
 #elif HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard)
 #  define HEDLEY_WARN_UNUSED_RESULT HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
 #  define HEDLEY_WARN_UNUSED_RESULT_MSG(msg) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])
-#elif \
-  HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 4, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) || \
-  (HEDLEY_SUNPRO_VERSION_CHECK(5, 15, 0) && defined(__cplusplus)) || \
-  HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
-#  define HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
-#  define HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))
 #elif defined(_Check_return_) /* SAL */
 #  define HEDLEY_WARN_UNUSED_RESULT _Check_return_
 #  define HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_
@@ -1034,9 +1133,10 @@
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(sentinel) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(5, 4, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_ARM_VERSION_CHECK(5,4,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))
 #else
 #  define HEDLEY_SENTINEL(position)
@@ -1045,9 +1145,11 @@
 #if defined(HEDLEY_NO_RETURN)
 #  undef HEDLEY_NO_RETURN
 #endif
-#if HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#if HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_NO_RETURN __noreturn
-#elif HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+#elif \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_NO_RETURN __attribute__((__noreturn__))
 #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
 #  define HEDLEY_NO_RETURN _Noreturn
@@ -1055,31 +1157,34 @@
 #  define HEDLEY_NO_RETURN HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]])
 #elif \
   HEDLEY_HAS_ATTRIBUTE(noreturn) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 2, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,2,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_IAR_VERSION_CHECK(8,10,0)
 #  define HEDLEY_NO_RETURN __attribute__((__noreturn__))
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 10, 0)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
 #  define HEDLEY_NO_RETURN _Pragma("does_not_return")
-#elif HEDLEY_MSVC_VERSION_CHECK(13, 10, 0)
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_NO_RETURN __declspec(noreturn)
-#elif HEDLEY_TI_CL6X_VERSION_CHECK(6, 0, 0) && defined(__cplusplus)
+#elif HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)
 #  define HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;")
-#elif HEDLEY_COMPCERT_VERSION_CHECK(3, 2, 0)
+#elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
 #  define HEDLEY_NO_RETURN __attribute((noreturn))
-#elif HEDLEY_PELLES_VERSION_CHECK(9, 0, 0)
+#elif HEDLEY_PELLES_VERSION_CHECK(9,0,0)
 #  define HEDLEY_NO_RETURN __declspec(noreturn)
 #else
 #  define HEDLEY_NO_RETURN
@@ -1104,14 +1209,15 @@
 #  undef HEDLEY_ASSUME
 #endif
 #if \
-  HEDLEY_MSVC_VERSION_CHECK(13, 10, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_ASSUME(expr) __assume(expr)
 #elif HEDLEY_HAS_BUILTIN(__builtin_assume)
 #  define HEDLEY_ASSUME(expr) __builtin_assume(expr)
 #elif \
-    HEDLEY_TI_CL2000_VERSION_CHECK(6, 2, 0) || \
-    HEDLEY_TI_CL6X_VERSION_CHECK(4, 0, 0)
+    HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \
+    HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)
 #  if defined(__cplusplus)
 #    define HEDLEY_ASSUME(expr) std::_nassert(expr)
 #  else
@@ -1120,10 +1226,12 @@
 #endif
 #if \
   (HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(HEDLEY_ARM_VERSION))) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 5, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(18, 10, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(13, 1, 5)
+  HEDLEY_GCC_VERSION_CHECK(4,5,0) || \
+  HEDLEY_PGI_VERSION_CHECK(18,10,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_IBM_VERSION_CHECK(13,1,5) || \
+  HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_UNREACHABLE() __builtin_unreachable()
 #elif defined(HEDLEY_ASSUME)
 #  define HEDLEY_UNREACHABLE() HEDLEY_ASSUME(0)
@@ -1157,7 +1265,7 @@ HEDLEY_DIAGNOSTIC_PUSH
 #if HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus)
 #  pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
 #endif
-#if HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros", 4, 0, 0)
+#if HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0)
 #  if defined(__clang__)
 #    pragma clang diagnostic ignored "-Wvariadic-macros"
 #  elif defined(HEDLEY_GCC_VERSION)
@@ -1169,9 +1277,9 @@ HEDLEY_DIAGNOSTIC_PUSH
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(nonnull) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 3, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0)
 #  define HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
 #else
 #  define HEDLEY_NON_NULL(...)
@@ -1181,32 +1289,33 @@ HEDLEY_DIAGNOSTIC_POP
 #if defined(HEDLEY_PRINTF_FORMAT)
 #  undef HEDLEY_PRINTF_FORMAT
 #endif
-#if defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format, 4, 4, 0) && !defined(__USE_MINGW_ANSI_STDIO)
+#if defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)
 #  define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))
-#elif defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format, 4, 4, 0) && defined(__USE_MINGW_ANSI_STDIO)
+#elif defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)
 #  define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))
 #elif \
   HEDLEY_HAS_ATTRIBUTE(format) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(5, 6, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_ARM_VERSION_CHECK(5,6,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))
-#elif HEDLEY_PELLES_VERSION_CHECK(6, 0, 0)
+#elif HEDLEY_PELLES_VERSION_CHECK(6,0,0)
 #  define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))
 #else
-#  define HEDLEY_PRINTF_FORMAT(string_idx, first_to_check)
+#  define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)
 #endif
 
 #if defined(HEDLEY_CONSTEXPR)
@@ -1237,29 +1346,31 @@ HEDLEY_DIAGNOSTIC_POP
 #  define HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr))
 #endif
 #if \
-  (HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \
-  HEDLEY_GCC_VERSION_CHECK(9, 0, 0)) && !defined(HEDLEY_INTEL_VERSION)
+  (HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(HEDLEY_PGI_VERSION)) || \
+  HEDLEY_GCC_VERSION_CHECK(9,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(  (expr), (value), (probability))
 #  define HEDLEY_PREDICT_TRUE(expr, probability)   __builtin_expect_with_probability(!!(expr),    1   , (probability))
 #  define HEDLEY_PREDICT_FALSE(expr, probability)  __builtin_expect_with_probability(!!(expr),    0   , (probability))
 #  define HEDLEY_LIKELY(expr)                      __builtin_expect                 (!!(expr),    1                  )
 #  define HEDLEY_UNLIKELY(expr)                    __builtin_expect                 (!!(expr),    0                  )
 #elif \
-  HEDLEY_HAS_BUILTIN(__builtin_expect) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  (HEDLEY_SUNPRO_VERSION_CHECK(5, 15, 0) && defined(__cplusplus)) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(4, 7, 0) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 1, 0) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(6, 1, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) || \
-  HEDLEY_TINYC_VERSION_CHECK(0, 9, 27) || \
-  HEDLEY_CRAY_VERSION_CHECK(8, 1, 0)
+  (HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(HEDLEY_INTEL_CL_VERSION)) || \
+  HEDLEY_GCC_VERSION_CHECK(3,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  (HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \
+  HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_PREDICT(expr, expected, probability) \
      (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (HEDLEY_STATIC_CAST(void, expected), (expr)))
 #  define HEDLEY_PREDICT_TRUE(expr, probability) \
@@ -1290,26 +1401,29 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(malloc) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(12, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_MALLOC __attribute__((__malloc__))
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 10, 0)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
 #  define HEDLEY_MALLOC _Pragma("returns_new_memory")
-#elif HEDLEY_MSVC_VERSION_CHECK(14, 0, 0)
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_MALLOC __declspec(restrict)
 #else
 #  define HEDLEY_MALLOC
@@ -1320,32 +1434,33 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(pure) || \
-  HEDLEY_GCC_VERSION_CHECK(2, 96, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
+  HEDLEY_GCC_VERSION_CHECK(2,96,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_PURE __attribute__((__pure__))
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 10, 0)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
 #  define HEDLEY_PURE _Pragma("does_not_write_global_data")
 #elif defined(__cplusplus) && \
-    (\
-      HEDLEY_TI_CL430_VERSION_CHECK(2, 0, 1) || \
-      HEDLEY_TI_CL6X_VERSION_CHECK(4, 0, 0) || \
-      HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) \
-)
+    ( \
+      HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \
+      HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \
+      HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \
+    )
 #  define HEDLEY_PURE _Pragma("FUNC_IS_PURE;")
 #else
 #  define HEDLEY_PURE
@@ -1356,26 +1471,27 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(const) || \
-  HEDLEY_GCC_VERSION_CHECK(2, 5, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(17, 10, 0)
+  HEDLEY_GCC_VERSION_CHECK(2,5,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_CONST __attribute__((__const__))
 #elif \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 10, 0)
+  HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)
 #  define HEDLEY_CONST _Pragma("no_side_effect")
 #else
 #  define HEDLEY_CONST HEDLEY_PURE
@@ -1387,21 +1503,23 @@ HEDLEY_DIAGNOSTIC_POP
 #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)
 #  define HEDLEY_RESTRICT restrict
 #elif \
-  HEDLEY_GCC_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(17, 10, 0) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 2, 4) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(8, 1, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  (HEDLEY_SUNPRO_VERSION_CHECK(5, 14, 0) && defined(__cplusplus)) || \
-  HEDLEY_IAR_VERSION_CHECK(8, 0, 0) || \
-  defined(__clang__)
+  HEDLEY_GCC_VERSION_CHECK(3,1,0) || \
+  HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_PGI_VERSION_CHECK(17,10,0) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  (HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \
+  HEDLEY_IAR_VERSION_CHECK(8,0,0) || \
+  defined(__clang__) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_RESTRICT __restrict
-#elif HEDLEY_SUNPRO_VERSION_CHECK(5, 3, 0) && !defined(__cplusplus)
+#elif HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)
 #  define HEDLEY_RESTRICT _Restrict
 #else
 #  define HEDLEY_RESTRICT
@@ -1416,17 +1534,19 @@ HEDLEY_DIAGNOSTIC_POP
 #  define HEDLEY_INLINE inline
 #elif \
   defined(HEDLEY_GCC_VERSION) || \
-  HEDLEY_ARM_VERSION_CHECK(6, 2, 0)
+  HEDLEY_ARM_VERSION_CHECK(6,2,0)
 #  define HEDLEY_INLINE __inline__
 #elif \
-  HEDLEY_MSVC_VERSION_CHECK(12, 0, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 1, 0) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(3, 1, 0) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 2, 0) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(8, 0, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_INLINE __inline
 #else
 #  define HEDLEY_INLINE
@@ -1437,36 +1557,40 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(always_inline) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+  HEDLEY_IAR_VERSION_CHECK(8,10,0)
 #  define HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) HEDLEY_INLINE
-#elif HEDLEY_MSVC_VERSION_CHECK(12, 0, 0)
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_ALWAYS_INLINE __forceinline
 #elif defined(__cplusplus) && \
-    (\
-      HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-      HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-      HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-      HEDLEY_TI_CL6X_VERSION_CHECK(6, 1, 0) || \
-      HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-      HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0) \
-)
+    ( \
+      HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+      HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+      HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+      HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+      HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+      HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \
+    )
 #  define HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;")
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_ALWAYS_INLINE _Pragma("inline=forced")
 #else
 #  define HEDLEY_ALWAYS_INLINE HEDLEY_INLINE
@@ -1477,34 +1601,38 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(noinline) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 0, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(10, 1, 0) || \
-  HEDLEY_TI_VERSION_CHECK(15, 12, 0) || \
-  (HEDLEY_TI_ARMCL_VERSION_CHECK(4, 8, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_ARMCL_VERSION_CHECK(5, 2, 0) || \
-  (HEDLEY_TI_CL2000_VERSION_CHECK(6, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL2000_VERSION_CHECK(6, 4, 0) || \
-  (HEDLEY_TI_CL430_VERSION_CHECK(4, 0, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL430_VERSION_CHECK(4, 3, 0) || \
-  (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) || \
-  HEDLEY_TI_CL7X_VERSION_CHECK(1, 2, 0) || \
-  HEDLEY_TI_CLPRU_VERSION_CHECK(2, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,0,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(10,1,0) || \
+  HEDLEY_TI_VERSION_CHECK(15,12,0) || \
+  (HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \
+  (HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \
+  (HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \
+  (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \
+  HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \
+  HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \
+  HEDLEY_IAR_VERSION_CHECK(8,10,0)
 #  define HEDLEY_NEVER_INLINE __attribute__((__noinline__))
-#elif HEDLEY_MSVC_VERSION_CHECK(13, 10, 0)
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_NEVER_INLINE __declspec(noinline)
-#elif HEDLEY_PGI_VERSION_CHECK(10, 2, 0)
+#elif HEDLEY_PGI_VERSION_CHECK(10,2,0)
 #  define HEDLEY_NEVER_INLINE _Pragma("noinline")
-#elif HEDLEY_TI_CL6X_VERSION_CHECK(6, 0, 0) && defined(__cplusplus)
+#elif HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)
 #  define HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;")
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_NEVER_INLINE _Pragma("inline=never")
-#elif HEDLEY_COMPCERT_VERSION_CHECK(3, 2, 0)
+#elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)
 #  define HEDLEY_NEVER_INLINE __attribute((noinline))
-#elif HEDLEY_PELLES_VERSION_CHECK(9, 0, 0)
+#elif HEDLEY_PELLES_VERSION_CHECK(9,0,0)
 #  define HEDLEY_NEVER_INLINE __declspec(noinline)
 #else
 #  define HEDLEY_NEVER_INLINE
@@ -1526,18 +1654,19 @@ HEDLEY_DIAGNOSTIC_POP
 #else
 #  if \
     HEDLEY_HAS_ATTRIBUTE(visibility) || \
-    HEDLEY_GCC_VERSION_CHECK(3, 3, 0) || \
-    HEDLEY_SUNPRO_VERSION_CHECK(5, 11, 0) || \
-    HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-    HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-    HEDLEY_IBM_VERSION_CHECK(13, 1, 0) || \
-    (\
+    HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+    HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \
+    HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+    HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+    HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+    ( \
       defined(__TI_EABI__) && \
-      (\
-        (HEDLEY_TI_CL6X_VERSION_CHECK(7, 2, 0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
-        HEDLEY_TI_CL6X_VERSION_CHECK(7, 5, 0) \
-) \
-)
+      ( \
+        (HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \
+        HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \
+      ) \
+    ) || \
+    HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #    define HEDLEY_PRIVATE __attribute__((__visibility__("hidden")))
 #    define HEDLEY_PUBLIC  __attribute__((__visibility__("default")))
 #  else
@@ -1552,12 +1681,14 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(nothrow) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 3, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,3,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_NO_THROW __attribute__((__nothrow__))
 #elif \
-  HEDLEY_MSVC_VERSION_CHECK(13, 1, 0) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0)
+  HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0)
 #  define HEDLEY_NO_THROW __declspec(nothrow)
 #else
 #  define HEDLEY_NO_THROW
@@ -1566,9 +1697,12 @@ HEDLEY_DIAGNOSTIC_POP
 #if defined(HEDLEY_FALL_THROUGH)
 # undef HEDLEY_FALL_THROUGH
 #endif
-#if HEDLEY_GNUC_HAS_ATTRIBUTE(fallthrough, 7, 0, 0) && !defined(HEDLEY_PGI_VERSION)
+#if \
+  HEDLEY_HAS_ATTRIBUTE(fallthrough) || \
+  HEDLEY_GCC_VERSION_CHECK(7,0,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))
-#elif HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang, fallthrough)
+#elif HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough)
 #  define HEDLEY_FALL_THROUGH HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]])
 #elif HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)
 #  define HEDLEY_FALL_THROUGH HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]])
@@ -1583,7 +1717,8 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \
-  HEDLEY_GCC_VERSION_CHECK(4, 9, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,9,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))
 #elif defined(_Ret_notnull_) /* SAL */
 #  define HEDLEY_RETURNS_NON_NULL _Ret_notnull_
@@ -1618,25 +1753,26 @@ HEDLEY_DIAGNOSTIC_POP
 #endif
 #if \
   HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \
-  HEDLEY_GCC_VERSION_CHECK(3, 4, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-  HEDLEY_TINYC_VERSION_CHECK(0, 9, 19) || \
-  HEDLEY_ARM_VERSION_CHECK(4, 1, 0) || \
-  HEDLEY_IBM_VERSION_CHECK(13, 1, 0) || \
-  HEDLEY_TI_CL6X_VERSION_CHECK(6, 1, 0) || \
-  (HEDLEY_SUNPRO_VERSION_CHECK(5, 10, 0) && !defined(__cplusplus)) || \
-  HEDLEY_CRAY_VERSION_CHECK(8, 1, 0)
+  HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+  HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \
+  HEDLEY_ARM_VERSION_CHECK(4,1,0) || \
+  HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+  HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \
+  (HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \
+  HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+  HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)
 #  define HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)
 #endif
 #if !defined(__cplusplus)
 #  if \
        HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \
-       HEDLEY_GCC_VERSION_CHECK(3, 4, 0) || \
-       HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
-       HEDLEY_IBM_VERSION_CHECK(13, 1, 0) || \
-       HEDLEY_CRAY_VERSION_CHECK(8, 1, 0) || \
-       HEDLEY_ARM_VERSION_CHECK(5, 4, 0) || \
-       HEDLEY_TINYC_VERSION_CHECK(0, 9, 24)
+       HEDLEY_GCC_VERSION_CHECK(3,4,0) || \
+       HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
+       HEDLEY_IBM_VERSION_CHECK(13,1,0) || \
+       HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \
+       HEDLEY_ARM_VERSION_CHECK(5,4,0) || \
+       HEDLEY_TINYC_VERSION_CHECK(0,9,24)
 #    if defined(__INTPTR_TYPE__)
 #      define HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)
 #    else
@@ -1644,16 +1780,16 @@ HEDLEY_DIAGNOSTIC_POP
 #      define HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)
 #    endif
 #  elif \
-       (\
+       ( \
           defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \
           !defined(HEDLEY_SUNPRO_VERSION) && \
           !defined(HEDLEY_PGI_VERSION) && \
           !defined(HEDLEY_IAR_VERSION)) || \
-       HEDLEY_HAS_EXTENSION(c_generic_selections) || \
-       HEDLEY_GCC_VERSION_CHECK(4, 9, 0) || \
-       HEDLEY_INTEL_VERSION_CHECK(17, 0, 0) || \
-       HEDLEY_IBM_VERSION_CHECK(12, 1, 0) || \
-       HEDLEY_ARM_VERSION_CHECK(5, 3, 0)
+       (HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(HEDLEY_IAR_VERSION)) || \
+       HEDLEY_GCC_VERSION_CHECK(4,9,0) || \
+       HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \
+       HEDLEY_IBM_VERSION_CHECK(12,1,0) || \
+       HEDLEY_ARM_VERSION_CHECK(5,3,0)
 #    if defined(__INTPTR_TYPE__)
 #      define HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)
 #    else
@@ -1665,7 +1801,7 @@ HEDLEY_DIAGNOSTIC_POP
        defined(HEDLEY_INTEL_VERSION) || \
        defined(HEDLEY_TINYC_VERSION) || \
        defined(HEDLEY_TI_ARMCL_VERSION) || \
-       HEDLEY_TI_CL430_VERSION_CHECK(18, 12, 0) || \
+       HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \
        defined(HEDLEY_TI_CL2000_VERSION) || \
        defined(HEDLEY_TI_CL6X_VERSION) || \
        defined(HEDLEY_TI_CL7X_VERSION) || \
@@ -1717,17 +1853,18 @@ HEDLEY_DIAGNOSTIC_POP
 #  undef HEDLEY_STATIC_ASSERT
 #endif
 #if \
-  !defined(__cplusplus) && (\
+  !defined(__cplusplus) && ( \
       (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \
-      HEDLEY_HAS_FEATURE(c_static_assert) || \
-      HEDLEY_GCC_VERSION_CHECK(6, 0, 0) || \
-      HEDLEY_INTEL_VERSION_CHECK(13, 0, 0) || \
+      (HEDLEY_HAS_FEATURE(c_static_assert) && !defined(HEDLEY_INTEL_CL_VERSION)) || \
+      HEDLEY_GCC_VERSION_CHECK(6,0,0) || \
+      HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \
       defined(_Static_assert) \
-)
+    )
 #  define HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)
 #elif \
   (defined(__cplusplus) && (__cplusplus >= 201103L)) || \
-  HEDLEY_MSVC_VERSION_CHECK(16, 0, 0)
+  HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_STATIC_ASSERT(expr, message) HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message))
 #else
 #  define HEDLEY_STATIC_ASSERT(expr, message)
@@ -1760,14 +1897,14 @@ HEDLEY_DIAGNOSTIC_POP
   HEDLEY_PRAGMA(message msg) \
   HEDLEY_DIAGNOSTIC_POP
 #elif \
-  HEDLEY_GCC_VERSION_CHECK(4, 4, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,4,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message msg)
-#elif HEDLEY_CRAY_VERSION_CHECK(5, 0, 0)
+#elif HEDLEY_CRAY_VERSION_CHECK(5,0,0)
 #  define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(_CRI message msg)
-#elif HEDLEY_IAR_VERSION_CHECK(8, 0, 0)
+#elif HEDLEY_IAR_VERSION_CHECK(8,0,0)
 #  define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg))
-#elif HEDLEY_PELLES_VERSION_CHECK(2, 0, 0)
+#elif HEDLEY_PELLES_VERSION_CHECK(2,0,0)
 #  define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg))
 #else
 #  define HEDLEY_MESSAGE(msg)
@@ -1783,11 +1920,13 @@ HEDLEY_DIAGNOSTIC_POP
   HEDLEY_PRAGMA(clang warning msg) \
   HEDLEY_DIAGNOSTIC_POP
 #elif \
-  HEDLEY_GCC_VERSION_CHECK(4, 8, 0) || \
-  HEDLEY_PGI_VERSION_CHECK(18, 4, 0) || \
-  HEDLEY_INTEL_VERSION_CHECK(13, 0, 0)
+  HEDLEY_GCC_VERSION_CHECK(4,8,0) || \
+  HEDLEY_PGI_VERSION_CHECK(18,4,0) || \
+  HEDLEY_INTEL_VERSION_CHECK(13,0,0)
 #  define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(GCC warning msg)
-#elif HEDLEY_MSVC_VERSION_CHECK(15, 0, 0)
+#elif \
+  HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(message(msg))
 #else
 #  define HEDLEY_WARNING(msg) HEDLEY_MESSAGE(msg)
@@ -1817,20 +1956,22 @@ HEDLEY_DIAGNOSTIC_POP
 #  endif
 #else
 #  define HEDLEY_REQUIRE(expr)
-#  define HEDLEY_REQUIRE_MSG(expr, msg)
+#  define HEDLEY_REQUIRE_MSG(expr,msg)
 #endif
 
 #if defined(HEDLEY_FLAGS)
 #  undef HEDLEY_FLAGS
 #endif
-#if HEDLEY_HAS_ATTRIBUTE(flag_enum)
+#if HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion"))
 #  define HEDLEY_FLAGS __attribute__((__flag_enum__))
+#else
+#  define HEDLEY_FLAGS
 #endif
 
 #if defined(HEDLEY_FLAGS_CAST)
 #  undef HEDLEY_FLAGS_CAST
 #endif
-#if HEDLEY_INTEL_VERSION_CHECK(19, 0, 0)
+#if HEDLEY_INTEL_VERSION_CHECK(19,0,0)
 #  define HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \
   HEDLEY_DIAGNOSTIC_PUSH \
       _Pragma("warning(disable:188)") \
@@ -1844,7 +1985,9 @@ HEDLEY_DIAGNOSTIC_POP
 #if defined(HEDLEY_EMPTY_BASES)
 #  undef HEDLEY_EMPTY_BASES
 #endif
-#if HEDLEY_MSVC_VERSION_CHECK(19, 0, 23918) && !HEDLEY_MSVC_VERSION_CHECK(20, 0, 0)
+#if \
+  (HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \
+  HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)
 #  define HEDLEY_EMPTY_BASES __declspec(empty_bases)
 #else
 #  define HEDLEY_EMPTY_BASES
@@ -1858,7 +2001,7 @@ HEDLEY_DIAGNOSTIC_POP
 #if defined(__clang__)
 #  define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)
 #else
-#  define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major, minor, patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
+#  define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch)
 #endif
 
 #if defined(HEDLEY_CLANG_HAS_ATTRIBUTE)
diff --git a/src/yencode/platform.cc b/src/yencode/platform.cc
index 6ba1880..e23facc 100644
--- a/src/yencode/platform.cc
+++ b/src/yencode/platform.cc
@@ -1,14 +1,7 @@
 #include "common.h"
-
 #ifdef PLATFORM_ARM
 # ifdef __ANDROID__
 #  include <cpu-features.h>
-# elif defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 12)
-#  include <sys/auxv.h>
-#  include <asm/hwcap.h>
-# elif (defined(__FreeBSD__) && __FreeBSD__ < 12)
-#  include <sys/sysctl.h>
-#  include <asm/hwcap.h>
 # elif defined(_WIN32)
 #  define WIN32_LEAN_AND_MEAN
 #  define NOMINMAX
@@ -16,40 +9,51 @@
 # elif defined(__APPLE__)
 #  include <sys/types.h>
 #  include <sys/sysctl.h>
+# elif defined(__has_include)
+#  if __has_include(<sys/auxv.h>)
+#   include <sys/auxv.h>
+#   if __has_include(<asm/hwcap.h>)
+#    include <asm/hwcap.h>
+#   endif
+#  endif
 # endif
-bool cpu_supports_neon() {
+bool RapidYenc::cpu_supports_neon() {
 # if defined(AT_HWCAP)
 #  ifdef __FreeBSD__
-    unsigned long supported;
-    elf_aux_info(AT_HWCAP, &supported, sizeof(supported));
+	unsigned long supported;
+	elf_aux_info(AT_HWCAP, &supported, sizeof(supported));
 #   ifdef __aarch64__
-    return supported & HWCAP_ASIMD;
+	return supported & HWCAP_ASIMD;
 #   else
-    return supported & HWCAP_NEON;
+	return supported & HWCAP_NEON;
 #   endif
 #  else
 #   ifdef __aarch64__
-    return getauxval(AT_HWCAP) & HWCAP_ASIMD;
+	return getauxval(AT_HWCAP) & HWCAP_ASIMD;
 #   else
-    return getauxval(AT_HWCAP) & HWCAP_NEON;
+	return getauxval(AT_HWCAP) & HWCAP_NEON;
 #   endif
 #  endif
 # elif defined(ANDROID_CPU_FAMILY_ARM)
 #  ifdef __aarch64__
-    return android_getCpuFeatures() & ANDROID_CPU_ARM64_FEATURE_ASIMD;
+	return android_getCpuFeatures() & ANDROID_CPU_ARM64_FEATURE_ASIMD;
 #  else
-    return android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON;
+	return android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON;
 #  endif
 # elif defined(_WIN32)
-    return IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);
+	return IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE);
 # elif defined(__APPLE__)
-    int supported = 0;
-    size_t len = sizeof(supported);
-    if(sysctlbyname("hw.optional.neon", &supported, &len, NULL, 0))
-        return false;
-    return (bool)supported;
+	int supported = 0;
+	size_t len = sizeof(supported);
+	if(sysctlbyname("hw.optional.neon", &supported, &len, NULL, 0))
+		return false;
+	return (bool)supported;
+# endif
+# ifdef __aarch64__
+	return true; // assume NEON support on AArch64
+# else
+	return false;
 # endif
-    return true; // assume NEON support, if compiled as such, otherwise (I think Windows and iOS require it)
 }
 #endif
 
@@ -73,97 +77,141 @@ bool cpu_supports_neon() {
 # define _cpuid1x(ar) __cpuid(0x80000001, ar[0], ar[1], ar[2], ar[3])
 # define _cpuidX(ar, eax, ecx) __cpuid_count(eax, ecx, ar[0], ar[1], ar[2], ar[3])
 static inline int _GET_XCR() {
-    int xcr0;
-    __asm__ __volatile__("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx");
-    return xcr0;
+	int xcr0;
+	__asm__ __volatile__("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx");
+	return xcr0;
 }
 #endif
 // checks if CPU has 128-bit AVX units; currently not used as AVX2 is beneficial even on Zen1
 // static bool cpu_has_slow_avx(cpuid1flag0) {
-    // int family = ((cpuid1flag0>>8) & 0xf) + ((cpuid1flag0>>16) & 0xff0),
-        // model = ((cpuid1flag0>>4) & 0xf) + ((cpuid1flag0>>12) & 0xf0);
-    // return (
-           // family == 0x6f // AMD Bulldozer family
-        // || family == 0x7f // AMD Jaguar/Puma family
-        // || (family == 0x8f && (model == 0 /*Summit Ridge ES*/ || model == 1 /*Zen*/ || model == 8 /*Zen+*/ || model == 0x11 /*Zen APU*/ || model == 0x18 /*Zen+ APU*/ || model == 0x50 /*Subor Z+*/)) // AMD Zen1 family
-        // || (family == 6 && model == 0xf) // Centaur/Zhaoxin; overlaps with Intel Core 2, but they don't support AVX
-    // );
+	// int family = ((cpuid1flag0>>8) & 0xf) + ((cpuid1flag0>>16) & 0xff0),
+		// model = ((cpuid1flag0>>4) & 0xf) + ((cpuid1flag0>>12) & 0xf0);
+	// return (
+		   // family == 0x6f // AMD Bulldozer family
+		// || family == 0x7f // AMD Jaguar/Puma family
+		// || (family == 0x8f && (model == 0 /*Summit Ridge ES*/ || model == 1 /*Zen*/ || model == 8 /*Zen+*/ || model == 0x11 /*Zen APU*/ || model == 0x18 /*Zen+ APU*/ || model == 0x50 /*Subor Z+*/)) // AMD Zen1 family
+		// || (family == 6 && model == 0xf) // Centaur/Zhaoxin; overlaps with Intel Core 2, but they don't support AVX
+	// );
 // }
 
 
-int cpu_supports_isa() {
-    int flags[4];
-    _cpuid1(flags);
-    int ret = 0;
-
-    if(flags[2] & 0x800000)
-        ret |= ISA_FEATURE_POPCNT;
-    int flags2[4];
-    _cpuid1x(flags2);
-    if(flags2[2] & 0x20) // ABM
-        ret |= ISA_FEATURE_LZCNT | ISA_FEATURE_POPCNT;
-
-    int family = ((flags[0]>>8) & 0xf) + ((flags[0]>>16) & 0xff0);
-    int model = ((flags[0]>>4) & 0xf) + ((flags[0]>>12) & 0xf0);
-
-    if(family == 6 && (
-        model == 0x1C || model == 0x26 || model == 0x27 || model == 0x35 || model == 0x36 || model == 0x37 || model == 0x4A || model == 0x4C || model == 0x4D || model == 0x5A || model == 0x5D
-    ))
-        // Intel Bonnell/Silvermont CPU with very slow PSHUFB and PBLENDVB - pretend SSSE3 doesn't exist
-        return ret | ISA_LEVEL_SSE2;
-
-    if(family == 0x5f && (model == 0 || model == 1 || model == 2))
-        // AMD Bobcat with slow SSSE3 instructions - pretend it doesn't exist
-        return ret | ISA_LEVEL_SSE2;
-
-    if((flags[2] & 0x200) == 0x200) { // SSSE3
-        if(family == 6 && (model == 0x5c || model == 0x5f || model == 0x7a || model == 0x9c))
-            // Intel Goldmont/plus / Tremont with slow PBLENDVB
-            return ret | ISA_LEVEL_SSSE3;
+int RapidYenc::cpu_supports_isa() {
+	int flags[4];
+	_cpuid1(flags);
+	int ret = 0;
+	
+	if(flags[2] & 0x800000)
+		ret |= ISA_FEATURE_POPCNT;
+	int flags2[4];
+	_cpuid1x(flags2);
+	if(flags2[2] & 0x20) // ABM
+		ret |= ISA_FEATURE_LZCNT | ISA_FEATURE_POPCNT;
+	
+	int family = ((flags[0]>>8) & 0xf) + ((flags[0]>>16) & 0xff0);
+	int model = ((flags[0]>>4) & 0xf) + ((flags[0]>>12) & 0xf0);
+	
+	if(family == 6 && (
+		model == 0x1C || model == 0x26 || model == 0x27 || model == 0x35 || model == 0x36 || model == 0x37 || model == 0x4A || model == 0x4C || model == 0x4D || model == 0x5A || model == 0x5D
+	))
+		// Intel Bonnell/Silvermont CPU with very slow PSHUFB and PBLENDVB - pretend SSSE3 doesn't exist
+		return ret | ISA_LEVEL_SSE2;
+	
+	if(family == 0x5f && (model == 0 || model == 1 || model == 2))
+		// AMD Bobcat with slow SSSE3 instructions - pretend it doesn't exist
+		return ret | ISA_LEVEL_SSE2;
+	
+	if((flags[2] & 0x200) == 0x200) { // SSSE3
+		if(family == 6 && (model == 0x5c || model == 0x5f || model == 0x7a || model == 0x9c))
+			// Intel Goldmont/plus / Tremont with slow PBLENDVB
+			return ret | ISA_LEVEL_SSSE3;
+		
+		if(flags[2] & 0x80000) { // SSE4.1
+			if((flags[2] & 0x1C800000) == 0x1C800000) { // POPCNT + OSXSAVE + XSAVE + AVX
+				int xcr = _GET_XCR() & 0xff; // ignore unused bits
+				if((xcr & 6) == 6) { // AVX enabled
+					int cpuInfo[4];
+					_cpuidX(cpuInfo, 7, 0);
+					if((cpuInfo[1] & 0x128) == 0x128 && (ret & ISA_FEATURE_LZCNT)) { // BMI2 + AVX2 + BMI1
+						if((xcr & 0xE0) == 0xE0) { // AVX512 XSTATE (also applies to AVX10)
+							// check AVX10
+							int cpuInfo2[4];
+							_cpuidX(cpuInfo2, 7, 1);
+							if(cpuInfo2[3] & 0x80000) {
+								_cpuidX(cpuInfo2, 0x24, 0);
+								if((cpuInfo2[1] & 0xff) >= 1 && ( // minimum AVX10.1
+#ifdef YENC_DISABLE_AVX256
+									cpuInfo2[1] & 0x10000 // AVX10/128
+#else
+									cpuInfo2[1] & 0x20000 // AVX10/256
+#endif
+								)) {
+									if(cpuInfo2[1] & 0x40000) ret |= ISA_FEATURE_EVEX512;
+									return ret | ISA_LEVEL_VBMI2;
+								}
+							}
+							
+							if((cpuInfo[1] & 0xC0010000) == 0xC0010000) { // AVX512BW + AVX512VL + AVX512F
+								ret |= ISA_FEATURE_EVEX512;
+								if(cpuInfo[2] & 0x40)
+									return ret | ISA_LEVEL_VBMI2;
+								return ret | ISA_LEVEL_AVX3;
+							}
+						}
+						// AVX2 is beneficial even on Zen1
+						return ret | ISA_LEVEL_AVX2;
+					}
+					return ret | ISA_LEVEL_AVX;
+				}
+			}
+			return ret | ISA_LEVEL_SSE41;
+		}
+		return ret | ISA_LEVEL_SSSE3;
+	}
+	return ret | ISA_LEVEL_SSE2;
+}
 
-        if(flags[2] & 0x80000) { // SSE4.1
-            if((flags[2] & 0x18800000) == 0x18800000) { // POPCNT + OSXSAVE + AVX
-                int xcr = _GET_XCR() & 0xff; // ignore unused bits
-                if((xcr & 6) == 6) { // AVX enabled
-                    int cpuInfo[4];
-                    _cpuidX(cpuInfo, 7, 0);
-                    if((cpuInfo[1] & 0x128) == 0x128 && (ret & ISA_FEATURE_LZCNT)) { // BMI2 + AVX2 + BMI1
-                        if(((xcr & 0xE0) == 0xE0) && (cpuInfo[1] & 0xC0010000) == 0xC0010000) { // AVX512BW + AVX512VL + AVX512F
-                            if(cpuInfo[2] & 0x40)
-                                return ret | ISA_LEVEL_VBMI2;
-                            return ret | ISA_LEVEL_AVX3;
-                        }
-                        // AVX2 is beneficial even on Zen1
-                        return ret | ISA_LEVEL_AVX2;
-                    }
-                    return ret | ISA_LEVEL_AVX;
-                }
-            }
-            return ret | ISA_LEVEL_SSE41;
-        }
-        return ret | ISA_LEVEL_SSSE3;
-    }
-    return ret | ISA_LEVEL_SSE2;
+int RapidYenc::cpu_supports_crc_isa() {
+	int flags[4];
+	_cpuid1(flags);
+	
+	if((flags[2] & 0x80202) == 0x80202) { // SSE4.1 + SSSE3 + CLMUL
+		if((flags[2] & 0x1C000000) == 0x1C000000) { // AVX + OSXSAVE + XSAVE
+			int xcr = _GET_XCR() & 0xff; // ignore unused bits
+			if((xcr & 6) == 6) { // AVX enabled
+				int cpuInfo[4];
+				_cpuidX(cpuInfo, 7, 0);
+				if((cpuInfo[1] & 0x20) == 0x20 && (cpuInfo[2] & 0x400) == 0x400) { // AVX2 + VPCLMULQDQ
+					return 2;
+				}
+			}
+		}
+		return 1;
+	}
+	return 0;
 }
 
-int cpu_supports_crc_isa() {
-    int flags[4];
-    _cpuid1(flags);
+#endif // PLATFORM_X86
 
-    if((flags[2] & 0x80202) == 0x80202) { // SSE4.1 + SSSE3 + CLMUL
-        if((flags[2] & 0x18000000) == 0x18000000) { // OSXSAVE + AVX
-            int xcr = _GET_XCR() & 0xff; // ignore unused bits
-            if((xcr & 6) == 6) { // AVX enabled
-                int cpuInfo[4];
-                _cpuidX(cpuInfo, 7, 0);
-                if((cpuInfo[1] & 0x20) == 0x20 && (cpuInfo[2] & 0x400) == 0x400) { // AVX2 + VPCLMULQDQ
-                    return 2;
-                }
-            }
-        }
-        return 1;
-    }
-    return 0;
+#ifdef __riscv
+# if defined(__has_include)
+#  if __has_include(<sys/auxv.h>)
+#   include <sys/auxv.h>
+#   if __has_include(<asm/hwcap.h>)
+#    include <asm/hwcap.h>
+#   endif
+#  endif
+# endif
+bool RapidYenc::cpu_supports_rvv() {
+# if defined(AT_HWCAP)
+	unsigned long ret;
+#  ifdef __FreeBSD__
+	elf_aux_info(AT_HWCAP, &ret, sizeof(ret));
+#  else
+	ret = getauxval(AT_HWCAP);
+#  endif
+	return (ret & 0x20112D) == 0x20112D; // IMAFDCV; TODO: how to detect Z* features of 'G'?
+# endif
+	return false;
 }
+#endif
 
-#endif // PLATFORM_X86


Test patch:
diff --git a/tests/test_crc32.py b/tests/test_crc32.py
index 6ff57e4..94864dd 100644
--- a/tests/test_crc32.py
+++ b/tests/test_crc32.py
@@ -13,6 +13,10 @@
         (18446744073709551615, 0, 0, 4294967295),
         (18446744073709551615, 18446744073709551615, 0, 0),
         (100, 200, 300, 1009376567),
+        (0, 0, 18446744073709551615, 0),
+        (4294967295, 4294967295, 18446744073709551615, 0),
+        (0, 100, 1234567890123, 100),
+        (100, 0, 1234567890123, 1829446317),
     ],
 )
 def test_crc32_combine_expected(crc1, crc2, len2, expected):
@@ -26,7 +30,6 @@ def test_crc32_combine_expected(crc1, crc2, len2, expected):
         (4294967295, 0, 0),
         (0, 4294967295, 0),
         (4294967295, 4294967295, 1048090088),
-        (18446744073709551615, 18446744073709551615, 2496806722),
         (100, 200, 4155012749),
     ],
 )
@@ -41,6 +44,9 @@ def test_crc32_multiply_expected(crc1, crc2, expected):
         (4294967295, 0, 4294967295),
         (4294967295, 4294967295, 4294967295),
         (100, 200, 1523530880),
+        (0, 18446744073709551615, 0),
+        (4294967295, 18446744073709551615, 4294967295),
+        (100, 1234567890123, 980217485),
     ],
 )
 def test_crc32_zero_unpad_expected(crc1, zeroes, expected):