Repository: secretflow/spu (language: python)

Issue:
在0.8版本小数据集的方差测试是正常的，在亿级数据的方差测试结果异常
### Issue Type

Others

### Source

binary

### Secretflow Version

0.8.0b0

### OS Platform and Distribution

centos7

### Python version

3.8.13

### Bazel version

_No response_

### GCC/Compiler version

_No response_

### What happend and What you expected to happen.

```shell
在0.8版本小数据集的方差测试是正常的，在亿级数据的方差测试结果每次都是：0，但是明文的方差测试结果是：6.66654400001397；两者的偏差是不是有异常？
下面是正常执行demo打印的日志：
INFO:root:Run secretflow in simulation mode.
2023-04-25 18:08:02,500 INFO worker.py:1352 -- Connecting to existing Ray cluster at address: 192.168.207.221:6379...
2023-04-25 18:08:02,510 INFO worker.py:1538 -- Connected to Ray cluster.
(SPURuntime pid=868) 2023-04-25 18:08:05.075 [error] [context.cc:operator():132] connect to rank=2 failed with error [external/yacl/yacl/link/transport/channel_brpc.cc:368] send, rpc failed=112, message=[E111]Fail to connect Socket{id=1 addr=192.168.207.244:6357} (0x0x4581600): Connection refused [R1][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R2][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R3][E112]Not connected to 192.168.207.244:6357 yet, server_id=1
(SPURuntime pid=11141, ip=192.168.207.222) 2023-04-25 18:09:09.140 [error] [context.cc:operator():132] connect to rank=0 failed with error [external/yacl/yacl/link/transport/channel_brpc.cc:368] send, rpc failed=112, message=[E111]Fail to connect Socket{id=0 addr=192.168.207.221:6337} (0x0x52c46c0): Connection refused [R1][E112]Not connected to 192.168.207.221:6337 yet, server_id=0 [R2][E112]Not connected to 192.168.207.221:6337 yet, server_id=0 [R3][E112]Not connected to 192.168.207.221:6337 yet, server_id=0
(SPURuntime pid=868) 2023-04-25 18:08:06.075 [error] [context.cc:operator():132] connect to rank=2 failed with error [external/yacl/yacl/link/transport/channel_brpc.cc:368] send, rpc failed=112, message=[E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R1][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R2][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R3][E112]Not connected to 192.168.207.244:6357 yet, server_id=1
(SPURuntime pid=868) 2023-04-25 18:08:07.075 [error] [context.cc:operator():132] connect to rank=2 failed with error [external/yacl/yacl/link/transport/channel_brpc.cc:368] send, rpc failed=112, message=[E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R1][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R2][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R3][E112]Not connected to 192.168.207.244:6357 yet, server_id=1
(SPURuntime pid=868) 2023-04-25 18:08:08.076 [error] [context.cc:operator():132] connect to rank=2 failed with error [external/yacl/yacl/link/transport/channel_brpc.cc:368] send, rpc failed=112, message=[E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R1][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R2][E112]Not connected to 192.168.207.244:6357 yet, server_id=1 [R3][E112]Not connected to 192.168.207.244:6357 yet, server_id=1
(_run pid=871) INFO:jax._src.xla_bridge:Unable to initialize backend 'cuda': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
(_run pid=871) INFO:jax._src.xla_bridge:Unable to initialize backend 'rocm': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
(_run pid=871) INFO:jax._src.xla_bridge:Unable to initialize backend 'tpu': INVALID_ARGUMENT: TpuPlatform is not available.
(_run pid=871) INFO:jax._src.xla_bridge:Unable to initialize backend 'plugin': xla_extension has no attributes named get_plugin_device_client. Compile TensorFlow with //tensorflow/compiler/xla/python:enable_plugin_device set to true (defaults to false) to enable this.
(_run pid=871) WARNING:jax._src.xla_bridge:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
(_run pid=871) [2023-04-25 18:08:12.225] [info] [thread_pool.cc:30] Create a fixed thread pool with size 63
(_run pid=11142, ip=192.168.207.222) INFO:jax._src.xla_bridge:Unable to initialize backend 'cuda': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
(_run pid=11142, ip=192.168.207.222) INFO:jax._src.xla_bridge:Unable to initialize backend 'rocm': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
(_run pid=11142, ip=192.168.207.222) INFO:jax._src.xla_bridge:Unable to initialize backend 'tpu': INVALID_ARGUMENT: TpuPlatform is not available.
(_run pid=11142, ip=192.168.207.222) INFO:jax._src.xla_bridge:Unable to initialize backend 'plugin': xla_extension has no attributes named get_plugin_device_client. Compile TensorFlow with //tensorflow/compiler/xla/python:enable_plugin_device set to true (defaults to false) to enable this.
(_run pid=11142, ip=192.168.207.222) WARNING:jax._src.xla_bridge:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
(_run pid=11142, ip=192.168.207.222) [2023-04-25 18:22:26.529] [info] [thread_pool.cc:30] Create a fixed thread pool with size 63
(SPURuntime pid=11141, ip=192.168.207.222) 2023-04-25 18:22:43.264 [info] [thread_pool.cc:ThreadPool:30] Create a fixed thread pool with size 63
(SPURuntime pid=10567, ip=192.168.207.244) 2023-04-25 18:23:14.665 [info] [thread_pool.cc:ThreadPool:30] Create a fixed thread pool with size 23
(SPURuntime pid=868) 2023-04-25 18:26:13.742 [info] [thread_pool.cc:ThreadPool:30] Create a fixed thread pool with size 63
方差结果：0.0
运行时间为1917.6149621009827s
```




Discussion / hints:
spu当前用protobuf序列化参数，亿级的数据会超过protobuf的限制，我们后续会考虑是否能通过重新定义消息结构等方式支持亿级数据计算。
1、我提的上一个issue才是报了超出protobuf限制的错误，这里没有报错也是protobuf的原因吗？
2、有一个疑问就是：同样的亿级数据，为什么加减乘除等这些计算是可以运行的，却没有报超出protobuf限制呢？
Hi @xiaoran33 

这个是一个不同的问题，现在 SPU 在计算 mean 的时候，如果数据量太大会因为精度不足导致
underflow

本质上是这么个事情
```
mean = sum(x)* (1/N) # N=number of elements
```
这个问题我们在后续版本会尝试优化

你可以先试一下 FM128 能不能满足当前需求

Thanks
'runtime_config': {'protocol': 3, 'field': 3}，我的field配置的3，本来就是FM128这个值吧？



> 'runtime_config': {'protocol': 3, 'field': 3}，我的field配置的3，本来就是FM128这个值吧？

加一个 `fxp_fraction_bits`: 30 试一下


'runtime_config': {'protocol': 3, 'field': 3, 'fxp_fraction_bits': 30} 这样加到后面可以吗？

> 'runtime_config': {'protocol': 3, 'field': 3, 'fxp_fraction_bits': 30} 这样加到后面可以吗？

可以，30位小数位大概可以支持到 `2^30` 个数
加了这个参数之后，结果为6.3185153，和明文的6.66654400001397接近了，如果想要更准确，请问fxp_fraction_bits这个参数可以继续调大吗?


> 加了这个参数之后，结果为6.3185153，和明文的6.66654400001397接近了，如果想要更准确，请问fxp_fraction_bits这个参数可以继续调大吗?

这个是牺牲整数位的有效位数换的，你可以试一下 31 32 这样的值，但是不建议调的太大，不然整数部分可能会不够用
好的，明白，那到时这个问题在后续版本尝试优化了之后，在哪可以看到你们的通知吗？
> 好的，明白，那到时这个问题在后续版本尝试优化了之后，在哪可以看到你们的通知吗？

到时候 fix 我会关联这个 issue 的
方便问一下，你测试的数据量大概是多大吗
两方，每一方都是1~10范围的随机数生成的单列五千万行数据，两方合起来一亿

> 两方，每一方都是1~10范围的随机数生成的单列五千万行数据，两方合起来一亿

了解了，谢谢
咨询一下呢：就是同样的测试数据集在测试加、减、乘的时候，时间大概在1300秒左右，但是做除法测试的时间就是9300秒左右，为什么除法的耗时会多这么多呢？有什么可以优化的参数可以减少除法的耗时吗？


Hi @xiaoran33 

除法本身是一个近似计算，计算量和复杂度远高于加法或者乘法。
参见[这里](https://en.wikipedia.org/wiki/Division_algorithm#Fast_division_methods)
https://en.wikipedia.org/wiki/Division_algorithm#Fast_division_methods

这个网址是不是无法访问？
> https://en.wikipedia.org/wiki/Division_algorithm#Fast_division_methods
> 
> 这个网址是不是无法访问？

可以访问的，检查一下网络
貌似真访问不了，用手机流量也无法访问

这是一个科学上网的问题。

Fix patch:
diff --git a/.gitattributes b/.gitattributes
index 543394a3..a71951a5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -50,3 +50,4 @@
 *.bazel linguist-language=Python
 BUILD linguist-language=Python
 .bazelversion text eol=lf
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9424d27f..3ee74995 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,9 @@
 - [Feature] Improve accuracy of tanh approximation
 - [Example] Add flax GPT2
 - [Example] Add ResNet
+- [Bugfix] Fix gen cache in unbalanced psi
 - [Bugfix] Fix possible compilation cache collision
+- [Improvement] Improve accuracy of mean with large size input
 
 
 ## 20230406
diff --git a/bazel/easyloggingpp.BUILD b/bazel/emp-tool.BUILD
similarity index 71%
rename from bazel/easyloggingpp.BUILD
rename to bazel/emp-tool.BUILD
index d5d8ba53..57a2c776 100644
--- a/bazel/easyloggingpp.BUILD
+++ b/bazel/emp-tool.BUILD
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@spulib//bazel:spu.bzl", "spu_cmake_external")
+load("@yacl//bazel:yacl.bzl", "yacl_cmake_external")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -21,13 +21,18 @@ filegroup(
     srcs = glob(["**"]),
 )
 
-spu_cmake_external(
-    name = "easyloggingpp",
+yacl_cmake_external(
+    name = "emp-tool",
     cache_entries = {
-        "build_static_lib": "ON",
+        "OPENSSL_ROOT_DIR": "$EXT_BUILD_DEPS/openssl",
+        "BUILD_TESTING": "OFF",
     },
     lib_source = ":all_srcs",
+    out_data_dirs = ["cmake"],
     out_static_libs = [
-        "libeasyloggingpp.a",
+        "libemp-tool.a",
+    ],
+    deps = [
+        "@com_github_openssl_openssl//:openssl",
     ],
 )
diff --git a/bazel/hexl.BUILD b/bazel/hexl.BUILD
index 7d48b632..40adfc30 100644
--- a/bazel/hexl.BUILD
+++ b/bazel/hexl.BUILD
@@ -24,22 +24,16 @@ filegroup(
 spu_cmake_external(
     name = "hexl",
     cache_entries = {
+        "CMAKE_BUILD_TYPE": "Release",
+        "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/",
         "HEXL_BENCHMARK": "OFF",
         "HEXL_TESTING": "OFF",
-        "EASYLOGGINGPP_LIBRARY": "$EXT_BUILD_DEPS/easyloggingpp",
-        "EASYLOGGINGPP_INCLUDEDIR": "$EXT_BUILD_DEPS/easyloggingpp/include",
+        "CMAKE_INSTALL_LIBDIR": "lib",
     },
     lib_source = ":all_srcs",
-    out_static_libs = select({
-        "@spulib//bazel:spu_build_as_debug": ["libhexl_debug.a"],
-        "//conditions:default": ["libhexl.a"],
-    }),
+    out_data_dirs = ["lib/cmake"],
+    out_static_libs = ["libhexl.a"],
     deps = [
         "@com_github_google_cpu_features//:cpu_features",
-    ] + select({
-        "@spulib//bazel:spu_build_as_debug": [
-            "@com_github_amrayn_easyloggingpp//:easyloggingpp",
-        ],
-        "//conditions:default": [],
-    }),
+    ],
 )
diff --git a/bazel/llvm.bzl b/bazel/llvm.bzl
deleted file mode 100644
index c257d817..00000000
--- a/bazel/llvm.bzl
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2022 Ant Group Co., Ltd.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-load("@llvm-raw//utils/bazel:configure.bzl", "llvm_configure", "llvm_disable_optional_support_deps")
-
-# The subset of LLVM targets that SPU cares about.
-_LLVM_TARGETS = [
-    "NVPTX",  # Somehow mlir tests need this....
-    "X86",
-    "AArch64",
-]
-
-def llvm_setup(name):
-    # Disable terminfo and zlib that are bundled with LLVM.
-    llvm_disable_optional_support_deps()
-
-    # Build @llvm-project from @llvm-raw using overlays.
-    llvm_configure(
-        name = name,
-        repo_mapping = {"@python_runtime": "@local_config_python"},
-        targets = _LLVM_TARGETS,
-    )
diff --git a/bazel/microsoft_apsi.BUILD b/bazel/microsoft_apsi.BUILD
index 5d79d940..b3f6f234 100644
--- a/bazel/microsoft_apsi.BUILD
+++ b/bazel/microsoft_apsi.BUILD
@@ -25,7 +25,10 @@ spu_cmake_external(
     name = "apsi",
     cache_entries = {
         "APSI_USE_LOG4CPLUS": "OFF",
+        "APSI_USE_ZMQ": "OFF",
         "CMAKE_INSTALL_LIBDIR": "lib",
+        "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/",
+        "EXT_BUILD_DEPS": "$EXT_BUILD_DEPS",
     },
     copts = [
         "-DAPSI_DISABLE_JSON",
diff --git a/bazel/microsoft_fourqlib.BUILD b/bazel/microsoft_fourqlib.BUILD
deleted file mode 100644
index 7c8943cd..00000000
--- a/bazel/microsoft_fourqlib.BUILD
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2022 Ant Group Co., Ltd.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-load("@rules_foreign_cc//foreign_cc:defs.bzl", "make")
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
-    name = "all_srcs",
-    srcs = glob(["**"]),
-)
-
-make(
-    name = "fourqlib",
-    args = select({
-        "@bazel_tools//src/conditions:darwin_x86_64": [
-            "ARCH=x64",
-            "AVX2=TRUE",
-            "ASM=FALSE",
-        ],
-        "@platforms//cpu:aarch64": [
-            "ARCH=ARM64",
-        ],
-        "//conditions:default": [
-            "ARCH=x64",
-            "AVX2=TRUE",
-            "ASM=TRUE",
-        ],
-    }),
-    defines = [
-        "__LINUX__",
-    ] + select({
-        "@platforms//cpu:x86_64": [
-            "_AMD64_",
-        ],
-        "//conditions:default": [
-            "_ARM64_",
-        ],
-    }),
-    env = select({
-        "@bazel_tools//src/conditions:darwin": {
-            "AR": "ar",
-        },
-        "//conditions:default": {},
-    }),
-    lib_source = ":all_srcs",
-    out_static_libs = ["libfourq.a"],
-    targets = ["install"],
-    tool_prefix = "cd $$BUILD_TMPDIR/FourQ_64bit_and_portable &&",
-)
diff --git a/bazel/patches/apsi-gen.patch b/bazel/patches/apsi-gen.patch
new file mode 100644
index 00000000..f23b9610
--- /dev/null
+++ b/bazel/patches/apsi-gen.patch
@@ -0,0 +1,1081 @@
+diff --git b/common/apsi/psi_params_generated.h b/common/apsi/psi_params_generated.h
+new file mode 100644
+index 0000000..ed2837b
+--- /dev/null
++++ b/common/apsi/psi_params_generated.h
+@@ -0,0 +1,310 @@
++// automatically generated by the FlatBuffers compiler, do not modify
++
++
++#ifndef FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
++#define FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
++
++#include "flatbuffers/flatbuffers.h"
++
++// Ensure the included flatbuffers.h is the same version as when this file was
++// generated, otherwise it may not be compatible.
++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
++              FLATBUFFERS_VERSION_MINOR == 3 &&
++              FLATBUFFERS_VERSION_REVISION == 3,
++             "Non-compatible flatbuffers version included");
++
++namespace apsi {
++namespace fbs {
++
++struct ItemParams;
++
++struct TableParams;
++
++struct QueryParams;
++struct QueryParamsBuilder;
++
++struct SEALParams;
++struct SEALParamsBuilder;
++
++struct PSIParams;
++struct PSIParamsBuilder;
++
++FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) ItemParams FLATBUFFERS_FINAL_CLASS {
++ private:
++  uint32_t felts_per_item_;
++
++ public:
++  ItemParams()
++      : felts_per_item_(0) {
++  }
++  ItemParams(uint32_t _felts_per_item)
++      : felts_per_item_(::flatbuffers::EndianScalar(_felts_per_item)) {
++  }
++  uint32_t felts_per_item() const {
++    return ::flatbuffers::EndianScalar(felts_per_item_);
++  }
++};
++FLATBUFFERS_STRUCT_END(ItemParams, 4);
++
++FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) TableParams FLATBUFFERS_FINAL_CLASS {
++ private:
++  uint32_t table_size_;
++  uint32_t max_items_per_bin_;
++  uint32_t hash_func_count_;
++
++ public:
++  TableParams()
++      : table_size_(0),
++        max_items_per_bin_(0),
++        hash_func_count_(0) {
++  }
++  TableParams(uint32_t _table_size, uint32_t _max_items_per_bin, uint32_t _hash_func_count)
++      : table_size_(::flatbuffers::EndianScalar(_table_size)),
++        max_items_per_bin_(::flatbuffers::EndianScalar(_max_items_per_bin)),
++        hash_func_count_(::flatbuffers::EndianScalar(_hash_func_count)) {
++  }
++  uint32_t table_size() const {
++    return ::flatbuffers::EndianScalar(table_size_);
++  }
++  uint32_t max_items_per_bin() const {
++    return ::flatbuffers::EndianScalar(max_items_per_bin_);
++  }
++  uint32_t hash_func_count() const {
++    return ::flatbuffers::EndianScalar(hash_func_count_);
++  }
++};
++FLATBUFFERS_STRUCT_END(TableParams, 12);
++
++struct QueryParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef QueryParamsBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_PS_LOW_DEGREE = 4,
++    VT_QUERY_POWERS = 6
++  };
++  uint32_t ps_low_degree() const {
++    return GetField<uint32_t>(VT_PS_LOW_DEGREE, 0);
++  }
++  const ::flatbuffers::Vector<uint32_t> *query_powers() const {
++    return GetPointer<const ::flatbuffers::Vector<uint32_t> *>(VT_QUERY_POWERS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyField<uint32_t>(verifier, VT_PS_LOW_DEGREE, 4) &&
++           VerifyOffset(verifier, VT_QUERY_POWERS) &&
++           verifier.VerifyVector(query_powers()) &&
++           verifier.EndTable();
++  }
++};
++
++struct QueryParamsBuilder {
++  typedef QueryParams Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_ps_low_degree(uint32_t ps_low_degree) {
++    fbb_.AddElement<uint32_t>(QueryParams::VT_PS_LOW_DEGREE, ps_low_degree, 0);
++  }
++  void add_query_powers(::flatbuffers::Offset<::flatbuffers::Vector<uint32_t>> query_powers) {
++    fbb_.AddOffset(QueryParams::VT_QUERY_POWERS, query_powers);
++  }
++  explicit QueryParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<QueryParams> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<QueryParams>(end);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<QueryParams> CreateQueryParams(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint32_t ps_low_degree = 0,
++    ::flatbuffers::Offset<::flatbuffers::Vector<uint32_t>> query_powers = 0) {
++  QueryParamsBuilder builder_(_fbb);
++  builder_.add_query_powers(query_powers);
++  builder_.add_ps_low_degree(ps_low_degree);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<QueryParams> CreateQueryParamsDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint32_t ps_low_degree = 0,
++    const std::vector<uint32_t> *query_powers = nullptr) {
++  auto query_powers__ = query_powers ? _fbb.CreateVector<uint32_t>(*query_powers) : 0;
++  return apsi::fbs::CreateQueryParams(
++      _fbb,
++      ps_low_degree,
++      query_powers__);
++}
++
++struct SEALParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef SEALParamsBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_DATA = 4
++  };
++  const ::flatbuffers::Vector<uint8_t> *data() const {
++    return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_DATA);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_DATA) &&
++           verifier.VerifyVector(data()) &&
++           verifier.EndTable();
++  }
++};
++
++struct SEALParamsBuilder {
++  typedef SEALParams Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_data(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data) {
++    fbb_.AddOffset(SEALParams::VT_DATA, data);
++  }
++  explicit SEALParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<SEALParams> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<SEALParams>(end);
++    fbb_.Required(o, SEALParams::VT_DATA);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<SEALParams> CreateSEALParams(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data = 0) {
++  SEALParamsBuilder builder_(_fbb);
++  builder_.add_data(data);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<SEALParams> CreateSEALParamsDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    const std::vector<uint8_t> *data = nullptr) {
++  auto data__ = data ? _fbb.CreateVector<uint8_t>(*data) : 0;
++  return apsi::fbs::CreateSEALParams(
++      _fbb,
++      data__);
++}
++
++struct PSIParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef PSIParamsBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_VERSION = 4,
++    VT_ITEM_PARAMS = 6,
++    VT_TABLE_PARAMS = 8,
++    VT_QUERY_PARAMS = 10,
++    VT_SEAL_PARAMS = 12
++  };
++  uint32_t version() const {
++    return GetField<uint32_t>(VT_VERSION, 0);
++  }
++  const apsi::fbs::ItemParams *item_params() const {
++    return GetStruct<const apsi::fbs::ItemParams *>(VT_ITEM_PARAMS);
++  }
++  const apsi::fbs::TableParams *table_params() const {
++    return GetStruct<const apsi::fbs::TableParams *>(VT_TABLE_PARAMS);
++  }
++  const apsi::fbs::QueryParams *query_params() const {
++    return GetPointer<const apsi::fbs::QueryParams *>(VT_QUERY_PARAMS);
++  }
++  const apsi::fbs::SEALParams *seal_params() const {
++    return GetPointer<const apsi::fbs::SEALParams *>(VT_SEAL_PARAMS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyField<uint32_t>(verifier, VT_VERSION, 4) &&
++           VerifyField<apsi::fbs::ItemParams>(verifier, VT_ITEM_PARAMS, 4) &&
++           VerifyField<apsi::fbs::TableParams>(verifier, VT_TABLE_PARAMS, 4) &&
++           VerifyOffset(verifier, VT_QUERY_PARAMS) &&
++           verifier.VerifyTable(query_params()) &&
++           VerifyOffsetRequired(verifier, VT_SEAL_PARAMS) &&
++           verifier.VerifyTable(seal_params()) &&
++           verifier.EndTable();
++  }
++};
++
++struct PSIParamsBuilder {
++  typedef PSIParams Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_version(uint32_t version) {
++    fbb_.AddElement<uint32_t>(PSIParams::VT_VERSION, version, 0);
++  }
++  void add_item_params(const apsi::fbs::ItemParams *item_params) {
++    fbb_.AddStruct(PSIParams::VT_ITEM_PARAMS, item_params);
++  }
++  void add_table_params(const apsi::fbs::TableParams *table_params) {
++    fbb_.AddStruct(PSIParams::VT_TABLE_PARAMS, table_params);
++  }
++  void add_query_params(::flatbuffers::Offset<apsi::fbs::QueryParams> query_params) {
++    fbb_.AddOffset(PSIParams::VT_QUERY_PARAMS, query_params);
++  }
++  void add_seal_params(::flatbuffers::Offset<apsi::fbs::SEALParams> seal_params) {
++    fbb_.AddOffset(PSIParams::VT_SEAL_PARAMS, seal_params);
++  }
++  explicit PSIParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<PSIParams> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<PSIParams>(end);
++    fbb_.Required(o, PSIParams::VT_SEAL_PARAMS);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<PSIParams> CreatePSIParams(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint32_t version = 0,
++    const apsi::fbs::ItemParams *item_params = nullptr,
++    const apsi::fbs::TableParams *table_params = nullptr,
++    ::flatbuffers::Offset<apsi::fbs::QueryParams> query_params = 0,
++    ::flatbuffers::Offset<apsi::fbs::SEALParams> seal_params = 0) {
++  PSIParamsBuilder builder_(_fbb);
++  builder_.add_seal_params(seal_params);
++  builder_.add_query_params(query_params);
++  builder_.add_table_params(table_params);
++  builder_.add_item_params(item_params);
++  builder_.add_version(version);
++  return builder_.Finish();
++}
++
++inline const apsi::fbs::PSIParams *GetPSIParams(const void *buf) {
++  return ::flatbuffers::GetRoot<apsi::fbs::PSIParams>(buf);
++}
++
++inline const apsi::fbs::PSIParams *GetSizePrefixedPSIParams(const void *buf) {
++  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::PSIParams>(buf);
++}
++
++inline bool VerifyPSIParamsBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifyBuffer<apsi::fbs::PSIParams>(nullptr);
++}
++
++inline bool VerifySizePrefixedPSIParamsBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifySizePrefixedBuffer<apsi::fbs::PSIParams>(nullptr);
++}
++
++inline void FinishPSIParamsBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::PSIParams> root) {
++  fbb.Finish(root);
++}
++
++inline void FinishSizePrefixedPSIParamsBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::PSIParams> root) {
++  fbb.FinishSizePrefixed(root);
++}
++
++}  // namespace fbs
++}  // namespace apsi
++
++#endif  // FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
+diff --git b/sender/apsi/bin_bundle_generated.h b/sender/apsi/bin_bundle_generated.h
+new file mode 100644
+index 0000000..e821e33
+--- /dev/null
++++ b/sender/apsi/bin_bundle_generated.h
+@@ -0,0 +1,490 @@
++// automatically generated by the FlatBuffers compiler, do not modify
++
++
++#ifndef FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
++#define FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
++
++#include "flatbuffers/flatbuffers.h"
++
++// Ensure the included flatbuffers.h is the same version as when this file was
++// generated, otherwise it may not be compatible.
++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
++              FLATBUFFERS_VERSION_MINOR == 3 &&
++              FLATBUFFERS_VERSION_REVISION == 3,
++             "Non-compatible flatbuffers version included");
++
++namespace apsi {
++namespace fbs {
++
++struct FEltArray;
++struct FEltArrayBuilder;
++
++struct FEltMatrix;
++struct FEltMatrixBuilder;
++
++struct Plaintext;
++struct PlaintextBuilder;
++
++struct BatchedPlaintextPolyn;
++struct BatchedPlaintextPolynBuilder;
++
++struct BinBundleCache;
++struct BinBundleCacheBuilder;
++
++struct BinBundle;
++struct BinBundleBuilder;
++
++struct FEltArray FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef FEltArrayBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_FELTS = 4
++  };
++  const ::flatbuffers::Vector<uint64_t> *felts() const {
++    return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_FELTS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_FELTS) &&
++           verifier.VerifyVector(felts()) &&
++           verifier.EndTable();
++  }
++};
++
++struct FEltArrayBuilder {
++  typedef FEltArray Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_felts(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> felts) {
++    fbb_.AddOffset(FEltArray::VT_FELTS, felts);
++  }
++  explicit FEltArrayBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<FEltArray> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<FEltArray>(end);
++    fbb_.Required(o, FEltArray::VT_FELTS);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<FEltArray> CreateFEltArray(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> felts = 0) {
++  FEltArrayBuilder builder_(_fbb);
++  builder_.add_felts(felts);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<FEltArray> CreateFEltArrayDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    const std::vector<uint64_t> *felts = nullptr) {
++  auto felts__ = felts ? _fbb.CreateVector<uint64_t>(*felts) : 0;
++  return apsi::fbs::CreateFEltArray(
++      _fbb,
++      felts__);
++}
++
++struct FEltMatrix FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef FEltMatrixBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_ROWS = 4
++  };
++  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *rows() const {
++    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *>(VT_ROWS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_ROWS) &&
++           verifier.VerifyVector(rows()) &&
++           verifier.VerifyVectorOfTables(rows()) &&
++           verifier.EndTable();
++  }
++};
++
++struct FEltMatrixBuilder {
++  typedef FEltMatrix Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_rows(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>>> rows) {
++    fbb_.AddOffset(FEltMatrix::VT_ROWS, rows);
++  }
++  explicit FEltMatrixBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<FEltMatrix> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<FEltMatrix>(end);
++    fbb_.Required(o, FEltMatrix::VT_ROWS);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<FEltMatrix> CreateFEltMatrix(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>>> rows = 0) {
++  FEltMatrixBuilder builder_(_fbb);
++  builder_.add_rows(rows);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<FEltMatrix> CreateFEltMatrixDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *rows = nullptr) {
++  auto rows__ = rows ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltArray>>(*rows) : 0;
++  return apsi::fbs::CreateFEltMatrix(
++      _fbb,
++      rows__);
++}
++
++struct Plaintext FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef PlaintextBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_DATA = 4
++  };
++  const ::flatbuffers::Vector<uint8_t> *data() const {
++    return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_DATA);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_DATA) &&
++           verifier.VerifyVector(data()) &&
++           verifier.EndTable();
++  }
++};
++
++struct PlaintextBuilder {
++  typedef Plaintext Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_data(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data) {
++    fbb_.AddOffset(Plaintext::VT_DATA, data);
++  }
++  explicit PlaintextBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<Plaintext> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<Plaintext>(end);
++    fbb_.Required(o, Plaintext::VT_DATA);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<Plaintext> CreatePlaintext(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data = 0) {
++  PlaintextBuilder builder_(_fbb);
++  builder_.add_data(data);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<Plaintext> CreatePlaintextDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    const std::vector<uint8_t> *data = nullptr) {
++  auto data__ = data ? _fbb.CreateVector<uint8_t>(*data) : 0;
++  return apsi::fbs::CreatePlaintext(
++      _fbb,
++      data__);
++}
++
++struct BatchedPlaintextPolyn FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef BatchedPlaintextPolynBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_COEFFS = 4
++  };
++  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs() const {
++    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *>(VT_COEFFS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_COEFFS) &&
++           verifier.VerifyVector(coeffs()) &&
++           verifier.VerifyVectorOfTables(coeffs()) &&
++           verifier.EndTable();
++  }
++};
++
++struct BatchedPlaintextPolynBuilder {
++  typedef BatchedPlaintextPolyn Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_coeffs(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs) {
++    fbb_.AddOffset(BatchedPlaintextPolyn::VT_COEFFS, coeffs);
++  }
++  explicit BatchedPlaintextPolynBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<BatchedPlaintextPolyn> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<BatchedPlaintextPolyn>(end);
++    fbb_.Required(o, BatchedPlaintextPolyn::VT_COEFFS);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolyn(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs = 0) {
++  BatchedPlaintextPolynBuilder builder_(_fbb);
++  builder_.add_coeffs(coeffs);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolynDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    const std::vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs = nullptr) {
++  auto coeffs__ = coeffs ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::Plaintext>>(*coeffs) : 0;
++  return apsi::fbs::CreateBatchedPlaintextPolyn(
++      _fbb,
++      coeffs__);
++}
++
++struct BinBundleCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef BinBundleCacheBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_FELT_MATCHING_POLYNS = 4,
++    VT_BATCHED_MATCHING_POLYN = 6,
++    VT_FELT_INTERP_POLYNS = 8,
++    VT_BATCHED_INTERP_POLYNS = 10
++  };
++  const apsi::fbs::FEltMatrix *felt_matching_polyns() const {
++    return GetPointer<const apsi::fbs::FEltMatrix *>(VT_FELT_MATCHING_POLYNS);
++  }
++  const apsi::fbs::BatchedPlaintextPolyn *batched_matching_polyn() const {
++    return GetPointer<const apsi::fbs::BatchedPlaintextPolyn *>(VT_BATCHED_MATCHING_POLYN);
++  }
++  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns() const {
++    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_FELT_INTERP_POLYNS);
++  }
++  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns() const {
++    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *>(VT_BATCHED_INTERP_POLYNS);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_FELT_MATCHING_POLYNS) &&
++           verifier.VerifyTable(felt_matching_polyns()) &&
++           VerifyOffsetRequired(verifier, VT_BATCHED_MATCHING_POLYN) &&
++           verifier.VerifyTable(batched_matching_polyn()) &&
++           VerifyOffset(verifier, VT_FELT_INTERP_POLYNS) &&
++           verifier.VerifyVector(felt_interp_polyns()) &&
++           verifier.VerifyVectorOfTables(felt_interp_polyns()) &&
++           VerifyOffset(verifier, VT_BATCHED_INTERP_POLYNS) &&
++           verifier.VerifyVector(batched_interp_polyns()) &&
++           verifier.VerifyVectorOfTables(batched_interp_polyns()) &&
++           verifier.EndTable();
++  }
++};
++
++struct BinBundleCacheBuilder {
++  typedef BinBundleCache Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_felt_matching_polyns(::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns) {
++    fbb_.AddOffset(BinBundleCache::VT_FELT_MATCHING_POLYNS, felt_matching_polyns);
++  }
++  void add_batched_matching_polyn(::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn) {
++    fbb_.AddOffset(BinBundleCache::VT_BATCHED_MATCHING_POLYN, batched_matching_polyn);
++  }
++  void add_felt_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns) {
++    fbb_.AddOffset(BinBundleCache::VT_FELT_INTERP_POLYNS, felt_interp_polyns);
++  }
++  void add_batched_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns) {
++    fbb_.AddOffset(BinBundleCache::VT_BATCHED_INTERP_POLYNS, batched_interp_polyns);
++  }
++  explicit BinBundleCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<BinBundleCache> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<BinBundleCache>(end);
++    fbb_.Required(o, BinBundleCache::VT_FELT_MATCHING_POLYNS);
++    fbb_.Required(o, BinBundleCache::VT_BATCHED_MATCHING_POLYN);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCache(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0,
++    ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0,
++    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns = 0,
++    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns = 0) {
++  BinBundleCacheBuilder builder_(_fbb);
++  builder_.add_batched_interp_polyns(batched_interp_polyns);
++  builder_.add_felt_interp_polyns(felt_interp_polyns);
++  builder_.add_batched_matching_polyn(batched_matching_polyn);
++  builder_.add_felt_matching_polyns(felt_matching_polyns);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCacheDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0,
++    ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0,
++    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns = nullptr,
++    const std::vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns = nullptr) {
++  auto felt_interp_polyns__ = felt_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*felt_interp_polyns) : 0;
++  auto batched_interp_polyns__ = batched_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>(*batched_interp_polyns) : 0;
++  return apsi::fbs::CreateBinBundleCache(
++      _fbb,
++      felt_matching_polyns,
++      batched_matching_polyn,
++      felt_interp_polyns__,
++      batched_interp_polyns__);
++}
++
++struct BinBundle FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef BinBundleBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_BUNDLE_IDX = 4,
++    VT_MOD = 6,
++    VT_ITEM_BINS = 8,
++    VT_LABEL_BINS = 10,
++    VT_CACHE = 12,
++    VT_STRIPPED = 14
++  };
++  uint32_t bundle_idx() const {
++    return GetField<uint32_t>(VT_BUNDLE_IDX, 0);
++  }
++  uint64_t mod() const {
++    return GetField<uint64_t>(VT_MOD, 0);
++  }
++  const apsi::fbs::FEltMatrix *item_bins() const {
++    return GetPointer<const apsi::fbs::FEltMatrix *>(VT_ITEM_BINS);
++  }
++  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins() const {
++    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_LABEL_BINS);
++  }
++  const apsi::fbs::BinBundleCache *cache() const {
++    return GetPointer<const apsi::fbs::BinBundleCache *>(VT_CACHE);
++  }
++  bool stripped() const {
++    return GetField<uint8_t>(VT_STRIPPED, 0) != 0;
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyField<uint32_t>(verifier, VT_BUNDLE_IDX, 4) &&
++           VerifyField<uint64_t>(verifier, VT_MOD, 8) &&
++           VerifyOffsetRequired(verifier, VT_ITEM_BINS) &&
++           verifier.VerifyTable(item_bins()) &&
++           VerifyOffset(verifier, VT_LABEL_BINS) &&
++           verifier.VerifyVector(label_bins()) &&
++           verifier.VerifyVectorOfTables(label_bins()) &&
++           VerifyOffset(verifier, VT_CACHE) &&
++           verifier.VerifyTable(cache()) &&
++           VerifyField<uint8_t>(verifier, VT_STRIPPED, 1) &&
++           verifier.EndTable();
++  }
++};
++
++struct BinBundleBuilder {
++  typedef BinBundle Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_bundle_idx(uint32_t bundle_idx) {
++    fbb_.AddElement<uint32_t>(BinBundle::VT_BUNDLE_IDX, bundle_idx, 0);
++  }
++  void add_mod(uint64_t mod) {
++    fbb_.AddElement<uint64_t>(BinBundle::VT_MOD, mod, 0);
++  }
++  void add_item_bins(::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins) {
++    fbb_.AddOffset(BinBundle::VT_ITEM_BINS, item_bins);
++  }
++  void add_label_bins(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins) {
++    fbb_.AddOffset(BinBundle::VT_LABEL_BINS, label_bins);
++  }
++  void add_cache(::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache) {
++    fbb_.AddOffset(BinBundle::VT_CACHE, cache);
++  }
++  void add_stripped(bool stripped) {
++    fbb_.AddElement<uint8_t>(BinBundle::VT_STRIPPED, static_cast<uint8_t>(stripped), 0);
++  }
++  explicit BinBundleBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<BinBundle> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<BinBundle>(end);
++    fbb_.Required(o, BinBundle::VT_ITEM_BINS);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<BinBundle> CreateBinBundle(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint32_t bundle_idx = 0,
++    uint64_t mod = 0,
++    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0,
++    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins = 0,
++    ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0,
++    bool stripped = false) {
++  BinBundleBuilder builder_(_fbb);
++  builder_.add_mod(mod);
++  builder_.add_cache(cache);
++  builder_.add_label_bins(label_bins);
++  builder_.add_item_bins(item_bins);
++  builder_.add_bundle_idx(bundle_idx);
++  builder_.add_stripped(stripped);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<BinBundle> CreateBinBundleDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint32_t bundle_idx = 0,
++    uint64_t mod = 0,
++    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0,
++    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins = nullptr,
++    ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0,
++    bool stripped = false) {
++  auto label_bins__ = label_bins ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*label_bins) : 0;
++  return apsi::fbs::CreateBinBundle(
++      _fbb,
++      bundle_idx,
++      mod,
++      item_bins,
++      label_bins__,
++      cache,
++      stripped);
++}
++
++inline const apsi::fbs::BinBundle *GetBinBundle(const void *buf) {
++  return ::flatbuffers::GetRoot<apsi::fbs::BinBundle>(buf);
++}
++
++inline const apsi::fbs::BinBundle *GetSizePrefixedBinBundle(const void *buf) {
++  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::BinBundle>(buf);
++}
++
++inline bool VerifyBinBundleBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifyBuffer<apsi::fbs::BinBundle>(nullptr);
++}
++
++inline bool VerifySizePrefixedBinBundleBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifySizePrefixedBuffer<apsi::fbs::BinBundle>(nullptr);
++}
++
++inline void FinishBinBundleBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::BinBundle> root) {
++  fbb.Finish(root);
++}
++
++inline void FinishSizePrefixedBinBundleBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::BinBundle> root) {
++  fbb.FinishSizePrefixed(root);
++}
++
++}  // namespace fbs
++}  // namespace apsi
++
++#endif  // FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
+diff --git b/sender/apsi/util/cuckoo_filter_generated.h b/sender/apsi/util/cuckoo_filter_generated.h
+new file mode 100644
+index 0000000..2d2b2b7
+--- /dev/null
++++ b/sender/apsi/util/cuckoo_filter_generated.h
+@@ -0,0 +1,263 @@
++// automatically generated by the FlatBuffers compiler, do not modify
++
++
++#ifndef FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
++#define FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
++
++#include "flatbuffers/flatbuffers.h"
++
++// Ensure the included flatbuffers.h is the same version as when this file was
++// generated, otherwise it may not be compatible.
++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
++              FLATBUFFERS_VERSION_MINOR == 3 &&
++              FLATBUFFERS_VERSION_REVISION == 3,
++             "Non-compatible flatbuffers version included");
++
++namespace apsi {
++namespace fbs {
++
++struct CuckooFilterOverflowCache;
++struct CuckooFilterOverflowCacheBuilder;
++
++struct CuckooFilterTable;
++struct CuckooFilterTableBuilder;
++
++struct CuckooFilter;
++struct CuckooFilterBuilder;
++
++struct CuckooFilterOverflowCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef CuckooFilterOverflowCacheBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_INDEX = 4,
++    VT_TAG = 6,
++    VT_USED = 8
++  };
++  uint64_t index() const {
++    return GetField<uint64_t>(VT_INDEX, 0);
++  }
++  uint64_t tag() const {
++    return GetField<uint64_t>(VT_TAG, 0);
++  }
++  bool used() const {
++    return GetField<uint8_t>(VT_USED, 0) != 0;
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyField<uint64_t>(verifier, VT_INDEX, 8) &&
++           VerifyField<uint64_t>(verifier, VT_TAG, 8) &&
++           VerifyField<uint8_t>(verifier, VT_USED, 1) &&
++           verifier.EndTable();
++  }
++};
++
++struct CuckooFilterOverflowCacheBuilder {
++  typedef CuckooFilterOverflowCache Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_index(uint64_t index) {
++    fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_INDEX, index, 0);
++  }
++  void add_tag(uint64_t tag) {
++    fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_TAG, tag, 0);
++  }
++  void add_used(bool used) {
++    fbb_.AddElement<uint8_t>(CuckooFilterOverflowCache::VT_USED, static_cast<uint8_t>(used), 0);
++  }
++  explicit CuckooFilterOverflowCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<CuckooFilterOverflowCache> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<CuckooFilterOverflowCache>(end);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<CuckooFilterOverflowCache> CreateCuckooFilterOverflowCache(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint64_t index = 0,
++    uint64_t tag = 0,
++    bool used = false) {
++  CuckooFilterOverflowCacheBuilder builder_(_fbb);
++  builder_.add_tag(tag);
++  builder_.add_index(index);
++  builder_.add_used(used);
++  return builder_.Finish();
++}
++
++struct CuckooFilterTable FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef CuckooFilterTableBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_NUM_BUCKETS = 4,
++    VT_BITS_PER_TAG = 6,
++    VT_TABLE = 8
++  };
++  uint64_t num_buckets() const {
++    return GetField<uint64_t>(VT_NUM_BUCKETS, 0);
++  }
++  uint64_t bits_per_tag() const {
++    return GetField<uint64_t>(VT_BITS_PER_TAG, 0);
++  }
++  const ::flatbuffers::Vector<uint64_t> *table() const {
++    return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_TABLE);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyField<uint64_t>(verifier, VT_NUM_BUCKETS, 8) &&
++           VerifyField<uint64_t>(verifier, VT_BITS_PER_TAG, 8) &&
++           VerifyOffsetRequired(verifier, VT_TABLE) &&
++           verifier.VerifyVector(table()) &&
++           verifier.EndTable();
++  }
++};
++
++struct CuckooFilterTableBuilder {
++  typedef CuckooFilterTable Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_num_buckets(uint64_t num_buckets) {
++    fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_NUM_BUCKETS, num_buckets, 0);
++  }
++  void add_bits_per_tag(uint64_t bits_per_tag) {
++    fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_BITS_PER_TAG, bits_per_tag, 0);
++  }
++  void add_table(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table) {
++    fbb_.AddOffset(CuckooFilterTable::VT_TABLE, table);
++  }
++  explicit CuckooFilterTableBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<CuckooFilterTable> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<CuckooFilterTable>(end);
++    fbb_.Required(o, CuckooFilterTable::VT_TABLE);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTable(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint64_t num_buckets = 0,
++    uint64_t bits_per_tag = 0,
++    ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table = 0) {
++  CuckooFilterTableBuilder builder_(_fbb);
++  builder_.add_bits_per_tag(bits_per_tag);
++  builder_.add_num_buckets(num_buckets);
++  builder_.add_table(table);
++  return builder_.Finish();
++}
++
++inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTableDirect(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    uint64_t num_buckets = 0,
++    uint64_t bits_per_tag = 0,
++    const std::vector<uint64_t> *table = nullptr) {
++  auto table__ = table ? _fbb.CreateVector<uint64_t>(*table) : 0;
++  return apsi::fbs::CreateCuckooFilterTable(
++      _fbb,
++      num_buckets,
++      bits_per_tag,
++      table__);
++}
++
++struct CuckooFilter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
++  typedef CuckooFilterBuilder Builder;
++  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
++    VT_TABLE = 4,
++    VT_NUM_ITEMS = 6,
++    VT_OVERFLOW = 8
++  };
++  const apsi::fbs::CuckooFilterTable *table() const {
++    return GetPointer<const apsi::fbs::CuckooFilterTable *>(VT_TABLE);
++  }
++  uint64_t num_items() const {
++    return GetField<uint64_t>(VT_NUM_ITEMS, 0);
++  }
++  const apsi::fbs::CuckooFilterOverflowCache *overflow() const {
++    return GetPointer<const apsi::fbs::CuckooFilterOverflowCache *>(VT_OVERFLOW);
++  }
++  bool Verify(::flatbuffers::Verifier &verifier) const {
++    return VerifyTableStart(verifier) &&
++           VerifyOffsetRequired(verifier, VT_TABLE) &&
++           verifier.VerifyTable(table()) &&
++           VerifyField<uint64_t>(verifier, VT_NUM_ITEMS, 8) &&
++           VerifyOffsetRequired(verifier, VT_OVERFLOW) &&
++           verifier.VerifyTable(overflow()) &&
++           verifier.EndTable();
++  }
++};
++
++struct CuckooFilterBuilder {
++  typedef CuckooFilter Table;
++  ::flatbuffers::FlatBufferBuilder &fbb_;
++  ::flatbuffers::uoffset_t start_;
++  void add_table(::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table) {
++    fbb_.AddOffset(CuckooFilter::VT_TABLE, table);
++  }
++  void add_num_items(uint64_t num_items) {
++    fbb_.AddElement<uint64_t>(CuckooFilter::VT_NUM_ITEMS, num_items, 0);
++  }
++  void add_overflow(::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow) {
++    fbb_.AddOffset(CuckooFilter::VT_OVERFLOW, overflow);
++  }
++  explicit CuckooFilterBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
++        : fbb_(_fbb) {
++    start_ = fbb_.StartTable();
++  }
++  ::flatbuffers::Offset<CuckooFilter> Finish() {
++    const auto end = fbb_.EndTable(start_);
++    auto o = ::flatbuffers::Offset<CuckooFilter>(end);
++    fbb_.Required(o, CuckooFilter::VT_TABLE);
++    fbb_.Required(o, CuckooFilter::VT_OVERFLOW);
++    return o;
++  }
++};
++
++inline ::flatbuffers::Offset<CuckooFilter> CreateCuckooFilter(
++    ::flatbuffers::FlatBufferBuilder &_fbb,
++    ::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table = 0,
++    uint64_t num_items = 0,
++    ::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow = 0) {
++  CuckooFilterBuilder builder_(_fbb);
++  builder_.add_num_items(num_items);
++  builder_.add_overflow(overflow);
++  builder_.add_table(table);
++  return builder_.Finish();
++}
++
++inline const apsi::fbs::CuckooFilter *GetCuckooFilter(const void *buf) {
++  return ::flatbuffers::GetRoot<apsi::fbs::CuckooFilter>(buf);
++}
++
++inline const apsi::fbs::CuckooFilter *GetSizePrefixedCuckooFilter(const void *buf) {
++  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::CuckooFilter>(buf);
++}
++
++inline bool VerifyCuckooFilterBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifyBuffer<apsi::fbs::CuckooFilter>(nullptr);
++}
++
++inline bool VerifySizePrefixedCuckooFilterBuffer(
++    ::flatbuffers::Verifier &verifier) {
++  return verifier.VerifySizePrefixedBuffer<apsi::fbs::CuckooFilter>(nullptr);
++}
++
++inline void FinishCuckooFilterBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) {
++  fbb.Finish(root);
++}
++
++inline void FinishSizePrefixedCuckooFilterBuffer(
++    ::flatbuffers::FlatBufferBuilder &fbb,
++    ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) {
++  fbb.FinishSizePrefixed(root);
++}
++
++}  // namespace fbs
++}  // namespace apsi
++
++#endif  // FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
diff --git a/bazel/patches/apsi.patch b/bazel/patches/apsi.patch
index ee554be7..dc2a9802 100644
--- a/bazel/patches/apsi.patch
+++ b/bazel/patches/apsi.patch
@@ -1,5 +1,5 @@
 diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 78d54a6..c1e32d3 100644
+index 78d54a6..07df321 100644
 --- a/CMakeLists.txt
 +++ b/CMakeLists.txt
 @@ -122,7 +122,7 @@ set(APSI_INCLUDES_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/APSI-${APSI_VERSION_MA
@@ -11,10 +11,11 @@ index 78d54a6..c1e32d3 100644
  if(NOT SEAL_FOUND)
      message(FATAL_ERROR "Microsoft SEAL: not found")
  else()
-@@ -141,27 +141,27 @@ else()
+@@ -140,24 +140,6 @@ else()
+     message(STATUS "Microsoft Kuku: found")
  endif()
  
- # Flatbuffers
+-# Flatbuffers
 -find_package(Flatbuffers REQUIRED)
 -if(NOT Flatbuffers_FOUND)
 -    message(FATAL_ERROR "Flatbuffers: not found")
@@ -24,47 +25,18 @@ index 78d54a6..c1e32d3 100644
 -    message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}")
 -    include(CompileSchemaCXX)
 -endif()
-+#find_package(Flatbuffers REQUIRED)
-+#if(NOT Flatbuffers_FOUND)
-+#    message(FATAL_ERROR "Flatbuffers: not found")
-+#else()
-+#    message(STATUS "Flatbuffers: found")
-+#    get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE)
-+#    message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}")
-+#    include(CompileSchemaCXX)
-+#endif()
- 
- # jsoncpp: for parameter configuration
+-
+-# jsoncpp: for parameter configuration
 -find_package(jsoncpp REQUIRED)
 -if (NOT jsoncpp_FOUND)
 -    message(FATAL_ERROR "jsoncpp: not found")
 -else()
 -    message(STATUS "jsoncpp: found")
 -endif()
-+#find_package(jsoncpp REQUIRED)
-+#if (NOT jsoncpp_FOUND)
-+#    message(STATUS "jsoncpp: not found")
-+#else()
-+#    message(STATUS "jsoncpp: found")
-+#endif()
  
  # [Option] APSI_USE_LOG4CPLUS (default: ON)
  set(APSI_USE_LOG4CPLUS_OPTION_STR "Use Log4cplus for logging")
--option(APSI_USE_LOG4CPLUS ${APSI_USE_LOG4CPLUS_OPTION_STR} ON)
-+option(APSI_USE_LOG4CPLUS ${APSI_USE_LOG4CPLUS_OPTION_STR} OFF)
- if(APSI_USE_LOG4CPLUS)
-     # Log4cplus
-     find_package(log4cplus REQUIRED)
-@@ -174,7 +174,7 @@ endif()
- 
- # [Option] APSI_USE_ZMQ (default: ON)
- set(APSI_USE_ZMQ_OPTION_STR "Use ZeroMQ for networking")
--option(APSI_USE_ZMQ ${APSI_USE_ZMQ_OPTION_STR} ON)
-+option(APSI_USE_ZMQ ${APSI_USE_ZMQ_OPTION_STR} OFF)
- if(APSI_USE_ZMQ)
-     # ZeroMQ base
-     find_package(ZeroMQ REQUIRED)
-@@ -263,9 +263,7 @@ apsi_install_target(apsi APSITargets)
+@@ -263,9 +245,7 @@ apsi_install_target(apsi APSITargets)
  
  target_link_libraries(apsi
      PUBLIC SEAL::seal
@@ -76,370 +48,243 @@ index 78d54a6..c1e32d3 100644
      target_link_libraries(apsi PUBLIC log4cplus::log4cplus)
  endif()
 diff --git a/common/apsi/CMakeLists.txt b/common/apsi/CMakeLists.txt
-index a65bbfe..3e66488 100644
+index a65bbfe..ee3b975 100644
 --- a/common/apsi/CMakeLists.txt
 +++ b/common/apsi/CMakeLists.txt
-@@ -29,7 +29,7 @@ install(
+@@ -29,7 +29,5 @@ install(
  )
  
  add_subdirectory(fourq)
 -add_subdirectory(network)
 -add_subdirectory(oprf)
-+#add_subdirectory(network)
-+#add_subdirectory(oprf)
  add_subdirectory(util)
  set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES} PARENT_SCOPE)
 diff --git a/common/apsi/fourq/CMakeLists.txt b/common/apsi/fourq/CMakeLists.txt
-index 3b15780..9e2632e 100644
+index 3b15780..5085038 100644
 --- a/common/apsi/fourq/CMakeLists.txt
 +++ b/common/apsi/fourq/CMakeLists.txt
-@@ -3,11 +3,11 @@
- 
- # Source files in this directory
- set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES}
--    ${CMAKE_CURRENT_LIST_DIR}/crypto_util.c
--    ${CMAKE_CURRENT_LIST_DIR}/eccp2_core.c
--    ${CMAKE_CURRENT_LIST_DIR}/eccp2_no_endo.c
--    ${CMAKE_CURRENT_LIST_DIR}/eccp2.c
--    ${CMAKE_CURRENT_LIST_DIR}/hash_to_curve.c
-+#    ${CMAKE_CURRENT_LIST_DIR}/crypto_util.c
-+#    ${CMAKE_CURRENT_LIST_DIR}/eccp2_core.c
-+#    ${CMAKE_CURRENT_LIST_DIR}/eccp2_no_endo.c
-+#    ${CMAKE_CURRENT_LIST_DIR}/eccp2.c
-+#    ${CMAKE_CURRENT_LIST_DIR}/hash_to_curve.c
+@@ -8,8 +8,21 @@ set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES}
+     ${CMAKE_CURRENT_LIST_DIR}/eccp2_no_endo.c
+     ${CMAKE_CURRENT_LIST_DIR}/eccp2.c
+     ${CMAKE_CURRENT_LIST_DIR}/hash_to_curve.c
++    ${CMAKE_CURRENT_LIST_DIR}/kex.c
      ${CMAKE_CURRENT_LIST_DIR}/random.c
  )
++# Add header files for installation
++install(
++    FILES
++        ${CMAKE_CURRENT_LIST_DIR}/FourQ_api.h
++        ${CMAKE_CURRENT_LIST_DIR}/FourQ_internal.h
++        ${CMAKE_CURRENT_LIST_DIR}/FourQ_params.h
++        ${CMAKE_CURRENT_LIST_DIR}/FourQ_tables.h
++        ${CMAKE_CURRENT_LIST_DIR}/FourQ.h
++        ${CMAKE_CURRENT_LIST_DIR}/table_lookup.h
++    DESTINATION
++        ${APSI_INCLUDES_INSTALL_DIR}/apsi/fourq
++)
  
-diff --git b/common/apsi/psi_params_generated.h b/common/apsi/psi_params_generated.h
+ if(APSI_FOURQ_AMD64)
+     add_subdirectory(amd64)
+diff --git a/common/apsi/fourq/kex.c b/common/apsi/fourq/kex.c
 new file mode 100644
-index 0000000..ed2837b
+index 0000000..5c37c60
 --- /dev/null
-+++ b/common/apsi/psi_params_generated.h
-@@ -0,0 +1,310 @@
-+// automatically generated by the FlatBuffers compiler, do not modify
-+
++++ b/common/apsi/fourq/kex.c
+@@ -0,0 +1,181 @@
++/********************************************************************************
++* FourQlib: a high-performance crypto library based on the elliptic curve FourQ
++*
++*    Copyright (c) Microsoft Corporation. All rights reserved.
++*
++* Abstract: Diffie-Hellman key exchange based on FourQ
++*           option 1: co-factor ecdh using compressed 32-byte public keys,
++*           (see https://datatracker.ietf.org/doc/draft-ladd-cfrg-4q/).         
++*           option 2: co-factor ecdh using uncompressed, 64-byte public keys. 
++*********************************************************************************/
++
++#include "apsi/fourq/FourQ_internal.h"
++#include "apsi/fourq/FourQ_params.h"
++#include "apsi/fourq/random.h"
++#include <string.h>
++
++
++static __inline bool is_neutral_point(point_t P)
++{ // Is P the neutral point (0,1)?
++  // SECURITY NOTE: this function does not run in constant time (input point P is assumed to be public).
++  
++    if (is_zero_ct((digit_t*)P->x, 2*NWORDS_FIELD) && is_zero_ct(&((digit_t*)P->y)[1], 2*NWORDS_FIELD-1) && is_digit_zero_ct(P->y[0][0] - 1)) {  
++		return true;
++    }
++    return false;
++}
++
++
++/*************** ECDH USING COMPRESSED, 32-BYTE PUBLIC KEYS ***************/
++
++ECCRYPTO_STATUS CompressedPublicKeyGeneration(const unsigned char* SecretKey, unsigned char* PublicKey)
++{ // Compressed public key generation for key exchange
++  // It produces a public key PublicKey, which is the encoding of P = SecretKey*G (G is the generator).
++  // Input:  32-byte SecretKey
++  // Output: 32-byte PublicKey
++    point_t P;
++    
++    ecc_mul_fixed((digit_t*)SecretKey, P);  // Compute public key                                       
++	encode(P, PublicKey);                   // Encode public key
++
++    return ECCRYPTO_SUCCESS;
++}
++
++
++ECCRYPTO_STATUS CompressedKeyGeneration(unsigned char* SecretKey, unsigned char* PublicKey)
++{ // Keypair generation for key exchange. Public key is compressed to 32 bytes
++  // It produces a private key SecretKey and a public key PublicKey, which is the encoding of P = SecretKey*G (G is the generator).
++  // Outputs: 32-byte SecretKey and 32-byte PublicKey 
++    ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN;
 +
-+#ifndef FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
-+#define FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
++	Status = RandomBytesFunction(SecretKey, 32);
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
++  
++    Status = CompressedPublicKeyGeneration(SecretKey, PublicKey);
++    if (Status != ECCRYPTO_SUCCESS) {
++        goto cleanup;
++    }
 +
-+#include "flatbuffers/flatbuffers.h"
++    return ECCRYPTO_SUCCESS;
 +
-+// Ensure the included flatbuffers.h is the same version as when this file was
-+// generated, otherwise it may not be compatible.
-+static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
-+              FLATBUFFERS_VERSION_MINOR == 3 &&
-+              FLATBUFFERS_VERSION_REVISION == 3,
-+             "Non-compatible flatbuffers version included");
-+
-+namespace apsi {
-+namespace fbs {
-+
-+struct ItemParams;
-+
-+struct TableParams;
-+
-+struct QueryParams;
-+struct QueryParamsBuilder;
-+
-+struct SEALParams;
-+struct SEALParamsBuilder;
++cleanup:
++    clear_words((unsigned int*)SecretKey, 256/(sizeof(unsigned int)*8));
++    clear_words((unsigned int*)PublicKey, 256/(sizeof(unsigned int)*8));
++
++    return Status;
++}
 +
-+struct PSIParams;
-+struct PSIParamsBuilder;
 +
-+FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) ItemParams FLATBUFFERS_FINAL_CLASS {
-+ private:
-+  uint32_t felts_per_item_;
++ECCRYPTO_STATUS CompressedSecretAgreement(const unsigned char* SecretKey, const unsigned char* PublicKey, unsigned char* SharedSecret)
++{ // Secret agreement computation for key exchange using a compressed, 32-byte public key
++  // The output is the y-coordinate of SecretKey*A, where A is the decoding of the public key PublicKey.   
++  // Inputs: 32-byte SecretKey and 32-byte PublicKey
++  // Output: 32-byte SharedSecret
++    point_t A;
++    ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN;
++
++    if ((PublicKey[15] & 0x80) != 0) {  // Is bit128(PublicKey) = 0?
++		Status = ECCRYPTO_ERROR_INVALID_PARAMETER;
++		goto cleanup;
++    }
++
++	Status = decode(PublicKey, A);    // Also verifies that A is on the curve. If it is not, it fails
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
++         
++    Status = ecc_mul(A, (digit_t*)SecretKey, A, true);
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
 +
-+ public:
-+  ItemParams()
-+      : felts_per_item_(0) {
-+  }
-+  ItemParams(uint32_t _felts_per_item)
-+      : felts_per_item_(::flatbuffers::EndianScalar(_felts_per_item)) {
-+  }
-+  uint32_t felts_per_item() const {
-+    return ::flatbuffers::EndianScalar(felts_per_item_);
-+  }
-+};
-+FLATBUFFERS_STRUCT_END(ItemParams, 4);
++    if (is_neutral_point(A)) {  // Is output = neutral point (0,1)?
++		Status = ECCRYPTO_ERROR_SHARED_KEY;
++		goto cleanup;
++    }
++  
++	memmove(SharedSecret, (unsigned char*)A->y, 32);
 +
-+FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) TableParams FLATBUFFERS_FINAL_CLASS {
-+ private:
-+  uint32_t table_size_;
-+  uint32_t max_items_per_bin_;
-+  uint32_t hash_func_count_;
++	return ECCRYPTO_SUCCESS;
++    
++cleanup:
++    clear_words((unsigned int*)SharedSecret, 256/(sizeof(unsigned int)*8));
++    
++    return Status;
++}
 +
-+ public:
-+  TableParams()
-+      : table_size_(0),
-+        max_items_per_bin_(0),
-+        hash_func_count_(0) {
-+  }
-+  TableParams(uint32_t _table_size, uint32_t _max_items_per_bin, uint32_t _hash_func_count)
-+      : table_size_(::flatbuffers::EndianScalar(_table_size)),
-+        max_items_per_bin_(::flatbuffers::EndianScalar(_max_items_per_bin)),
-+        hash_func_count_(::flatbuffers::EndianScalar(_hash_func_count)) {
-+  }
-+  uint32_t table_size() const {
-+    return ::flatbuffers::EndianScalar(table_size_);
-+  }
-+  uint32_t max_items_per_bin() const {
-+    return ::flatbuffers::EndianScalar(max_items_per_bin_);
-+  }
-+  uint32_t hash_func_count() const {
-+    return ::flatbuffers::EndianScalar(hash_func_count_);
-+  }
-+};
-+FLATBUFFERS_STRUCT_END(TableParams, 12);
 +
-+struct QueryParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef QueryParamsBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_PS_LOW_DEGREE = 4,
-+    VT_QUERY_POWERS = 6
-+  };
-+  uint32_t ps_low_degree() const {
-+    return GetField<uint32_t>(VT_PS_LOW_DEGREE, 0);
-+  }
-+  const ::flatbuffers::Vector<uint32_t> *query_powers() const {
-+    return GetPointer<const ::flatbuffers::Vector<uint32_t> *>(VT_QUERY_POWERS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyField<uint32_t>(verifier, VT_PS_LOW_DEGREE, 4) &&
-+           VerifyOffset(verifier, VT_QUERY_POWERS) &&
-+           verifier.VerifyVector(query_powers()) &&
-+           verifier.EndTable();
-+  }
-+};
++/*************** ECDH USING UNCOMPRESSED PUBLIC KEYS ***************/
 +
-+struct QueryParamsBuilder {
-+  typedef QueryParams Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_ps_low_degree(uint32_t ps_low_degree) {
-+    fbb_.AddElement<uint32_t>(QueryParams::VT_PS_LOW_DEGREE, ps_low_degree, 0);
-+  }
-+  void add_query_powers(::flatbuffers::Offset<::flatbuffers::Vector<uint32_t>> query_powers) {
-+    fbb_.AddOffset(QueryParams::VT_QUERY_POWERS, query_powers);
-+  }
-+  explicit QueryParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<QueryParams> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<QueryParams>(end);
-+    return o;
-+  }
-+};
++ECCRYPTO_STATUS PublicKeyGeneration(const unsigned char* SecretKey, unsigned char* PublicKey)
++{ // Public key generation for key exchange
++  // It produces the public key PublicKey = SecretKey*G, where G is the generator.
++  // Input:  32-byte SecretKey
++  // Output: 64-byte PublicKey
 +
-+inline ::flatbuffers::Offset<QueryParams> CreateQueryParams(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint32_t ps_low_degree = 0,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<uint32_t>> query_powers = 0) {
-+  QueryParamsBuilder builder_(_fbb);
-+  builder_.add_query_powers(query_powers);
-+  builder_.add_ps_low_degree(ps_low_degree);
-+  return builder_.Finish();
-+}
++	ecc_mul_fixed((digit_t*)SecretKey, (point_affine*)PublicKey);  // Compute public key
 +
-+inline ::flatbuffers::Offset<QueryParams> CreateQueryParamsDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint32_t ps_low_degree = 0,
-+    const std::vector<uint32_t> *query_powers = nullptr) {
-+  auto query_powers__ = query_powers ? _fbb.CreateVector<uint32_t>(*query_powers) : 0;
-+  return apsi::fbs::CreateQueryParams(
-+      _fbb,
-+      ps_low_degree,
-+      query_powers__);
++	return ECCRYPTO_SUCCESS;
 +}
 +
-+struct SEALParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef SEALParamsBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_DATA = 4
-+  };
-+  const ::flatbuffers::Vector<uint8_t> *data() const {
-+    return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_DATA);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_DATA) &&
-+           verifier.VerifyVector(data()) &&
-+           verifier.EndTable();
-+  }
-+};
 +
-+struct SEALParamsBuilder {
-+  typedef SEALParams Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_data(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data) {
-+    fbb_.AddOffset(SEALParams::VT_DATA, data);
-+  }
-+  explicit SEALParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<SEALParams> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<SEALParams>(end);
-+    fbb_.Required(o, SEALParams::VT_DATA);
-+    return o;
-+  }
-+};
++ECCRYPTO_STATUS KeyGeneration(unsigned char* SecretKey, unsigned char* PublicKey)
++{ // Keypair generation for key exchange
++  // It produces a private key SecretKey and computes the public key PublicKey = SecretKey*G, where G is the generator.
++  // Outputs: 32-byte SecretKey and 64-byte PublicKey 
++	ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN;
 +
-+inline ::flatbuffers::Offset<SEALParams> CreateSEALParams(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data = 0) {
-+  SEALParamsBuilder builder_(_fbb);
-+  builder_.add_data(data);
-+  return builder_.Finish();
-+}
++	Status = RandomBytesFunction(SecretKey, 32);
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
 +
-+inline ::flatbuffers::Offset<SEALParams> CreateSEALParamsDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    const std::vector<uint8_t> *data = nullptr) {
-+  auto data__ = data ? _fbb.CreateVector<uint8_t>(*data) : 0;
-+  return apsi::fbs::CreateSEALParams(
-+      _fbb,
-+      data__);
-+}
++	Status = PublicKeyGeneration(SecretKey, PublicKey);
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
 +
-+struct PSIParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef PSIParamsBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_VERSION = 4,
-+    VT_ITEM_PARAMS = 6,
-+    VT_TABLE_PARAMS = 8,
-+    VT_QUERY_PARAMS = 10,
-+    VT_SEAL_PARAMS = 12
-+  };
-+  uint32_t version() const {
-+    return GetField<uint32_t>(VT_VERSION, 0);
-+  }
-+  const apsi::fbs::ItemParams *item_params() const {
-+    return GetStruct<const apsi::fbs::ItemParams *>(VT_ITEM_PARAMS);
-+  }
-+  const apsi::fbs::TableParams *table_params() const {
-+    return GetStruct<const apsi::fbs::TableParams *>(VT_TABLE_PARAMS);
-+  }
-+  const apsi::fbs::QueryParams *query_params() const {
-+    return GetPointer<const apsi::fbs::QueryParams *>(VT_QUERY_PARAMS);
-+  }
-+  const apsi::fbs::SEALParams *seal_params() const {
-+    return GetPointer<const apsi::fbs::SEALParams *>(VT_SEAL_PARAMS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyField<uint32_t>(verifier, VT_VERSION, 4) &&
-+           VerifyField<apsi::fbs::ItemParams>(verifier, VT_ITEM_PARAMS, 4) &&
-+           VerifyField<apsi::fbs::TableParams>(verifier, VT_TABLE_PARAMS, 4) &&
-+           VerifyOffset(verifier, VT_QUERY_PARAMS) &&
-+           verifier.VerifyTable(query_params()) &&
-+           VerifyOffsetRequired(verifier, VT_SEAL_PARAMS) &&
-+           verifier.VerifyTable(seal_params()) &&
-+           verifier.EndTable();
-+  }
-+};
++	return ECCRYPTO_SUCCESS;
 +
-+struct PSIParamsBuilder {
-+  typedef PSIParams Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_version(uint32_t version) {
-+    fbb_.AddElement<uint32_t>(PSIParams::VT_VERSION, version, 0);
-+  }
-+  void add_item_params(const apsi::fbs::ItemParams *item_params) {
-+    fbb_.AddStruct(PSIParams::VT_ITEM_PARAMS, item_params);
-+  }
-+  void add_table_params(const apsi::fbs::TableParams *table_params) {
-+    fbb_.AddStruct(PSIParams::VT_TABLE_PARAMS, table_params);
-+  }
-+  void add_query_params(::flatbuffers::Offset<apsi::fbs::QueryParams> query_params) {
-+    fbb_.AddOffset(PSIParams::VT_QUERY_PARAMS, query_params);
-+  }
-+  void add_seal_params(::flatbuffers::Offset<apsi::fbs::SEALParams> seal_params) {
-+    fbb_.AddOffset(PSIParams::VT_SEAL_PARAMS, seal_params);
-+  }
-+  explicit PSIParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<PSIParams> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<PSIParams>(end);
-+    fbb_.Required(o, PSIParams::VT_SEAL_PARAMS);
-+    return o;
-+  }
-+};
++cleanup:
++	clear_words((unsigned int*)SecretKey, 256/(sizeof(unsigned int)*8));
++	clear_words((unsigned int*)PublicKey, 512/(sizeof(unsigned int)*8));
 +
-+inline ::flatbuffers::Offset<PSIParams> CreatePSIParams(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint32_t version = 0,
-+    const apsi::fbs::ItemParams *item_params = nullptr,
-+    const apsi::fbs::TableParams *table_params = nullptr,
-+    ::flatbuffers::Offset<apsi::fbs::QueryParams> query_params = 0,
-+    ::flatbuffers::Offset<apsi::fbs::SEALParams> seal_params = 0) {
-+  PSIParamsBuilder builder_(_fbb);
-+  builder_.add_seal_params(seal_params);
-+  builder_.add_query_params(query_params);
-+  builder_.add_table_params(table_params);
-+  builder_.add_item_params(item_params);
-+  builder_.add_version(version);
-+  return builder_.Finish();
++	return Status;
 +}
 +
-+inline const apsi::fbs::PSIParams *GetPSIParams(const void *buf) {
-+  return ::flatbuffers::GetRoot<apsi::fbs::PSIParams>(buf);
-+}
 +
-+inline const apsi::fbs::PSIParams *GetSizePrefixedPSIParams(const void *buf) {
-+  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::PSIParams>(buf);
-+}
++ECCRYPTO_STATUS SecretAgreement(const unsigned char* SecretKey, const unsigned char* PublicKey, unsigned char* SharedSecret)
++{ // Secret agreement computation for key exchange
++  // The output is the y-coordinate of SecretKey*PublicKey. 
++  // Inputs: 32-byte SecretKey and 64-byte PublicKey
++  // Output: 32-byte SharedSecret
++	point_t A;
++	ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN;
 +
-+inline bool VerifyPSIParamsBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifyBuffer<apsi::fbs::PSIParams>(nullptr);
-+}
++    if (((PublicKey[15] & 0x80) != 0) || ((PublicKey[31] & 0x80) != 0) || ((PublicKey[47] & 0x80) != 0) || ((PublicKey[63] & 0x80) != 0)) {  // Are PublicKey_x[i] and PublicKey_y[i] < 2^127?
++		Status = ECCRYPTO_ERROR_INVALID_PARAMETER;
++		goto cleanup;
++    }
 +
-+inline bool VerifySizePrefixedPSIParamsBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifySizePrefixedBuffer<apsi::fbs::PSIParams>(nullptr);
-+}
++	Status = ecc_mul((point_affine*)PublicKey, (digit_t*)SecretKey, A, true);  // Also verifies that PublicKey is a point on the curve. If it is not, it fails
++	if (Status != ECCRYPTO_SUCCESS) {
++		goto cleanup;
++	}
 +
-+inline void FinishPSIParamsBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::PSIParams> root) {
-+  fbb.Finish(root);
-+}
++    if (is_neutral_point(A)) {  // Is output = neutral point (0,1)?
++		Status = ECCRYPTO_ERROR_SHARED_KEY;
++		goto cleanup;
++    }
++  
++	memmove(SharedSecret, (unsigned char*)A->y, 32);
 +
-+inline void FinishSizePrefixedPSIParamsBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::PSIParams> root) {
-+  fbb.FinishSizePrefixed(root);
-+}
++	return ECCRYPTO_SUCCESS;
 +
-+}  // namespace fbs
-+}  // namespace apsi
++cleanup:
++	clear_words((unsigned int*)SharedSecret, 256/(sizeof(unsigned int)*8));
 +
-+#endif  // FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_
++	return Status;
++}
 diff --git a/receiver/apsi/CMakeLists.txt b/receiver/apsi/CMakeLists.txt
-index afce298..2e2d6e5 100644
+index afce298..7757b68 100644
 --- a/receiver/apsi/CMakeLists.txt
 +++ b/receiver/apsi/CMakeLists.txt
-@@ -4,7 +4,7 @@
+@@ -4,7 +4,6 @@
  # Source files in this directory
  set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES}
      ${CMAKE_CURRENT_LIST_DIR}/plaintext_powers.cpp
 -    ${CMAKE_CURRENT_LIST_DIR}/receiver.cpp
-+#    ${CMAKE_CURRENT_LIST_DIR}/receiver.cpp
  )
  
  # Add header files for installation
-@@ -12,6 +12,7 @@ install(
+@@ -12,6 +11,7 @@ install(
      FILES
          ${CMAKE_CURRENT_LIST_DIR}/itt.h
          ${CMAKE_CURRENT_LIST_DIR}/match_record.h
@@ -478,19 +323,16 @@ index e683045..105e14c 100644
  
              std::unordered_map<std::size_t, std::size_t> table_idx_to_item_idx_;
 diff --git a/sender/apsi/CMakeLists.txt b/sender/apsi/CMakeLists.txt
-index fd245d7..8d36af4 100644
+index fd245d7..99e4228 100644
 --- a/sender/apsi/CMakeLists.txt
 +++ b/sender/apsi/CMakeLists.txt
-@@ -4,18 +4,18 @@
+@@ -4,18 +4,12 @@
  # Source files in this directory
  set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES}
      ${CMAKE_CURRENT_LIST_DIR}/bin_bundle.cpp
 -    ${CMAKE_CURRENT_LIST_DIR}/query.cpp
 -    ${CMAKE_CURRENT_LIST_DIR}/sender.cpp
 -    ${CMAKE_CURRENT_LIST_DIR}/sender_db.cpp
-+#    ${CMAKE_CURRENT_LIST_DIR}/query.cpp
-+#    ${CMAKE_CURRENT_LIST_DIR}/sender.cpp
-+#    ${CMAKE_CURRENT_LIST_DIR}/sender_db.cpp
  )
  
  # Add header files for installation
@@ -500,786 +342,6 @@ index fd245d7..8d36af4 100644
 -        ${CMAKE_CURRENT_LIST_DIR}/query.h
 -        ${CMAKE_CURRENT_LIST_DIR}/sender.h
 -        ${CMAKE_CURRENT_LIST_DIR}/sender_db.h
-+#        ${CMAKE_CURRENT_LIST_DIR}/query.h
-+#        ${CMAKE_CURRENT_LIST_DIR}/sender.h
-+#        ${CMAKE_CURRENT_LIST_DIR}/sender_db.h
      DESTINATION
          ${APSI_INCLUDES_INSTALL_DIR}/apsi
  )
-diff --git b/sender/apsi/bin_bundle_generated.h b/sender/apsi/bin_bundle_generated.h
-new file mode 100644
-index 0000000..e821e33
---- /dev/null
-+++ b/sender/apsi/bin_bundle_generated.h
-@@ -0,0 +1,490 @@
-+// automatically generated by the FlatBuffers compiler, do not modify
-+
-+
-+#ifndef FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
-+#define FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
-+
-+#include "flatbuffers/flatbuffers.h"
-+
-+// Ensure the included flatbuffers.h is the same version as when this file was
-+// generated, otherwise it may not be compatible.
-+static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
-+              FLATBUFFERS_VERSION_MINOR == 3 &&
-+              FLATBUFFERS_VERSION_REVISION == 3,
-+             "Non-compatible flatbuffers version included");
-+
-+namespace apsi {
-+namespace fbs {
-+
-+struct FEltArray;
-+struct FEltArrayBuilder;
-+
-+struct FEltMatrix;
-+struct FEltMatrixBuilder;
-+
-+struct Plaintext;
-+struct PlaintextBuilder;
-+
-+struct BatchedPlaintextPolyn;
-+struct BatchedPlaintextPolynBuilder;
-+
-+struct BinBundleCache;
-+struct BinBundleCacheBuilder;
-+
-+struct BinBundle;
-+struct BinBundleBuilder;
-+
-+struct FEltArray FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef FEltArrayBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_FELTS = 4
-+  };
-+  const ::flatbuffers::Vector<uint64_t> *felts() const {
-+    return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_FELTS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_FELTS) &&
-+           verifier.VerifyVector(felts()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct FEltArrayBuilder {
-+  typedef FEltArray Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_felts(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> felts) {
-+    fbb_.AddOffset(FEltArray::VT_FELTS, felts);
-+  }
-+  explicit FEltArrayBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<FEltArray> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<FEltArray>(end);
-+    fbb_.Required(o, FEltArray::VT_FELTS);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<FEltArray> CreateFEltArray(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> felts = 0) {
-+  FEltArrayBuilder builder_(_fbb);
-+  builder_.add_felts(felts);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<FEltArray> CreateFEltArrayDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    const std::vector<uint64_t> *felts = nullptr) {
-+  auto felts__ = felts ? _fbb.CreateVector<uint64_t>(*felts) : 0;
-+  return apsi::fbs::CreateFEltArray(
-+      _fbb,
-+      felts__);
-+}
-+
-+struct FEltMatrix FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef FEltMatrixBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_ROWS = 4
-+  };
-+  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *rows() const {
-+    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *>(VT_ROWS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_ROWS) &&
-+           verifier.VerifyVector(rows()) &&
-+           verifier.VerifyVectorOfTables(rows()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct FEltMatrixBuilder {
-+  typedef FEltMatrix Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_rows(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>>> rows) {
-+    fbb_.AddOffset(FEltMatrix::VT_ROWS, rows);
-+  }
-+  explicit FEltMatrixBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<FEltMatrix> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<FEltMatrix>(end);
-+    fbb_.Required(o, FEltMatrix::VT_ROWS);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<FEltMatrix> CreateFEltMatrix(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltArray>>> rows = 0) {
-+  FEltMatrixBuilder builder_(_fbb);
-+  builder_.add_rows(rows);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<FEltMatrix> CreateFEltMatrixDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltArray>> *rows = nullptr) {
-+  auto rows__ = rows ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltArray>>(*rows) : 0;
-+  return apsi::fbs::CreateFEltMatrix(
-+      _fbb,
-+      rows__);
-+}
-+
-+struct Plaintext FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef PlaintextBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_DATA = 4
-+  };
-+  const ::flatbuffers::Vector<uint8_t> *data() const {
-+    return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_DATA);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_DATA) &&
-+           verifier.VerifyVector(data()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct PlaintextBuilder {
-+  typedef Plaintext Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_data(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data) {
-+    fbb_.AddOffset(Plaintext::VT_DATA, data);
-+  }
-+  explicit PlaintextBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<Plaintext> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<Plaintext>(end);
-+    fbb_.Required(o, Plaintext::VT_DATA);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<Plaintext> CreatePlaintext(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data = 0) {
-+  PlaintextBuilder builder_(_fbb);
-+  builder_.add_data(data);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<Plaintext> CreatePlaintextDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    const std::vector<uint8_t> *data = nullptr) {
-+  auto data__ = data ? _fbb.CreateVector<uint8_t>(*data) : 0;
-+  return apsi::fbs::CreatePlaintext(
-+      _fbb,
-+      data__);
-+}
-+
-+struct BatchedPlaintextPolyn FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef BatchedPlaintextPolynBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_COEFFS = 4
-+  };
-+  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs() const {
-+    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *>(VT_COEFFS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_COEFFS) &&
-+           verifier.VerifyVector(coeffs()) &&
-+           verifier.VerifyVectorOfTables(coeffs()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct BatchedPlaintextPolynBuilder {
-+  typedef BatchedPlaintextPolyn Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_coeffs(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs) {
-+    fbb_.AddOffset(BatchedPlaintextPolyn::VT_COEFFS, coeffs);
-+  }
-+  explicit BatchedPlaintextPolynBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<BatchedPlaintextPolyn> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<BatchedPlaintextPolyn>(end);
-+    fbb_.Required(o, BatchedPlaintextPolyn::VT_COEFFS);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolyn(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs = 0) {
-+  BatchedPlaintextPolynBuilder builder_(_fbb);
-+  builder_.add_coeffs(coeffs);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolynDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    const std::vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs = nullptr) {
-+  auto coeffs__ = coeffs ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::Plaintext>>(*coeffs) : 0;
-+  return apsi::fbs::CreateBatchedPlaintextPolyn(
-+      _fbb,
-+      coeffs__);
-+}
-+
-+struct BinBundleCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef BinBundleCacheBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_FELT_MATCHING_POLYNS = 4,
-+    VT_BATCHED_MATCHING_POLYN = 6,
-+    VT_FELT_INTERP_POLYNS = 8,
-+    VT_BATCHED_INTERP_POLYNS = 10
-+  };
-+  const apsi::fbs::FEltMatrix *felt_matching_polyns() const {
-+    return GetPointer<const apsi::fbs::FEltMatrix *>(VT_FELT_MATCHING_POLYNS);
-+  }
-+  const apsi::fbs::BatchedPlaintextPolyn *batched_matching_polyn() const {
-+    return GetPointer<const apsi::fbs::BatchedPlaintextPolyn *>(VT_BATCHED_MATCHING_POLYN);
-+  }
-+  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns() const {
-+    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_FELT_INTERP_POLYNS);
-+  }
-+  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns() const {
-+    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *>(VT_BATCHED_INTERP_POLYNS);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_FELT_MATCHING_POLYNS) &&
-+           verifier.VerifyTable(felt_matching_polyns()) &&
-+           VerifyOffsetRequired(verifier, VT_BATCHED_MATCHING_POLYN) &&
-+           verifier.VerifyTable(batched_matching_polyn()) &&
-+           VerifyOffset(verifier, VT_FELT_INTERP_POLYNS) &&
-+           verifier.VerifyVector(felt_interp_polyns()) &&
-+           verifier.VerifyVectorOfTables(felt_interp_polyns()) &&
-+           VerifyOffset(verifier, VT_BATCHED_INTERP_POLYNS) &&
-+           verifier.VerifyVector(batched_interp_polyns()) &&
-+           verifier.VerifyVectorOfTables(batched_interp_polyns()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct BinBundleCacheBuilder {
-+  typedef BinBundleCache Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_felt_matching_polyns(::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns) {
-+    fbb_.AddOffset(BinBundleCache::VT_FELT_MATCHING_POLYNS, felt_matching_polyns);
-+  }
-+  void add_batched_matching_polyn(::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn) {
-+    fbb_.AddOffset(BinBundleCache::VT_BATCHED_MATCHING_POLYN, batched_matching_polyn);
-+  }
-+  void add_felt_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns) {
-+    fbb_.AddOffset(BinBundleCache::VT_FELT_INTERP_POLYNS, felt_interp_polyns);
-+  }
-+  void add_batched_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns) {
-+    fbb_.AddOffset(BinBundleCache::VT_BATCHED_INTERP_POLYNS, batched_interp_polyns);
-+  }
-+  explicit BinBundleCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<BinBundleCache> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<BinBundleCache>(end);
-+    fbb_.Required(o, BinBundleCache::VT_FELT_MATCHING_POLYNS);
-+    fbb_.Required(o, BinBundleCache::VT_BATCHED_MATCHING_POLYN);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCache(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0,
-+    ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns = 0,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns = 0) {
-+  BinBundleCacheBuilder builder_(_fbb);
-+  builder_.add_batched_interp_polyns(batched_interp_polyns);
-+  builder_.add_felt_interp_polyns(felt_interp_polyns);
-+  builder_.add_batched_matching_polyn(batched_matching_polyn);
-+  builder_.add_felt_matching_polyns(felt_matching_polyns);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCacheDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0,
-+    ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0,
-+    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns = nullptr,
-+    const std::vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns = nullptr) {
-+  auto felt_interp_polyns__ = felt_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*felt_interp_polyns) : 0;
-+  auto batched_interp_polyns__ = batched_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>(*batched_interp_polyns) : 0;
-+  return apsi::fbs::CreateBinBundleCache(
-+      _fbb,
-+      felt_matching_polyns,
-+      batched_matching_polyn,
-+      felt_interp_polyns__,
-+      batched_interp_polyns__);
-+}
-+
-+struct BinBundle FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef BinBundleBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_BUNDLE_IDX = 4,
-+    VT_MOD = 6,
-+    VT_ITEM_BINS = 8,
-+    VT_LABEL_BINS = 10,
-+    VT_CACHE = 12,
-+    VT_STRIPPED = 14
-+  };
-+  uint32_t bundle_idx() const {
-+    return GetField<uint32_t>(VT_BUNDLE_IDX, 0);
-+  }
-+  uint64_t mod() const {
-+    return GetField<uint64_t>(VT_MOD, 0);
-+  }
-+  const apsi::fbs::FEltMatrix *item_bins() const {
-+    return GetPointer<const apsi::fbs::FEltMatrix *>(VT_ITEM_BINS);
-+  }
-+  const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins() const {
-+    return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_LABEL_BINS);
-+  }
-+  const apsi::fbs::BinBundleCache *cache() const {
-+    return GetPointer<const apsi::fbs::BinBundleCache *>(VT_CACHE);
-+  }
-+  bool stripped() const {
-+    return GetField<uint8_t>(VT_STRIPPED, 0) != 0;
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyField<uint32_t>(verifier, VT_BUNDLE_IDX, 4) &&
-+           VerifyField<uint64_t>(verifier, VT_MOD, 8) &&
-+           VerifyOffsetRequired(verifier, VT_ITEM_BINS) &&
-+           verifier.VerifyTable(item_bins()) &&
-+           VerifyOffset(verifier, VT_LABEL_BINS) &&
-+           verifier.VerifyVector(label_bins()) &&
-+           verifier.VerifyVectorOfTables(label_bins()) &&
-+           VerifyOffset(verifier, VT_CACHE) &&
-+           verifier.VerifyTable(cache()) &&
-+           VerifyField<uint8_t>(verifier, VT_STRIPPED, 1) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct BinBundleBuilder {
-+  typedef BinBundle Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_bundle_idx(uint32_t bundle_idx) {
-+    fbb_.AddElement<uint32_t>(BinBundle::VT_BUNDLE_IDX, bundle_idx, 0);
-+  }
-+  void add_mod(uint64_t mod) {
-+    fbb_.AddElement<uint64_t>(BinBundle::VT_MOD, mod, 0);
-+  }
-+  void add_item_bins(::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins) {
-+    fbb_.AddOffset(BinBundle::VT_ITEM_BINS, item_bins);
-+  }
-+  void add_label_bins(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins) {
-+    fbb_.AddOffset(BinBundle::VT_LABEL_BINS, label_bins);
-+  }
-+  void add_cache(::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache) {
-+    fbb_.AddOffset(BinBundle::VT_CACHE, cache);
-+  }
-+  void add_stripped(bool stripped) {
-+    fbb_.AddElement<uint8_t>(BinBundle::VT_STRIPPED, static_cast<uint8_t>(stripped), 0);
-+  }
-+  explicit BinBundleBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<BinBundle> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<BinBundle>(end);
-+    fbb_.Required(o, BinBundle::VT_ITEM_BINS);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<BinBundle> CreateBinBundle(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint32_t bundle_idx = 0,
-+    uint64_t mod = 0,
-+    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins = 0,
-+    ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0,
-+    bool stripped = false) {
-+  BinBundleBuilder builder_(_fbb);
-+  builder_.add_mod(mod);
-+  builder_.add_cache(cache);
-+  builder_.add_label_bins(label_bins);
-+  builder_.add_item_bins(item_bins);
-+  builder_.add_bundle_idx(bundle_idx);
-+  builder_.add_stripped(stripped);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<BinBundle> CreateBinBundleDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint32_t bundle_idx = 0,
-+    uint64_t mod = 0,
-+    ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0,
-+    const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins = nullptr,
-+    ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0,
-+    bool stripped = false) {
-+  auto label_bins__ = label_bins ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*label_bins) : 0;
-+  return apsi::fbs::CreateBinBundle(
-+      _fbb,
-+      bundle_idx,
-+      mod,
-+      item_bins,
-+      label_bins__,
-+      cache,
-+      stripped);
-+}
-+
-+inline const apsi::fbs::BinBundle *GetBinBundle(const void *buf) {
-+  return ::flatbuffers::GetRoot<apsi::fbs::BinBundle>(buf);
-+}
-+
-+inline const apsi::fbs::BinBundle *GetSizePrefixedBinBundle(const void *buf) {
-+  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::BinBundle>(buf);
-+}
-+
-+inline bool VerifyBinBundleBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifyBuffer<apsi::fbs::BinBundle>(nullptr);
-+}
-+
-+inline bool VerifySizePrefixedBinBundleBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifySizePrefixedBuffer<apsi::fbs::BinBundle>(nullptr);
-+}
-+
-+inline void FinishBinBundleBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::BinBundle> root) {
-+  fbb.Finish(root);
-+}
-+
-+inline void FinishSizePrefixedBinBundleBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::BinBundle> root) {
-+  fbb.FinishSizePrefixed(root);
-+}
-+
-+}  // namespace fbs
-+}  // namespace apsi
-+
-+#endif  // FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_
-diff --git a/sender/apsi/util/cuckoo_filter.h b/sender/apsi/util/cuckoo_filter.h
-index 7c951ca..d69c97a 100644
---- a/sender/apsi/util/cuckoo_filter.h
-+++ b/sender/apsi/util/cuckoo_filter.h
-@@ -78,7 +78,6 @@ namespace apsi {
-                 {
-                     return num_items_;
-                 }
--
-                 /**
-                 Saves the CuckooFilter to a stream.
-                 */
-diff --git b/sender/apsi/util/cuckoo_filter_generated.h b/sender/apsi/util/cuckoo_filter_generated.h
-new file mode 100644
-index 0000000..2d2b2b7
---- /dev/null
-+++ b/sender/apsi/util/cuckoo_filter_generated.h
-@@ -0,0 +1,263 @@
-+// automatically generated by the FlatBuffers compiler, do not modify
-+
-+
-+#ifndef FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
-+#define FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
-+
-+#include "flatbuffers/flatbuffers.h"
-+
-+// Ensure the included flatbuffers.h is the same version as when this file was
-+// generated, otherwise it may not be compatible.
-+static_assert(FLATBUFFERS_VERSION_MAJOR == 23 &&
-+              FLATBUFFERS_VERSION_MINOR == 3 &&
-+              FLATBUFFERS_VERSION_REVISION == 3,
-+             "Non-compatible flatbuffers version included");
-+
-+namespace apsi {
-+namespace fbs {
-+
-+struct CuckooFilterOverflowCache;
-+struct CuckooFilterOverflowCacheBuilder;
-+
-+struct CuckooFilterTable;
-+struct CuckooFilterTableBuilder;
-+
-+struct CuckooFilter;
-+struct CuckooFilterBuilder;
-+
-+struct CuckooFilterOverflowCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef CuckooFilterOverflowCacheBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_INDEX = 4,
-+    VT_TAG = 6,
-+    VT_USED = 8
-+  };
-+  uint64_t index() const {
-+    return GetField<uint64_t>(VT_INDEX, 0);
-+  }
-+  uint64_t tag() const {
-+    return GetField<uint64_t>(VT_TAG, 0);
-+  }
-+  bool used() const {
-+    return GetField<uint8_t>(VT_USED, 0) != 0;
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyField<uint64_t>(verifier, VT_INDEX, 8) &&
-+           VerifyField<uint64_t>(verifier, VT_TAG, 8) &&
-+           VerifyField<uint8_t>(verifier, VT_USED, 1) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct CuckooFilterOverflowCacheBuilder {
-+  typedef CuckooFilterOverflowCache Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_index(uint64_t index) {
-+    fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_INDEX, index, 0);
-+  }
-+  void add_tag(uint64_t tag) {
-+    fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_TAG, tag, 0);
-+  }
-+  void add_used(bool used) {
-+    fbb_.AddElement<uint8_t>(CuckooFilterOverflowCache::VT_USED, static_cast<uint8_t>(used), 0);
-+  }
-+  explicit CuckooFilterOverflowCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<CuckooFilterOverflowCache> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<CuckooFilterOverflowCache>(end);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<CuckooFilterOverflowCache> CreateCuckooFilterOverflowCache(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint64_t index = 0,
-+    uint64_t tag = 0,
-+    bool used = false) {
-+  CuckooFilterOverflowCacheBuilder builder_(_fbb);
-+  builder_.add_tag(tag);
-+  builder_.add_index(index);
-+  builder_.add_used(used);
-+  return builder_.Finish();
-+}
-+
-+struct CuckooFilterTable FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef CuckooFilterTableBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_NUM_BUCKETS = 4,
-+    VT_BITS_PER_TAG = 6,
-+    VT_TABLE = 8
-+  };
-+  uint64_t num_buckets() const {
-+    return GetField<uint64_t>(VT_NUM_BUCKETS, 0);
-+  }
-+  uint64_t bits_per_tag() const {
-+    return GetField<uint64_t>(VT_BITS_PER_TAG, 0);
-+  }
-+  const ::flatbuffers::Vector<uint64_t> *table() const {
-+    return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_TABLE);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyField<uint64_t>(verifier, VT_NUM_BUCKETS, 8) &&
-+           VerifyField<uint64_t>(verifier, VT_BITS_PER_TAG, 8) &&
-+           VerifyOffsetRequired(verifier, VT_TABLE) &&
-+           verifier.VerifyVector(table()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct CuckooFilterTableBuilder {
-+  typedef CuckooFilterTable Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_num_buckets(uint64_t num_buckets) {
-+    fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_NUM_BUCKETS, num_buckets, 0);
-+  }
-+  void add_bits_per_tag(uint64_t bits_per_tag) {
-+    fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_BITS_PER_TAG, bits_per_tag, 0);
-+  }
-+  void add_table(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table) {
-+    fbb_.AddOffset(CuckooFilterTable::VT_TABLE, table);
-+  }
-+  explicit CuckooFilterTableBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<CuckooFilterTable> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<CuckooFilterTable>(end);
-+    fbb_.Required(o, CuckooFilterTable::VT_TABLE);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTable(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint64_t num_buckets = 0,
-+    uint64_t bits_per_tag = 0,
-+    ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table = 0) {
-+  CuckooFilterTableBuilder builder_(_fbb);
-+  builder_.add_bits_per_tag(bits_per_tag);
-+  builder_.add_num_buckets(num_buckets);
-+  builder_.add_table(table);
-+  return builder_.Finish();
-+}
-+
-+inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTableDirect(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    uint64_t num_buckets = 0,
-+    uint64_t bits_per_tag = 0,
-+    const std::vector<uint64_t> *table = nullptr) {
-+  auto table__ = table ? _fbb.CreateVector<uint64_t>(*table) : 0;
-+  return apsi::fbs::CreateCuckooFilterTable(
-+      _fbb,
-+      num_buckets,
-+      bits_per_tag,
-+      table__);
-+}
-+
-+struct CuckooFilter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
-+  typedef CuckooFilterBuilder Builder;
-+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
-+    VT_TABLE = 4,
-+    VT_NUM_ITEMS = 6,
-+    VT_OVERFLOW = 8
-+  };
-+  const apsi::fbs::CuckooFilterTable *table() const {
-+    return GetPointer<const apsi::fbs::CuckooFilterTable *>(VT_TABLE);
-+  }
-+  uint64_t num_items() const {
-+    return GetField<uint64_t>(VT_NUM_ITEMS, 0);
-+  }
-+  const apsi::fbs::CuckooFilterOverflowCache *overflow() const {
-+    return GetPointer<const apsi::fbs::CuckooFilterOverflowCache *>(VT_OVERFLOW);
-+  }
-+  bool Verify(::flatbuffers::Verifier &verifier) const {
-+    return VerifyTableStart(verifier) &&
-+           VerifyOffsetRequired(verifier, VT_TABLE) &&
-+           verifier.VerifyTable(table()) &&
-+           VerifyField<uint64_t>(verifier, VT_NUM_ITEMS, 8) &&
-+           VerifyOffsetRequired(verifier, VT_OVERFLOW) &&
-+           verifier.VerifyTable(overflow()) &&
-+           verifier.EndTable();
-+  }
-+};
-+
-+struct CuckooFilterBuilder {
-+  typedef CuckooFilter Table;
-+  ::flatbuffers::FlatBufferBuilder &fbb_;
-+  ::flatbuffers::uoffset_t start_;
-+  void add_table(::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table) {
-+    fbb_.AddOffset(CuckooFilter::VT_TABLE, table);
-+  }
-+  void add_num_items(uint64_t num_items) {
-+    fbb_.AddElement<uint64_t>(CuckooFilter::VT_NUM_ITEMS, num_items, 0);
-+  }
-+  void add_overflow(::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow) {
-+    fbb_.AddOffset(CuckooFilter::VT_OVERFLOW, overflow);
-+  }
-+  explicit CuckooFilterBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
-+        : fbb_(_fbb) {
-+    start_ = fbb_.StartTable();
-+  }
-+  ::flatbuffers::Offset<CuckooFilter> Finish() {
-+    const auto end = fbb_.EndTable(start_);
-+    auto o = ::flatbuffers::Offset<CuckooFilter>(end);
-+    fbb_.Required(o, CuckooFilter::VT_TABLE);
-+    fbb_.Required(o, CuckooFilter::VT_OVERFLOW);
-+    return o;
-+  }
-+};
-+
-+inline ::flatbuffers::Offset<CuckooFilter> CreateCuckooFilter(
-+    ::flatbuffers::FlatBufferBuilder &_fbb,
-+    ::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table = 0,
-+    uint64_t num_items = 0,
-+    ::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow = 0) {
-+  CuckooFilterBuilder builder_(_fbb);
-+  builder_.add_num_items(num_items);
-+  builder_.add_overflow(overflow);
-+  builder_.add_table(table);
-+  return builder_.Finish();
-+}
-+
-+inline const apsi::fbs::CuckooFilter *GetCuckooFilter(const void *buf) {
-+  return ::flatbuffers::GetRoot<apsi::fbs::CuckooFilter>(buf);
-+}
-+
-+inline const apsi::fbs::CuckooFilter *GetSizePrefixedCuckooFilter(const void *buf) {
-+  return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::CuckooFilter>(buf);
-+}
-+
-+inline bool VerifyCuckooFilterBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifyBuffer<apsi::fbs::CuckooFilter>(nullptr);
-+}
-+
-+inline bool VerifySizePrefixedCuckooFilterBuffer(
-+    ::flatbuffers::Verifier &verifier) {
-+  return verifier.VerifySizePrefixedBuffer<apsi::fbs::CuckooFilter>(nullptr);
-+}
-+
-+inline void FinishCuckooFilterBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) {
-+  fbb.Finish(root);
-+}
-+
-+inline void FinishSizePrefixedCuckooFilterBuffer(
-+    ::flatbuffers::FlatBufferBuilder &fbb,
-+    ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) {
-+  fbb.FinishSizePrefixed(root);
-+}
-+
-+}  // namespace fbs
-+}  // namespace apsi
-+
-+#endif  // FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_
diff --git a/bazel/patches/emp-ot.patch b/bazel/patches/emp-ot.patch
index 7c986ece..31cb353a 100644
--- a/bazel/patches/emp-ot.patch
+++ b/bazel/patches/emp-ot.patch
@@ -1,23 +1,30 @@
 diff --git a/emp-ot/ferret/ferret_cot.hpp b/emp-ot/ferret/ferret_cot.hpp
-index 3798998..055932b 100644
+index 9dc8222..fbd6170 100644
 --- a/emp-ot/ferret/ferret_cot.hpp
 +++ b/emp-ot/ferret/ferret_cot.hpp
-@@ -137,12 +137,14 @@ void FerretCOT<T>::setup(std::string pre_file) {
- 		OTPre<T> pre_ot_ini(ios[0], mpcot_ini.tree_height-1, mpcot_ini.tree_n);
- 		LpnF2<T, 10> lpn(party, n_pre, k_pre, pool, io, pool->size());
- 
--		block pre_data_ini[k_pre+mpcot_ini.consist_check_cot_num];
-+		block *pre_data_ini = new block[k_pre+mpcot_ini.consist_check_cot_num];
- 		memset(this->ot_pre_data, 0, n_pre*16);
- 
- 		base_cot->cot_gen(&pre_ot_ini, pre_ot_ini.n);
- 		base_cot->cot_gen(pre_data_ini, k_pre+mpcot_ini.consist_check_cot_num);
- 		extend(ot_pre_data, &mpcot_ini, &pre_ot_ini, &lpn, pre_data_ini);
-+
-+        delete []pre_data_ini;
+@@ -28,8 +28,8 @@ FerretCOT<T>::FerretCOT(int party, int threads, T **ios,
+ template<typename T>
+ FerretCOT<T>::~FerretCOT() {
+ 	if (ot_pre_data != nullptr) {
+-		if(party == ALICE) write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)Delta, pre_ot_filename);
+-		else write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)0, pre_ot_filename);
++		// if(party == ALICE) write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)Delta, pre_ot_filename);
++		// else write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)0, pre_ot_filename);
+ 		delete[] ot_pre_data;
  	}
+ 	if (ot_data != nullptr) delete[] ot_data;
+@@ -100,7 +100,9 @@ void FerretCOT<T>::setup(std::string pre_file) {
+ 	});
  
- 	fut.get();
+ 	ot_pre_data = new block[param.n_pre];
+-	bool hasfile = file_exists(pre_ot_filename), hasfile2;
++	//bool hasfile = file_exists(pre_ot_filename), hasfile2;
++	bool hasfile = false; 
++	bool hasfile2 = false;
+ 	if(party == ALICE) {
+ 		io->send_data(&hasfile, sizeof(bool));
+ 		io->flush();
+
 diff --git a/emp-ot/ferret/mpcot_reg.h b/emp-ot/ferret/mpcot_reg.h
 index 6659aa7..6b01601 100644
 --- a/emp-ot/ferret/mpcot_reg.h
@@ -42,6 +49,7 @@ index 6659aa7..6b01601 100644
  		if(is_malicious)
  			sender->consistency_check_msg_gen(consist_check_VW+i);
  	}
+
 diff --git a/emp-ot/ferret/preot.h b/emp-ot/ferret/preot.h
 index 0ac7641..a0ae2d3 100644
 --- a/emp-ot/ferret/preot.h
@@ -57,20 +65,21 @@ index 0ac7641..a0ae2d3 100644
  
  	CCRH ccrh;
  	int length, count;
+
 diff --git a/emp-ot/ferret/twokeyprp.h b/emp-ot/ferret/twokeyprp.h
 index fd6236d..c2361a3 100644
 --- a/emp-ot/ferret/twokeyprp.h
 +++ b/emp-ot/ferret/twokeyprp.h
 @@ -9,8 +9,8 @@ class TwoKeyPRP { public:
  	emp::AES_KEY aes_key[2];
- 
+
  	TwoKeyPRP(block seed0, block seed1) {
 -		AES_set_encrypt_key((const block)seed0, aes_key);
 -		AES_set_encrypt_key((const block)seed1, &aes_key[1]);
 +		AES_set_encrypt_key(seed0, aes_key);
 +		AES_set_encrypt_key(seed1, &aes_key[1]);
  	}
- 
+
  	void node_expand_1to2(block *children, block parent) {
 
 diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -80,7 +89,7 @@ index fa06fd7..faf9802 100755
 @@ -12,5 +12,8 @@ include_directories(${EMP-TOOL_INCLUDE_DIRS})
  install(FILES cmake/emp-ot-config.cmake DESTINATION cmake/)
  install(DIRECTORY emp-ot DESTINATION include/)
- 
+
 -ENABLE_TESTING()
 -ADD_SUBDIRECTORY(test)
 +option(ENABLE_TESTS "Enable tests" OFF)
@@ -88,65 +97,3 @@ index fa06fd7..faf9802 100755
 +    ENABLE_TESTING()
 +    ADD_SUBDIRECTORY(test)
 +endif()
-
-diff --git a/emp-ot/ferret/twokeyprp.h b/emp-ot/ferret/twokeyprp.h
-index fd6236d..76e1f89 100644
---- a/emp-ot/ferret/twokeyprp.h
-+++ b/emp-ot/ferret/twokeyprp.h
-@@ -4,6 +4,10 @@
- #include "emp-tool/emp-tool.h"
- using namespace emp;
- 
-+#ifdef __aarch64__
-+#include "emp-tool/utils/sse2neon.h"
-+#endif
-+
- //kappa->2kappa PRG, implemented as G(k) = PRF_seed0(k)\xor k || PRF_seed1(k)\xor k
- class TwoKeyPRP { public:
- 	emp::AES_KEY aes_key[2];
-@@ -35,8 +39,10 @@ class TwoKeyPRP { public:
- 		children[0] = children[0] ^ tmp[0];
- 	}
- 
-+#if defined (__x86_64__) || defined (__aarch64__)
- #ifdef __x86_64__
- 	__attribute__((target("aes,sse2")))
-+#endif
- 	inline void permute_block_4blks(block *blks) {
- 		blks[0] = _mm_xor_si128(blks[0], aes_key[0].rd_key[0]);
- 		blks[1] = _mm_xor_si128(blks[1], aes_key[1].rd_key[0]);
-@@ -54,7 +60,9 @@ class TwoKeyPRP { public:
- 		blks[3] = _mm_aesenclast_si128(blks[3], aes_key[1].rd_key[aes_key[1].rounds]);
- 	}
- 
-+#ifdef __x86_64__
- 	__attribute__((target("aes,sse2")))
-+#endif
- 	inline void permute_block_2blks(block *blks) {
- 		blks[0] = _mm_xor_si128(blks[0], aes_key[0].rd_key[0]);
- 		blks[1] = _mm_xor_si128(blks[1], aes_key[1].rd_key[0]);
-@@ -65,24 +73,6 @@ class TwoKeyPRP { public:
- 		blks[0] = _mm_aesenclast_si128(blks[0], aes_key[0].rd_key[aes_key[0].rounds]);
- 		blks[1] = _mm_aesenclast_si128(blks[1], aes_key[1].rd_key[aes_key[1].rounds]);
- 	}
--#elif __aarch64__
--	inline void permute_block_4blks(block *_blks) {
--		uint8x16_t * blks = (uint8x16_t*)(_blks);
--		for (unsigned int i = 0; i < 10; ++i) {
--			blks[0] = vaesmcq_u8(vaeseq_u8(blks[0], vreinterpretq_u8_m128i(aes_key[0].rd_key[i])));
--			blks[2] = vaesmcq_u8(vaeseq_u8(blks[2], vreinterpretq_u8_m128i(aes_key[0].rd_key[i])));
--			blks[1] = vaesmcq_u8(vaeseq_u8(blks[1], vreinterpretq_u8_m128i(aes_key[1].rd_key[i])));
--			blks[3] = vaesmcq_u8(vaeseq_u8(blks[3], vreinterpretq_u8_m128i(aes_key[1].rd_key[i])));
--		}
--	}
--
--	inline void permute_block_2blks(block *_blks) {
--		uint8x16_t * blks = (uint8x16_t*)(_blks);
--		for (unsigned int i = 0; i < 10; ++i) {
--			blks[0] = vaesmcq_u8(vaeseq_u8(blks[2], vreinterpretq_u8_m128i(aes_key[0].rd_key[i])));
--			blks[1] = vaesmcq_u8(vaeseq_u8(blks[1], vreinterpretq_u8_m128i(aes_key[1].rd_key[i])));
--		}
--	}
- #endif
- };
- #endif
diff --git a/bazel/patches/emp-tool-cmake.patch b/bazel/patches/emp-tool-cmake.patch
new file mode 100644
index 00000000..01aa13db
--- /dev/null
+++ b/bazel/patches/emp-tool-cmake.patch
@@ -0,0 +1,22 @@
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index d9abb31..4c2c171 100755
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -56,11 +56,14 @@ find_package(OpenSSL REQUIRED)
+ include_directories(${OPENSSL_INCLUDE_DIR})
+ 
+ 
+-add_library(${NAME} SHARED ${sources})
++add_library(${NAME} STATIC ${sources})
+ 
+ install(DIRECTORY emp-tool DESTINATION include/)
+ install(DIRECTORY cmake/ DESTINATION cmake/)
+ install(TARGETS ${NAME} DESTINATION lib)
+ 
+-ENABLE_TESTING()
+-ADD_SUBDIRECTORY(test)
++option(ENABLE_TESTS "Enable tests" OFF)
++if (${ENABLE_TESTS})
++    ENABLE_TESTING()
++    ADD_SUBDIRECTORY(test)
++endif()
diff --git a/bazel/patches/emp-tool-sse2neon.patch b/bazel/patches/emp-tool-sse2neon.patch
new file mode 100644
index 00000000..e94b22e0
--- /dev/null
+++ b/bazel/patches/emp-tool-sse2neon.patch
@@ -0,0 +1,6507 @@
+diff --git a/emp-tool/utils/sse2neon.h b/emp-tool/utils/sse2neon.h
+index d09b9c7..efa63a4 100644
+--- a/emp-tool/utils/sse2neon.h
++++ b/emp-tool/utils/sse2neon.h
+@@ -113,7 +113,7 @@
+ #ifdef _MSC_VER
+ #include <intrin.h>
+ #if (defined(_M_AMD64) || defined(__x86_64__)) || \
+-    (defined(_M_ARM) || defined(__arm__))
++    (defined(_M_ARM64) || defined(__arm64__))
+ #define SSE2NEON_HAS_BITSCAN64
+ #endif
+ #endif
+@@ -441,7 +441,7 @@ typedef int64x2_t __m128i; /* 128-bit vector containing integers */
+ // by applications which attempt to access the contents of an __m128 struct
+ // directly.  It is important to note that accessing the __m128 struct directly
+ // is bad coding practice by Microsoft: @see:
+-// https://docs.microsoft.com/en-us/cpp/cpp/m128
++// https://learn.microsoft.com/en-us/cpp/cpp/m128
+ //
+ // However, some legacy source code may try to access the contents of an __m128
+ // struct directly so the developer can use the SIMDVec as an alias for it.  Any
+@@ -621,47 +621,6 @@ FORCE_INLINE uint16_t _sse2neon_vaddvq_u16(uint16x8_t a)
+  *                                  4, 5, 12, 13, 6, 7, 14, 15);
+  *   // Shuffle packed 8-bit integers
+  *   __m128i v_out = _mm_shuffle_epi8(v_in, v_perm); // pshufb
+- *
+- * Data (Number, Binary, Byte Index):
+-    +------+------+-------------+------+------+-------------+
+-    |      1      |      2      |      3      |      4      | Number
+-    +------+------+------+------+------+------+------+------+
+-    | 0000 | 0001 | 0000 | 0010 | 0000 | 0011 | 0000 | 0100 | Binary
+-    +------+------+------+------+------+------+------+------+
+-    |    0 |    1 |    2 |    3 |    4 |    5 |    6 |    7 | Index
+-    +------+------+------+------+------+------+------+------+
+-
+-    +------+------+------+------+------+------+------+------+
+-    |      5      |      6      |      7      |      8      | Number
+-    +------+------+------+------+------+------+------+------+
+-    | 0000 | 0101 | 0000 | 0110 | 0000 | 0111 | 0000 | 1000 | Binary
+-    +------+------+------+------+------+------+------+------+
+-    |    8 |    9 |   10 |   11 |   12 |   13 |   14 |   15 | Index
+-    +------+------+------+------+------+------+------+------+
+- * Index (Byte Index):
+-    +------+------+------+------+------+------+------+------+
+-    |    1 |    0 |    2 |    3 |    8 |    9 |   10 |   11 |
+-    +------+------+------+------+------+------+------+------+
+-
+-    +------+------+------+------+------+------+------+------+
+-    |    4 |    5 |   12 |   13 |    6 |    7 |   14 |   15 |
+-    +------+------+------+------+------+------+------+------+
+- * Result:
+-    +------+------+------+------+------+------+------+------+
+-    |    1 |    0 |    2 |    3 |    8 |    9 |   10 |   11 | Index
+-    +------+------+------+------+------+------+------+------+
+-    | 0001 | 0000 | 0000 | 0010 | 0000 | 0101 | 0000 | 0110 | Binary
+-    +------+------+------+------+------+------+------+------+
+-    |     256     |      2      |      5      |      6      | Number
+-    +------+------+------+------+------+------+------+------+
+-
+-    +------+------+------+------+------+------+------+------+
+-    |    4 |    5 |   12 |   13 |    6 |    7 |   14 |   15 | Index
+-    +------+------+------+------+------+------+------+------+
+-    | 0000 | 0011 | 0000 | 0111 | 0000 | 0100 | 0000 | 1000 | Binary
+-    +------+------+------+------+------+------+------+------+
+-    |      3      |      7      |      4      |      8      | Number
+-    +------+------+------+------+------+------+-------------+
+  */
+ 
+ /* Constants for use with _mm_prefetch. */
+@@ -1069,9 +1028,9 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a)
+     })
+ #endif
+ 
+-// NEON does not support a general purpose permute intrinsic
+-// Selects four specific single-precision, floating-point values from a and b,
+-// based on the mask i.
++// NEON does not support a general purpose permute intrinsic.
++// Shuffle single-precision (32-bit) floating-point elements in a using the
++// control in imm8, and store the results in dst.
+ //
+ // C equivalent:
+ //   __m128 _mm_shuffle_ps_default(__m128 a, __m128 b,
+@@ -1082,7 +1041,7 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a)
+ //       return ret;
+ //   }
+ //
+-// https://msdn.microsoft.com/en-us/library/vstudio/5f0858x0(v=vs.100).aspx
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_ps
+ #define _mm_shuffle_ps_default(a, b, imm)                                  \
+     __extension__({                                                        \
+         float32x4_t ret;                                                   \
+@@ -1100,12 +1059,10 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a)
+         vreinterpretq_m128_f32(ret);                                       \
+     })
+ 
+-// Shuffles the lower 4 signed or unsigned 16-bit integers in a as specified
+-// by imm.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/y41dkk37(v=vs.100)
+-// FORCE_INLINE __m128i _mm_shufflelo_epi16_function(__m128i a,
+-//                                                   __constrange(0,255) int
+-//                                                   imm)
++// Shuffle 16-bit integers in the low 64 bits of a using the control in imm8.
++// Store the results in the low 64 bits of dst, with the high 64 bits being
++// copied from from a to dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shufflelo_epi16
+ #define _mm_shufflelo_epi16_function(a, imm)                                  \
+     __extension__({                                                           \
+         int16x8_t ret = vreinterpretq_s16_m128i(a);                           \
+@@ -1120,12 +1077,10 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a)
+         vreinterpretq_m128i_s16(ret);                                         \
+     })
+ 
+-// Shuffles the upper 4 signed or unsigned 16-bit integers in a as specified
+-// by imm.
+-// https://msdn.microsoft.com/en-us/library/13ywktbs(v=vs.100).aspx
+-// FORCE_INLINE __m128i _mm_shufflehi_epi16_function(__m128i a,
+-//                                                   __constrange(0,255) int
+-//                                                   imm)
++// Shuffle 16-bit integers in the high 64 bits of a using the control in imm8.
++// Store the results in the high 64 bits of dst, with the low 64 bits being
++// copied from from a to dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shufflehi_epi16
+ #define _mm_shufflehi_epi16_function(a, imm)                                   \
+     __extension__({                                                            \
+         int16x8_t ret = vreinterpretq_s16_m128i(a);                            \
+@@ -1147,22 +1102,19 @@ FORCE_INLINE void _mm_empty(void) {}
+ 
+ /* SSE */
+ 
+-// Adds the four single-precision, floating-point values of a and b.
+-//
+-//   r0 := a0 + b0
+-//   r1 := a1 + b1
+-//   r2 := a2 + b2
+-//   r3 := a3 + b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/c9848chc(v=vs.100).aspx
++// Add packed single-precision (32-bit) floating-point elements in a and b, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_ps
+ FORCE_INLINE __m128 _mm_add_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_f32(
+         vaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// adds the scalar single-precision floating point values of a and b.
+-// https://msdn.microsoft.com/en-us/library/be94x2y6(v=vs.100).aspx
++// Add the lower single-precision (32-bit) floating-point element in a and b,
++// store the result in the lower element of dst, and copy the upper 3 packed
++// elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_ss
+ FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b)
+ {
+     float32_t b0 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 0);
+@@ -1171,30 +1123,18 @@ FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b)
+     return vreinterpretq_m128_f32(vaddq_f32(a, value));
+ }
+ 
+-// Computes the bitwise AND of the four single-precision, floating-point values
+-// of a and b.
+-//
+-//   r0 := a0 & b0
+-//   r1 := a1 & b1
+-//   r2 := a2 & b2
+-//   r3 := a3 & b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/73ck1xc5(v=vs.100).aspx
++// Compute the bitwise AND of packed single-precision (32-bit) floating-point
++// elements in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_ps
+ FORCE_INLINE __m128 _mm_and_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_s32(
+         vandq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b)));
+ }
+ 
+-// Computes the bitwise AND-NOT of the four single-precision, floating-point
+-// values of a and b.
+-//
+-//   r0 := ~a0 & b0
+-//   r1 := ~a1 & b1
+-//   r2 := ~a2 & b2
+-//   r3 := ~a3 & b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/68h7wd02(v=vs.100).aspx
++// Compute the bitwise NOT of packed single-precision (32-bit) floating-point
++// elements in a and then AND with b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_ps
+ FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_s32(
+@@ -1204,13 +1144,7 @@ FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b)
+ 
+ // Average packed unsigned 16-bit integers in a and b, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*16
+-//     dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_pu16
+ FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u16(
+@@ -1219,186 +1153,199 @@ FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b)
+ 
+ // Average packed unsigned 8-bit integers in a and b, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*8
+-//     dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_pu8
+ FORCE_INLINE __m64 _mm_avg_pu8(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u8(
+         vrhadd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b)));
+ }
+ 
+-// Compares for equality.
+-// https://msdn.microsoft.com/en-us/library/vstudio/36aectz5(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for equality, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_ps
+ FORCE_INLINE __m128 _mm_cmpeq_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(
+         vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// Compares for equality.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/k423z28e(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for equality, store the result in the lower element of dst, and copy the
++// upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_ss
+ FORCE_INLINE __m128 _mm_cmpeq_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpeq_ps(a, b));
+ }
+ 
+-// Compares for greater than or equal.
+-// https://msdn.microsoft.com/en-us/library/vstudio/fs813y2t(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for greater-than-or-equal, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_ps
+ FORCE_INLINE __m128 _mm_cmpge_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(
+         vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// Compares for greater than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/kesh3ddc(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for greater-than-or-equal, store the result in the lower element of dst,
++// and copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_ss
+ FORCE_INLINE __m128 _mm_cmpge_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpge_ps(a, b));
+ }
+ 
+-// Compares for greater than.
+-//
+-//   r0 := (a0 > b0) ? 0xffffffff : 0x0
+-//   r1 := (a1 > b1) ? 0xffffffff : 0x0
+-//   r2 := (a2 > b2) ? 0xffffffff : 0x0
+-//   r3 := (a3 > b3) ? 0xffffffff : 0x0
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/11dy102s(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for greater-than, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_ps
+ FORCE_INLINE __m128 _mm_cmpgt_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(
+         vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// Compares for greater than.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/1xyyyy9e(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for greater-than, store the result in the lower element of dst, and copy
++// the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_ss
+ FORCE_INLINE __m128 _mm_cmpgt_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpgt_ps(a, b));
+ }
+ 
+-// Compares for less than or equal.
+-//
+-//   r0 := (a0 <= b0) ? 0xffffffff : 0x0
+-//   r1 := (a1 <= b1) ? 0xffffffff : 0x0
+-//   r2 := (a2 <= b2) ? 0xffffffff : 0x0
+-//   r3 := (a3 <= b3) ? 0xffffffff : 0x0
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/1s75w83z(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for less-than-or-equal, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_ps
+ FORCE_INLINE __m128 _mm_cmple_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(
+         vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// Compares for less than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/a7x0hbhw(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for less-than-or-equal, store the result in the lower element of dst, and
++// copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_ss
+ FORCE_INLINE __m128 _mm_cmple_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmple_ps(a, b));
+ }
+ 
+-// Compares for less than
+-// https://msdn.microsoft.com/en-us/library/vstudio/f330yhc8(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for less-than, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_ps
+ FORCE_INLINE __m128 _mm_cmplt_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(
+         vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ }
+ 
+-// Compares for less than
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fy94wye7(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for less-than, store the result in the lower element of dst, and copy the
++// upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_ss
+ FORCE_INLINE __m128 _mm_cmplt_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmplt_ps(a, b));
+ }
+ 
+-// Compares for inequality.
+-// https://msdn.microsoft.com/en-us/library/sf44thbx(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for not-equal, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_ps
+ FORCE_INLINE __m128 _mm_cmpneq_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(vmvnq_u32(
+         vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))));
+ }
+ 
+-// Compares for inequality.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ekya8fh4(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for not-equal, store the result in the lower element of dst, and copy the
++// upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_ss
+ FORCE_INLINE __m128 _mm_cmpneq_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpneq_ps(a, b));
+ }
+ 
+-// Compares for not greater than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/wsexys62(v=vs.100)
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for not-greater-than-or-equal, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_ps
+ FORCE_INLINE __m128 _mm_cmpnge_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(vmvnq_u32(
+         vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))));
+ }
+ 
+-// Compares for not greater than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fk2y80s8(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for not-greater-than-or-equal, store the result in the lower element of
++// dst, and copy the upper 3 packed elements from a to the upper elements of
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_ss
+ FORCE_INLINE __m128 _mm_cmpnge_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpnge_ps(a, b));
+ }
+ 
+-// Compares for not greater than.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/d0xh7w0s(v=vs.100)
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for not-greater-than, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_ps
+ FORCE_INLINE __m128 _mm_cmpngt_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(vmvnq_u32(
+         vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))));
+ }
+ 
+-// Compares for not greater than.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for not-greater-than, store the result in the lower element of dst, and
++// copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_ss
+ FORCE_INLINE __m128 _mm_cmpngt_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpngt_ps(a, b));
+ }
+ 
+-// Compares for not less than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/6a330kxw(v=vs.100)
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for not-less-than-or-equal, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_ps
+ FORCE_INLINE __m128 _mm_cmpnle_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(vmvnq_u32(
+         vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))));
+ }
+ 
+-// Compares for not less than or equal.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for not-less-than-or-equal, store the result in the lower element of dst,
++// and copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_ss
+ FORCE_INLINE __m128 _mm_cmpnle_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpnle_ps(a, b));
+ }
+ 
+-// Compares for not less than.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/4686bbdw(v=vs.100)
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// for not-less-than, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_ps
+ FORCE_INLINE __m128 _mm_cmpnlt_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_u32(vmvnq_u32(
+         vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))));
+ }
+ 
+-// Compares for not less than.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/56b9z2wf(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b for not-less-than, store the result in the lower element of dst, and copy
++// the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_ss
+ FORCE_INLINE __m128 _mm_cmpnlt_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpnlt_ps(a, b));
+ }
+ 
+-// Compares the four 32-bit floats in a and b to check if any values are NaN.
+-// Ordered compare between each value returns true for "orderable" and false for
+-// "not orderable" (NaN).
+-// https://msdn.microsoft.com/en-us/library/vstudio/0h9w00fx(v=vs.100).aspx see
+-// also:
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// to see if neither is NaN, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_ps
++//
++// See also:
+ // http://stackoverflow.com/questions/8627331/what-does-ordered-unordered-comparison-mean
+ // http://stackoverflow.com/questions/29349621/neon-isnanval-intrinsics
+ FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b)
+@@ -1413,15 +1360,18 @@ FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b)
+     return vreinterpretq_m128_u32(vandq_u32(ceqaa, ceqbb));
+ }
+ 
+-// Compares for ordered.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/343t62da(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b to see if neither is NaN, store the result in the lower element of dst, and
++// copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_ss
+ FORCE_INLINE __m128 _mm_cmpord_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpord_ps(a, b));
+ }
+ 
+-// Compares for unordered.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/khy6fk1t(v=vs.100)
++// Compare packed single-precision (32-bit) floating-point elements in a and b
++// to see if either is NaN, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_ps
+ FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b)
+ {
+     uint32x4_t f32a =
+@@ -1431,16 +1381,18 @@ FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b)
+     return vreinterpretq_m128_u32(vmvnq_u32(vandq_u32(f32a, f32b)));
+ }
+ 
+-// Compares for unordered.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/2as2387b(v=vs.100)
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b to see if either is NaN, store the result in the lower element of dst, and
++// copy the upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_ss
+ FORCE_INLINE __m128 _mm_cmpunord_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_cmpunord_ps(a, b));
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using an equality operation. :
+-// https://msdn.microsoft.com/en-us/library/93yx2h2b(v=vs.100).aspx
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for equality, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comieq_ss
+ FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b)
+ {
+     uint32x4_t a_eq_b =
+@@ -1448,9 +1400,9 @@ FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b)
+     return vgetq_lane_u32(a_eq_b, 0) & 0x1;
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using a greater than or equal operation. :
+-// https://msdn.microsoft.com/en-us/library/8t80des6(v=vs.100).aspx
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for greater-than-or-equal, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comige_ss
+ FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b)
+ {
+     uint32x4_t a_ge_b =
+@@ -1458,9 +1410,9 @@ FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b)
+     return vgetq_lane_u32(a_ge_b, 0) & 0x1;
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using a greater than operation. :
+-// https://msdn.microsoft.com/en-us/library/b0738e0t(v=vs.100).aspx
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for greater-than, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comigt_ss
+ FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b)
+ {
+     uint32x4_t a_gt_b =
+@@ -1468,9 +1420,9 @@ FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b)
+     return vgetq_lane_u32(a_gt_b, 0) & 0x1;
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using a less than or equal operation. :
+-// https://msdn.microsoft.com/en-us/library/1w4t7c57(v=vs.90).aspx
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for less-than-or-equal, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comile_ss
+ FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b)
+ {
+     uint32x4_t a_le_b =
+@@ -1478,11 +1430,9 @@ FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b)
+     return vgetq_lane_u32(a_le_b, 0) & 0x1;
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using a less than operation. :
+-// https://msdn.microsoft.com/en-us/library/2kwe606b(v=vs.90).aspx Important
+-// note!! The documentation on MSDN is incorrect!  If either of the values is a
+-// NAN the docs say you will get a one, but in fact, it will return a zero!!
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for less-than, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comilt_ss
+ FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b)
+ {
+     uint32x4_t a_lt_b =
+@@ -1490,9 +1440,9 @@ FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b)
+     return vgetq_lane_u32(a_lt_b, 0) & 0x1;
+ }
+ 
+-// Compares the lower single-precision floating point scalar values of a and b
+-// using an inequality operation. :
+-// https://msdn.microsoft.com/en-us/library/bafh5e0a(v=vs.90).aspx
++// Compare the lower single-precision (32-bit) floating-point element in a and b
++// for not-equal, and return the boolean result (0 or 1).
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comineq_ss
+ FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b)
+ {
+     return !_mm_comieq_ss(a, b);
+@@ -1502,13 +1452,7 @@ FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b)
+ // (32-bit) floating-point elements, store the results in the lower 2 elements
+ // of dst, and copy the upper 2 packed elements from a to the upper elements of
+ // dst.
+-//
+-//   dst[31:0] := Convert_Int32_To_FP32(b[31:0])
+-//   dst[63:32] := Convert_Int32_To_FP32(b[63:32])
+-//   dst[95:64] := a[95:64]
+-//   dst[127:96] := a[127:96]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_pi2ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_pi2ps
+ FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1518,13 +1462,7 @@ FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b)
+ 
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed 32-bit integers, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//       i := 32*j
+-//       dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ps2pi
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_ps2pi
+ FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a)
+ {
+ #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING)
+@@ -1539,11 +1477,7 @@ FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a)
+ // Convert the signed 32-bit integer b to a single-precision (32-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper 3 packed elements from a to the upper elements of dst.
+-//
+-//   dst[31:0] := Convert_Int32_To_FP32(b[31:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_si2ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_si2ss
+ FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1552,7 +1486,7 @@ FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 32-bit integer, and store the result in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ss2si
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_ss2si
+ FORCE_INLINE int _mm_cvt_ss2si(__m128 a)
+ {
+ #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING)
+@@ -1567,14 +1501,7 @@ FORCE_INLINE int _mm_cvt_ss2si(__m128 a)
+ 
+ // Convert packed 16-bit integers in a to packed single-precision (32-bit)
+ // floating-point elements, and store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*16
+-//      m := j*32
+-//      dst[m+31:m] := Convert_Int16_To_FP32(a[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi16_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi16_ps
+ FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1584,13 +1511,7 @@ FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a)
+ // Convert packed 32-bit integers in b to packed single-precision (32-bit)
+ // floating-point elements, store the results in the lower 2 elements of dst,
+ // and copy the upper 2 packed elements from a to the upper elements of dst.
+-//
+-//   dst[31:0] := Convert_Int32_To_FP32(b[31:0])
+-//   dst[63:32] := Convert_Int32_To_FP32(b[63:32])
+-//   dst[95:64] := a[95:64]
+-//   dst[127:96] := a[127:96]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32_ps
+ FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1603,13 +1524,7 @@ FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b)
+ // of dst, then convert the packed signed 32-bit integers in b to
+ // single-precision (32-bit) floating-point element, and store the results in
+ // the upper 2 elements of dst.
+-//
+-//   dst[31:0] := Convert_Int32_To_FP32(a[31:0])
+-//   dst[63:32] := Convert_Int32_To_FP32(a[63:32])
+-//   dst[95:64] := Convert_Int32_To_FP32(b[31:0])
+-//   dst[127:96] := Convert_Int32_To_FP32(b[63:32])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32x2_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32x2_ps
+ FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b)
+ {
+     return vreinterpretq_m128_f32(vcvtq_f32_s32(
+@@ -1618,14 +1533,7 @@ FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b)
+ 
+ // Convert the lower packed 8-bit integers in a to packed single-precision
+ // (32-bit) floating-point elements, and store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*8
+-//      m := j*32
+-//      dst[m+31:m] := Convert_Int8_To_FP32(a[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi8_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi8_ps
+ FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a)
+ {
+     return vreinterpretq_m128_f32(vcvtq_f32_s32(
+@@ -1636,18 +1544,7 @@ FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a)
+ // packed 16-bit integers, and store the results in dst. Note: this intrinsic
+ // will generate 0x7FFF, rather than 0x8000, for input values between 0x7FFF and
+ // 0x7FFFFFFF.
+-//
+-//   FOR j := 0 to 3
+-//     i := 16*j
+-//     k := 32*j
+-//     IF a[k+31:k] >= FP32(0x7FFF) && a[k+31:k] <= FP32(0x7FFFFFFF)
+-//       dst[i+15:i] := 0x7FFF
+-//     ELSE
+-//       dst[i+15:i] := Convert_FP32_To_Int16(a[k+31:k])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi16
+ FORCE_INLINE __m64 _mm_cvtps_pi16(__m128 a)
+ {
+     return vreinterpret_m64_s16(
+@@ -1656,31 +1553,14 @@ FORCE_INLINE __m64 _mm_cvtps_pi16(__m128 a)
+ 
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed 32-bit integers, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//       i := 32*j
+-//       dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi32
+ #define _mm_cvtps_pi32(a) _mm_cvt_ps2pi(a)
+ 
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed 8-bit integers, and store the results in lower 4 elements of dst.
+ // Note: this intrinsic will generate 0x7F, rather than 0x80, for input values
+ // between 0x7F and 0x7FFFFFFF.
+-//
+-//   FOR j := 0 to 3
+-//     i := 8*j
+-//     k := 32*j
+-//     IF a[k+31:k] >= FP32(0x7F) && a[k+31:k] <= FP32(0x7FFFFFFF)
+-//       dst[i+7:i] := 0x7F
+-//     ELSE
+-//       dst[i+7:i] := Convert_FP32_To_Int8(a[k+31:k])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi8
+ FORCE_INLINE __m64 _mm_cvtps_pi8(__m128 a)
+ {
+     return vreinterpret_m64_s8(vqmovn_s16(
+@@ -1689,14 +1569,7 @@ FORCE_INLINE __m64 _mm_cvtps_pi8(__m128 a)
+ 
+ // Convert packed unsigned 16-bit integers in a to packed single-precision
+ // (32-bit) floating-point elements, and store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*16
+-//      m := j*32
+-//      dst[m+31:m] := Convert_UInt16_To_FP32(a[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu16_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpu16_ps
+ FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1706,14 +1579,7 @@ FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a)
+ // Convert the lower packed unsigned 8-bit integers in a to packed
+ // single-precision (32-bit) floating-point elements, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*8
+-//      m := j*32
+-//      dst[m+31:m] := Convert_UInt8_To_FP32(a[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu8_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpu8_ps
+ FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a)
+ {
+     return vreinterpretq_m128_f32(vcvtq_f32_u32(
+@@ -1723,21 +1589,13 @@ FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a)
+ // Convert the signed 32-bit integer b to a single-precision (32-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper 3 packed elements from a to the upper elements of dst.
+-//
+-//   dst[31:0] := Convert_Int32_To_FP32(b[31:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_ss
+ #define _mm_cvtsi32_ss(a, b) _mm_cvt_si2ss(a, b)
+ 
+ // Convert the signed 64-bit integer b to a single-precision (32-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper 3 packed elements from a to the upper elements of dst.
+-//
+-//   dst[31:0] := Convert_Int64_To_FP32(b[63:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_ss
+ FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -1745,10 +1603,7 @@ FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b)
+ }
+ 
+ // Copy the lower single-precision (32-bit) floating-point element of a to dst.
+-//
+-//   dst[31:0] := a[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_f32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_f32
+ FORCE_INLINE float _mm_cvtss_f32(__m128 a)
+ {
+     return vgetq_lane_f32(vreinterpretq_f32_m128(a), 0);
+@@ -1756,18 +1611,12 @@ FORCE_INLINE float _mm_cvtss_f32(__m128 a)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 32-bit integer, and store the result in dst.
+-//
+-//   dst[31:0] := Convert_FP32_To_Int32(a[31:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_si32
+ #define _mm_cvtss_si32(a) _mm_cvt_ss2si(a)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 64-bit integer, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP32_To_Int64(a[31:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_si64
+ FORCE_INLINE int64_t _mm_cvtss_si64(__m128 a)
+ {
+ #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING)
+@@ -1781,13 +1630,7 @@ FORCE_INLINE int64_t _mm_cvtss_si64(__m128 a)
+ 
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed 32-bit integers with truncation, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//      i := 32*j
+-//      dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ps2pi
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtt_ps2pi
+ FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a)
+ {
+     return vreinterpret_m64_s32(
+@@ -1796,10 +1639,7 @@ FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 32-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ss2si
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtt_ss2si
+ FORCE_INLINE int _mm_cvtt_ss2si(__m128 a)
+ {
+     return vgetq_lane_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)), 0);
+@@ -1807,60 +1647,49 @@ FORCE_INLINE int _mm_cvtt_ss2si(__m128 a)
+ 
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed 32-bit integers with truncation, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//      i := 32*j
+-//      dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttps_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttps_pi32
+ #define _mm_cvttps_pi32(a) _mm_cvtt_ps2pi(a)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 32-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttss_si32
+ #define _mm_cvttss_si32(a) _mm_cvtt_ss2si(a)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in a to a
+ // 64-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP32_To_Int64_Truncate(a[31:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttss_si64
+ FORCE_INLINE int64_t _mm_cvttss_si64(__m128 a)
+ {
+     return (int64_t) vgetq_lane_f32(vreinterpretq_f32_m128(a), 0);
+ }
+ 
+-// Divides the four single-precision, floating-point values of a and b.
+-//
+-//   r0 := a0 / b0
+-//   r1 := a1 / b1
+-//   r2 := a2 / b2
+-//   r3 := a3 / b3
+-//
+-// https://msdn.microsoft.com/en-us/library/edaw8147(v=vs.100).aspx
++// Divide packed single-precision (32-bit) floating-point elements in a by
++// packed elements in b, and store the results in dst.
++// Due to ARMv7-A NEON's lack of a precise division intrinsic, we implement
++// division by multiplying a by b's reciprocal before using the Newton-Raphson
++// method to approximate the results.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_ps
+ FORCE_INLINE __m128 _mm_div_ps(__m128 a, __m128 b)
+ {
+-#if defined(__aarch64__) && !SSE2NEON_PRECISE_DIV
++#if defined(__aarch64__)
+     return vreinterpretq_m128_f32(
+         vdivq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
+ #else
+     float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(b));
+     recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b)));
+-#if SSE2NEON_PRECISE_DIV
+     // Additional Netwon-Raphson iteration for accuracy
+     recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b)));
+-#endif
+     return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(a), recip));
+ #endif
+ }
+ 
+-// Divides the scalar single-precision floating point value of a by b.
+-// https://msdn.microsoft.com/en-us/library/4y73xa49(v=vs.100).aspx
++// Divide the lower single-precision (32-bit) floating-point element in a by the
++// lower single-precision (32-bit) floating-point element in b, store the result
++// in the lower element of dst, and copy the upper 3 packed elements from a to
++// the upper elements of dst.
++// Warning: ARMv7-A does not produce the same result compared to Intel and not
++// IEEE-compliant.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_ss
+ FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b)
+ {
+     float32_t value =
+@@ -1871,12 +1700,12 @@ FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b)
+ 
+ // Extract a 16-bit integer from a, selected with imm8, and store the result in
+ // the lower element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_pi16
+ #define _mm_extract_pi16(a, imm) \
+     (int32_t) vget_lane_u16(vreinterpret_u16_m64(a), (imm))
+ 
+ // Free aligned memory that was allocated with _mm_malloc.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_free
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_free
+ #if !defined(SSE2NEON_ALLOC_DEFINED)
+ FORCE_INLINE void _mm_free(void *addr)
+ {
+@@ -1887,7 +1716,7 @@ FORCE_INLINE void _mm_free(void *addr)
+ // Macro: Get the flush zero bits from the MXCSR control and status register.
+ // The flush zero may contain any of the following flags: _MM_FLUSH_ZERO_ON or
+ // _MM_FLUSH_ZERO_OFF
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_FLUSH_ZERO_MODE
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_GET_FLUSH_ZERO_MODE
+ FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode()
+ {
+     union {
+@@ -1911,7 +1740,7 @@ FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode()
+ // Macro: Get the rounding mode bits from the MXCSR control and status register.
+ // The rounding mode may contain any of the following flags: _MM_ROUND_NEAREST,
+ // _MM_ROUND_DOWN, _MM_ROUND_UP, _MM_ROUND_TOWARD_ZERO
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_ROUNDING_MODE
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_GET_ROUNDING_MODE
+ FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE()
+ {
+     union {
+@@ -1938,15 +1767,17 @@ FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE()
+ 
+ // Copy a to dst, and insert the 16-bit integer i into dst at the location
+ // specified by imm8.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_insert_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_pi16
+ #define _mm_insert_pi16(a, b, imm)                               \
+     __extension__({                                              \
+         vreinterpret_m64_s16(                                    \
+             vset_lane_s16((b), vreinterpret_s16_m64(a), (imm))); \
+     })
+ 
+-// Loads four single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/vstudio/zzd50xxt(v=vs.100).aspx
++// Load 128-bits (composed of 4 packed single-precision (32-bit) floating-point
++// elements) from memory into dst. mem_addr must be aligned on a 16-byte
++// boundary or a general-protection exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ps
+ FORCE_INLINE __m128 _mm_load_ps(const float *p)
+ {
+     return vreinterpretq_m128_f32(vld1q_f32(p));
+@@ -1960,52 +1791,40 @@ FORCE_INLINE __m128 _mm_load_ps(const float *p)
+ //   dst[95:64] := MEM[mem_addr+31:mem_addr]
+ //   dst[127:96] := MEM[mem_addr+31:mem_addr]
+ //
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_ps1
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ps1
+ #define _mm_load_ps1 _mm_load1_ps
+ 
+-// Loads an single - precision, floating - point value into the low word and
+-// clears the upper three words.
+-// https://msdn.microsoft.com/en-us/library/548bb9h4%28v=vs.90%29.aspx
++// Load a single-precision (32-bit) floating-point element from memory into the
++// lower of dst, and zero the upper 3 elements. mem_addr does not need to be
++// aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ss
+ FORCE_INLINE __m128 _mm_load_ss(const float *p)
+ {
+     return vreinterpretq_m128_f32(vsetq_lane_f32(*p, vdupq_n_f32(0), 0));
+ }
+ 
+-// Loads a single single-precision, floating-point value, copying it into all
+-// four words
+-// https://msdn.microsoft.com/en-us/library/vstudio/5cdkf716(v=vs.100).aspx
++// Load a single-precision (32-bit) floating-point element from memory into all
++// elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load1_ps
+ FORCE_INLINE __m128 _mm_load1_ps(const float *p)
+ {
+     return vreinterpretq_m128_f32(vld1q_dup_f32(p));
+ }
+ 
+-// Sets the upper two single-precision, floating-point values with 64
+-// bits of data loaded from the address p; the lower two values are passed
+-// through from a.
+-//
+-//   r0 := a0
+-//   r1 := a1
+-//   r2 := *p0
+-//   r3 := *p1
+-//
+-// https://msdn.microsoft.com/en-us/library/w92wta0x(v%3dvs.100).aspx
++// Load 2 single-precision (32-bit) floating-point elements from memory into the
++// upper 2 elements of dst, and copy the lower 2 elements from a to dst.
++// mem_addr does not need to be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadh_pi
+ FORCE_INLINE __m128 _mm_loadh_pi(__m128 a, __m64 const *p)
+ {
+     return vreinterpretq_m128_f32(
+         vcombine_f32(vget_low_f32(a), vld1_f32((const float32_t *) p)));
+ }
+ 
+-// Sets the lower two single-precision, floating-point values with 64
+-// bits of data loaded from the address p; the upper two values are passed
+-// through from a.
+-//
+-// Return Value
+-//   r0 := *p0
+-//   r1 := *p1
+-//   r2 := a2
+-//   r3 := a3
+-//
+-// https://msdn.microsoft.com/en-us/library/s57cyak2(v=vs.100).aspx
++// Load 2 single-precision (32-bit) floating-point elements from memory into the
++// lower 2 elements of dst, and copy the upper 2 elements from a to dst.
++// mem_addr does not need to be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_pi
+ FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p)
+ {
+     return vreinterpretq_m128_f32(
+@@ -2015,21 +1834,17 @@ FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p)
+ // Load 4 single-precision (32-bit) floating-point elements from memory into dst
+ // in reverse order. mem_addr must be aligned on a 16-byte boundary or a
+ // general-protection exception may be generated.
+-//
+-//   dst[31:0] := MEM[mem_addr+127:mem_addr+96]
+-//   dst[63:32] := MEM[mem_addr+95:mem_addr+64]
+-//   dst[95:64] := MEM[mem_addr+63:mem_addr+32]
+-//   dst[127:96] := MEM[mem_addr+31:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadr_ps
+ FORCE_INLINE __m128 _mm_loadr_ps(const float *p)
+ {
+     float32x4_t v = vrev64q_f32(vld1q_f32(p));
+     return vreinterpretq_m128_f32(vextq_f32(v, v, 2));
+ }
+ 
+-// Loads four single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/x1b16s7z%28v=vs.90%29.aspx
++// Load 128-bits (composed of 4 packed single-precision (32-bit) floating-point
++// elements) from memory into dst. mem_addr does not need to be aligned on any
++// particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_ps
+ FORCE_INLINE __m128 _mm_loadu_ps(const float *p)
+ {
+     // for neon, alignment doesn't matter, so _mm_load_ps and _mm_loadu_ps are
+@@ -2038,11 +1853,7 @@ FORCE_INLINE __m128 _mm_loadu_ps(const float *p)
+ }
+ 
+ // Load unaligned 16-bit integer from memory into the first element of dst.
+-//
+-//   dst[15:0] := MEM[mem_addr+15:mem_addr]
+-//   dst[MAX:16] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si16
+ FORCE_INLINE __m128i _mm_loadu_si16(const void *p)
+ {
+     return vreinterpretq_m128i_s16(
+@@ -2050,20 +1861,17 @@ FORCE_INLINE __m128i _mm_loadu_si16(const void *p)
+ }
+ 
+ // Load unaligned 64-bit integer from memory into the first element of dst.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[MAX:64] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si64
+ FORCE_INLINE __m128i _mm_loadu_si64(const void *p)
+ {
+     return vreinterpretq_m128i_s64(
+         vcombine_s64(vld1_s64((const int64_t *) p), vdup_n_s64(0)));
+ }
+ 
+-// Allocate aligned blocks of memory.
+-// https://software.intel.com/en-us/
+-//         cpp-compiler-developer-guide-and-reference-allocating-and-freeing-aligned-memory-blocks
++// Allocate size bytes of memory, aligned to the alignment specified in align,
++// and return a pointer to the allocated memory. _mm_free should be used to free
++// memory that is allocated with _mm_malloc.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_malloc
+ #if !defined(SSE2NEON_ALLOC_DEFINED)
+ FORCE_INLINE void *_mm_malloc(size_t size, size_t align)
+ {
+@@ -2081,7 +1889,7 @@ FORCE_INLINE void *_mm_malloc(size_t size, size_t align)
+ // Conditionally store 8-bit integer elements from a into memory using mask
+ // (elements are not stored when the highest bit is not set in the corresponding
+ // element) and a non-temporal memory hint.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmove_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maskmove_si64
+ FORCE_INLINE void _mm_maskmove_si64(__m64 a, __m64 mask, char *mem_addr)
+ {
+     int8x8_t shr_mask = vshr_n_s8(vreinterpret_s8_m64(mask), 7);
+@@ -2095,27 +1903,23 @@ FORCE_INLINE void _mm_maskmove_si64(__m64 a, __m64 mask, char *mem_addr)
+ // Conditionally store 8-bit integer elements from a into memory using mask
+ // (elements are not stored when the highest bit is not set in the corresponding
+ // element) and a non-temporal memory hint.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_maskmovq
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_maskmovq
+ #define _m_maskmovq(a, mask, mem_addr) _mm_maskmove_si64(a, mask, mem_addr)
+ 
+ // Compare packed signed 16-bit integers in a and b, and store packed maximum
+ // values in dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*16
+-//      dst[i+15:i] := MAX(a[i+15:i], b[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pi16
+ FORCE_INLINE __m64 _mm_max_pi16(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s16(
+         vmax_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b)));
+ }
+ 
+-// Computes the maximums of the four single-precision, floating-point values of
+-// a and b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/ff5d607a(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b,
++// and store packed maximum values in dst. dst does not follow the IEEE Standard
++// for Floating-Point Arithmetic (IEEE 754) maximum value when inputs are NaN or
++// signed-zero values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_ps
+ FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b)
+ {
+ #if SSE2NEON_PRECISE_MINMAX
+@@ -2130,22 +1934,19 @@ FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b)
+ 
+ // Compare packed unsigned 8-bit integers in a and b, and store packed maximum
+ // values in dst.
+-//
+-//   FOR j := 0 to 7
+-//      i := j*8
+-//      dst[i+7:i] := MAX(a[i+7:i], b[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pu8
+ FORCE_INLINE __m64 _mm_max_pu8(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u8(
+         vmax_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b)));
+ }
+ 
+-// Computes the maximum of the two lower scalar single-precision floating point
+-// values of a and b.
+-// https://msdn.microsoft.com/en-us/library/s6db5esz(v=vs.100).aspx
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b, store the maximum value in the lower element of dst, and copy the upper 3
++// packed elements from a to the upper element of dst. dst does not follow the
++// IEEE Standard for Floating-Point Arithmetic (IEEE 754) maximum value when
++// inputs are NaN or signed-zero values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_ss
+ FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b)
+ {
+     float32_t value = vgetq_lane_f32(_mm_max_ps(a, b), 0);
+@@ -2155,22 +1956,18 @@ FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b)
+ 
+ // Compare packed signed 16-bit integers in a and b, and store packed minimum
+ // values in dst.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*16
+-//      dst[i+15:i] := MIN(a[i+15:i], b[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pi16
+ FORCE_INLINE __m64 _mm_min_pi16(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s16(
+         vmin_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b)));
+ }
+ 
+-// Computes the minima of the four single-precision, floating-point values of a
+-// and b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/wh13kadz(v=vs.100).aspx
++// Compare packed single-precision (32-bit) floating-point elements in a and b,
++// and store packed minimum values in dst. dst does not follow the IEEE Standard
++// for Floating-Point Arithmetic (IEEE 754) minimum value when inputs are NaN or
++// signed-zero values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_ps
+ FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b)
+ {
+ #if SSE2NEON_PRECISE_MINMAX
+@@ -2185,22 +1982,19 @@ FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b)
+ 
+ // Compare packed unsigned 8-bit integers in a and b, and store packed minimum
+ // values in dst.
+-//
+-//   FOR j := 0 to 7
+-//      i := j*8
+-//      dst[i+7:i] := MIN(a[i+7:i], b[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pu8
+ FORCE_INLINE __m64 _mm_min_pu8(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u8(
+         vmin_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b)));
+ }
+ 
+-// Computes the minimum of the two lower scalar single-precision floating point
+-// values of a and b.
+-// https://msdn.microsoft.com/en-us/library/0a9y7xaa(v=vs.100).aspx
++// Compare the lower single-precision (32-bit) floating-point elements in a and
++// b, store the minimum value in the lower element of dst, and copy the upper 3
++// packed elements from a to the upper element of dst. dst does not follow the
++// IEEE Standard for Floating-Point Arithmetic (IEEE 754) minimum value when
++// inputs are NaN or signed-zero values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_ss
+ FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b)
+ {
+     float32_t value = vgetq_lane_f32(_mm_min_ps(a, b), 0);
+@@ -2208,8 +2002,10 @@ FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b)
+         vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0));
+ }
+ 
+-// Sets the low word to the single-precision, floating-point value of b
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/35hdzazd(v=vs.100)
++// Move the lower single-precision (32-bit) floating-point element from b to the
++// lower element of dst, and copy the upper 3 packed elements from a to the
++// upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_ss
+ FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -2217,25 +2013,26 @@ FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b)
+                        vreinterpretq_f32_m128(a), 0));
+ }
+ 
+-// Moves the upper two values of B into the lower two values of A.
+-//
+-//   r3 := a3
+-//   r2 := a2
+-//   r1 := b3
+-//   r0 := b2
+-FORCE_INLINE __m128 _mm_movehl_ps(__m128 __A, __m128 __B)
+-{
+-    float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(__A));
+-    float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(__B));
++// Move the upper 2 single-precision (32-bit) floating-point elements from b to
++// the lower 2 elements of dst, and copy the upper 2 elements from a to the
++// upper 2 elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movehl_ps
++FORCE_INLINE __m128 _mm_movehl_ps(__m128 a, __m128 b)
++{
++#if defined(aarch64__)
++    return vreinterpretq_m128_u64(
++        vzip2q_u64(vreinterpretq_u64_m128(b), vreinterpretq_u64_m128(a)));
++#else
++    float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a));
++    float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b));
+     return vreinterpretq_m128_f32(vcombine_f32(b32, a32));
++#endif
+ }
+ 
+-// Moves the lower two values of B into the upper two values of A.
+-//
+-//   r3 := b1
+-//   r2 := b0
+-//   r1 := a1
+-//   r0 := a0
++// Move the lower 2 single-precision (32-bit) floating-point elements from b to
++// the upper 2 elements of dst, and copy the lower 2 elements from a to the
++// lower 2 elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movelh_ps
+ FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B)
+ {
+     float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(__A));
+@@ -2245,7 +2042,7 @@ FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B)
+ 
+ // Create mask from the most significant bit of each 8-bit element in a, and
+ // store the result in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_pi8
+ FORCE_INLINE int _mm_movemask_pi8(__m64 a)
+ {
+     uint8x8_t input = vreinterpret_u8_m64(a);
+@@ -2264,10 +2061,9 @@ FORCE_INLINE int _mm_movemask_pi8(__m64 a)
+ #endif
+ }
+ 
+-// NEON does not provide this method
+-// Creates a 4-bit mask from the most significant bits of the four
+-// single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/vstudio/4490ys29(v=vs.100).aspx
++// Set each bit of mask dst based on the most significant bit of the
++// corresponding packed single-precision (32-bit) floating-point element in a.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_ps
+ FORCE_INLINE int _mm_movemask_ps(__m128 a)
+ {
+     uint32x4_t input = vreinterpretq_u32_m128(a);
+@@ -2288,14 +2084,9 @@ FORCE_INLINE int _mm_movemask_ps(__m128 a)
+ #endif
+ }
+ 
+-// Multiplies the four single-precision, floating-point values of a and b.
+-//
+-//   r0 := a0 * b0
+-//   r1 := a1 * b1
+-//   r2 := a2 * b2
+-//   r3 := a3 * b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/22kbk6t9(v=vs.100).aspx
++// Multiply packed single-precision (32-bit) floating-point elements in a and b,
++// and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_ps
+ FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -2305,11 +2096,7 @@ FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b)
+ // Multiply the lower single-precision (32-bit) floating-point element in a and
+ // b, store the result in the lower element of dst, and copy the upper 3 packed
+ // elements from a to the upper elements of dst.
+-//
+-//   dst[31:0] := a[31:0] * b[31:0]
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_ss
+ FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_mul_ps(a, b));
+@@ -2318,16 +2105,16 @@ FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b)
+ // Multiply the packed unsigned 16-bit integers in a and b, producing
+ // intermediate 32-bit integers, and store the high 16 bits of the intermediate
+ // integers in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_pu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_pu16
+ FORCE_INLINE __m64 _mm_mulhi_pu16(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u16(vshrn_n_u32(
+         vmull_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b)), 16));
+ }
+ 
+-// Computes the bitwise OR of the four single-precision, floating-point values
+-// of a and b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/7ctdsyy0(v=vs.100).aspx
++// Compute the bitwise OR of packed single-precision (32-bit) floating-point
++// elements in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_or_ps
+ FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_s32(
+@@ -2336,65 +2123,53 @@ FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b)
+ 
+ // Average packed unsigned 8-bit integers in a and b, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*8
+-//     dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgb
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pavgb
+ #define _m_pavgb(a, b) _mm_avg_pu8(a, b)
+ 
+ // Average packed unsigned 16-bit integers in a and b, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*16
+-//     dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pavgw
+ #define _m_pavgw(a, b) _mm_avg_pu16(a, b)
+ 
+ // Extract a 16-bit integer from a, selected with imm8, and store the result in
+ // the lower element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pextrw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pextrw
+ #define _m_pextrw(a, imm) _mm_extract_pi16(a, imm)
+ 
+ // Copy a to dst, and insert the 16-bit integer i into dst at the location
+ // specified by imm8.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_pinsrw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=m_pinsrw
+ #define _m_pinsrw(a, i, imm) _mm_insert_pi16(a, i, imm)
+ 
+ // Compare packed signed 16-bit integers in a and b, and store packed maximum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxsw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmaxsw
+ #define _m_pmaxsw(a, b) _mm_max_pi16(a, b)
+ 
+ // Compare packed unsigned 8-bit integers in a and b, and store packed maximum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxub
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmaxub
+ #define _m_pmaxub(a, b) _mm_max_pu8(a, b)
+ 
+ // Compare packed signed 16-bit integers in a and b, and store packed minimum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminsw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pminsw
+ #define _m_pminsw(a, b) _mm_min_pi16(a, b)
+ 
+ // Compare packed unsigned 8-bit integers in a and b, and store packed minimum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminub
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pminub
+ #define _m_pminub(a, b) _mm_min_pu8(a, b)
+ 
+ // Create mask from the most significant bit of each 8-bit element in a, and
+ // store the result in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmovmskb
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmovmskb
+ #define _m_pmovmskb(a) _mm_movemask_pi8(a)
+ 
+ // Multiply the packed unsigned 16-bit integers in a and b, producing
+ // intermediate 32-bit integers, and store the high 16 bits of the intermediate
+ // integers in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmulhuw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmulhuw
+ #define _m_pmulhuw(a, b) _mm_mulhi_pu16(a, b)
+ 
+ // Fetch the line of data from memory that contains address p to a location in
+@@ -2422,26 +2197,22 @@ FORCE_INLINE void _mm_prefetch(char const *p, int i)
+ // b, then horizontally sum each consecutive 8 differences to produce four
+ // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low
+ // 16 bits of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_psadbw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=m_psadbw
+ #define _m_psadbw(a, b) _mm_sad_pu8(a, b)
+ 
+ // Shuffle 16-bit integers in a using the control in imm8, and store the results
+ // in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pshufw
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pshufw
+ #define _m_pshufw(a, imm) _mm_shuffle_pi16(a, imm)
+ 
+ // Compute the approximate reciprocal of packed single-precision (32-bit)
+ // floating-point elements in a, and store the results in dst. The maximum
+ // relative error for this approximation is less than 1.5*2^-12.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rcp_ps
+ FORCE_INLINE __m128 _mm_rcp_ps(__m128 in)
+ {
+     float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(in));
+     recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in)));
+-#if SSE2NEON_PRECISE_DIV
+-    // Additional Netwon-Raphson iteration for accuracy
+-    recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in)));
+-#endif
+     return vreinterpretq_m128_f32(recip);
+ }
+ 
+@@ -2449,30 +2220,21 @@ FORCE_INLINE __m128 _mm_rcp_ps(__m128 in)
+ // floating-point element in a, store the result in the lower element of dst,
+ // and copy the upper 3 packed elements from a to the upper elements of dst. The
+ // maximum relative error for this approximation is less than 1.5*2^-12.
+-//
+-//   dst[31:0] := (1.0 / a[31:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rcp_ss
+ FORCE_INLINE __m128 _mm_rcp_ss(__m128 a)
+ {
+     return _mm_move_ss(a, _mm_rcp_ps(a));
+ }
+ 
+-// Computes the approximations of the reciprocal square roots of the four
+-// single-precision floating point values of in.
+-// The current precision is 1% error.
+-// https://msdn.microsoft.com/en-us/library/22hfsh53(v=vs.100).aspx
++// Compute the approximate reciprocal square root of packed single-precision
++// (32-bit) floating-point elements in a, and store the results in dst. The
++// maximum relative error for this approximation is less than 1.5*2^-12.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rsqrt_ps
+ FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in)
+ {
+     float32x4_t out = vrsqrteq_f32(vreinterpretq_f32_m128(in));
+-#if SSE2NEON_PRECISE_SQRT
+-    // Additional Netwon-Raphson iteration for accuracy
+     out = vmulq_f32(
+         out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out));
+-    out = vmulq_f32(
+-        out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out));
+-#endif
+     return vreinterpretq_m128_f32(out);
+ }
+ 
+@@ -2480,7 +2242,7 @@ FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in)
+ // (32-bit) floating-point element in a, store the result in the lower element
+ // of dst, and copy the upper 3 packed elements from a to the upper elements of
+ // dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rsqrt_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rsqrt_ss
+ FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in)
+ {
+     return vsetq_lane_f32(vgetq_lane_f32(_mm_rsqrt_ps(in), 0), in, 0);
+@@ -2490,7 +2252,7 @@ FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in)
+ // b, then horizontally sum each consecutive 8 differences to produce four
+ // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low
+ // 16 bits of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_pu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_pu8
+ FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b)
+ {
+     uint64x1_t t = vpaddl_u32(vpaddl_u16(
+@@ -2502,7 +2264,7 @@ FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b)
+ // Macro: Set the flush zero bits of the MXCSR control and status register to
+ // the value in unsigned 32-bit integer a. The flush zero may contain any of the
+ // following flags: _MM_FLUSH_ZERO_ON or _MM_FLUSH_ZERO_OFF
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_FLUSH_ZERO_MODE
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_SET_FLUSH_ZERO_MODE
+ FORCE_INLINE void _sse2neon_mm_set_flush_zero_mode(unsigned int flag)
+ {
+     // AArch32 Advanced SIMD arithmetic always uses the Flush-to-zero setting,
+@@ -2531,16 +2293,18 @@ FORCE_INLINE void _sse2neon_mm_set_flush_zero_mode(unsigned int flag)
+ #endif
+ }
+ 
+-// Sets the four single-precision, floating-point values to the four inputs.
+-// https://msdn.microsoft.com/en-us/library/vstudio/afh0zf75(v=vs.100).aspx
++// Set packed single-precision (32-bit) floating-point elements in dst with the
++// supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ps
+ FORCE_INLINE __m128 _mm_set_ps(float w, float z, float y, float x)
+ {
+     float ALIGN_STRUCT(16) data[4] = {x, y, z, w};
+     return vreinterpretq_m128_f32(vld1q_f32(data));
+ }
+ 
+-// Sets the four single-precision, floating-point values to w.
+-// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx
++// Broadcast single-precision (32-bit) floating-point value a to all elements of
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ps1
+ FORCE_INLINE __m128 _mm_set_ps1(float _w)
+ {
+     return vreinterpretq_m128_f32(vdupq_n_f32(_w));
+@@ -2550,7 +2314,7 @@ FORCE_INLINE __m128 _mm_set_ps1(float _w)
+ // the value in unsigned 32-bit integer a. The rounding mode may contain any of
+ // the following flags: _MM_ROUND_NEAREST, _MM_ROUND_DOWN, _MM_ROUND_UP,
+ // _MM_ROUND_TOWARD_ZERO
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_ROUNDING_MODE
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_SET_ROUNDING_MODE
+ FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding)
+ {
+     union {
+@@ -2595,45 +2359,48 @@ FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding)
+ 
+ // Copy single-precision (32-bit) floating-point element a to the lower element
+ // of dst, and zero the upper 3 elements.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ss
+ FORCE_INLINE __m128 _mm_set_ss(float a)
+ {
+     return vreinterpretq_m128_f32(vsetq_lane_f32(a, vdupq_n_f32(0), 0));
+ }
+ 
+-// Sets the four single-precision, floating-point values to w.
+-//
+-//   r0 := r1 := r2 := r3 := w
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx
++// Broadcast single-precision (32-bit) floating-point value a to all elements of
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_ps
+ FORCE_INLINE __m128 _mm_set1_ps(float _w)
+ {
+     return vreinterpretq_m128_f32(vdupq_n_f32(_w));
+ }
+ 
++// Set the MXCSR control and status register with the value in unsigned 32-bit
++// integer a.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setcsr
+ // FIXME: _mm_setcsr() implementation supports changing the rounding mode only.
+ FORCE_INLINE void _mm_setcsr(unsigned int a)
+ {
+     _MM_SET_ROUNDING_MODE(a);
+ }
+ 
++// Get the unsigned 32-bit value of the MXCSR control and status register.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_getcsr
+ // FIXME: _mm_getcsr() implementation supports reading the rounding mode only.
+ FORCE_INLINE unsigned int _mm_getcsr()
+ {
+     return _MM_GET_ROUNDING_MODE();
+ }
+ 
+-// Sets the four single-precision, floating-point values to the four inputs in
+-// reverse order.
+-// https://msdn.microsoft.com/en-us/library/vstudio/d2172ct3(v=vs.100).aspx
++// Set packed single-precision (32-bit) floating-point elements in dst with the
++// supplied values in reverse order.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_ps
+ FORCE_INLINE __m128 _mm_setr_ps(float w, float z, float y, float x)
+ {
+     float ALIGN_STRUCT(16) data[4] = {w, z, y, x};
+     return vreinterpretq_m128_f32(vld1q_f32(data));
+ }
+ 
+-// Clears the four single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/vstudio/tk1t2tbz(v=vs.100).aspx
++// Return vector of type __m128 with all elements set to zero.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_ps
+ FORCE_INLINE __m128 _mm_setzero_ps(void)
+ {
+     return vreinterpretq_m128_f32(vdupq_n_f32(0));
+@@ -2641,7 +2408,7 @@ FORCE_INLINE __m128 _mm_setzero_ps(void)
+ 
+ // Shuffle 16-bit integers in a using the control in imm8, and store the results
+ // in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pi16
+ #ifdef _sse2neon_shuffle
+ #define _mm_shuffle_pi16(a, imm)                                           \
+     __extension__({                                                        \
+@@ -2775,19 +2542,17 @@ FORCE_INLINE void _mm_lfence(void)
+     })
+ #endif
+ 
+-// Computes the approximations of square roots of the four single-precision,
+-// floating-point values of a. First computes reciprocal square roots and then
+-// reciprocals of the four values.
+-//
+-//   r0 := sqrt(a0)
+-//   r1 := sqrt(a1)
+-//   r2 := sqrt(a2)
+-//   r3 := sqrt(a3)
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/8z67bwwk(v=vs.100).aspx
++// Compute the square root of packed single-precision (32-bit) floating-point
++// elements in a, and store the results in dst.
++// Due to ARMv7-A NEON's lack of a precise square root intrinsic, we implement
++// square root by multiplying input in with its reciprocal square root before
++// using the Newton-Raphson method to approximate the results.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_ps
+ FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in)
+ {
+-#if SSE2NEON_PRECISE_SQRT
++#if defined(__aarch64__)
++    return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in)));
++#else
+     float32x4_t recip = vrsqrteq_f32(vreinterpretq_f32_m128(in));
+ 
+     // Test for vrsqrteq_f32(0) -> positive infinity case.
+@@ -2798,28 +2563,23 @@ FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in)
+     recip = vreinterpretq_f32_u32(
+         vandq_u32(vmvnq_u32(div_by_zero), vreinterpretq_u32_f32(recip)));
+ 
+-    // Additional Netwon-Raphson iteration for accuracy
+     recip = vmulq_f32(
+         vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)),
+         recip);
++    // Additional Netwon-Raphson iteration for accuracy
+     recip = vmulq_f32(
+         vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)),
+         recip);
+ 
+     // sqrt(s) = s * 1/sqrt(s)
+     return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(in), recip));
+-#elif defined(__aarch64__)
+-    return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in)));
+-#else
+-    float32x4_t recipsq = vrsqrteq_f32(vreinterpretq_f32_m128(in));
+-    float32x4_t sq = vrecpeq_f32(recipsq);
+-    return vreinterpretq_m128_f32(sq);
+ #endif
+ }
+ 
+-// Computes the approximation of the square root of the scalar single-precision
+-// floating point value of in.
+-// https://msdn.microsoft.com/en-us/library/ahfsc22d(v=vs.100).aspx
++// Compute the square root of the lower single-precision (32-bit) floating-point
++// element in a, store the result in the lower element of dst, and copy the
++// upper 3 packed elements from a to the upper elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_ss
+ FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in)
+ {
+     float32_t value =
+@@ -2828,8 +2588,10 @@ FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in)
+         vsetq_lane_f32(value, vreinterpretq_f32_m128(in), 0));
+ }
+ 
+-// Stores four single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/vstudio/s3h4ay6y(v=vs.100).aspx
++// Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point
++// elements) from a into memory. mem_addr must be aligned on a 16-byte boundary
++// or a general-protection exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ps
+ FORCE_INLINE void _mm_store_ps(float *p, __m128 a)
+ {
+     vst1q_f32(p, vreinterpretq_f32_m128(a));
+@@ -2838,21 +2600,16 @@ FORCE_INLINE void _mm_store_ps(float *p, __m128 a)
+ // Store the lower single-precision (32-bit) floating-point element from a into
+ // 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte
+ // boundary or a general-protection exception may be generated.
+-//
+-//   MEM[mem_addr+31:mem_addr] := a[31:0]
+-//   MEM[mem_addr+63:mem_addr+32] := a[31:0]
+-//   MEM[mem_addr+95:mem_addr+64] := a[31:0]
+-//   MEM[mem_addr+127:mem_addr+96] := a[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_ps1
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ps1
+ FORCE_INLINE void _mm_store_ps1(float *p, __m128 a)
+ {
+     float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0);
+     vst1q_f32(p, vdupq_n_f32(a0));
+ }
+ 
+-// Stores the lower single - precision, floating - point value.
+-// https://msdn.microsoft.com/en-us/library/tzz10fbx(v=vs.100).aspx
++// Store the lower single-precision (32-bit) floating-point element from a into
++// memory. mem_addr does not need to be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ss
+ FORCE_INLINE void _mm_store_ss(float *p, __m128 a)
+ {
+     vst1q_lane_f32(p, vreinterpretq_f32_m128(a), 0);
+@@ -2861,34 +2618,20 @@ FORCE_INLINE void _mm_store_ss(float *p, __m128 a)
+ // Store the lower single-precision (32-bit) floating-point element from a into
+ // 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte
+ // boundary or a general-protection exception may be generated.
+-//
+-//   MEM[mem_addr+31:mem_addr] := a[31:0]
+-//   MEM[mem_addr+63:mem_addr+32] := a[31:0]
+-//   MEM[mem_addr+95:mem_addr+64] := a[31:0]
+-//   MEM[mem_addr+127:mem_addr+96] := a[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store1_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store1_ps
+ #define _mm_store1_ps _mm_store_ps1
+ 
+-// Stores the upper two single-precision, floating-point values of a to the
+-// address p.
+-//
+-//   *p0 := a2
+-//   *p1 := a3
+-//
+-// https://msdn.microsoft.com/en-us/library/a7525fs8(v%3dvs.90).aspx
++// Store the upper 2 single-precision (32-bit) floating-point elements from a
++// into memory.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeh_pi
+ FORCE_INLINE void _mm_storeh_pi(__m64 *p, __m128 a)
+ {
+     *p = vreinterpret_m64_f32(vget_high_f32(a));
+ }
+ 
+-// Stores the lower two single-precision floating point values of a to the
+-// address p.
+-//
+-//   *p0 := a0
+-//   *p1 := a1
+-//
+-// https://msdn.microsoft.com/en-us/library/h54t98ks(v=vs.90).aspx
++// Store the lower 2 single-precision (32-bit) floating-point elements from a
++// into memory.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_pi
+ FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a)
+ {
+     *p = vreinterpret_m64_f32(vget_low_f32(a));
+@@ -2897,13 +2640,7 @@ FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a)
+ // Store 4 single-precision (32-bit) floating-point elements from a into memory
+ // in reverse order. mem_addr must be aligned on a 16-byte boundary or a
+ // general-protection exception may be generated.
+-//
+-//   MEM[mem_addr+31:mem_addr] := a[127:96]
+-//   MEM[mem_addr+63:mem_addr+32] := a[95:64]
+-//   MEM[mem_addr+95:mem_addr+64] := a[63:32]
+-//   MEM[mem_addr+127:mem_addr+96] := a[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storer_ps
+ FORCE_INLINE void _mm_storer_ps(float *p, __m128 a)
+ {
+     float32x4_t tmp = vrev64q_f32(vreinterpretq_f32_m128(a));
+@@ -2911,22 +2648,24 @@ FORCE_INLINE void _mm_storer_ps(float *p, __m128 a)
+     vst1q_f32(p, rev);
+ }
+ 
+-// Stores four single-precision, floating-point values.
+-// https://msdn.microsoft.com/en-us/library/44e30x22(v=vs.100).aspx
++// Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point
++// elements) from a into memory. mem_addr does not need to be aligned on any
++// particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_ps
+ FORCE_INLINE void _mm_storeu_ps(float *p, __m128 a)
+ {
+     vst1q_f32(p, vreinterpretq_f32_m128(a));
+ }
+ 
+ // Stores 16-bits of integer data a at the address p.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si16
+ FORCE_INLINE void _mm_storeu_si16(void *p, __m128i a)
+ {
+     vst1q_lane_s16((int16_t *) p, vreinterpretq_s16_m128i(a), 0);
+ }
+ 
+ // Stores 64-bits of integer data a at the address p.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si64
+ FORCE_INLINE void _mm_storeu_si64(void *p, __m128i a)
+ {
+     vst1q_lane_s64((int64_t *) p, vreinterpretq_s64_m128i(a), 0);
+@@ -2934,7 +2673,7 @@ FORCE_INLINE void _mm_storeu_si64(void *p, __m128i a)
+ 
+ // Store 64-bits of integer data from a into memory using a non-temporal memory
+ // hint.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pi
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_pi
+ FORCE_INLINE void _mm_stream_pi(__m64 *p, __m64 a)
+ {
+     vst1_s64((int64_t *) p, vreinterpret_s64_m64(a));
+@@ -2942,7 +2681,7 @@ FORCE_INLINE void _mm_stream_pi(__m64 *p, __m64 a)
+ 
+ // Store 128-bits (composed of 4 packed single-precision (32-bit) floating-
+ // point elements) from a into memory using a non-temporal memory hint.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_ps
+ FORCE_INLINE void _mm_stream_ps(float *p, __m128 a)
+ {
+ #if __has_builtin(__builtin_nontemporal_store)
+@@ -2952,14 +2691,10 @@ FORCE_INLINE void _mm_stream_ps(float *p, __m128 a)
+ #endif
+ }
+ 
+-// Subtracts the four single-precision, floating-point values of a and b.
+-//
+-//   r0 := a0 - b0
+-//   r1 := a1 - b1
+-//   r2 := a2 - b2
+-//   r3 := a3 - b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/1zad2k61(v=vs.100).aspx
++// Subtract packed single-precision (32-bit) floating-point elements in b from
++// packed single-precision (32-bit) floating-point elements in a, and store the
++// results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_ps
+ FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_f32(
+@@ -2970,11 +2705,7 @@ FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b)
+ // the lower single-precision (32-bit) floating-point element in a, store the
+ // result in the lower element of dst, and copy the upper 3 packed elements from
+ // a to the upper elements of dst.
+-//
+-//   dst[31:0] := a[31:0] - b[31:0]
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_ss
+ FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_sub_ps(a, b));
+@@ -2983,7 +2714,7 @@ FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b)
+ // Macro: Transpose the 4x4 matrix formed by the 4 rows of single-precision
+ // (32-bit) floating-point elements in row0, row1, row2, and row3, and store the
+ // transposed matrix in these vectors (row0 now contains column 0, etc.).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=MM_TRANSPOSE4_PS
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=MM_TRANSPOSE4_PS
+ #define _MM_TRANSPOSE4_PS(row0, row1, row2, row3)         \
+     do {                                                  \
+         float32x4x2_t ROW01 = vtrnq_f32(row0, row1);      \
+@@ -3008,7 +2739,7 @@ FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b)
+ #define _mm_ucomineq_ss _mm_comineq_ss
+ 
+ // Return vector of type __m128i with undefined elements.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_undefined_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_undefined_si128
+ FORCE_INLINE __m128i _mm_undefined_si128(void)
+ {
+ #if defined(__GNUC__) || defined(__clang__)
+@@ -3023,7 +2754,7 @@ FORCE_INLINE __m128i _mm_undefined_si128(void)
+ }
+ 
+ // Return vector of type __m128 with undefined elements.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_undefined_ps
+ FORCE_INLINE __m128 _mm_undefined_ps(void)
+ {
+ #if defined(__GNUC__) || defined(__clang__)
+@@ -3037,15 +2768,9 @@ FORCE_INLINE __m128 _mm_undefined_ps(void)
+ #endif
+ }
+ 
+-// Selects and interleaves the upper two single-precision, floating-point values
+-// from a and b.
+-//
+-//   r0 := a2
+-//   r1 := b2
+-//   r2 := a3
+-//   r3 := b3
+-//
+-// https://msdn.microsoft.com/en-us/library/skccxx7d%28v=vs.90%29.aspx
++// Unpack and interleave single-precision (32-bit) floating-point elements from
++// the high half a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_ps
+ FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b)
+ {
+ #if defined(__aarch64__)
+@@ -3059,15 +2784,9 @@ FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b)
+ #endif
+ }
+ 
+-// Selects and interleaves the lower two single-precision, floating-point values
+-// from a and b.
+-//
+-//   r0 := a0
+-//   r1 := b0
+-//   r2 := a1
+-//   r3 := b1
+-//
+-// https://msdn.microsoft.com/en-us/library/25st103b%28v=vs.90%29.aspx
++// Unpack and interleave single-precision (32-bit) floating-point elements from
++// the low half of a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_ps
+ FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b)
+ {
+ #if defined(__aarch64__)
+@@ -3081,9 +2800,9 @@ FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b)
+ #endif
+ }
+ 
+-// Computes bitwise EXOR (exclusive-or) of the four single-precision,
+-// floating-point values of a and b.
+-// https://msdn.microsoft.com/en-us/library/ss6k3wk8(v=vs.100).aspx
++// Compute the bitwise XOR of packed single-precision (32-bit) floating-point
++// elements in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_ps
+ FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b)
+ {
+     return vreinterpretq_m128_s32(
+@@ -3092,42 +2811,32 @@ FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b)
+ 
+ /* SSE2 */
+ 
+-// Adds the 8 signed or unsigned 16-bit integers in a to the 8 signed or
+-// unsigned 16-bit integers in b.
+-// https://msdn.microsoft.com/en-us/library/fceha5k4(v=vs.100).aspx
++// Add packed 16-bit integers in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi16
+ FORCE_INLINE __m128i _mm_add_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+         vaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Adds the 4 signed or unsigned 32-bit integers in a to the 4 signed or
+-// unsigned 32-bit integers in b.
+-//
+-//   r0 := a0 + b0
+-//   r1 := a1 + b1
+-//   r2 := a2 + b2
+-//   r3 := a3 + b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx
++// Add packed 32-bit integers in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi32
+ FORCE_INLINE __m128i _mm_add_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+         vaddq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Adds the 4 signed or unsigned 64-bit integers in a to the 4 signed or
+-// unsigned 32-bit integers in b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx
++// Add packed 64-bit integers in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi64
+ FORCE_INLINE __m128i _mm_add_epi64(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s64(
+         vaddq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b)));
+ }
+ 
+-// Adds the 16 signed or unsigned 8-bit integers in a to the 16 signed or
+-// unsigned 8-bit integers in b.
+-// https://technet.microsoft.com/en-us/subscriptions/yc7tcyzs(v=vs.90)
++// Add packed 8-bit integers in a and b, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi8
+ FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -3136,7 +2845,7 @@ FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b)
+ 
+ // Add packed double-precision (64-bit) floating-point elements in a and b, and
+ // store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_pd
+ FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3155,11 +2864,7 @@ FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b)
+ // Add the lower double-precision (64-bit) floating-point element in a and b,
+ // store the result in the lower element of dst, and copy the upper element from
+ // a to the upper element of dst.
+-//
+-//   dst[63:0] := a[63:0] + b[63:0]
+-//   dst[127:64] := a[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_sd
+ FORCE_INLINE __m128d _mm_add_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3175,25 +2880,16 @@ FORCE_INLINE __m128d _mm_add_sd(__m128d a, __m128d b)
+ }
+ 
+ // Add 64-bit integers a and b, and store the result in dst.
+-//
+-//   dst[63:0] := a[63:0] + b[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_si64
+ FORCE_INLINE __m64 _mm_add_si64(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s64(
+         vadd_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b)));
+ }
+ 
+-// Adds the 8 signed 16-bit integers in a to the 8 signed 16-bit integers in b
+-// and saturates.
+-//
+-//   r0 := SignedSaturate(a0 + b0)
+-//   r1 := SignedSaturate(a1 + b1)
+-//   ...
+-//   r7 := SignedSaturate(a7 + b7)
+-//
+-// https://msdn.microsoft.com/en-us/library/1a306ef8(v=vs.100).aspx
++// Add packed signed 16-bit integers in a and b using saturation, and store the
++// results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epi16
+ FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+@@ -3202,13 +2898,7 @@ FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b)
+ 
+ // Add packed signed 8-bit integers in a and b using saturation, and store the
+ // results in dst.
+-//
+-//   FOR j := 0 to 15
+-//     i := j*8
+-//     dst[i+7:i] := Saturate8( a[i+7:i] + b[i+7:i] )
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epi8
+ FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -3217,16 +2907,16 @@ FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b)
+ 
+ // Add packed unsigned 16-bit integers in a and b using saturation, and store
+ // the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epu16
+ FORCE_INLINE __m128i _mm_adds_epu16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+         vqaddq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b)));
+ }
+ 
+-// Adds the 16 unsigned 8-bit integers in a to the 16 unsigned 8-bit integers in
+-// b and saturates..
+-// https://msdn.microsoft.com/en-us/library/9hahyddy(v=vs.100).aspx
++// Add packed unsigned 8-bit integers in a and b using saturation, and store the
++// results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epu8
+ FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -3235,25 +2925,16 @@ FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b)
+ 
+ // Compute the bitwise AND of packed double-precision (64-bit) floating-point
+ // elements in a and b, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     dst[i+63:i] := a[i+63:i] AND b[i+63:i]
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_and_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_pd
+ FORCE_INLINE __m128d _mm_and_pd(__m128d a, __m128d b)
+ {
+     return vreinterpretq_m128d_s64(
+         vandq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b)));
+ }
+ 
+-// Computes the bitwise AND of the 128-bit value in a and the 128-bit value in
+-// b.
+-//
+-//   r := a & b
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/6d1txsa8(v=vs.100).aspx
++// Compute the bitwise AND of 128 bits (representing integer data) in a and b,
++// and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_si128
+ FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+@@ -3262,13 +2943,7 @@ FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b)
+ 
+ // Compute the bitwise NOT of packed double-precision (64-bit) floating-point
+ // elements in a and then AND with b, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-// 	     i := j*64
+-// 	     dst[i+63:i] := ((NOT a[i+63:i]) AND b[i+63:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_andnot_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_pd
+ FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b)
+ {
+     // *NOTE* argument swap
+@@ -3276,12 +2951,9 @@ FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b)
+         vbicq_s64(vreinterpretq_s64_m128d(b), vreinterpretq_s64_m128d(a)));
+ }
+ 
+-// Computes the bitwise AND of the 128-bit value in b and the bitwise NOT of the
+-// 128-bit value in a.
+-//
+-//   r := (~a) & b
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/1beaceh8(v=vs.100).aspx
++// Compute the bitwise NOT of 128 bits (representing integer data) in a and then
++// AND with b, and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_si128
+ FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+@@ -3289,30 +2961,18 @@ FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b)
+                   vreinterpretq_s32_m128i(a)));  // *NOTE* argument swap
+ }
+ 
+-// Computes the average of the 8 unsigned 16-bit integers in a and the 8
+-// unsigned 16-bit integers in b and rounds.
+-//
+-//   r0 := (a0 + b0) / 2
+-//   r1 := (a1 + b1) / 2
+-//   ...
+-//   r7 := (a7 + b7) / 2
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/y13ca3c8(v=vs.90).aspx
++// Average packed unsigned 16-bit integers in a and b, and store the results in
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_epu16
+ FORCE_INLINE __m128i _mm_avg_epu16(__m128i a, __m128i b)
+ {
+     return (__m128i) vrhaddq_u16(vreinterpretq_u16_m128i(a),
+                                  vreinterpretq_u16_m128i(b));
+ }
+ 
+-// Computes the average of the 16 unsigned 8-bit integers in a and the 16
+-// unsigned 8-bit integers in b and rounds.
+-//
+-//   r0 := (a0 + b0) / 2
+-//   r1 := (a1 + b1) / 2
+-//   ...
+-//   r15 := (a15 + b15) / 2
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/8zwh554a(v%3dvs.90).aspx
++// Average packed unsigned 8-bit integers in a and b, and store the results in
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_epu8
+ FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -3321,17 +2981,17 @@ FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b)
+ 
+ // Shift a left by imm8 bytes while shifting in zeros, and store the results in
+ // dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bslli_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_bslli_si128
+ #define _mm_bslli_si128(a, imm) _mm_slli_si128(a, imm)
+ 
+ // Shift a right by imm8 bytes while shifting in zeros, and store the results in
+ // dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bsrli_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_bsrli_si128
+ #define _mm_bsrli_si128(a, imm) _mm_srli_si128(a, imm)
+ 
+ // Cast vector of type __m128d to type __m128. This intrinsic is only used for
+ // compilation and does not generate any instructions, thus it has zero latency.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castpd_ps
+ FORCE_INLINE __m128 _mm_castpd_ps(__m128d a)
+ {
+     return vreinterpretq_m128_s64(vreinterpretq_s64_m128d(a));
+@@ -3339,7 +2999,7 @@ FORCE_INLINE __m128 _mm_castpd_ps(__m128d a)
+ 
+ // Cast vector of type __m128d to type __m128i. This intrinsic is only used for
+ // compilation and does not generate any instructions, thus it has zero latency.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castpd_si128
+ FORCE_INLINE __m128i _mm_castpd_si128(__m128d a)
+ {
+     return vreinterpretq_m128i_s64(vreinterpretq_s64_m128d(a));
+@@ -3347,15 +3007,15 @@ FORCE_INLINE __m128i _mm_castpd_si128(__m128d a)
+ 
+ // Cast vector of type __m128 to type __m128d. This intrinsic is only used for
+ // compilation and does not generate any instructions, thus it has zero latency.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castps_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castps_pd
+ FORCE_INLINE __m128d _mm_castps_pd(__m128 a)
+ {
+     return vreinterpretq_m128d_s32(vreinterpretq_s32_m128(a));
+ }
+ 
+-// Applies a type cast to reinterpret four 32-bit floating point values passed
+-// in as a 128-bit parameter as packed 32-bit integers.
+-// https://msdn.microsoft.com/en-us/library/bb514099.aspx
++// Cast vector of type __m128 to type __m128i. This intrinsic is only used for
++// compilation and does not generate any instructions, thus it has zero latency.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castps_si128
+ FORCE_INLINE __m128i _mm_castps_si128(__m128 a)
+ {
+     return vreinterpretq_m128i_s32(vreinterpretq_s32_m128(a));
+@@ -3363,7 +3023,7 @@ FORCE_INLINE __m128i _mm_castps_si128(__m128 a)
+ 
+ // Cast vector of type __m128i to type __m128d. This intrinsic is only used for
+ // compilation and does not generate any instructions, thus it has zero latency.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castsi128_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_pd
+ FORCE_INLINE __m128d _mm_castsi128_pd(__m128i a)
+ {
+ #if defined(__aarch64__)
+@@ -3373,9 +3033,9 @@ FORCE_INLINE __m128d _mm_castsi128_pd(__m128i a)
+ #endif
+ }
+ 
+-// Applies a type cast to reinterpret four 32-bit integers passed in as a
+-// 128-bit parameter as packed 32-bit floating point values.
+-// https://msdn.microsoft.com/en-us/library/bb514029.aspx
++// Cast vector of type __m128i to type __m128. This intrinsic is only used for
++// compilation and does not generate any instructions, thus it has zero latency.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_ps
+ FORCE_INLINE __m128 _mm_castsi128_ps(__m128i a)
+ {
+     return vreinterpretq_m128_s32(vreinterpretq_s32_m128i(a));
+@@ -3406,9 +3066,9 @@ FORCE_INLINE void _mm_clflush(void const *p)
+ #endif
+ }
+ 
+-// Compares the 8 signed or unsigned 16-bit integers in a and the 8 signed or
+-// unsigned 16-bit integers in b for equality.
+-// https://msdn.microsoft.com/en-us/library/2ay060te(v=vs.100).aspx
++// Compare packed 16-bit integers in a and b for equality, and store the results
++// in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi16
+ FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+@@ -3416,16 +3076,17 @@ FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b)
+ }
+ 
+ // Compare packed 32-bit integers in a and b for equality, and store the results
+-// in dst
++// in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi32
+ FORCE_INLINE __m128i _mm_cmpeq_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u32(
+         vceqq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Compares the 16 signed or unsigned 8-bit integers in a and the 16 signed or
+-// unsigned 8-bit integers in b for equality.
+-// https://msdn.microsoft.com/en-us/library/windows/desktop/bz5xk21a(v=vs.90).aspx
++// Compare packed 8-bit integers in a and b for equality, and store the results
++// in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi8
+ FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -3434,7 +3095,7 @@ FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for equality, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_pd
+ FORCE_INLINE __m128d _mm_cmpeq_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3452,7 +3113,7 @@ FORCE_INLINE __m128d _mm_cmpeq_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for equality, store the result in the lower element of dst, and copy the
+ // upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_sd
+ FORCE_INLINE __m128d _mm_cmpeq_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpeq_pd(a, b));
+@@ -3460,7 +3121,7 @@ FORCE_INLINE __m128d _mm_cmpeq_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for greater-than-or-equal, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_pd
+ FORCE_INLINE __m128d _mm_cmpge_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3482,7 +3143,7 @@ FORCE_INLINE __m128d _mm_cmpge_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for greater-than-or-equal, store the result in the lower element of dst,
+ // and copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_sd
+ FORCE_INLINE __m128d _mm_cmpge_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3500,39 +3161,27 @@ FORCE_INLINE __m128d _mm_cmpge_sd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers
+-// in b for greater than.
+-//
+-//   r0 := (a0 > b0) ? 0xffff : 0x0
+-//   r1 := (a1 > b1) ? 0xffff : 0x0
+-//   ...
+-//   r7 := (a7 > b7) ? 0xffff : 0x0
+-//
+-// https://technet.microsoft.com/en-us/library/xd43yfsa(v=vs.100).aspx
++// Compare packed signed 16-bit integers in a and b for greater-than, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi16
+ FORCE_INLINE __m128i _mm_cmpgt_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+         vcgtq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers
+-// in b for greater than.
+-// https://msdn.microsoft.com/en-us/library/vstudio/1s9f2z0y(v=vs.100).aspx
++// Compare packed signed 32-bit integers in a and b for greater-than, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi32
+ FORCE_INLINE __m128i _mm_cmpgt_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u32(
+         vcgtq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers
+-// in b for greater than.
+-//
+-//   r0 := (a0 > b0) ? 0xff : 0x0
+-//   r1 := (a1 > b1) ? 0xff : 0x0
+-//   ...
+-//   r15 := (a15 > b15) ? 0xff : 0x0
+-//
+-// https://msdn.microsoft.com/zh-tw/library/wf45zt2b(v=vs.100).aspx
++// Compare packed signed 8-bit integers in a and b for greater-than, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi8
+ FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -3541,7 +3190,7 @@ FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for greater-than, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_pd
+ FORCE_INLINE __m128d _mm_cmpgt_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3563,7 +3212,7 @@ FORCE_INLINE __m128d _mm_cmpgt_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for greater-than, store the result in the lower element of dst, and copy
+ // the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_sd
+ FORCE_INLINE __m128d _mm_cmpgt_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3583,7 +3232,7 @@ FORCE_INLINE __m128d _mm_cmpgt_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for less-than-or-equal, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_pd
+ FORCE_INLINE __m128d _mm_cmple_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3605,7 +3254,7 @@ FORCE_INLINE __m128d _mm_cmple_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for less-than-or-equal, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_sd
+ FORCE_INLINE __m128d _mm_cmple_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3623,34 +3272,30 @@ FORCE_INLINE __m128d _mm_cmple_sd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers
+-// in b for less than.
+-//
+-//   r0 := (a0 < b0) ? 0xffff : 0x0
+-//   r1 := (a1 < b1) ? 0xffff : 0x0
+-//   ...
+-//   r7 := (a7 < b7) ? 0xffff : 0x0
+-//
+-// https://technet.microsoft.com/en-us/library/t863edb2(v=vs.100).aspx
++// Compare packed signed 16-bit integers in a and b for less-than, and store the
++// results in dst. Note: This intrinsic emits the pcmpgtw instruction with the
++// order of the operands switched.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi16
+ FORCE_INLINE __m128i _mm_cmplt_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+         vcltq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-
+-// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers
+-// in b for less than.
+-// https://msdn.microsoft.com/en-us/library/vstudio/4ak0bf5d(v=vs.100).aspx
++// Compare packed signed 32-bit integers in a and b for less-than, and store the
++// results in dst. Note: This intrinsic emits the pcmpgtd instruction with the
++// order of the operands switched.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi32
+ FORCE_INLINE __m128i _mm_cmplt_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u32(
+         vcltq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers
+-// in b for lesser than.
+-// https://msdn.microsoft.com/en-us/library/windows/desktop/9s46csht(v=vs.90).aspx
++// Compare packed signed 8-bit integers in a and b for less-than, and store the
++// results in dst. Note: This intrinsic emits the pcmpgtb instruction with the
++// order of the operands switched.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi8
+ FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -3659,7 +3304,7 @@ FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for less-than, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_pd
+ FORCE_INLINE __m128d _mm_cmplt_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3681,7 +3326,7 @@ FORCE_INLINE __m128d _mm_cmplt_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for less-than, store the result in the lower element of dst, and copy the
+ // upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_sd
+ FORCE_INLINE __m128d _mm_cmplt_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3700,7 +3345,7 @@ FORCE_INLINE __m128d _mm_cmplt_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for not-equal, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_pd
+ FORCE_INLINE __m128d _mm_cmpneq_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3718,7 +3363,7 @@ FORCE_INLINE __m128d _mm_cmpneq_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for not-equal, store the result in the lower element of dst, and copy the
+ // upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_sd
+ FORCE_INLINE __m128d _mm_cmpneq_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpneq_pd(a, b));
+@@ -3726,7 +3371,7 @@ FORCE_INLINE __m128d _mm_cmpneq_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for not-greater-than-or-equal, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_pd
+ FORCE_INLINE __m128d _mm_cmpnge_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3751,7 +3396,7 @@ FORCE_INLINE __m128d _mm_cmpnge_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for not-greater-than-or-equal, store the result in the lower element of
+ // dst, and copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_sd
+ FORCE_INLINE __m128d _mm_cmpnge_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpnge_pd(a, b));
+@@ -3759,7 +3404,7 @@ FORCE_INLINE __m128d _mm_cmpnge_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for not-greater-than, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_cmpngt_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_cmpngt_pd
+ FORCE_INLINE __m128d _mm_cmpngt_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3784,7 +3429,7 @@ FORCE_INLINE __m128d _mm_cmpngt_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for not-greater-than, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpngt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_sd
+ FORCE_INLINE __m128d _mm_cmpngt_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpngt_pd(a, b));
+@@ -3792,7 +3437,7 @@ FORCE_INLINE __m128d _mm_cmpngt_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for not-less-than-or-equal, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_pd
+ FORCE_INLINE __m128d _mm_cmpnle_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3817,7 +3462,7 @@ FORCE_INLINE __m128d _mm_cmpnle_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for not-less-than-or-equal, store the result in the lower element of dst,
+ // and copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_sd
+ FORCE_INLINE __m128d _mm_cmpnle_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpnle_pd(a, b));
+@@ -3825,7 +3470,7 @@ FORCE_INLINE __m128d _mm_cmpnle_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // for not-less-than, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_pd
+ FORCE_INLINE __m128d _mm_cmpnlt_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3850,7 +3495,7 @@ FORCE_INLINE __m128d _mm_cmpnlt_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b for not-less-than, store the result in the lower element of dst, and copy
+ // the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_sd
+ FORCE_INLINE __m128d _mm_cmpnlt_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_cmpnlt_pd(a, b));
+@@ -3858,7 +3503,7 @@ FORCE_INLINE __m128d _mm_cmpnlt_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // to see if neither is NaN, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_pd
+ FORCE_INLINE __m128d _mm_cmpord_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3890,7 +3535,7 @@ FORCE_INLINE __m128d _mm_cmpord_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b to see if neither is NaN, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_sd
+ FORCE_INLINE __m128d _mm_cmpord_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3912,7 +3557,7 @@ FORCE_INLINE __m128d _mm_cmpord_sd(__m128d a, __m128d b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b
+ // to see if either is NaN, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_pd
+ FORCE_INLINE __m128d _mm_cmpunord_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3945,7 +3590,7 @@ FORCE_INLINE __m128d _mm_cmpunord_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b to see if either is NaN, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_sd
+ FORCE_INLINE __m128d _mm_cmpunord_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3967,7 +3612,7 @@ FORCE_INLINE __m128d _mm_cmpunord_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for greater-than-or-equal, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comige_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comige_sd
+ FORCE_INLINE int _mm_comige_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3982,7 +3627,7 @@ FORCE_INLINE int _mm_comige_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for greater-than, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comigt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comigt_sd
+ FORCE_INLINE int _mm_comigt_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -3997,7 +3642,7 @@ FORCE_INLINE int _mm_comigt_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for less-than-or-equal, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comile_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comile_sd
+ FORCE_INLINE int _mm_comile_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4012,7 +3657,7 @@ FORCE_INLINE int _mm_comile_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for less-than, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comilt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comilt_sd
+ FORCE_INLINE int _mm_comilt_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4027,7 +3672,7 @@ FORCE_INLINE int _mm_comilt_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for equality, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comieq_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comieq_sd
+ FORCE_INLINE int _mm_comieq_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4048,7 +3693,7 @@ FORCE_INLINE int _mm_comieq_sd(__m128d a, __m128d b)
+ 
+ // Compare the lower double-precision (64-bit) floating-point element in a and b
+ // for not-equal, and return the boolean result (0 or 1).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comineq_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comineq_sd
+ FORCE_INLINE int _mm_comineq_sd(__m128d a, __m128d b)
+ {
+     return !_mm_comieq_sd(a, b);
+@@ -4056,14 +3701,7 @@ FORCE_INLINE int _mm_comineq_sd(__m128d a, __m128d b)
+ 
+ // Convert packed signed 32-bit integers in a to packed double-precision
+ // (64-bit) floating-point elements, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*32
+-//     m := j*64
+-//     dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepi32_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_pd
+ FORCE_INLINE __m128d _mm_cvtepi32_pd(__m128i a)
+ {
+ #if defined(__aarch64__)
+@@ -4076,9 +3714,9 @@ FORCE_INLINE __m128d _mm_cvtepi32_pd(__m128i a)
+ #endif
+ }
+ 
+-// Converts the four signed 32-bit integer values of a to single-precision,
+-// floating-point values
+-// https://msdn.microsoft.com/en-us/library/vstudio/36bwxcx5(v=vs.100).aspx
++// Convert packed signed 32-bit integers in a to packed single-precision
++// (32-bit) floating-point elements, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_ps
+ FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a)
+ {
+     return vreinterpretq_m128_f32(vcvtq_f32_s32(vreinterpretq_s32_m128i(a)));
+@@ -4086,14 +3724,7 @@ FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a)
+ 
+ // Convert packed double-precision (64-bit) floating-point elements in a to
+ // packed 32-bit integers, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//      i := 32*j
+-//      k := 64*j
+-//      dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_epi32
+ FORCE_INLINE __m128i _mm_cvtpd_epi32(__m128d a)
+ {
+ // vrnd32xq_f64 not supported on clang
+@@ -4112,14 +3743,7 @@ FORCE_INLINE __m128i _mm_cvtpd_epi32(__m128d a)
+ 
+ // Convert packed double-precision (64-bit) floating-point elements in a to
+ // packed 32-bit integers, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//      i := 32*j
+-//      k := 64*j
+-//      dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_pi32
+ FORCE_INLINE __m64 _mm_cvtpd_pi32(__m128d a)
+ {
+     __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION);
+@@ -4132,15 +3756,7 @@ FORCE_INLINE __m64 _mm_cvtpd_pi32(__m128d a)
+ // Convert packed double-precision (64-bit) floating-point elements in a to
+ // packed single-precision (32-bit) floating-point elements, and store the
+ // results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := 32*j
+-//     k := 64*j
+-//     dst[i+31:i] := Convert_FP64_To_FP32(a[k+64:k])
+-//   ENDFOR
+-//   dst[127:64] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_ps
+ FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -4155,14 +3771,7 @@ FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a)
+ 
+ // Convert packed signed 32-bit integers in a to packed double-precision
+ // (64-bit) floating-point elements, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*32
+-//     m := j*64
+-//     dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32_pd
+ FORCE_INLINE __m128d _mm_cvtpi32_pd(__m64 a)
+ {
+ #if defined(__aarch64__)
+@@ -4175,15 +3784,9 @@ FORCE_INLINE __m128d _mm_cvtpi32_pd(__m64 a)
+ #endif
+ }
+ 
+-// Converts the four single-precision, floating-point values of a to signed
+-// 32-bit integer values.
+-//
+-//   r0 := (int) a0
+-//   r1 := (int) a1
+-//   r2 := (int) a2
+-//   r3 := (int) a3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/xdc42k5e(v=vs.100).aspx
++// Convert packed single-precision (32-bit) floating-point elements in a to
++// packed 32-bit integers, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_epi32
+ // *NOTE*. The default rounding mode on SSE is 'round to even', which ARMv7-A
+ // does not support! It is supported on ARMv8-A however.
+ FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a)
+@@ -4240,14 +3843,7 @@ FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a)
+ // Convert packed single-precision (32-bit) floating-point elements in a to
+ // packed double-precision (64-bit) floating-point elements, and store the
+ // results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := 64*j
+-//     k := 32*j
+-//     dst[i+63:i] := Convert_FP32_To_FP64(a[k+31:k])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pd
+ FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a)
+ {
+ #if defined(__aarch64__)
+@@ -4261,10 +3857,7 @@ FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a)
+ }
+ 
+ // Copy the lower double-precision (64-bit) floating-point element of a to dst.
+-//
+-//   dst[63:0] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_f64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_f64
+ FORCE_INLINE double _mm_cvtsd_f64(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -4276,10 +3869,7 @@ FORCE_INLINE double _mm_cvtsd_f64(__m128d a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 32-bit integer, and store the result in dst.
+-//
+-//   dst[31:0] := Convert_FP64_To_Int32(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si32
+ FORCE_INLINE int32_t _mm_cvtsd_si32(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -4293,10 +3883,7 @@ FORCE_INLINE int32_t _mm_cvtsd_si32(__m128d a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 64-bit integer, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP64_To_Int64(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si64
+ FORCE_INLINE int64_t _mm_cvtsd_si64(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -4310,17 +3897,14 @@ FORCE_INLINE int64_t _mm_cvtsd_si64(__m128d a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 64-bit integer, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP64_To_Int64(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64x
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si64x
+ #define _mm_cvtsd_si64x _mm_cvtsd_si64
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in b to a
+ // single-precision (32-bit) floating-point element, store the result in the
+ // lower element of dst, and copy the upper 3 packed elements from a to the
+ // upper elements of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_ss
+ FORCE_INLINE __m128 _mm_cvtsd_ss(__m128 a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4334,33 +3918,27 @@ FORCE_INLINE __m128 _mm_cvtsd_ss(__m128 a, __m128d b)
+ }
+ 
+ // Copy the lower 32-bit integer in a to dst.
+-//
+-//   dst[31:0] := a[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si32
+ FORCE_INLINE int _mm_cvtsi128_si32(__m128i a)
+ {
+     return vgetq_lane_s32(vreinterpretq_s32_m128i(a), 0);
+ }
+ 
+ // Copy the lower 64-bit integer in a to dst.
+-//
+-//   dst[63:0] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64
+ FORCE_INLINE int64_t _mm_cvtsi128_si64(__m128i a)
+ {
+     return vgetq_lane_s64(vreinterpretq_s64_m128i(a), 0);
+ }
+ 
+ // Copy the lower 64-bit integer in a to dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64x
+ #define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a)
+ 
+ // Convert the signed 32-bit integer b to a double-precision (64-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_sd
+ FORCE_INLINE __m128d _mm_cvtsi32_sd(__m128d a, int32_t b)
+ {
+ #if defined(__aarch64__)
+@@ -4374,21 +3952,12 @@ FORCE_INLINE __m128d _mm_cvtsi32_sd(__m128d a, int32_t b)
+ }
+ 
+ // Copy the lower 64-bit integer in a to dst.
+-//
+-//   dst[63:0] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64x
+ #define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a)
+ 
+-// Moves 32-bit integer a to the least significant 32 bits of an __m128 object,
+-// zero extending the upper bits.
+-//
+-//   r0 := a
+-//   r1 := 0x0
+-//   r2 := 0x0
+-//   r3 := 0x0
+-//
+-// https://msdn.microsoft.com/en-us/library/ct3539ha%28v=vs.90%29.aspx
++// Copy 32-bit integer a to the lower elements of dst, and zero the upper
++// elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_si128
+ FORCE_INLINE __m128i _mm_cvtsi32_si128(int a)
+ {
+     return vreinterpretq_m128i_s32(vsetq_lane_s32(a, vdupq_n_s32(0), 0));
+@@ -4397,7 +3966,7 @@ FORCE_INLINE __m128i _mm_cvtsi32_si128(int a)
+ // Convert the signed 64-bit integer b to a double-precision (64-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_sd
+ FORCE_INLINE __m128d _mm_cvtsi64_sd(__m128d a, int64_t b)
+ {
+ #if defined(__aarch64__)
+@@ -4410,11 +3979,9 @@ FORCE_INLINE __m128d _mm_cvtsi64_sd(__m128d a, int64_t b)
+ #endif
+ }
+ 
+-// Moves 64-bit integer a to the least significant 64 bits of an __m128 object,
+-// zero extending the upper bits.
+-//
+-//   r0 := a
+-//   r1 := 0x0
++// Copy 64-bit integer a to the lower element of dst, and zero the upper
++// element.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_si128
+ FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a)
+ {
+     return vreinterpretq_m128i_s64(vsetq_lane_s64(a, vdupq_n_s64(0), 0));
+@@ -4422,24 +3989,20 @@ FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a)
+ 
+ // Copy 64-bit integer a to the lower element of dst, and zero the upper
+ // element.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64x_si128
+ #define _mm_cvtsi64x_si128(a) _mm_cvtsi64_si128(a)
+ 
+ // Convert the signed 64-bit integer b to a double-precision (64-bit)
+ // floating-point element, store the result in the lower element of dst, and
+ // copy the upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64x_sd
+ #define _mm_cvtsi64x_sd(a, b) _mm_cvtsi64_sd(a, b)
+ 
+ // Convert the lower single-precision (32-bit) floating-point element in b to a
+ // double-precision (64-bit) floating-point element, store the result in the
+ // lower element of dst, and copy the upper element from a to the upper element
+ // of dst.
+-//
+-//   dst[63:0] := Convert_FP32_To_FP64(b[31:0])
+-//   dst[127:64] := a[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_sd
+ FORCE_INLINE __m128d _mm_cvtss_sd(__m128d a, __m128 b)
+ {
+     double d = (double) vgetq_lane_f32(vreinterpretq_f32_m128(b), 0);
+@@ -4454,7 +4017,7 @@ FORCE_INLINE __m128d _mm_cvtss_sd(__m128d a, __m128 b)
+ 
+ // Convert packed double-precision (64-bit) floating-point elements in a to
+ // packed 32-bit integers with truncation, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttpd_epi32
+ FORCE_INLINE __m128i _mm_cvttpd_epi32(__m128d a)
+ {
+     double a0 = ((double *) &a)[0];
+@@ -4464,7 +4027,7 @@ FORCE_INLINE __m128i _mm_cvttpd_epi32(__m128d a)
+ 
+ // Convert packed double-precision (64-bit) floating-point elements in a to
+ // packed 32-bit integers with truncation, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttpd_pi32
+ FORCE_INLINE __m64 _mm_cvttpd_pi32(__m128d a)
+ {
+     double a0 = ((double *) &a)[0];
+@@ -4473,9 +4036,9 @@ FORCE_INLINE __m64 _mm_cvttpd_pi32(__m128d a)
+     return vreinterpret_m64_s32(vld1_s32(data));
+ }
+ 
+-// Converts the four single-precision, floating-point values of a to signed
+-// 32-bit integer values using truncate.
+-// https://msdn.microsoft.com/en-us/library/vstudio/1h005y6x(v=vs.100).aspx
++// Convert packed single-precision (32-bit) floating-point elements in a to
++// packed 32-bit integers with truncation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttps_epi32
+ FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a)
+ {
+     return vreinterpretq_m128i_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)));
+@@ -4483,10 +4046,7 @@ FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 32-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP64_To_Int32_Truncate(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si32
+ FORCE_INLINE int32_t _mm_cvttsd_si32(__m128d a)
+ {
+     double ret = *((double *) &a);
+@@ -4495,10 +4055,7 @@ FORCE_INLINE int32_t _mm_cvttsd_si32(__m128d a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 64-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si64
+ FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -4511,21 +4068,12 @@ FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a)
+ 
+ // Convert the lower double-precision (64-bit) floating-point element in a to a
+ // 64-bit integer with truncation, and store the result in dst.
+-//
+-//   dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64x
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si64x
+ #define _mm_cvttsd_si64x(a) _mm_cvttsd_si64(a)
+ 
+ // Divide packed double-precision (64-bit) floating-point elements in a by
+ // packed elements in b, and store the results in dst.
+-//
+-//  FOR j := 0 to 1
+-//    i := 64*j
+-//    dst[i+63:i] := a[i+63:i] / b[i+63:i]
+-//  ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_pd
+ FORCE_INLINE __m128d _mm_div_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4545,7 +4093,7 @@ FORCE_INLINE __m128d _mm_div_pd(__m128d a, __m128d b)
+ // lower double-precision (64-bit) floating-point element in b, store the result
+ // in the lower element of dst, and copy the upper element from a to the upper
+ // element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_sd
+ FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4558,16 +4106,16 @@ FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Extracts the selected signed or unsigned 16-bit integer from a and zero
+-// extends.
+-// https://msdn.microsoft.com/en-us/library/6dceta0c(v=vs.100).aspx
++// Extract a 16-bit integer from a, selected with imm8, and store the result in
++// the lower element of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi16
+ // FORCE_INLINE int _mm_extract_epi16(__m128i a, __constrange(0,8) int imm)
+ #define _mm_extract_epi16(a, imm) \
+     vgetq_lane_u16(vreinterpretq_u16_m128i(a), (imm))
+ 
+-// Inserts the least significant 16 bits of b into the selected 16-bit integer
+-// of a.
+-// https://msdn.microsoft.com/en-us/library/kaze8hz1%28v=vs.100%29.aspx
++// Copy a to dst, and insert the 16-bit integer i into dst at the location
++// specified by imm8.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi16
+ // FORCE_INLINE __m128i _mm_insert_epi16(__m128i a, int b,
+ //                                       __constrange(0,8) int imm)
+ #define _mm_insert_epi16(a, b, imm)                                  \
+@@ -4576,12 +4124,10 @@ FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b)
+             vsetq_lane_s16((b), vreinterpretq_s16_m128i(a), (imm))); \
+     })
+ 
+-// Loads two double-precision from 16-byte aligned memory, floating-point
+-// values.
+-//
+-//   dst[127:0] := MEM[mem_addr+127:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd
++// Load 128-bits (composed of 2 packed double-precision (64-bit) floating-point
++// elements) from memory into dst. mem_addr must be aligned on a 16-byte
++// boundary or a general-protection exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_pd
+ FORCE_INLINE __m128d _mm_load_pd(const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4595,21 +4141,13 @@ FORCE_INLINE __m128d _mm_load_pd(const double *p)
+ 
+ // Load a double-precision (64-bit) floating-point element from memory into both
+ // elements of dst.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[127:64] := MEM[mem_addr+63:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd1
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_pd1
+ #define _mm_load_pd1 _mm_load1_pd
+ 
+ // Load a double-precision (64-bit) floating-point element from memory into the
+ // lower of dst, and zero the upper element. mem_addr does not need to be
+ // aligned on any particular boundary.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[127:64] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_sd
+ FORCE_INLINE __m128d _mm_load_sd(const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4621,8 +4159,9 @@ FORCE_INLINE __m128d _mm_load_sd(const double *p)
+ #endif
+ }
+ 
+-// Loads 128-bit value. :
+-// https://msdn.microsoft.com/en-us/library/atzzad1h(v=vs.80).aspx
++// Load 128-bits of integer data from memory into dst. mem_addr must be aligned
++// on a 16-byte boundary or a general-protection exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_si128
+ FORCE_INLINE __m128i _mm_load_si128(const __m128i *p)
+ {
+     return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p));
+@@ -4630,11 +4169,7 @@ FORCE_INLINE __m128i _mm_load_si128(const __m128i *p)
+ 
+ // Load a double-precision (64-bit) floating-point element from memory into both
+ // elements of dst.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[127:64] := MEM[mem_addr+63:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load1_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load1_pd
+ FORCE_INLINE __m128d _mm_load1_pd(const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4647,11 +4182,7 @@ FORCE_INLINE __m128d _mm_load1_pd(const double *p)
+ // Load a double-precision (64-bit) floating-point element from memory into the
+ // upper element of dst, and copy the lower element from a to dst. mem_addr does
+ // not need to be aligned on any particular boundary.
+-//
+-//   dst[63:0] := a[63:0]
+-//   dst[127:64] := MEM[mem_addr+63:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadh_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadh_pd
+ FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4664,7 +4195,7 @@ FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p)
+ }
+ 
+ // Load 64-bit integer from memory into the first element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_epi64
+ FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p)
+ {
+     /* Load the lower 64 bits of the value pointed to by p into the
+@@ -4677,11 +4208,7 @@ FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p)
+ // Load a double-precision (64-bit) floating-point element from memory into the
+ // lower element of dst, and copy the upper element from a to dst. mem_addr does
+ // not need to be aligned on any particular boundary.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[127:64] := a[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_pd
+ FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4697,11 +4224,7 @@ FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p)
+ // Load 2 double-precision (64-bit) floating-point elements from memory into dst
+ // in reverse order. mem_addr must be aligned on a 16-byte boundary or a
+ // general-protection exception may be generated.
+-//
+-//   dst[63:0] := MEM[mem_addr+127:mem_addr+64]
+-//   dst[127:64] := MEM[mem_addr+63:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadr_pd
+ FORCE_INLINE __m128d _mm_loadr_pd(const double *p)
+ {
+ #if defined(__aarch64__)
+@@ -4714,39 +4237,32 @@ FORCE_INLINE __m128d _mm_loadr_pd(const double *p)
+ }
+ 
+ // Loads two double-precision from unaligned memory, floating-point values.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_pd
+ FORCE_INLINE __m128d _mm_loadu_pd(const double *p)
+ {
+     return _mm_load_pd(p);
+ }
+ 
+-// Loads 128-bit value. :
+-// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx
++// Load 128-bits of integer data from memory into dst. mem_addr does not need to
++// be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si128
+ FORCE_INLINE __m128i _mm_loadu_si128(const __m128i *p)
+ {
+     return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p));
+ }
+ 
+ // Load unaligned 32-bit integer from memory into the first element of dst.
+-//
+-//   dst[31:0] := MEM[mem_addr+31:mem_addr]
+-//   dst[MAX:32] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si32
+ FORCE_INLINE __m128i _mm_loadu_si32(const void *p)
+ {
+     return vreinterpretq_m128i_s32(
+         vsetq_lane_s32(*(const int32_t *) p, vdupq_n_s32(0), 0));
+ }
+ 
+-// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit
+-// integers from b.
+-//
+-//   r0 := (a0 * b0) + (a1 * b1)
+-//   r1 := (a2 * b2) + (a3 * b3)
+-//   r2 := (a4 * b4) + (a5 * b5)
+-//   r3 := (a6 * b6) + (a7 * b7)
+-// https://msdn.microsoft.com/en-us/library/yht36sa6(v=vs.90).aspx
++// Multiply packed signed 16-bit integers in a and b, producing intermediate
++// signed 32-bit integers. Horizontally add adjacent pairs of intermediate
++// 32-bit integers, and pack the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_madd_epi16
+ FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b)
+ {
+     int32x4_t low = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)),
+@@ -4771,7 +4287,7 @@ FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b)
+ // (elements are not stored when the highest bit is not set in the corresponding
+ // element) and a non-temporal memory hint. mem_addr does not need to be aligned
+ // on any particular boundary.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmoveu_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maskmoveu_si128
+ FORCE_INLINE void _mm_maskmoveu_si128(__m128i a, __m128i mask, char *mem_addr)
+ {
+     int8x16_t shr_mask = vshrq_n_s8(vreinterpretq_s8_m128i(mask), 7);
+@@ -4782,18 +4298,18 @@ FORCE_INLINE void _mm_maskmoveu_si128(__m128i a, __m128i mask, char *mem_addr)
+     vst1q_s8((int8_t *) mem_addr, masked);
+ }
+ 
+-// Computes the pairwise maxima of the 8 signed 16-bit integers from a and the 8
+-// signed 16-bit integers from b.
+-// https://msdn.microsoft.com/en-us/LIBRary/3x060h7c(v=vs.100).aspx
++// Compare packed signed 16-bit integers in a and b, and store packed maximum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi16
+ FORCE_INLINE __m128i _mm_max_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+         vmaxq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Computes the pairwise maxima of the 16 unsigned 8-bit integers from a and the
+-// 16 unsigned 8-bit integers from b.
+-// https://msdn.microsoft.com/en-us/library/st6634za(v=vs.100).aspx
++// Compare packed unsigned 8-bit integers in a and b, and store packed maximum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu8
+ FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -4802,7 +4318,7 @@ FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b,
+ // and store packed maximum values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pd
+ FORCE_INLINE __m128d _mm_max_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4830,7 +4346,7 @@ FORCE_INLINE __m128d _mm_max_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b, store the maximum value in the lower element of dst, and copy the upper
+ // element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_sd
+ FORCE_INLINE __m128d _mm_max_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4843,18 +4359,18 @@ FORCE_INLINE __m128d _mm_max_sd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Computes the pairwise minima of the 8 signed 16-bit integers from a and the 8
+-// signed 16-bit integers from b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/6te997ew(v=vs.100).aspx
++// Compare packed signed 16-bit integers in a and b, and store packed minimum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi16
+ FORCE_INLINE __m128i _mm_min_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+         vminq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Computes the pairwise minima of the 16 unsigned 8-bit integers from a and the
+-// 16 unsigned 8-bit integers from b.
+-// https://msdn.microsoft.com/ko-kr/library/17k8cf58(v=vs.100).aspxx
++// Compare packed unsigned 8-bit integers in a and b, and store packed minimum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epu8
+ FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -4863,7 +4379,7 @@ FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b)
+ 
+ // Compare packed double-precision (64-bit) floating-point elements in a and b,
+ // and store packed minimum values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pd
+ FORCE_INLINE __m128d _mm_min_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4890,7 +4406,7 @@ FORCE_INLINE __m128d _mm_min_pd(__m128d a, __m128d b)
+ // Compare the lower double-precision (64-bit) floating-point elements in a and
+ // b, store the minimum value in the lower element of dst, and copy the upper
+ // element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_sd
+ FORCE_INLINE __m128d _mm_min_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -4905,11 +4421,7 @@ FORCE_INLINE __m128d _mm_min_sd(__m128d a, __m128d b)
+ 
+ // Copy the lower 64-bit integer in a to the lower element of dst, and zero the
+ // upper element.
+-//
+-//   dst[63:0] := a[63:0]
+-//   dst[127:64] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_epi64
+ FORCE_INLINE __m128i _mm_move_epi64(__m128i a)
+ {
+     return vreinterpretq_m128i_s64(
+@@ -4919,11 +4431,7 @@ FORCE_INLINE __m128i _mm_move_epi64(__m128i a)
+ // Move the lower double-precision (64-bit) floating-point element from b to the
+ // lower element of dst, and copy the upper element from a to the upper element
+ // of dst.
+-//
+-//   dst[63:0] := b[63:0]
+-//   dst[127:64] := a[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_sd
+ FORCE_INLINE __m128d _mm_move_sd(__m128d a, __m128d b)
+ {
+     return vreinterpretq_m128d_f32(
+@@ -4931,10 +4439,9 @@ FORCE_INLINE __m128d _mm_move_sd(__m128d a, __m128d b)
+                      vget_high_f32(vreinterpretq_f32_m128d(a))));
+ }
+ 
+-// NEON does not provide a version of this function.
+-// Creates a 16-bit mask from the most significant bits of the 16 signed or
+-// unsigned 8-bit integers in a and zero extends the upper bits.
+-// https://msdn.microsoft.com/en-us/library/vstudio/s090c8fk(v=vs.100).aspx
++// Create mask from the most significant bit of each 8-bit element in a, and
++// store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_epi8
+ FORCE_INLINE int _mm_movemask_epi8(__m128i a)
+ {
+     // Use increasingly wide shifts+adds to collect the sign bits
+@@ -5017,7 +4524,7 @@ FORCE_INLINE int _mm_movemask_epi8(__m128i a)
+ 
+ // Set each bit of mask dst based on the most significant bit of the
+ // corresponding packed double-precision (64-bit) floating-point element in a.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_pd
+ FORCE_INLINE int _mm_movemask_pd(__m128d a)
+ {
+     uint64x2_t input = vreinterpretq_u64_m128d(a);
+@@ -5026,10 +4533,7 @@ FORCE_INLINE int _mm_movemask_pd(__m128d a)
+ }
+ 
+ // Copy the lower 64-bit integer in a to dst.
+-//
+-//   dst[63:0] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movepi64_pi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movepi64_pi64
+ FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a)
+ {
+     return vreinterpret_m64_s64(vget_low_s64(vreinterpretq_s64_m128i(a)));
+@@ -5037,11 +4541,7 @@ FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a)
+ 
+ // Copy the 64-bit integer a to the lower element of dst, and zero the upper
+ // element.
+-//
+-//   dst[63:0] := a[63:0]
+-//   dst[127:64] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movpi64_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movpi64_epi64
+ FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a)
+ {
+     return vreinterpretq_m128i_s64(
+@@ -5050,9 +4550,7 @@ FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a)
+ 
+ // Multiply the low unsigned 32-bit integers from each packed 64-bit element in
+ // a and b, and store the unsigned 64-bit results in dst.
+-//
+-//   r0 :=  (a0 & 0xFFFFFFFF) * (b0 & 0xFFFFFFFF)
+-//   r1 :=  (a2 & 0xFFFFFFFF) * (b2 & 0xFFFFFFFF)
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_epu32
+ FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b)
+ {
+     // vmull_u32 upcasts instead of masking, so we downcast.
+@@ -5063,7 +4561,7 @@ FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b)
+ 
+ // Multiply packed double-precision (64-bit) floating-point elements in a and b,
+ // and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_pd
+ FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -5082,7 +4580,7 @@ FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b)
+ // Multiply the lower double-precision (64-bit) floating-point element in a and
+ // b, store the result in the lower element of dst, and copy the upper element
+ // from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_mul_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_mul_sd
+ FORCE_INLINE __m128d _mm_mul_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_mul_pd(a, b));
+@@ -5090,25 +4588,17 @@ FORCE_INLINE __m128d _mm_mul_sd(__m128d a, __m128d b)
+ 
+ // Multiply the low unsigned 32-bit integers from a and b, and store the
+ // unsigned 64-bit result in dst.
+-//
+-//   dst[63:0] := a[31:0] * b[31:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_su32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_su32
+ FORCE_INLINE __m64 _mm_mul_su32(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_u64(vget_low_u64(
+         vmull_u32(vreinterpret_u32_m64(a), vreinterpret_u32_m64(b))));
+ }
+ 
+-// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit
+-// integers from b.
+-//
+-//   r0 := (a0 * b0)[31:16]
+-//   r1 := (a1 * b1)[31:16]
+-//   ...
+-//   r7 := (a7 * b7)[31:16]
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/59hddw1d(v=vs.100).aspx
++// Multiply the packed signed 16-bit integers in a and b, producing intermediate
++// 32-bit integers, and store the high 16 bits of the intermediate integers in
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_epi16
+ FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b)
+ {
+     /* FIXME: issue with large values because of result saturation */
+@@ -5129,7 +4619,7 @@ FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b)
+ // Multiply the packed unsigned 16-bit integers in a and b, producing
+ // intermediate 32-bit integers, and store the high 16 bits of the intermediate
+ // integers in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_epu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_epu16
+ FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b)
+ {
+     uint16x4_t a3210 = vget_low_u16(vreinterpretq_u16_m128i(a));
+@@ -5151,15 +4641,9 @@ FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b)
+ #endif
+ }
+ 
+-// Multiplies the 8 signed or unsigned 16-bit integers from a by the 8 signed or
+-// unsigned 16-bit integers from b.
+-//
+-//   r0 := (a0 * b0)[15:0]
+-//   r1 := (a1 * b1)[15:0]
+-//   ...
+-//   r7 := (a7 * b7)[15:0]
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/9ks1472s(v=vs.100).aspx
++// Multiply the packed 16-bit integers in a and b, producing intermediate 32-bit
++// integers, and store the low 16 bits of the intermediate integers in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mullo_epi16
+ FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+@@ -5168,27 +4652,25 @@ FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b)
+ 
+ // Compute the bitwise OR of packed double-precision (64-bit) floating-point
+ // elements in a and b, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_or_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_or_pd
+ FORCE_INLINE __m128d _mm_or_pd(__m128d a, __m128d b)
+ {
+     return vreinterpretq_m128d_s64(
+         vorrq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b)));
+ }
+ 
+-// Computes the bitwise OR of the 128-bit value in a and the 128-bit value in b.
+-//
+-//   r := a | b
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/ew8ty0db(v=vs.100).aspx
++// Compute the bitwise OR of 128 bits (representing integer data) in a and b,
++// and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_or_si128
+ FORCE_INLINE __m128i _mm_or_si128(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+         vorrq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Packs the 16 signed 16-bit integers from a and b into 8-bit integers and
+-// saturates.
+-// https://msdn.microsoft.com/en-us/library/k4y4f7w5%28v=vs.90%29.aspx
++// Convert packed signed 16-bit integers from a and b to packed 8-bit integers
++// using signed saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packs_epi16
+ FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -5196,19 +4678,9 @@ FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b)
+                     vqmovn_s16(vreinterpretq_s16_m128i(b))));
+ }
+ 
+-// Packs the 8 signed 32-bit integers from a and b into signed 16-bit integers
+-// and saturates.
+-//
+-//   r0 := SignedSaturate(a0)
+-//   r1 := SignedSaturate(a1)
+-//   r2 := SignedSaturate(a2)
+-//   r3 := SignedSaturate(a3)
+-//   r4 := SignedSaturate(b0)
+-//   r5 := SignedSaturate(b1)
+-//   r6 := SignedSaturate(b2)
+-//   r7 := SignedSaturate(b3)
+-//
+-// https://msdn.microsoft.com/en-us/library/393t56f9%28v=vs.90%29.aspx
++// Convert packed signed 32-bit integers from a and b to packed 16-bit integers
++// using signed saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packs_epi32
+ FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+@@ -5216,19 +4688,9 @@ FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b)
+                      vqmovn_s32(vreinterpretq_s32_m128i(b))));
+ }
+ 
+-// Packs the 16 signed 16 - bit integers from a and b into 8 - bit unsigned
+-// integers and saturates.
+-//
+-//   r0 := UnsignedSaturate(a0)
+-//   r1 := UnsignedSaturate(a1)
+-//   ...
+-//   r7 := UnsignedSaturate(a7)
+-//   r8 := UnsignedSaturate(b0)
+-//   r9 := UnsignedSaturate(b1)
+-//   ...
+-//   r15 := UnsignedSaturate(b7)
+-//
+-// https://msdn.microsoft.com/en-us/library/07ad1wx4(v=vs.100).aspx
++// Convert packed signed 16-bit integers from a and b to packed 8-bit integers
++// using unsigned saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packus_epi16
+ FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -5241,6 +4703,7 @@ FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b)
+ // 'yield' instruction isn't a good fit because it's effectively a nop on most
+ // Arm cores. Experience with several databases has shown has shown an 'isb' is
+ // a reasonable approximation.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_pause
+ FORCE_INLINE void _mm_pause()
+ {
+     __asm__ __volatile__("isb\n");
+@@ -5250,15 +4713,15 @@ FORCE_INLINE void _mm_pause()
+ // b, then horizontally sum each consecutive 8 differences to produce two
+ // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low
+ // 16 bits of 64-bit elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_epu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8
+ FORCE_INLINE __m128i _mm_sad_epu8(__m128i a, __m128i b)
+ {
+     uint16x8_t t = vpaddlq_u8(vabdq_u8((uint8x16_t) a, (uint8x16_t) b));
+     return vreinterpretq_m128i_u64(vpaddlq_u32(vpaddlq_u16(t)));
+ }
+ 
+-// Sets the 8 signed 16-bit integer values.
+-// https://msdn.microsoft.com/en-au/library/3e0fek84(v=vs.90).aspx
++// Set packed 16-bit integers in dst with the supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi16
+ FORCE_INLINE __m128i _mm_set_epi16(short i7,
+                                    short i6,
+                                    short i5,
+@@ -5272,33 +4735,31 @@ FORCE_INLINE __m128i _mm_set_epi16(short i7,
+     return vreinterpretq_m128i_s16(vld1q_s16(data));
+ }
+ 
+-// Sets the 4 signed 32-bit integer values.
+-// https://msdn.microsoft.com/en-us/library/vstudio/019beekt(v=vs.100).aspx
++// Set packed 32-bit integers in dst with the supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi32
+ FORCE_INLINE __m128i _mm_set_epi32(int i3, int i2, int i1, int i0)
+ {
+     int32_t ALIGN_STRUCT(16) data[4] = {i0, i1, i2, i3};
+     return vreinterpretq_m128i_s32(vld1q_s32(data));
+ }
+ 
+-// Returns the __m128i structure with its two 64-bit integer values
+-// initialized to the values of the two 64-bit integers passed in.
+-// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx
++// Set packed 64-bit integers in dst with the supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi64
+ FORCE_INLINE __m128i _mm_set_epi64(__m64 i1, __m64 i2)
+ {
+     return _mm_set_epi64x((int64_t) i1, (int64_t) i2);
+ }
+ 
+-// Returns the __m128i structure with its two 64-bit integer values
+-// initialized to the values of the two 64-bit integers passed in.
+-// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx
++// Set packed 64-bit integers in dst with the supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi64x
+ FORCE_INLINE __m128i _mm_set_epi64x(int64_t i1, int64_t i2)
+ {
+     return vreinterpretq_m128i_s64(
+         vcombine_s64(vcreate_s64(i2), vcreate_s64(i1)));
+ }
+ 
+-// Sets the 16 signed 8-bit integer values.
+-// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx
++// Set packed 8-bit integers in dst with the supplied values.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi8
+ FORCE_INLINE __m128i _mm_set_epi8(signed char b15,
+                                   signed char b14,
+                                   signed char b13,
+@@ -5326,7 +4787,7 @@ FORCE_INLINE __m128i _mm_set_epi8(signed char b15,
+ 
+ // Set packed double-precision (64-bit) floating-point elements in dst with the
+ // supplied values.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_pd
+ FORCE_INLINE __m128d _mm_set_pd(double e1, double e0)
+ {
+     double ALIGN_STRUCT(16) data[2] = {e0, e1};
+@@ -5339,12 +4800,12 @@ FORCE_INLINE __m128d _mm_set_pd(double e1, double e0)
+ 
+ // Broadcast double-precision (64-bit) floating-point value a to all elements of
+ // dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd1
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_pd1
+ #define _mm_set_pd1 _mm_set1_pd
+ 
+ // Copy double-precision (64-bit) floating-point element a to the lower element
+ // of dst, and zero the upper element.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_sd
+ FORCE_INLINE __m128d _mm_set_sd(double a)
+ {
+ #if defined(__aarch64__)
+@@ -5354,54 +4815,36 @@ FORCE_INLINE __m128d _mm_set_sd(double a)
+ #endif
+ }
+ 
+-// Sets the 8 signed 16-bit integer values to w.
+-//
+-//   r0 := w
+-//   r1 := w
+-//   ...
+-//   r7 := w
+-//
+-// https://msdn.microsoft.com/en-us/library/k0ya3x0e(v=vs.90).aspx
++// Broadcast 16-bit integer a to all all elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi16
+ FORCE_INLINE __m128i _mm_set1_epi16(short w)
+ {
+     return vreinterpretq_m128i_s16(vdupq_n_s16(w));
+ }
+ 
+-// Sets the 4 signed 32-bit integer values to i.
+-//
+-//   r0 := i
+-//   r1 := i
+-//   r2 := i
+-//   r3 := I
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/h4xscxat(v=vs.100).aspx
++// Broadcast 32-bit integer a to all elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi32
+ FORCE_INLINE __m128i _mm_set1_epi32(int _i)
+ {
+     return vreinterpretq_m128i_s32(vdupq_n_s32(_i));
+ }
+ 
+-// Sets the 2 signed 64-bit integer values to i.
+-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/whtfzhzk(v=vs.100)
++// Broadcast 64-bit integer a to all elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi64
+ FORCE_INLINE __m128i _mm_set1_epi64(__m64 _i)
+ {
+     return vreinterpretq_m128i_s64(vdupq_n_s64((int64_t) _i));
+ }
+ 
+-// Sets the 2 signed 64-bit integer values to i.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_epi64x
++// Broadcast 64-bit integer a to all elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi64x
+ FORCE_INLINE __m128i _mm_set1_epi64x(int64_t _i)
+ {
+     return vreinterpretq_m128i_s64(vdupq_n_s64(_i));
+ }
+ 
+-// Sets the 16 signed 8-bit integer values to b.
+-//
+-//   r0 := b
+-//   r1 := b
+-//   ...
+-//   r15 := b
+-//
+-// https://msdn.microsoft.com/en-us/library/6e14xhyf(v=vs.100).aspx
++// Broadcast 8-bit integer a to all elements of dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi8
+ FORCE_INLINE __m128i _mm_set1_epi8(signed char w)
+ {
+     return vreinterpretq_m128i_s8(vdupq_n_s8(w));
+@@ -5409,7 +4852,7 @@ FORCE_INLINE __m128i _mm_set1_epi8(signed char w)
+ 
+ // Broadcast double-precision (64-bit) floating-point value a to all elements of
+ // dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_pd
+ FORCE_INLINE __m128d _mm_set1_pd(double d)
+ {
+ #if defined(__aarch64__)
+@@ -5419,13 +4862,8 @@ FORCE_INLINE __m128d _mm_set1_pd(double d)
+ #endif
+ }
+ 
+-// Sets the 8 signed 16-bit integer values in reverse order.
+-//
+-// Return Value
+-//   r0 := w0
+-//   r1 := w1
+-//   ...
+-//   r7 := w7
++// Set packed 16-bit integers in dst with the supplied values in reverse order.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi16
+ FORCE_INLINE __m128i _mm_setr_epi16(short w0,
+                                     short w1,
+                                     short w2,
+@@ -5439,8 +4877,8 @@ FORCE_INLINE __m128i _mm_setr_epi16(short w0,
+     return vreinterpretq_m128i_s16(vld1q_s16((int16_t *) data));
+ }
+ 
+-// Sets the 4 signed 32-bit integer values in reverse order
+-// https://technet.microsoft.com/en-us/library/security/27yb3ee5(v=vs.90).aspx
++// Set packed 32-bit integers in dst with the supplied values in reverse order.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi32
+ FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0)
+ {
+     int32_t ALIGN_STRUCT(16) data[4] = {i3, i2, i1, i0};
+@@ -5448,14 +4886,14 @@ FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0)
+ }
+ 
+ // Set packed 64-bit integers in dst with the supplied values in reverse order.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi64
+ FORCE_INLINE __m128i _mm_setr_epi64(__m64 e1, __m64 e0)
+ {
+     return vreinterpretq_m128i_s64(vcombine_s64(e1, e0));
+ }
+ 
+-// Sets the 16 signed 8-bit integer values in reverse order.
+-// https://msdn.microsoft.com/en-us/library/2khb9c7k(v=vs.90).aspx
++// Set packed 8-bit integers in dst with the supplied values in reverse order.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi8
+ FORCE_INLINE __m128i _mm_setr_epi8(signed char b0,
+                                    signed char b1,
+                                    signed char b2,
+@@ -5483,14 +4921,14 @@ FORCE_INLINE __m128i _mm_setr_epi8(signed char b0,
+ 
+ // Set packed double-precision (64-bit) floating-point elements in dst with the
+ // supplied values in reverse order.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_pd
+ FORCE_INLINE __m128d _mm_setr_pd(double e1, double e0)
+ {
+     return _mm_set_pd(e0, e1);
+ }
+ 
+ // Return vector of type __m128d with all elements set to zero.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setzero_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_pd
+ FORCE_INLINE __m128d _mm_setzero_pd(void)
+ {
+ #if defined(__aarch64__)
+@@ -5500,15 +4938,16 @@ FORCE_INLINE __m128d _mm_setzero_pd(void)
+ #endif
+ }
+ 
+-// Sets the 128-bit value to zero
+-// https://msdn.microsoft.com/en-us/library/vstudio/ys7dw0kh(v=vs.100).aspx
++// Return vector of type __m128i with all elements set to zero.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_si128
+ FORCE_INLINE __m128i _mm_setzero_si128(void)
+ {
+     return vreinterpretq_m128i_s32(vdupq_n_s32(0));
+ }
+ 
+-// Shuffles the 4 signed or unsigned 32-bit integers in a as specified by imm.
+-// https://msdn.microsoft.com/en-us/library/56f67xbk%28v=vs.90%29.aspx
++// Shuffle 32-bit integers in a using the control in imm8, and store the results
++// in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi32
+ // FORCE_INLINE __m128i _mm_shuffle_epi32(__m128i a,
+ //                                        __constrange(0,255) int imm)
+ #ifdef _sse2neon_shuffle
+@@ -5577,11 +5016,7 @@ FORCE_INLINE __m128i _mm_setzero_si128(void)
+ 
+ // Shuffle double-precision (64-bit) floating-point elements using the control
+ // in imm8, and store the results in dst.
+-//
+-//   dst[63:0] := (imm8[0] == 0) ? a[63:0] : a[127:64]
+-//   dst[127:64] := (imm8[1] == 0) ? b[63:0] : b[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pd
+ #ifdef _sse2neon_shuffle
+ #define _mm_shuffle_pd(a, b, imm8)                                            \
+     vreinterpretq_m128d_s64(                                                  \
+@@ -5627,17 +5062,7 @@ FORCE_INLINE __m128i _mm_setzero_si128(void)
+ 
+ // Shift packed 16-bit integers in a left by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF count[63:0] > 15
+-//       dst[i+15:i] := 0
+-//     ELSE
+-//       dst[i+15:i] := ZeroExtend16(a[i+15:i] << count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi16
+ FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5650,17 +5075,7 @@ FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count)
+ 
+ // Shift packed 32-bit integers in a left by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF count[63:0] > 31
+-//       dst[i+31:i] := 0
+-//     ELSE
+-//       dst[i+31:i] := ZeroExtend32(a[i+31:i] << count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi32
+ FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5673,17 +5088,7 @@ FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count)
+ 
+ // Shift packed 64-bit integers in a left by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     IF count[63:0] > 63
+-//       dst[i+63:i] := 0
+-//     ELSE
+-//       dst[i+63:i] := ZeroExtend64(a[i+63:i] << count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi64
+ FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5696,17 +5101,7 @@ FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count)
+ 
+ // Shift packed 16-bit integers in a left by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF imm8[7:0] > 15
+-//       dst[i+15:i] := 0
+-//     ELSE
+-//       dst[i+15:i] := ZeroExtend16(a[i+15:i] << imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi16
+ FORCE_INLINE __m128i _mm_slli_epi16(__m128i a, int imm)
+ {
+     if (_sse2neon_unlikely(imm & ~15))
+@@ -5717,17 +5112,7 @@ FORCE_INLINE __m128i _mm_slli_epi16(__m128i a, int imm)
+ 
+ // Shift packed 32-bit integers in a left by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF imm8[7:0] > 31
+-//       dst[i+31:i] := 0
+-//     ELSE
+-//       dst[i+31:i] := ZeroExtend32(a[i+31:i] << imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi32
+ FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm)
+ {
+     if (_sse2neon_unlikely(imm & ~31))
+@@ -5738,17 +5123,7 @@ FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm)
+ 
+ // Shift packed 64-bit integers in a left by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     IF imm8[7:0] > 63
+-//       dst[i+63:i] := 0
+-//     ELSE
+-//       dst[i+63:i] := ZeroExtend64(a[i+63:i] << imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi64
+ FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm)
+ {
+     if (_sse2neon_unlikely(imm & ~63))
+@@ -5759,14 +5134,7 @@ FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm)
+ 
+ // Shift a left by imm8 bytes while shifting in zeros, and store the results in
+ // dst.
+-//
+-//   tmp := imm8[7:0]
+-//   IF tmp > 15
+-//     tmp := 16
+-//   FI
+-//   dst[127:0] := a[127:0] << (tmp*8)
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_si128
+ #define _mm_slli_si128(a, imm)                                         \
+     __extension__({                                                    \
+         int8x16_t ret;                                                 \
+@@ -5782,7 +5150,7 @@ FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm)
+ 
+ // Compute the square root of packed double-precision (64-bit) floating-point
+ // elements in a, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_pd
+ FORCE_INLINE __m128d _mm_sqrt_pd(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -5797,7 +5165,7 @@ FORCE_INLINE __m128d _mm_sqrt_pd(__m128d a)
+ // Compute the square root of the lower double-precision (64-bit) floating-point
+ // element in b, store the result in the lower element of dst, and copy the
+ // upper element from a to the upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_sd
+ FORCE_INLINE __m128d _mm_sqrt_sd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -5809,17 +5177,7 @@ FORCE_INLINE __m128d _mm_sqrt_sd(__m128d a, __m128d b)
+ 
+ // Shift packed 16-bit integers in a right by count while shifting in sign bits,
+ // and store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF count[63:0] > 15
+-//       dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0)
+-//     ELSE
+-//       dst[i+15:i] := SignExtend16(a[i+15:i] >> count[63:0])
+-//     FI
+-//  ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sra_epi16
+ FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count)
+ {
+     int64_t c = (int64_t) vget_low_s64((int64x2_t) count);
+@@ -5830,17 +5188,7 @@ FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count)
+ 
+ // Shift packed 32-bit integers in a right by count while shifting in sign bits,
+ // and store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF count[63:0] > 31
+-//       dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0)
+-//     ELSE
+-//       dst[i+31:i] := SignExtend32(a[i+31:i] >> count[63:0])
+-//     FI
+-//  ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sra_epi32
+ FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count)
+ {
+     int64_t c = (int64_t) vget_low_s64((int64x2_t) count);
+@@ -5851,17 +5199,7 @@ FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count)
+ 
+ // Shift packed 16-bit integers in a right by imm8 while shifting in sign
+ // bits, and store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF imm8[7:0] > 15
+-//       dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0)
+-//     ELSE
+-//       dst[i+15:i] := SignExtend16(a[i+15:i] >> imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srai_epi16
+ FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm)
+ {
+     const int count = (imm & ~15) ? 15 : imm;
+@@ -5870,17 +5208,7 @@ FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm)
+ 
+ // Shift packed 32-bit integers in a right by imm8 while shifting in sign bits,
+ // and store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF imm8[7:0] > 31
+-//       dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0)
+-//     ELSE
+-//       dst[i+31:i] := SignExtend32(a[i+31:i] >> imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srai_epi32
+ // FORCE_INLINE __m128i _mm_srai_epi32(__m128i a, __constrange(0,255) int imm)
+ #define _mm_srai_epi32(a, imm)                                               \
+     __extension__({                                                          \
+@@ -5899,17 +5227,7 @@ FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm)
+ 
+ // Shift packed 16-bit integers in a right by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF count[63:0] > 15
+-//       dst[i+15:i] := 0
+-//     ELSE
+-//       dst[i+15:i] := ZeroExtend16(a[i+15:i] >> count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi16
+ FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5922,17 +5240,7 @@ FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count)
+ 
+ // Shift packed 32-bit integers in a right by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF count[63:0] > 31
+-//       dst[i+31:i] := 0
+-//     ELSE
+-//       dst[i+31:i] := ZeroExtend32(a[i+31:i] >> count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi32
+ FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5945,17 +5253,7 @@ FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count)
+ 
+ // Shift packed 64-bit integers in a right by count while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     IF count[63:0] > 63
+-//       dst[i+63:i] := 0
+-//     ELSE
+-//       dst[i+63:i] := ZeroExtend64(a[i+63:i] >> count[63:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi64
+ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ {
+     uint64_t c = vreinterpretq_nth_u64_m128i(count, 0);
+@@ -5968,17 +5266,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ 
+ // Shift packed 16-bit integers in a right by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     IF imm8[7:0] > 15
+-//       dst[i+15:i] := 0
+-//     ELSE
+-//       dst[i+15:i] := ZeroExtend16(a[i+15:i] >> imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi16
+ #define _mm_srli_epi16(a, imm)                                               \
+     __extension__({                                                          \
+         __m128i ret;                                                         \
+@@ -5993,17 +5281,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ 
+ // Shift packed 32-bit integers in a right by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     IF imm8[7:0] > 31
+-//       dst[i+31:i] := 0
+-//     ELSE
+-//       dst[i+31:i] := ZeroExtend32(a[i+31:i] >> imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi32
+ // FORCE_INLINE __m128i _mm_srli_epi32(__m128i a, __constrange(0,255) int imm)
+ #define _mm_srli_epi32(a, imm)                                               \
+     __extension__({                                                          \
+@@ -6019,17 +5297,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ 
+ // Shift packed 64-bit integers in a right by imm8 while shifting in zeros, and
+ // store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     IF imm8[7:0] > 63
+-//       dst[i+63:i] := 0
+-//     ELSE
+-//       dst[i+63:i] := ZeroExtend64(a[i+63:i] >> imm8[7:0])
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi64
+ #define _mm_srli_epi64(a, imm)                                               \
+     __extension__({                                                          \
+         __m128i ret;                                                         \
+@@ -6044,14 +5312,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ 
+ // Shift a right by imm8 bytes while shifting in zeros, and store the results in
+ // dst.
+-//
+-//   tmp := imm8[7:0]
+-//   IF tmp > 15
+-//     tmp := 16
+-//   FI
+-//   dst[127:0] := a[127:0] >> (tmp*8)
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_si128
+ #define _mm_srli_si128(a, imm)                                       \
+     __extension__({                                                  \
+         int8x16_t ret;                                               \
+@@ -6066,7 +5327,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count)
+ // Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point
+ // elements) from a into memory. mem_addr must be aligned on a 16-byte boundary
+ // or a general-protection exception may be generated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_pd
+ FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6079,7 +5340,7 @@ FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a)
+ // Store the lower double-precision (64-bit) floating-point element from a into
+ // 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte
+ // boundary or a general-protection exception may be generated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd1
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_pd1
+ FORCE_INLINE void _mm_store_pd1(double *mem_addr, __m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6095,7 +5356,7 @@ FORCE_INLINE void _mm_store_pd1(double *mem_addr, __m128d a)
+ 
+ // Store the lower double-precision (64-bit) floating-point element from a into
+ // memory. mem_addr does not need to be aligned on any particular boundary.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_store_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_store_sd
+ FORCE_INLINE void _mm_store_sd(double *mem_addr, __m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6105,8 +5366,9 @@ FORCE_INLINE void _mm_store_sd(double *mem_addr, __m128d a)
+ #endif
+ }
+ 
+-// Stores four 32-bit integer values as (as a __m128i value) at the address p.
+-// https://msdn.microsoft.com/en-us/library/vstudio/edk11s13(v=vs.100).aspx
++// Store 128-bits of integer data from a into memory. mem_addr must be aligned
++// on a 16-byte boundary or a general-protection exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_si128
+ FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a)
+ {
+     vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a));
+@@ -6115,15 +5377,12 @@ FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a)
+ // Store the lower double-precision (64-bit) floating-point element from a into
+ // 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte
+ // boundary or a general-protection exception may be generated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=9,526,5601&text=_mm_store1_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#expand=9,526,5601&text=_mm_store1_pd
+ #define _mm_store1_pd _mm_store_pd1
+ 
+ // Store the upper double-precision (64-bit) floating-point element from a into
+ // memory.
+-//
+-//   MEM[mem_addr+63:mem_addr] := a[127:64]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeh_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeh_pd
+ FORCE_INLINE void _mm_storeh_pd(double *mem_addr, __m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6133,8 +5392,8 @@ FORCE_INLINE void _mm_storeh_pd(double *mem_addr, __m128d a)
+ #endif
+ }
+ 
+-// Reads the lower 64 bits of b and stores them into the lower 64 bits of a.
+-// https://msdn.microsoft.com/en-us/library/hhwf428f%28v=vs.90%29.aspx
++// Store 64-bit integer from the first element of a into memory.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_epi64
+ FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b)
+ {
+     vst1_u64((uint64_t *) a, vget_low_u64(vreinterpretq_u64_m128i(b)));
+@@ -6142,10 +5401,7 @@ FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b)
+ 
+ // Store the lower double-precision (64-bit) floating-point element from a into
+ // memory.
+-//
+-//   MEM[mem_addr+63:mem_addr] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storel_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_pd
+ FORCE_INLINE void _mm_storel_pd(double *mem_addr, __m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6158,11 +5414,7 @@ FORCE_INLINE void _mm_storel_pd(double *mem_addr, __m128d a)
+ // Store 2 double-precision (64-bit) floating-point elements from a into memory
+ // in reverse order. mem_addr must be aligned on a 16-byte boundary or a
+ // general-protection exception may be generated.
+-//
+-//   MEM[mem_addr+63:mem_addr] := a[127:64]
+-//   MEM[mem_addr+127:mem_addr+64] := a[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storer_pd
+ FORCE_INLINE void _mm_storer_pd(double *mem_addr, __m128d a)
+ {
+     float32x4_t f = vreinterpretq_f32_m128d(a);
+@@ -6172,21 +5424,23 @@ FORCE_INLINE void _mm_storer_pd(double *mem_addr, __m128d a)
+ // Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point
+ // elements) from a into memory. mem_addr does not need to be aligned on any
+ // particular boundary.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_pd
+ FORCE_INLINE void _mm_storeu_pd(double *mem_addr, __m128d a)
+ {
+     _mm_store_pd(mem_addr, a);
+ }
+ 
+-// Stores 128-bits of integer data a at the address p.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si128
++// Store 128-bits of integer data from a into memory. mem_addr does not need to
++// be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si128
+ FORCE_INLINE void _mm_storeu_si128(__m128i *p, __m128i a)
+ {
+     vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a));
+ }
+ 
+-// Stores 32-bits of integer data a at the address p.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si32
++// Store 32-bit integer from the first element of a into memory. mem_addr does
++// not need to be aligned on any particular boundary.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si32
+ FORCE_INLINE void _mm_storeu_si32(void *p, __m128i a)
+ {
+     vst1q_lane_s32((int32_t *) p, vreinterpretq_s32_m128i(a), 0);
+@@ -6196,7 +5450,7 @@ FORCE_INLINE void _mm_storeu_si32(void *p, __m128i a)
+ // elements) from a into memory using a non-temporal memory hint. mem_addr must
+ // be aligned on a 16-byte boundary or a general-protection exception may be
+ // generated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_pd
+ FORCE_INLINE void _mm_stream_pd(double *p, __m128d a)
+ {
+ #if __has_builtin(__builtin_nontemporal_store)
+@@ -6208,10 +5462,10 @@ FORCE_INLINE void _mm_stream_pd(double *p, __m128d a)
+ #endif
+ }
+ 
+-// Stores the data in a to the address p without polluting the caches.  If the
+-// cache line containing address p is already in the cache, the cache will be
+-// updated.
+-// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx
++// Store 128-bits of integer data from a into memory using a non-temporal memory
++// hint. mem_addr must be aligned on a 16-byte boundary or a general-protection
++// exception may be generated.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si128
+ FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a)
+ {
+ #if __has_builtin(__builtin_nontemporal_store)
+@@ -6224,7 +5478,7 @@ FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a)
+ // Store 32-bit integer a into memory using a non-temporal hint to minimize
+ // cache pollution. If the cache line containing address mem_addr is already in
+ // the cache, the cache will be updated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_si32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si32
+ FORCE_INLINE void _mm_stream_si32(int *p, int a)
+ {
+     vst1q_lane_s32((int32_t *) p, vdupq_n_s32(a), 0);
+@@ -6233,7 +5487,7 @@ FORCE_INLINE void _mm_stream_si32(int *p, int a)
+ // Store 64-bit integer a into memory using a non-temporal hint to minimize
+ // cache pollution. If the cache line containing address mem_addr is already in
+ // the cache, the cache will be updated.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si64
+ FORCE_INLINE void _mm_stream_si64(__int64 *p, __int64 a)
+ {
+     vst1_s64((int64_t *) p, vdup_n_s64((int64_t) a));
+@@ -6241,32 +5495,25 @@ FORCE_INLINE void _mm_stream_si64(__int64 *p, __int64 a)
+ 
+ // Subtract packed 16-bit integers in b from packed 16-bit integers in a, and
+ // store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi16
+ FORCE_INLINE __m128i _mm_sub_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+         vsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Subtracts the 4 signed or unsigned 32-bit integers of b from the 4 signed or
+-// unsigned 32-bit integers of a.
+-//
+-//   r0 := a0 - b0
+-//   r1 := a1 - b1
+-//   r2 := a2 - b2
+-//   r3 := a3 - b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/fhh866h0(v=vs.100).aspx
++// Subtract packed 32-bit integers in b from packed 32-bit integers in a, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi32
+ FORCE_INLINE __m128i _mm_sub_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+         vsubq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Subtract 2 packed 64-bit integers in b from 2 packed 64-bit integers in a,
+-// and store the results in dst.
+-//    r0 := a0 - b0
+-//    r1 := a1 - b1
++// Subtract packed 64-bit integers in b from packed 64-bit integers in a, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi64
+ FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s64(
+@@ -6275,7 +5522,7 @@ FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b)
+ 
+ // Subtract packed 8-bit integers in b from packed 8-bit integers in a, and
+ // store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi8
+ FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -6285,13 +5532,7 @@ FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b)
+ // Subtract packed double-precision (64-bit) floating-point elements in b from
+ // packed double-precision (64-bit) floating-point elements in a, and store the
+ // results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*64
+-//     dst[i+63:i] := a[i+63:i] - b[i+63:i]
+-//   ENDFOR
+-//
+-//  https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_sub_pd
++//  https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_sub_pd
+ FORCE_INLINE __m128d _mm_sub_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -6311,71 +5552,50 @@ FORCE_INLINE __m128d _mm_sub_pd(__m128d a, __m128d b)
+ // the lower double-precision (64-bit) floating-point element in a, store the
+ // result in the lower element of dst, and copy the upper element from a to the
+ // upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_sd
+ FORCE_INLINE __m128d _mm_sub_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_sub_pd(a, b));
+ }
+ 
+ // Subtract 64-bit integer b from 64-bit integer a, and store the result in dst.
+-//
+-//   dst[63:0] := a[63:0] - b[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_si64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_si64
+ FORCE_INLINE __m64 _mm_sub_si64(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s64(
+         vsub_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b)));
+ }
+ 
+-// Subtracts the 8 signed 16-bit integers of b from the 8 signed 16-bit integers
+-// of a and saturates.
+-//
+-//   r0 := SignedSaturate(a0 - b0)
+-//   r1 := SignedSaturate(a1 - b1)
+-//   ...
+-//   r7 := SignedSaturate(a7 - b7)
+-//
+-// https://technet.microsoft.com/en-us/subscriptions/3247z5b8(v=vs.90)
++// Subtract packed signed 16-bit integers in b from packed 16-bit integers in a
++// using saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epi16
+ FORCE_INLINE __m128i _mm_subs_epi16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s16(
+         vqsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b)));
+ }
+ 
+-// Subtracts the 16 signed 8-bit integers of b from the 16 signed 8-bit integers
+-// of a and saturates.
+-//
+-//   r0 := SignedSaturate(a0 - b0)
+-//   r1 := SignedSaturate(a1 - b1)
+-//   ...
+-//   r15 := SignedSaturate(a15 - b15)
+-//
+-// https://technet.microsoft.com/en-us/subscriptions/by7kzks1(v=vs.90)
++// Subtract packed signed 8-bit integers in b from packed 8-bit integers in a
++// using saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epi8
+ FORCE_INLINE __m128i _mm_subs_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+         vqsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b)));
+ }
+ 
+-// Subtracts the 8 unsigned 16-bit integers of bfrom the 8 unsigned 16-bit
+-// integers of a and saturates..
+-// https://technet.microsoft.com/en-us/subscriptions/index/f44y0s19(v=vs.90).aspx
++// Subtract packed unsigned 16-bit integers in b from packed unsigned 16-bit
++// integers in a using saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epu16
+ FORCE_INLINE __m128i _mm_subs_epu16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+         vqsubq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b)));
+ }
+ 
+-// Subtracts the 16 unsigned 8-bit integers of b from the 16 unsigned 8-bit
+-// integers of a and saturates.
+-//
+-//   r0 := UnsignedSaturate(a0 - b0)
+-//   r1 := UnsignedSaturate(a1 - b1)
+-//   ...
+-//   r15 := UnsignedSaturate(a15 - b15)
+-//
+-// https://technet.microsoft.com/en-us/subscriptions/yadkxc18(v=vs.90)
++// Subtract packed unsigned 8-bit integers in b from packed unsigned 8-bit
++// integers in a using saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epu8
+ FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u8(
+@@ -6390,7 +5610,7 @@ FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b)
+ #define _mm_ucomineq_sd _mm_comineq_sd
+ 
+ // Return vector of type __m128d with undefined elements.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_undefined_pd
+ FORCE_INLINE __m128d _mm_undefined_pd(void)
+ {
+ #if defined(__GNUC__) || defined(__clang__)
+@@ -6404,19 +5624,9 @@ FORCE_INLINE __m128d _mm_undefined_pd(void)
+ #endif
+ }
+ 
+-// Interleaves the upper 4 signed or unsigned 16-bit integers in a with the
+-// upper 4 signed or unsigned 16-bit integers in b.
+-//
+-//   r0 := a4
+-//   r1 := b4
+-//   r2 := a5
+-//   r3 := b5
+-//   r4 := a6
+-//   r5 := b6
+-//   r6 := a7
+-//   r7 := b7
+-//
+-// https://msdn.microsoft.com/en-us/library/03196cz7(v=vs.100).aspx
++// Unpack and interleave 16-bit integers from the high half of a and b, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi16
+ FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6430,9 +5640,9 @@ FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b)
+ #endif
+ }
+ 
+-// Interleaves the upper 2 signed or unsigned 32-bit integers in a with the
+-// upper 2 signed or unsigned 32-bit integers in b.
+-// https://msdn.microsoft.com/en-us/library/65sa7cbs(v=vs.100).aspx
++// Unpack and interleave 32-bit integers from the high half of a and b, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi32
+ FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6446,30 +5656,24 @@ FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b)
+ #endif
+ }
+ 
+-// Interleaves the upper signed or unsigned 64-bit integer in a with the
+-// upper signed or unsigned 64-bit integer in b.
+-//
+-//   r0 := a1
+-//   r1 := b1
++// Unpack and interleave 64-bit integers from the high half of a and b, and
++// store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi64
+ FORCE_INLINE __m128i _mm_unpackhi_epi64(__m128i a, __m128i b)
+ {
++#if defined(__aarch64__)
++    return vreinterpretq_m128i_s64(
++        vzip2q_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b)));
++#else
+     int64x1_t a_h = vget_high_s64(vreinterpretq_s64_m128i(a));
+     int64x1_t b_h = vget_high_s64(vreinterpretq_s64_m128i(b));
+     return vreinterpretq_m128i_s64(vcombine_s64(a_h, b_h));
++#endif
+ }
+ 
+-// Interleaves the upper 8 signed or unsigned 8-bit integers in a with the upper
+-// 8 signed or unsigned 8-bit integers in b.
+-//
+-//   r0 := a8
+-//   r1 := b8
+-//   r2 := a9
+-//   r3 := b9
+-//   ...
+-//   r14 := a15
+-//   r15 := b15
+-//
+-// https://msdn.microsoft.com/en-us/library/t5h7783k(v=vs.100).aspx
++// Unpack and interleave 8-bit integers from the high half of a and b, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi8
+ FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6487,15 +5691,7 @@ FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b)
+ 
+ // Unpack and interleave double-precision (64-bit) floating-point elements from
+ // the high half of a and b, and store the results in dst.
+-//
+-//   DEFINE INTERLEAVE_HIGH_QWORDS(src1[127:0], src2[127:0]) {
+-//     dst[63:0] := src1[127:64]
+-//     dst[127:64] := src2[127:64]
+-//     RETURN dst[127:0]
+-//   }
+-//   dst[127:0] := INTERLEAVE_HIGH_QWORDS(a[127:0], b[127:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpackhi_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_pd
+ FORCE_INLINE __m128d _mm_unpackhi_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -6508,19 +5704,9 @@ FORCE_INLINE __m128d _mm_unpackhi_pd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Interleaves the lower 4 signed or unsigned 16-bit integers in a with the
+-// lower 4 signed or unsigned 16-bit integers in b.
+-//
+-//   r0 := a0
+-//   r1 := b0
+-//   r2 := a1
+-//   r3 := b1
+-//   r4 := a2
+-//   r5 := b2
+-//   r6 := a3
+-//   r7 := b3
+-//
+-// https://msdn.microsoft.com/en-us/library/btxb17bw%28v=vs.90%29.aspx
++// Unpack and interleave 16-bit integers from the low half of a and b, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi16
+ FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6534,15 +5720,9 @@ FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b)
+ #endif
+ }
+ 
+-// Interleaves the lower 2 signed or unsigned 32 - bit integers in a with the
+-// lower 2 signed or unsigned 32 - bit integers in b.
+-//
+-//   r0 := a0
+-//   r1 := b0
+-//   r2 := a1
+-//   r3 := b1
+-//
+-// https://msdn.microsoft.com/en-us/library/x8atst9d(v=vs.100).aspx
++// Unpack and interleave 32-bit integers from the low half of a and b, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi32
+ FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6556,25 +5736,24 @@ FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b)
+ #endif
+ }
+ 
++// Unpack and interleave 64-bit integers from the low half of a and b, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi64
+ FORCE_INLINE __m128i _mm_unpacklo_epi64(__m128i a, __m128i b)
+ {
++#if defined(__aarch64__)
++    return vreinterpretq_m128i_s64(
++        vzip1q_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b)));
++#else
+     int64x1_t a_l = vget_low_s64(vreinterpretq_s64_m128i(a));
+     int64x1_t b_l = vget_low_s64(vreinterpretq_s64_m128i(b));
+     return vreinterpretq_m128i_s64(vcombine_s64(a_l, b_l));
++#endif
+ }
+ 
+-// Interleaves the lower 8 signed or unsigned 8-bit integers in a with the lower
+-// 8 signed or unsigned 8-bit integers in b.
+-//
+-//   r0 := a0
+-//   r1 := b0
+-//   r2 := a1
+-//   r3 := b1
+-//   ...
+-//   r14 := a7
+-//   r15 := b7
+-//
+-// https://msdn.microsoft.com/en-us/library/xf7k860c%28v=vs.90%29.aspx
++// Unpack and interleave 8-bit integers from the low half of a and b, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi8
+ FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b)
+ {
+ #if defined(__aarch64__)
+@@ -6590,15 +5769,7 @@ FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b)
+ 
+ // Unpack and interleave double-precision (64-bit) floating-point elements from
+ // the low half of a and b, and store the results in dst.
+-//
+-//   DEFINE INTERLEAVE_QWORDS(src1[127:0], src2[127:0]) {
+-//     dst[63:0] := src1[63:0]
+-//     dst[127:64] := src2[63:0]
+-//     RETURN dst[127:0]
+-//   }
+-//   dst[127:0] := INTERLEAVE_QWORDS(a[127:0], b[127:0])
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpacklo_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_pd
+ FORCE_INLINE __m128d _mm_unpacklo_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -6613,21 +5784,16 @@ FORCE_INLINE __m128d _mm_unpacklo_pd(__m128d a, __m128d b)
+ 
+ // Compute the bitwise XOR of packed double-precision (64-bit) floating-point
+ // elements in a and b, and store the results in dst.
+-//
+-//   FOR j := 0 to 1
+-//      i := j*64
+-//      dst[i+63:i] := a[i+63:i] XOR b[i+63:i]
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_xor_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_pd
+ FORCE_INLINE __m128d _mm_xor_pd(__m128d a, __m128d b)
+ {
+     return vreinterpretq_m128d_s64(
+         veorq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b)));
+ }
+ 
+-// Computes the bitwise XOR of the 128-bit value in a and the 128-bit value in
+-// b.  https://msdn.microsoft.com/en-us/library/fzt08www(v=vs.100).aspx
++// Compute the bitwise XOR of 128 bits (representing integer data) in a and b,
++// and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_si128
+ FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+@@ -6639,17 +5805,7 @@ FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b)
+ // Alternatively add and subtract packed double-precision (64-bit)
+ // floating-point elements in a to/from packed elements in b, and store the
+ // results in dst.
+-//
+-// FOR j := 0 to 1
+-//   i := j*64
+-//   IF ((j & 1) == 0)
+-//     dst[i+63:i] := a[i+63:i] - b[i+63:i]
+-//   ELSE
+-//     dst[i+63:i] := a[i+63:i] + b[i+63:i]
+-//   FI
+-// ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_addsub_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_addsub_pd
+ FORCE_INLINE __m128d _mm_addsub_pd(__m128d a, __m128d b)
+ {
+     _sse2neon_const __m128d mask = _mm_set_pd(1.0f, -1.0f);
+@@ -6665,7 +5821,7 @@ FORCE_INLINE __m128d _mm_addsub_pd(__m128d a, __m128d b)
+ // Alternatively add and subtract packed single-precision (32-bit)
+ // floating-point elements in a to/from packed elements in b, and store the
+ // results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=addsub_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=addsub_ps
+ FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b)
+ {
+     _sse2neon_const __m128 mask = _mm_setr_ps(-1.0f, 1.0f, -1.0f, 1.0f);
+@@ -6680,7 +5836,7 @@ FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b)
+ 
+ // Horizontally add adjacent pairs of double-precision (64-bit) floating-point
+ // elements in a and b, and pack the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pd
+ FORCE_INLINE __m128d _mm_hadd_pd(__m128d a, __m128d b)
+ {
+ #if defined(__aarch64__)
+@@ -6694,9 +5850,9 @@ FORCE_INLINE __m128d _mm_hadd_pd(__m128d a, __m128d b)
+ #endif
+ }
+ 
+-// Computes pairwise add of each argument as single-precision, floating-point
+-// values a and b.
+-// https://msdn.microsoft.com/en-us/library/yd9wecaa.aspx
++// Horizontally add adjacent pairs of single-precision (32-bit) floating-point
++// elements in a and b, and pack the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_ps
+ FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b)
+ {
+ #if defined(__aarch64__)
+@@ -6714,7 +5870,7 @@ FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b)
+ 
+ // Horizontally subtract adjacent pairs of double-precision (64-bit)
+ // floating-point elements in a and b, and pack the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_pd
+ FORCE_INLINE __m128d _mm_hsub_pd(__m128d _a, __m128d _b)
+ {
+ #if defined(__aarch64__)
+@@ -6732,7 +5888,7 @@ FORCE_INLINE __m128d _mm_hsub_pd(__m128d _a, __m128d _b)
+ 
+ // Horizontally subtract adjacent pairs of single-precision (32-bit)
+ // floating-point elements in a and b, and pack the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_ps
+ FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b)
+ {
+     float32x4_t a = vreinterpretq_f32_m128(_a);
+@@ -6749,24 +5905,17 @@ FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b)
+ // Load 128-bits of integer data from unaligned memory into dst. This intrinsic
+ // may perform better than _mm_loadu_si128 when the data crosses a cache line
+ // boundary.
+-//
+-//   dst[127:0] := MEM[mem_addr+127:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_lddqu_si128
+ #define _mm_lddqu_si128 _mm_loadu_si128
+ 
+ // Load a double-precision (64-bit) floating-point element from memory into both
+ // elements of dst.
+-//
+-//   dst[63:0] := MEM[mem_addr+63:mem_addr]
+-//   dst[127:64] := MEM[mem_addr+63:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loaddup_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loaddup_pd
+ #define _mm_loaddup_pd _mm_load1_pd
+ 
+ // Duplicate the low double-precision (64-bit) floating-point element from a,
+ // and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movedup_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movedup_pd
+ FORCE_INLINE __m128d _mm_movedup_pd(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -6780,7 +5929,7 @@ FORCE_INLINE __m128d _mm_movedup_pd(__m128d a)
+ 
+ // Duplicate odd-indexed single-precision (32-bit) floating-point elements
+ // from a, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movehdup_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movehdup_ps
+ FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a)
+ {
+ #if defined(__aarch64__)
+@@ -6799,7 +5948,7 @@ FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a)
+ 
+ // Duplicate even-indexed single-precision (32-bit) floating-point elements
+ // from a, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_moveldup_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_moveldup_ps
+ FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a)
+ {
+ #if defined(__aarch64__)
+@@ -6820,13 +5969,7 @@ FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a)
+ 
+ // Compute the absolute value of packed signed 16-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*16
+-//     dst[i+15:i] := ABS(a[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi16
+ FORCE_INLINE __m128i _mm_abs_epi16(__m128i a)
+ {
+     return vreinterpretq_m128i_s16(vabsq_s16(vreinterpretq_s16_m128i(a)));
+@@ -6834,13 +5977,7 @@ FORCE_INLINE __m128i _mm_abs_epi16(__m128i a)
+ 
+ // Compute the absolute value of packed signed 32-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*32
+-//     dst[i+31:i] := ABS(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi32
+ FORCE_INLINE __m128i _mm_abs_epi32(__m128i a)
+ {
+     return vreinterpretq_m128i_s32(vabsq_s32(vreinterpretq_s32_m128i(a)));
+@@ -6848,13 +5985,7 @@ FORCE_INLINE __m128i _mm_abs_epi32(__m128i a)
+ 
+ // Compute the absolute value of packed signed 8-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 15
+-//     i := j*8
+-//     dst[i+7:i] := ABS(a[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi8
+ FORCE_INLINE __m128i _mm_abs_epi8(__m128i a)
+ {
+     return vreinterpretq_m128i_s8(vabsq_s8(vreinterpretq_s8_m128i(a)));
+@@ -6862,13 +5993,7 @@ FORCE_INLINE __m128i _mm_abs_epi8(__m128i a)
+ 
+ // Compute the absolute value of packed signed 16-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 3
+-//     i := j*16
+-//     dst[i+15:i] := ABS(a[i+15:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi16
+ FORCE_INLINE __m64 _mm_abs_pi16(__m64 a)
+ {
+     return vreinterpret_m64_s16(vabs_s16(vreinterpret_s16_m64(a)));
+@@ -6876,13 +6001,7 @@ FORCE_INLINE __m64 _mm_abs_pi16(__m64 a)
+ 
+ // Compute the absolute value of packed signed 32-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 1
+-//     i := j*32
+-//     dst[i+31:i] := ABS(a[i+31:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi32
+ FORCE_INLINE __m64 _mm_abs_pi32(__m64 a)
+ {
+     return vreinterpret_m64_s32(vabs_s32(vreinterpret_s32_m64(a)));
+@@ -6890,13 +6009,7 @@ FORCE_INLINE __m64 _mm_abs_pi32(__m64 a)
+ 
+ // Compute the absolute value of packed signed 8-bit integers in a, and store
+ // the unsigned results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*8
+-//     dst[i+7:i] := ABS(a[i+7:i])
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi8
+ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a)
+ {
+     return vreinterpret_m64_s8(vabs_s8(vreinterpret_s8_m64(a)));
+@@ -6904,11 +6017,7 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a)
+ 
+ // Concatenate 16-byte blocks in a and b into a 32-byte temporary result, shift
+ // the result right by imm8 bytes, and store the low 16 bytes in dst.
+-//
+-//   tmp[255:0] := ((a[127:0] << 128)[255:0] OR b[127:0]) >> (imm8*8)
+-//   dst[127:0] := tmp[127:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_alignr_epi8
+ #define _mm_alignr_epi8(a, b, imm)                                            \
+     __extension__({                                                           \
+         uint8x16_t _a = vreinterpretq_u8_m128i(a);                            \
+@@ -6926,11 +6035,7 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a)
+ 
+ // Concatenate 8-byte blocks in a and b into a 16-byte temporary result, shift
+ // the result right by imm8 bytes, and store the low 8 bytes in dst.
+-//
+-//   tmp[127:0] := ((a[63:0] << 64)[127:0] OR b[63:0]) >> (imm8*8)
+-//   dst[63:0] := tmp[63:0]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_alignr_pi8
+ #define _mm_alignr_pi8(a, b, imm)                                           \
+     __extension__({                                                         \
+         __m64 ret;                                                          \
+@@ -6953,8 +6058,9 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a)
+         ret;                                                                \
+     })
+ 
+-// Computes pairwise add of each argument as a 16-bit signed or unsigned integer
+-// values a and b.
++// Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the
++// signed 16-bit results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_epi16
+ FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b)
+ {
+     int16x8_t a = vreinterpretq_s16_m128i(_a);
+@@ -6968,8 +6074,9 @@ FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b)
+ #endif
+ }
+ 
+-// Computes pairwise add of each argument as a 32-bit signed or unsigned integer
+-// values a and b.
++// Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the
++// signed 32-bit results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_epi32
+ FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b)
+ {
+     int32x4_t a = vreinterpretq_s32_m128i(_a);
+@@ -6985,7 +6092,7 @@ FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b)
+ 
+ // Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the
+ // signed 16-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pi16
+ FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s16(
+@@ -6994,15 +6101,16 @@ FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b)
+ 
+ // Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the
+ // signed 32-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pi32
+ FORCE_INLINE __m64 _mm_hadd_pi32(__m64 a, __m64 b)
+ {
+     return vreinterpret_m64_s32(
+         vpadd_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b)));
+ }
+ 
+-// Computes saturated pairwise sub of each argument as a 16-bit signed
+-// integer values a and b.
++// Horizontally add adjacent pairs of signed 16-bit integers in a and b using
++// saturation, and pack the signed 16-bit results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadds_epi16
+ FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b)
+ {
+ #if defined(__aarch64__)
+@@ -7025,7 +6133,7 @@ FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b)
+ 
+ // Horizontally add adjacent pairs of signed 16-bit integers in a and b using
+ // saturation, and pack the signed 16-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadds_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadds_pi16
+ FORCE_INLINE __m64 _mm_hadds_pi16(__m64 _a, __m64 _b)
+ {
+     int16x4_t a = vreinterpret_s16_m64(_a);
+@@ -7040,7 +6148,7 @@ FORCE_INLINE __m64 _mm_hadds_pi16(__m64 _a, __m64 _b)
+ 
+ // Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack
+ // the signed 16-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_epi16
+ FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b)
+ {
+     int16x8_t a = vreinterpretq_s16_m128i(_a);
+@@ -7056,7 +6164,7 @@ FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b)
+ 
+ // Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack
+ // the signed 32-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_epi32
+ FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b)
+ {
+     int32x4_t a = vreinterpretq_s32_m128i(_a);
+@@ -7072,7 +6180,7 @@ FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b)
+ 
+ // Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack
+ // the signed 16-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_pi16
+ FORCE_INLINE __m64 _mm_hsub_pi16(__m64 _a, __m64 _b)
+ {
+     int16x4_t a = vreinterpret_s16_m64(_a);
+@@ -7087,7 +6195,7 @@ FORCE_INLINE __m64 _mm_hsub_pi16(__m64 _a, __m64 _b)
+ 
+ // Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack
+ // the signed 32-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_hsub_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_hsub_pi32
+ FORCE_INLINE __m64 _mm_hsub_pi32(__m64 _a, __m64 _b)
+ {
+     int32x2_t a = vreinterpret_s32_m64(_a);
+@@ -7100,9 +6208,9 @@ FORCE_INLINE __m64 _mm_hsub_pi32(__m64 _a, __m64 _b)
+ #endif
+ }
+ 
+-// Computes saturated pairwise difference of each argument as a 16-bit signed
+-// integer values a and b.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_epi16
++// Horizontally subtract adjacent pairs of signed 16-bit integers in a and b
++// using saturation, and pack the signed 16-bit results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsubs_epi16
+ FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b)
+ {
+     int16x8_t a = vreinterpretq_s16_m128i(_a);
+@@ -7118,7 +6226,7 @@ FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b)
+ 
+ // Horizontally subtract adjacent pairs of signed 16-bit integers in a and b
+ // using saturation, and pack the signed 16-bit results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsubs_pi16
+ FORCE_INLINE __m64 _mm_hsubs_pi16(__m64 _a, __m64 _b)
+ {
+     int16x4_t a = vreinterpret_s16_m64(_a);
+@@ -7135,12 +6243,7 @@ FORCE_INLINE __m64 _mm_hsubs_pi16(__m64 _a, __m64 _b)
+ // signed 8-bit integer from b, producing intermediate signed 16-bit integers.
+ // Horizontally add adjacent pairs of intermediate signed 16-bit integers,
+ // and pack the saturated results in dst.
+-//
+-//   FOR j := 0 to 7
+-//      i := j*16
+-//      dst[i+15:i] := Saturate_To_Int16( a[i+15:i+8]*b[i+15:i+8] +
+-//      a[i+7:i]*b[i+7:i] )
+-//   ENDFOR
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_epi16
+ FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b)
+ {
+ #if defined(__aarch64__)
+@@ -7179,7 +6282,7 @@ FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b)
+ // signed 8-bit integer from b, producing intermediate signed 16-bit integers.
+ // Horizontally add adjacent pairs of intermediate signed 16-bit integers, and
+ // pack the saturated results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maddubs_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_pi16
+ FORCE_INLINE __m64 _mm_maddubs_pi16(__m64 _a, __m64 _b)
+ {
+     uint16x4_t a = vreinterpret_u16_m64(_a);
+@@ -7204,12 +6307,7 @@ FORCE_INLINE __m64 _mm_maddubs_pi16(__m64 _a, __m64 _b)
+ // Multiply packed signed 16-bit integers in a and b, producing intermediate
+ // signed 32-bit integers. Shift right by 15 bits while rounding up, and store
+ // the packed 16-bit integers in dst.
+-//
+-//   r0 := Round(((int32_t)a0 * (int32_t)b0) >> 15)
+-//   r1 := Round(((int32_t)a1 * (int32_t)b1) >> 15)
+-//   r2 := Round(((int32_t)a2 * (int32_t)b2) >> 15)
+-//   ...
+-//   r7 := Round(((int32_t)a7 * (int32_t)b7) >> 15)
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_epi16
+ FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b)
+ {
+     // Has issues due to saturation
+@@ -7233,7 +6331,7 @@ FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b)
+ // Multiply packed signed 16-bit integers in a and b, producing intermediate
+ // signed 32-bit integers. Truncate each intermediate integer to the 18 most
+ // significant bits, round by adding 1, and store bits [16:1] to dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhrs_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_pi16
+ FORCE_INLINE __m64 _mm_mulhrs_pi16(__m64 a, __m64 b)
+ {
+     int32x4_t mul_extend =
+@@ -7245,7 +6343,7 @@ FORCE_INLINE __m64 _mm_mulhrs_pi16(__m64 a, __m64 b)
+ 
+ // Shuffle packed 8-bit integers in a according to shuffle control mask in the
+ // corresponding 8-bit element of b, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi8
+ FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b)
+ {
+     int8x16_t tbl = vreinterpretq_s8_m128i(a);   // input a
+@@ -7275,18 +6373,7 @@ FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b)
+ 
+ // Shuffle packed 8-bit integers in a according to shuffle control mask in the
+ // corresponding 8-bit element of b, and store the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//     i := j*8
+-//     IF b[i+7] == 1
+-//       dst[i+7:i] := 0
+-//     ELSE
+-//       index[2:0] := b[i+2:i]
+-//       dst[i+7:i] := a[index*8+7:index*8]
+-//     FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pi8
+ FORCE_INLINE __m64 _mm_shuffle_pi8(__m64 a, __m64 b)
+ {
+     const int8x8_t controlMask =
+@@ -7299,16 +6386,7 @@ FORCE_INLINE __m64 _mm_shuffle_pi8(__m64 a, __m64 b)
+ // 16-bit integer in b is negative, and store the results in dst.
+ // Element in dst are zeroed out when the corresponding element
+ // in b is zero.
+-//
+-//   for i in 0..7
+-//     if b[i] < 0
+-//       r[i] := -a[i]
+-//     else if b[i] == 0
+-//       r[i] := 0
+-//     else
+-//       r[i] := a[i]
+-//     fi
+-//   done
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi16
+ FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b)
+ {
+     int16x8_t a = vreinterpretq_s16_m128i(_a);
+@@ -7336,16 +6414,7 @@ FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b)
+ // 32-bit integer in b is negative, and store the results in dst.
+ // Element in dst are zeroed out when the corresponding element
+ // in b is zero.
+-//
+-//   for i in 0..3
+-//     if b[i] < 0
+-//       r[i] := -a[i]
+-//     else if b[i] == 0
+-//       r[i] := 0
+-//     else
+-//       r[i] := a[i]
+-//     fi
+-//   done
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi32
+ FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b)
+ {
+     int32x4_t a = vreinterpretq_s32_m128i(_a);
+@@ -7374,16 +6443,7 @@ FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b)
+ // 8-bit integer in b is negative, and store the results in dst.
+ // Element in dst are zeroed out when the corresponding element
+ // in b is zero.
+-//
+-//   for i in 0..15
+-//     if b[i] < 0
+-//       r[i] := -a[i]
+-//     else if b[i] == 0
+-//       r[i] := 0
+-//     else
+-//       r[i] := a[i]
+-//     fi
+-//   done
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi8
+ FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b)
+ {
+     int8x16_t a = vreinterpretq_s8_m128i(_a);
+@@ -7412,19 +6472,7 @@ FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b)
+ // Negate packed 16-bit integers in a when the corresponding signed 16-bit
+ // integer in b is negative, and store the results in dst. Element in dst are
+ // zeroed out when the corresponding element in b is zero.
+-//
+-//   FOR j := 0 to 3
+-//      i := j*16
+-//      IF b[i+15:i] < 0
+-//        dst[i+15:i] := -(a[i+15:i])
+-//      ELSE IF b[i+15:i] == 0
+-//        dst[i+15:i] := 0
+-//      ELSE
+-//        dst[i+15:i] := a[i+15:i]
+-//      FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi16
+ FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b)
+ {
+     int16x4_t a = vreinterpret_s16_m64(_a);
+@@ -7453,19 +6501,7 @@ FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b)
+ // Negate packed 32-bit integers in a when the corresponding signed 32-bit
+ // integer in b is negative, and store the results in dst. Element in dst are
+ // zeroed out when the corresponding element in b is zero.
+-//
+-//   FOR j := 0 to 1
+-//      i := j*32
+-//      IF b[i+31:i] < 0
+-//        dst[i+31:i] := -(a[i+31:i])
+-//      ELSE IF b[i+31:i] == 0
+-//        dst[i+31:i] := 0
+-//      ELSE
+-//        dst[i+31:i] := a[i+31:i]
+-//      FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi32
+ FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b)
+ {
+     int32x2_t a = vreinterpret_s32_m64(_a);
+@@ -7494,19 +6530,7 @@ FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b)
+ // Negate packed 8-bit integers in a when the corresponding signed 8-bit integer
+ // in b is negative, and store the results in dst. Element in dst are zeroed out
+ // when the corresponding element in b is zero.
+-//
+-//   FOR j := 0 to 7
+-//      i := j*8
+-//      IF b[i+7:i] < 0
+-//        dst[i+7:i] := -(a[i+7:i])
+-//      ELSE IF b[i+7:i] == 0
+-//        dst[i+7:i] := 0
+-//      ELSE
+-//        dst[i+7:i] := a[i+7:i]
+-//      FI
+-//   ENDFOR
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi8
+ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b)
+ {
+     int8x8_t a = vreinterpret_s8_m64(_a);
+@@ -7536,15 +6560,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b)
+ 
+ // Blend packed 16-bit integers from a and b using control mask imm8, and store
+ // the results in dst.
+-//
+-//   FOR j := 0 to 7
+-//       i := j*16
+-//       IF imm8[j]
+-//           dst[i+15:i] := b[i+15:i]
+-//       ELSE
+-//           dst[i+15:i] := a[i+15:i]
+-//       FI
+-//   ENDFOR
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_epi16
+ // FORCE_INLINE __m128i _mm_blend_epi16(__m128i a, __m128i b,
+ //                                      __constrange(0,255) int imm)
+ #define _mm_blend_epi16(a, b, imm)                                            \
+@@ -7565,7 +6581,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b)
+ 
+ // Blend packed double-precision (64-bit) floating-point elements from a and b
+ // using control mask imm8, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_pd
+ #define _mm_blend_pd(a, b, imm)                                \
+     __extension__({                                            \
+         const uint64_t _mask[2] = {                            \
+@@ -7579,7 +6595,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b)
+ 
+ // Blend packed single-precision (32-bit) floating-point elements from a and b
+ // using mask, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_ps
+ FORCE_INLINE __m128 _mm_blend_ps(__m128 _a, __m128 _b, const char imm8)
+ {
+     const uint32_t ALIGN_STRUCT(16)
+@@ -7595,15 +6611,7 @@ FORCE_INLINE __m128 _mm_blend_ps(__m128 _a, __m128 _b, const char imm8)
+ 
+ // Blend packed 8-bit integers from a and b using mask, and store the results in
+ // dst.
+-//
+-//   FOR j := 0 to 15
+-//       i := j*8
+-//       IF mask[i+7]
+-//           dst[i+7:i] := b[i+7:i]
+-//       ELSE
+-//           dst[i+7:i] := a[i+7:i]
+-//       FI
+-//   ENDFOR
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8
+ FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask)
+ {
+     // Use a signed shift right to create a mask with the sign bit
+@@ -7616,7 +6624,7 @@ FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask)
+ 
+ // Blend packed double-precision (64-bit) floating-point elements from a and b
+ // using mask, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_pd
+ FORCE_INLINE __m128d _mm_blendv_pd(__m128d _a, __m128d _b, __m128d _mask)
+ {
+     uint64x2_t mask =
+@@ -7634,7 +6642,7 @@ FORCE_INLINE __m128d _mm_blendv_pd(__m128d _a, __m128d _b, __m128d _mask)
+ 
+ // Blend packed single-precision (32-bit) floating-point elements from a and b
+ // using mask, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_ps
+ FORCE_INLINE __m128 _mm_blendv_ps(__m128 _a, __m128 _b, __m128 _mask)
+ {
+     // Use a signed shift right to create a mask with the sign bit
+@@ -7648,7 +6656,7 @@ FORCE_INLINE __m128 _mm_blendv_ps(__m128 _a, __m128 _b, __m128 _mask)
+ // Round the packed double-precision (64-bit) floating-point elements in a up
+ // to an integer value, and store the results as packed double-precision
+ // floating-point elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_pd
+ FORCE_INLINE __m128d _mm_ceil_pd(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -7662,7 +6670,7 @@ FORCE_INLINE __m128d _mm_ceil_pd(__m128d a)
+ // Round the packed single-precision (32-bit) floating-point elements in a up to
+ // an integer value, and store the results as packed single-precision
+ // floating-point elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_ps
+ FORCE_INLINE __m128 _mm_ceil_ps(__m128 a)
+ {
+ #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING)
+@@ -7677,7 +6685,7 @@ FORCE_INLINE __m128 _mm_ceil_ps(__m128 a)
+ // an integer value, store the result as a double-precision floating-point
+ // element in the lower element of dst, and copy the upper element from a to the
+ // upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_sd
+ FORCE_INLINE __m128d _mm_ceil_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_ceil_pd(b));
+@@ -7687,11 +6695,7 @@ FORCE_INLINE __m128d _mm_ceil_sd(__m128d a, __m128d b)
+ // an integer value, store the result as a single-precision floating-point
+ // element in the lower element of dst, and copy the upper 3 packed elements
+ // from a to the upper elements of dst.
+-//
+-//   dst[31:0] := CEIL(b[31:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_ss
+ FORCE_INLINE __m128 _mm_ceil_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_ceil_ps(b));
+@@ -7714,16 +6718,18 @@ FORCE_INLINE __m128i _mm_cmpeq_epi64(__m128i a, __m128i b)
+ #endif
+ }
+ 
+-// Converts the four signed 16-bit integers in the lower 64 bits to four signed
+-// 32-bit integers.
++// Sign extend packed 16-bit integers in a to packed 32-bit integers, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi16_epi32
+ FORCE_INLINE __m128i _mm_cvtepi16_epi32(__m128i a)
+ {
+     return vreinterpretq_m128i_s32(
+         vmovl_s16(vget_low_s16(vreinterpretq_s16_m128i(a))));
+ }
+ 
+-// Converts the two signed 16-bit integers in the lower 32 bits two signed
+-// 32-bit integers.
++// Sign extend packed 16-bit integers in a to packed 64-bit integers, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi16_epi64
+ FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a)
+ {
+     int16x8_t s16x8 = vreinterpretq_s16_m128i(a);     /* xxxx xxxx xxxx 0B0A */
+@@ -7732,16 +6738,18 @@ FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a)
+     return vreinterpretq_m128i_s64(s64x2);
+ }
+ 
+-// Converts the two signed 32-bit integers in the lower 64 bits to two signed
+-// 64-bit integers.
++// Sign extend packed 32-bit integers in a to packed 64-bit integers, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_epi64
+ FORCE_INLINE __m128i _mm_cvtepi32_epi64(__m128i a)
+ {
+     return vreinterpretq_m128i_s64(
+         vmovl_s32(vget_low_s32(vreinterpretq_s32_m128i(a))));
+ }
+ 
+-// Converts the four unsigned 8-bit integers in the lower 16 bits to four
+-// unsigned 32-bit integers.
++// Sign extend packed 8-bit integers in a to packed 16-bit integers, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi16
+ FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a)
+ {
+     int8x16_t s8x16 = vreinterpretq_s8_m128i(a);    /* xxxx xxxx xxxx DCBA */
+@@ -7749,8 +6757,9 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a)
+     return vreinterpretq_m128i_s16(s16x8);
+ }
+ 
+-// Converts the four unsigned 8-bit integers in the lower 32 bits to four
+-// unsigned 32-bit integers.
++// Sign extend packed 8-bit integers in a to packed 32-bit integers, and store
++// the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi32
+ FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a)
+ {
+     int8x16_t s8x16 = vreinterpretq_s8_m128i(a);      /* xxxx xxxx xxxx DCBA */
+@@ -7759,8 +6768,9 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a)
+     return vreinterpretq_m128i_s32(s32x4);
+ }
+ 
+-// Converts the two signed 8-bit integers in the lower 32 bits to four
+-// signed 64-bit integers.
++// Sign extend packed 8-bit integers in the low 8 bytes of a to packed 64-bit
++// integers, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi64
+ FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a)
+ {
+     int8x16_t s8x16 = vreinterpretq_s8_m128i(a);      /* xxxx xxxx xxxx xxBA */
+@@ -7770,16 +6780,18 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a)
+     return vreinterpretq_m128i_s64(s64x2);
+ }
+ 
+-// Converts the four unsigned 16-bit integers in the lower 64 bits to four
+-// unsigned 32-bit integers.
++// Zero extend packed unsigned 16-bit integers in a to packed 32-bit integers,
++// and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu16_epi32
+ FORCE_INLINE __m128i _mm_cvtepu16_epi32(__m128i a)
+ {
+     return vreinterpretq_m128i_u32(
+         vmovl_u16(vget_low_u16(vreinterpretq_u16_m128i(a))));
+ }
+ 
+-// Converts the two unsigned 16-bit integers in the lower 32 bits to two
+-// unsigned 64-bit integers.
++// Zero extend packed unsigned 16-bit integers in a to packed 64-bit integers,
++// and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu16_epi64
+ FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a)
+ {
+     uint16x8_t u16x8 = vreinterpretq_u16_m128i(a);     /* xxxx xxxx xxxx 0B0A */
+@@ -7788,8 +6800,9 @@ FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a)
+     return vreinterpretq_m128i_u64(u64x2);
+ }
+ 
+-// Converts the two unsigned 32-bit integers in the lower 64 bits to two
+-// unsigned 64-bit integers.
++// Zero extend packed unsigned 32-bit integers in a to packed 64-bit integers,
++// and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu32_epi64
+ FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a)
+ {
+     return vreinterpretq_m128i_u64(
+@@ -7798,7 +6811,7 @@ FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a)
+ 
+ // Zero extend packed unsigned 8-bit integers in a to packed 16-bit integers,
+ // and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepu8_epi16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi16
+ FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a)
+ {
+     uint8x16_t u8x16 = vreinterpretq_u8_m128i(a);    /* xxxx xxxx HGFE DCBA */
+@@ -7806,9 +6819,9 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a)
+     return vreinterpretq_m128i_u16(u16x8);
+ }
+ 
+-// Converts the four unsigned 8-bit integers in the lower 32 bits to four
+-// unsigned 32-bit integers.
+-// https://msdn.microsoft.com/en-us/library/bb531467%28v=vs.100%29.aspx
++// Zero extend packed unsigned 8-bit integers in a to packed 32-bit integers,
++// and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi32
+ FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a)
+ {
+     uint8x16_t u8x16 = vreinterpretq_u8_m128i(a);      /* xxxx xxxx xxxx DCBA */
+@@ -7817,8 +6830,9 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a)
+     return vreinterpretq_m128i_u32(u32x4);
+ }
+ 
+-// Converts the two unsigned 8-bit integers in the lower 16 bits to two
+-// unsigned 64-bit integers.
++// Zero extend packed unsigned 8-bit integers in the low 8 byte sof a to packed
++// 64-bit integers, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi64
+ FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a)
+ {
+     uint8x16_t u8x16 = vreinterpretq_u8_m128i(a);      /* xxxx xxxx xxxx xxBA */
+@@ -7831,7 +6845,7 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a)
+ // Conditionally multiply the packed double-precision (64-bit) floating-point
+ // elements in a and b using the high 4 bits in imm8, sum the four products, and
+ // conditionally store the sum in dst using the low 4 bits of imm8.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dp_pd
+ FORCE_INLINE __m128d _mm_dp_pd(__m128d a, __m128d b, const int imm)
+ {
+     // Generate mask value from constant immediate bit value
+@@ -7877,7 +6891,7 @@ FORCE_INLINE __m128d _mm_dp_pd(__m128d a, __m128d b, const int imm)
+ // Conditionally multiply the packed single-precision (32-bit) floating-point
+ // elements in a and b using the high 4 bits in imm8, sum the four products,
+ // and conditionally store the sum in dst using the low 4 bits of imm.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dp_ps
+ FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm)
+ {
+ #if defined(__aarch64__)
+@@ -7918,22 +6932,24 @@ FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm)
+     return vreinterpretq_m128_f32(res);
+ }
+ 
+-// Extracts the selected signed or unsigned 32-bit integer from a and zero
+-// extends.
++// Extract a 32-bit integer from a, selected with imm8, and store the result in
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi32
+ // FORCE_INLINE int _mm_extract_epi32(__m128i a, __constrange(0,4) int imm)
+ #define _mm_extract_epi32(a, imm) \
+     vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm))
+ 
+-// Extracts the selected signed or unsigned 64-bit integer from a and zero
+-// extends.
++// Extract a 64-bit integer from a, selected with imm8, and store the result in
++// dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi64
+ // FORCE_INLINE __int64 _mm_extract_epi64(__m128i a, __constrange(0,2) int imm)
+ #define _mm_extract_epi64(a, imm) \
+     vgetq_lane_s64(vreinterpretq_s64_m128i(a), (imm))
+ 
+-// Extracts the selected signed or unsigned 8-bit integer from a and zero
+-// extends.
+-// FORCE_INLINE int _mm_extract_epi8(__m128i a, __constrange(0,16) int imm)
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_epi8
++// Extract an 8-bit integer from a, selected with imm8, and store the result in
++// the lower element of dst. FORCE_INLINE int _mm_extract_epi8(__m128i a,
++// __constrange(0,16) int imm)
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi8
+ #define _mm_extract_epi8(a, imm) vgetq_lane_u8(vreinterpretq_u8_m128i(a), (imm))
+ 
+ // Extracts the selected single-precision (32-bit) floating-point from a.
+@@ -7943,7 +6959,7 @@ FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm)
+ // Round the packed double-precision (64-bit) floating-point elements in a down
+ // to an integer value, and store the results as packed double-precision
+ // floating-point elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_pd
+ FORCE_INLINE __m128d _mm_floor_pd(__m128d a)
+ {
+ #if defined(__aarch64__)
+@@ -7957,7 +6973,7 @@ FORCE_INLINE __m128d _mm_floor_pd(__m128d a)
+ // Round the packed single-precision (32-bit) floating-point elements in a down
+ // to an integer value, and store the results as packed single-precision
+ // floating-point elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_ps
+ FORCE_INLINE __m128 _mm_floor_ps(__m128 a)
+ {
+ #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING)
+@@ -7972,7 +6988,7 @@ FORCE_INLINE __m128 _mm_floor_ps(__m128 a)
+ // an integer value, store the result as a double-precision floating-point
+ // element in the lower element of dst, and copy the upper element from a to the
+ // upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_sd
+ FORCE_INLINE __m128d _mm_floor_sd(__m128d a, __m128d b)
+ {
+     return _mm_move_sd(a, _mm_floor_pd(b));
+@@ -7982,18 +6998,15 @@ FORCE_INLINE __m128d _mm_floor_sd(__m128d a, __m128d b)
+ // an integer value, store the result as a single-precision floating-point
+ // element in the lower element of dst, and copy the upper 3 packed elements
+ // from a to the upper elements of dst.
+-//
+-//   dst[31:0] := FLOOR(b[31:0])
+-//   dst[127:32] := a[127:32]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_ss
+ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b)
+ {
+     return _mm_move_ss(a, _mm_floor_ps(b));
+ }
+ 
+-// Inserts the least significant 32 bits of b into the selected 32-bit integer
+-// of a.
++// Copy a to dst, and insert the 32-bit integer i into dst at the location
++// specified by imm8.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi32
+ // FORCE_INLINE __m128i _mm_insert_epi32(__m128i a, int b,
+ //                                       __constrange(0,4) int imm)
+ #define _mm_insert_epi32(a, b, imm)                                  \
+@@ -8002,8 +7015,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b)
+             vsetq_lane_s32((b), vreinterpretq_s32_m128i(a), (imm))); \
+     })
+ 
+-// Inserts the least significant 64 bits of b into the selected 64-bit integer
+-// of a.
++// Copy a to dst, and insert the 64-bit integer i into dst at the location
++// specified by imm8.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi64
+ // FORCE_INLINE __m128i _mm_insert_epi64(__m128i a, __int64 b,
+ //                                       __constrange(0,2) int imm)
+ #define _mm_insert_epi64(a, b, imm)                                  \
+@@ -8012,8 +7026,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b)
+             vsetq_lane_s64((b), vreinterpretq_s64_m128i(a), (imm))); \
+     })
+ 
+-// Inserts the least significant 8 bits of b into the selected 8-bit integer
+-// of a.
++// Copy a to dst, and insert the lower 8-bit integer from i into dst at the
++// location specified by imm8.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi8
+ // FORCE_INLINE __m128i _mm_insert_epi8(__m128i a, int b,
+ //                                      __constrange(0,16) int imm)
+ #define _mm_insert_epi8(a, b, imm)                                 \
+@@ -8025,7 +7040,7 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b)
+ // Copy a to tmp, then insert a single-precision (32-bit) floating-point
+ // element from b into tmp using the control in imm8. Store tmp to dst using
+ // the mask in imm8 (elements are zeroed out when the corresponding bit is set).
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=insert_ps
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=insert_ps
+ #define _mm_insert_ps(a, b, imm8)                                              \
+     __extension__({                                                            \
+         float32x4_t tmp1 =                                                     \
+@@ -8045,17 +7060,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b)
+             vbslq_f32(mask, all_zeros, vreinterpretq_f32_m128(tmp2)));         \
+     })
+ 
+-// epi versions of min/max
+-// Computes the pariwise maximums of the four signed 32-bit integer values of a
+-// and b.
+-//
+-// A 128-bit parameter that can be defined with the following equations:
+-//   r0 := (a0 > b0) ? a0 : b0
+-//   r1 := (a1 > b1) ? a1 : b1
+-//   r2 := (a2 > b2) ? a2 : b2
+-//   r3 := (a3 > b3) ? a3 : b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/bb514055(v=vs.100).aspx
++// Compare packed signed 32-bit integers in a and b, and store packed maximum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi32
+ FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+@@ -8064,7 +7071,7 @@ FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b)
+ 
+ // Compare packed signed 8-bit integers in a and b, and store packed maximum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi8
+ FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -8073,7 +7080,7 @@ FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b)
+ 
+ // Compare packed unsigned 16-bit integers in a and b, and store packed maximum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu16
+ FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+@@ -8082,23 +7089,16 @@ FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b)
+ 
+ // Compare packed unsigned 32-bit integers in a and b, and store packed maximum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu32
+ FORCE_INLINE __m128i _mm_max_epu32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u32(
+         vmaxq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b)));
+ }
+ 
+-// Computes the pariwise minima of the four signed 32-bit integer values of a
+-// and b.
+-//
+-// A 128-bit parameter that can be defined with the following equations:
+-//   r0 := (a0 < b0) ? a0 : b0
+-//   r1 := (a1 < b1) ? a1 : b1
+-//   r2 := (a2 < b2) ? a2 : b2
+-//   r3 := (a3 < b3) ? a3 : b3
+-//
+-// https://msdn.microsoft.com/en-us/library/vstudio/bb531476(v=vs.100).aspx
++// Compare packed signed 32-bit integers in a and b, and store packed minimum
++// values in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi32
+ FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+@@ -8107,7 +7107,7 @@ FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b)
+ 
+ // Compare packed signed 8-bit integers in a and b, and store packed minimum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epi8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi8
+ FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s8(
+@@ -8116,7 +7116,7 @@ FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b)
+ 
+ // Compare packed unsigned 16-bit integers in a and b, and store packed minimum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epu16
+ FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+@@ -8125,7 +7125,7 @@ FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b)
+ 
+ // Compare packed unsigned 32-bit integers in a and b, and store packed minimum
+ // values in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu32
+ FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u32(
+@@ -8134,21 +7134,7 @@ FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b)
+ 
+ // Horizontally compute the minimum amongst the packed unsigned 16-bit integers
+ // in a, store the minimum and index in dst, and zero the remaining bits in dst.
+-//
+-//   index[2:0] := 0
+-//   min[15:0] := a[15:0]
+-//   FOR j := 0 to 7
+-//       i := j*16
+-//       IF a[i+15:i] < min[15:0]
+-//           index[2:0] := j
+-//           min[15:0] := a[i+15:i]
+-//       FI
+-//   ENDFOR
+-//   dst[15:0] := min[15:0]
+-//   dst[18:16] := index[2:0]
+-//   dst[127:19] := 0
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_minpos_epu16
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_minpos_epu16
+ FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a)
+ {
+     __m128i dst;
+@@ -8198,7 +7184,7 @@ FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a)
+ // quadruplets from a. One quadruplet is selected from b starting at on the
+ // offset specified in imm8. Eight quadruplets are formed from sequential 8-bit
+ // integers selected from a starting at the offset specified in imm8.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mpsadbw_epu8
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mpsadbw_epu8
+ FORCE_INLINE __m128i _mm_mpsadbw_epu8(__m128i a, __m128i b, const int imm)
+ {
+     uint8x16_t _a, _b;
+@@ -8278,9 +7264,7 @@ FORCE_INLINE __m128i _mm_mpsadbw_epu8(__m128i a, __m128i b, const int imm)
+ 
+ // Multiply the low signed 32-bit integers from each packed 64-bit element in
+ // a and b, and store the signed 64-bit results in dst.
+-//
+-//   r0 :=  (int64_t)(int32_t)a0 * (int64_t)(int32_t)b0
+-//   r1 :=  (int64_t)(int32_t)a2 * (int64_t)(int32_t)b2
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_epi32
+ FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b)
+ {
+     // vmull_s32 upcasts instead of masking, so we downcast.
+@@ -8289,26 +7273,18 @@ FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b)
+     return vreinterpretq_m128i_s64(vmull_s32(a_lo, b_lo));
+ }
+ 
+-// Multiplies the 4 signed or unsigned 32-bit integers from a by the 4 signed or
+-// unsigned 32-bit integers from b.
+-// https://msdn.microsoft.com/en-us/library/vstudio/bb531409(v=vs.100).aspx
++// Multiply the packed 32-bit integers in a and b, producing intermediate 64-bit
++// integers, and store the low 32 bits of the intermediate integers in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mullo_epi32
+ FORCE_INLINE __m128i _mm_mullo_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_s32(
+         vmulq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b)));
+ }
+ 
+-// Packs the 8 unsigned 32-bit integers from a and b into unsigned 16-bit
+-// integers and saturates.
+-//
+-//   r0 := UnsignedSaturate(a0)
+-//   r1 := UnsignedSaturate(a1)
+-//   r2 := UnsignedSaturate(a2)
+-//   r3 := UnsignedSaturate(a3)
+-//   r4 := UnsignedSaturate(b0)
+-//   r5 := UnsignedSaturate(b1)
+-//   r6 := UnsignedSaturate(b2)
+-//   r7 := UnsignedSaturate(b3)
++// Convert packed signed 32-bit integers from a and b to packed 16-bit integers
++// using unsigned saturation, and store the results in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packus_epi32
+ FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b)
+ {
+     return vreinterpretq_m128i_u16(
+@@ -8319,7 +7295,7 @@ FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b)
+ // Round the packed double-precision (64-bit) floating-point elements in a using
+ // the rounding parameter, and store the results as packed double-precision
+ // floating-point elements in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_pd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_pd
+ FORCE_INLINE __m128d _mm_round_pd(__m128d a, int rounding)
+ {
+ #if defined(__aarch64__)
+@@ -8448,7 +7424,7 @@ FORCE_INLINE __m128 _mm_round_ps(__m128 a, int rounding)
+ // the rounding parameter, store the result as a double-precision floating-point
+ // element in the lower element of dst, and copy the upper element from a to the
+ // upper element of dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_sd
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_sd
+ FORCE_INLINE __m128d _mm_round_sd(__m128d a, __m128d b, int rounding)
+ {
+     return _mm_move_sd(a, _mm_round_pd(b, rounding));
+@@ -8468,7 +7444,7 @@ FORCE_INLINE __m128d _mm_round_sd(__m128d a, __m128d b, int rounding)
+ //     (_MM_FROUND_TO_ZERO |_MM_FROUND_NO_EXC)        // truncate, and suppress
+ //     exceptions _MM_FROUND_CUR_DIRECTION // use MXCSR.RC; see
+ //     _MM_SET_ROUNDING_MODE
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_ss
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_ss
+ FORCE_INLINE __m128 _mm_round_ss(__m128 a, __m128 b, int rounding)
+ {
+     return _mm_move_ss(a, _mm_round_ps(b, rounding));
+@@ -8477,10 +7453,7 @@ FORCE_INLINE __m128 _mm_round_ss(__m128 a, __m128 b, int rounding)
+ // Load 128-bits of integer data from memory into dst using a non-temporal
+ // memory hint. mem_addr must be aligned on a 16-byte boundary or a
+ // general-protection exception may be generated.
+-//
+-//   dst[127:0] := MEM[mem_addr+127:mem_addr]
+-//
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_load_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_load_si128
+ FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p)
+ {
+ #if __has_builtin(__builtin_nontemporal_store)
+@@ -8492,7 +7465,7 @@ FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p)
+ 
+ // Compute the bitwise NOT of a and then AND with a 128-bit vector containing
+ // all 1's, and return 1 if the result is zero, otherwise return 0.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_ones
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_test_all_ones
+ FORCE_INLINE int _mm_test_all_ones(__m128i a)
+ {
+     return (uint64_t) (vgetq_lane_s64(a, 0) & vgetq_lane_s64(a, 1)) ==
+@@ -8501,7 +7474,7 @@ FORCE_INLINE int _mm_test_all_ones(__m128i a)
+ 
+ // Compute the bitwise AND of 128 bits (representing integer data) in a and
+ // mask, and return 1 if the result is zero, otherwise return 0.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_zeros
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_test_all_zeros
+ FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask)
+ {
+     int64x2_t a_and_mask =
+@@ -8514,7 +7487,7 @@ FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask)
+ // the bitwise NOT of a and then AND with mask, and set CF to 1 if the result is
+ // zero, otherwise set CF to 0. Return 1 if both the ZF and CF values are zero,
+ // otherwise return 0.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_test_mix_ones_zero
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_test_mix_ones_zero
+ FORCE_INLINE int _mm_test_mix_ones_zeros(__m128i a, __m128i mask)
+ {
+     uint64x2_t zf =
+@@ -8529,7 +7502,7 @@ FORCE_INLINE int _mm_test_mix_ones_zeros(__m128i a, __m128i mask)
+ // and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the
+ // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero,
+ // otherwise set CF to 0. Return the CF value.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testc_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testc_si128
+ FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b)
+ {
+     int64x2_t s64 =
+@@ -8542,14 +7515,14 @@ FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b)
+ // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero,
+ // otherwise set CF to 0. Return 1 if both the ZF and CF values are zero,
+ // otherwise return 0.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testnzc_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testnzc_si128
+ #define _mm_testnzc_si128(a, b) _mm_test_mix_ones_zeros(a, b)
+ 
+ // Compute the bitwise AND of 128 bits (representing integer data) in a and b,
+ // and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the
+ // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero,
+ // otherwise set CF to 0. Return the ZF value.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testz_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testz_si128
+ FORCE_INLINE int _mm_testz_si128(__m128i a, __m128i b)
+ {
+     int64x2_t s64 =
+@@ -9028,7 +8001,7 @@ FORCE_INLINE int _sse2neon_sido_negative(int res, int lb, int imm8, int bound)
+ FORCE_INLINE int _sse2neon_clz(unsigned int x)
+ {
+ #if _MSC_VER
+-    DWORD cnt = 0;
++    unsigned long cnt = 0;
+     if (_BitScanForward(&cnt, x))
+         return cnt;
+     return 32;
+@@ -9040,7 +8013,7 @@ FORCE_INLINE int _sse2neon_clz(unsigned int x)
+ FORCE_INLINE int _sse2neon_ctz(unsigned int x)
+ {
+ #if _MSC_VER
+-    DWORD cnt = 0;
++    unsigned long cnt = 0;
+     if (_BitScanReverse(&cnt, x))
+         return 31 - cnt;
+     return 32;
+@@ -9053,18 +8026,16 @@ FORCE_INLINE int _sse2neon_ctzll(unsigned long long x)
+ {
+ #if _MSC_VER
+     unsigned long cnt;
+-#ifdef defined(SSE2NEON_HAS_BITSCAN64)
+-    (defined(_M_AMD64) || defined(__x86_64__))
+-        if((_BitScanForward64(&cnt, x))
+-            return (int)(cnt);
++#if defined(SSE2NEON_HAS_BITSCAN64)
++    if ((_BitScanForward64(&cnt, x))
++        return (int)(cnt);
+ #else
+     if (_BitScanForward(&cnt, (unsigned long) (x)))
+         return (int) cnt;
+     if (_BitScanForward(&cnt, (unsigned long) (x >> 32)))
+         return (int) (cnt + 32);
+-#endif
+-    return 64;
+-#else
++#endif /* SSE2NEON_HAS_BITSCAN64 */
++#else  /* assume GNU compatible compilers */
+     return x != 0 ? __builtin_ctzll(x) : 64;
+ #endif
+ }
+@@ -9155,7 +8126,7 @@ FORCE_INLINE int _mm_cmpestrc(__m128i a,
+ 
+ // Compare packed strings in a and b with lengths la and lb using the control
+ // in imm8, and store the generated index in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpestri
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpestri
+ FORCE_INLINE int _mm_cmpestri(__m128i a,
+                               int la,
+                               __m128i b,
+@@ -9168,7 +8139,7 @@ FORCE_INLINE int _mm_cmpestri(__m128i a,
+ 
+ // Compare packed strings in a and b with lengths la and lb using the control
+ // in imm8, and store the generated mask in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpestrm
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpestrm
+ FORCE_INLINE __m128i
+ _mm_cmpestrm(__m128i a, int la, __m128i b, int lb, const int imm8)
+ {
+@@ -9324,8 +8295,8 @@ FORCE_INLINE __m128i _mm_cmpgt_epi64(__m128i a, __m128i b)
+ }
+ 
+ // Starting with the initial value in crc, accumulates a CRC32 value for
+-// unsigned 16-bit integer v.
+-// https://msdn.microsoft.com/en-us/library/bb531411(v=vs.100)
++// unsigned 16-bit integer v, and stores the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u16
+ FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v)
+ {
+ #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)
+@@ -9342,8 +8313,8 @@ FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v)
+ }
+ 
+ // Starting with the initial value in crc, accumulates a CRC32 value for
+-// unsigned 32-bit integer v.
+-// https://msdn.microsoft.com/en-us/library/bb531394(v=vs.100)
++// unsigned 32-bit integer v, and stores the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u32
+ FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v)
+ {
+ #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)
+@@ -9360,8 +8331,8 @@ FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v)
+ }
+ 
+ // Starting with the initial value in crc, accumulates a CRC32 value for
+-// unsigned 64-bit integer v.
+-// https://msdn.microsoft.com/en-us/library/bb514033(v=vs.100)
++// unsigned 64-bit integer v, and stores the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u64
+ FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v)
+ {
+ #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)
+@@ -9376,8 +8347,8 @@ FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v)
+ }
+ 
+ // Starting with the initial value in crc, accumulates a CRC32 value for
+-// unsigned 8-bit integer v.
+-// https://msdn.microsoft.com/en-us/library/bb514036(v=vs.100)
++// unsigned 8-bit integer v, and stores the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u8
+ FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v)
+ {
+ #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)
+@@ -9486,43 +8457,61 @@ FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v)
+ 
+ /* X Macro trick. See https://en.wikipedia.org/wiki/X_Macro */
+ #define SSE2NEON_AES_H0(x) (x)
+-static const uint8_t SSE2NEON_sbox[256] = SSE2NEON_AES_SBOX(SSE2NEON_AES_H0);
+-static const uint8_t SSE2NEON_rsbox[256] = SSE2NEON_AES_RSBOX(SSE2NEON_AES_H0);
++static const uint8_t _sse2neon_sbox[256] = SSE2NEON_AES_SBOX(SSE2NEON_AES_H0);
++static const uint8_t _sse2neon_rsbox[256] = SSE2NEON_AES_RSBOX(SSE2NEON_AES_H0);
+ #undef SSE2NEON_AES_H0
+ 
+-// In the absence of crypto extensions, implement aesenc using regular neon
++/* x_time function and matrix multiply function */
++#if !defined(__aarch64__)
++#define SSE2NEON_XT(x) (((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b))
++#define SSE2NEON_MULTIPLY(x, y)                                  \
++    (((y & 1) * x) ^ ((y >> 1 & 1) * SSE2NEON_XT(x)) ^           \
++     ((y >> 2 & 1) * SSE2NEON_XT(SSE2NEON_XT(x))) ^              \
++     ((y >> 3 & 1) * SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(x)))) ^ \
++     ((y >> 4 & 1) * SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(x))))))
++#endif
++
++// In the absence of crypto extensions, implement aesenc using regular NEON
+ // intrinsics instead. See:
+ // https://www.workofard.com/2017/01/accelerated-aes-for-the-arm64-linux-kernel/
+ // https://www.workofard.com/2017/07/ghash-for-low-end-cores/ and
+-// https://github.com/ColinIanKing/linux-next-mirror/blob/b5f466091e130caaf0735976648f72bd5e09aa84/crypto/aegis128-neon-inner.c#L52
+-// for more information Reproduced with permission of the author.
++// for more information.
+ FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i RoundKey)
+ {
+ #if defined(__aarch64__)
+-    static const uint8_t shift_rows[] = {0x0, 0x5, 0xa, 0xf, 0x4, 0x9,
+-                                         0xe, 0x3, 0x8, 0xd, 0x2, 0x7,
+-                                         0xc, 0x1, 0x6, 0xb};
+-    static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4,
+-                                       0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc};
++    static const uint8_t shift_rows[] = {
++        0x0, 0x5, 0xa, 0xf, 0x4, 0x9, 0xe, 0x3,
++        0x8, 0xd, 0x2, 0x7, 0xc, 0x1, 0x6, 0xb,
++    };
++    static const uint8_t ror32by8[] = {
++        0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4,
++        0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc,
++    };
+ 
+     uint8x16_t v;
+     uint8x16_t w = vreinterpretq_u8_m128i(a);
+ 
+-    // shift rows
++    /* shift rows */
+     w = vqtbl1q_u8(w, vld1q_u8(shift_rows));
+ 
+-    // sub bytes
+-    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_sbox), w);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0);
++    /* sub bytes */
++    // Here, we separate the whole 256-bytes table into 4 64-bytes tables, and
++    // look up each of the table. After each lookup, we load the next table
++    // which locates at the next 64-bytes. In the meantime, the index in the
++    // table would be smaller than it was, so the index parameters of
++    // `vqtbx4q_u8()` need to be added the same constant as the loaded tables.
++    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), w);
++    // 'w-0x40' equals to 'vsubq_u8(w, vdupq_n_u8(0x40))'
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), w - 0x40);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), w - 0x80);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), w - 0xc0);
+ 
+-    // mix columns
++    /* mix columns */
+     w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b);
+     w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v);
+     w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8));
+ 
+-    //  add round key
++    /* add round key */
+     return vreinterpretq_m128i_u8(w) ^ RoundKey;
+ 
+ #else /* ARMv7-A implementation for a table-based AES */
+@@ -9587,31 +8576,34 @@ FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i RoundKey)
+ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey)
+ {
+ #if defined(__aarch64__)
+-    static const uint8_t inv_shift_rows[] = {0x0, 0xd, 0xa, 0x7, 0x4, 0x1,
+-                                             0xe, 0xb, 0x8, 0x5, 0x2, 0xf,
+-                                             0xc, 0x9, 0x6, 0x3};
+-    static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4,
+-                                       0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc};
++    static const uint8_t inv_shift_rows[] = {
++        0x0, 0xd, 0xa, 0x7, 0x4, 0x1, 0xe, 0xb,
++        0x8, 0x5, 0x2, 0xf, 0xc, 0x9, 0x6, 0x3,
++    };
++    static const uint8_t ror32by8[] = {
++        0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4,
++        0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc,
++    };
+ 
+     uint8x16_t v;
+     uint8x16_t w = vreinterpretq_u8_m128i(a);
+ 
+-    // shift rows
++    // inverse shift rows
+     w = vqtbl1q_u8(w, vld1q_u8(inv_shift_rows));
+ 
+-    // sub bytes
+-    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_rsbox), w);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0x40), w - 0x40);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0x80), w - 0x80);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0xc0), w - 0xc0);
++    // inverse sub bytes
++    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_rsbox), w);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x40), w - 0x40);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x80), w - 0x80);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0xc0), w - 0xc0);
+ 
++    // inverse mix columns
+     // muliplying 'v' by 4 in GF(2^8)
+     w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b);
+     w = (w << 1) ^ (uint8x16_t) (((int8x16_t) w >> 7) & 0x1b);
+     v ^= w;
+     v ^= (uint8x16_t) vrev32q_u16((uint16x8_t) w);
+ 
+-    // mix columns
+     w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) &
+                                  0x1b);  // muliplying 'v' by 2 in GF(2^8)
+     w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v);
+@@ -9621,35 +8613,29 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey)
+     return vreinterpretq_m128i_u8(w) ^ RoundKey;
+ 
+ #else /* ARMv7-A NEON implementation */
+-/* FIXME: optimized for NEON */
+-#define XT(x) (((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b))
+-#define MULTIPLY(x, y)                                                     \
+-    (((y & 1) * x) ^ ((y >> 1 & 1) * XT(x)) ^ ((y >> 2 & 1) * XT(XT(x))) ^ \
+-     ((y >> 3 & 1) * XT(XT(XT(x)))) ^ ((y >> 4 & 1) * XT(XT(XT(XT(x))))))
+-
++    /* FIXME: optimized for NEON */
+     uint8_t i, e, f, g, h, v[4][4];
+     uint8_t *_a = (uint8_t *) &a;
+     for (i = 0; i < 16; ++i) {
+-        v[((i / 4) + (i % 4)) % 4][i % 4] = SSE2NEON_rsbox[_a[i]];
++        v[((i / 4) + (i % 4)) % 4][i % 4] = _sse2neon_rsbox[_a[i]];
+     }
+ 
++    // inverse mix columns
+     for (i = 0; i < 4; ++i) {
+         e = v[i][0];
+         f = v[i][1];
+         g = v[i][2];
+         h = v[i][3];
+ 
+-        v[i][0] = MULTIPLY(e, 0x0e) ^ MULTIPLY(f, 0x0b) ^ MULTIPLY(g, 0x0d) ^
+-                  MULTIPLY(h, 0x09);
+-        v[i][1] = MULTIPLY(e, 0x09) ^ MULTIPLY(f, 0x0e) ^ MULTIPLY(g, 0x0b) ^
+-                  MULTIPLY(h, 0x0d);
+-        v[i][2] = MULTIPLY(e, 0x0d) ^ MULTIPLY(f, 0x09) ^ MULTIPLY(g, 0x0e) ^
+-                  MULTIPLY(h, 0x0b);
+-        v[i][3] = MULTIPLY(e, 0x0b) ^ MULTIPLY(f, 0x0d) ^ MULTIPLY(g, 0x09) ^
+-                  MULTIPLY(h, 0x0e);
++        v[i][0] = SSE2NEON_MULTIPLY(e, 0x0e) ^ SSE2NEON_MULTIPLY(f, 0x0b) ^
++                  SSE2NEON_MULTIPLY(g, 0x0d) ^ SSE2NEON_MULTIPLY(h, 0x09);
++        v[i][1] = SSE2NEON_MULTIPLY(e, 0x09) ^ SSE2NEON_MULTIPLY(f, 0x0e) ^
++                  SSE2NEON_MULTIPLY(g, 0x0b) ^ SSE2NEON_MULTIPLY(h, 0x0d);
++        v[i][2] = SSE2NEON_MULTIPLY(e, 0x0d) ^ SSE2NEON_MULTIPLY(f, 0x09) ^
++                  SSE2NEON_MULTIPLY(g, 0x0e) ^ SSE2NEON_MULTIPLY(h, 0x0b);
++        v[i][3] = SSE2NEON_MULTIPLY(e, 0x0b) ^ SSE2NEON_MULTIPLY(f, 0x0d) ^
++                  SSE2NEON_MULTIPLY(g, 0x09) ^ SSE2NEON_MULTIPLY(h, 0x0e);
+     }
+-#undef XT
+-#undef MULTIPLY
+ 
+     return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v)) ^ RoundKey;
+ #endif
+@@ -9657,7 +8643,7 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey)
+ 
+ // Perform the last round of an AES encryption flow on data (state) in a using
+ // the round key in RoundKey, and store the result in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128
+ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey)
+ {
+ #if defined(__aarch64__)
+@@ -9673,59 +8659,166 @@ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey)
+     w = vqtbl1q_u8(w, vld1q_u8(shift_rows));
+ 
+     // sub bytes
+-    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_sbox), w);
+-    // 'w-0x40' equals to 'vsubq_u8(w, vdupq_n_u8(0x40))'
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80);
+-    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0);
++    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), w);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), w - 0x40);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), w - 0x80);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), w - 0xc0);
+ 
+-    //  add round key
++    // add round key
+     return vreinterpretq_m128i_u8(v) ^ RoundKey;
+ 
+ #else /* ARMv7-A implementation */
+     uint8_t v[16] = {
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 0)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 5)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 10)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 15)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 4)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 9)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 14)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 3)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 8)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 13)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 2)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 7)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 12)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 1)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 6)],
+-        SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 11)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 0)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 5)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 10)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 15)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 4)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 9)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 14)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 3)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 8)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 13)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 2)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 7)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 12)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 1)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 6)],
++        _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 11)],
+     };
+ 
+     return vreinterpretq_m128i_u8(vld1q_u8(v)) ^ RoundKey;
+ #endif
+ }
+ 
++// Perform the last round of an AES decryption flow on data (state) in a using
++// the round key in RoundKey, and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128
++FORCE_INLINE __m128i _mm_aesdeclast_si128(__m128i a, __m128i RoundKey)
++{
++#if defined(__aarch64__)
++    static const uint8_t inv_shift_rows[] = {
++        0x0, 0xd, 0xa, 0x7, 0x4, 0x1, 0xe, 0xb,
++        0x8, 0x5, 0x2, 0xf, 0xc, 0x9, 0x6, 0x3,
++    };
++
++    uint8x16_t v;
++    uint8x16_t w = vreinterpretq_u8_m128i(a);
++
++    // inverse shift rows
++    w = vqtbl1q_u8(w, vld1q_u8(inv_shift_rows));
++
++    // inverse sub bytes
++    v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_rsbox), w);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x40), w - 0x40);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x80), w - 0x80);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0xc0), w - 0xc0);
++
++    // add round key
++    return vreinterpretq_m128i_u8(v) ^ RoundKey;
++
++#else /* ARMv7-A NEON implementation */
++    /* FIXME: optimized for NEON */
++    uint8_t v[4][4];
++    uint8_t *_a = (uint8_t *) &a;
++    for (int i = 0; i < 16; ++i) {
++        v[((i / 4) + (i % 4)) % 4][i % 4] = _sse2neon_rsbox[_a[i]];
++    }
++
++    return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v)) ^ RoundKey;
++#endif
++}
++
++// Perform the InvMixColumns transformation on a and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesimc_si128
++FORCE_INLINE __m128i _mm_aesimc_si128(__m128i a)
++{
++#if defined(__aarch64__)
++    static const uint8_t ror32by8[] = {
++        0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4,
++        0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc,
++    };
++    uint8x16_t v = vreinterpretq_u8_m128i(a);
++    uint8x16_t w;
++
++    // multiplying 'v' by 4 in GF(2^8)
++    w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b);
++    w = (w << 1) ^ (uint8x16_t) (((int8x16_t) w >> 7) & 0x1b);
++    v ^= w;
++    v ^= (uint8x16_t) vrev32q_u16((uint16x8_t) w);
++
++    // multiplying 'v' by 2 in GF(2^8)
++    w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b);
++    w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v);
++    w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8));
++    return vreinterpretq_m128i_u8(w);
++
++#else /* ARMv7-A NEON implementation */
++    uint8_t i, e, f, g, h, v[4][4];
++    vst1q_u8((uint8_t *) v, vreinterpretq_u8_m128i(a));
++    for (i = 0; i < 4; ++i) {
++        e = v[i][0];
++        f = v[i][1];
++        g = v[i][2];
++        h = v[i][3];
++
++        v[i][0] = SSE2NEON_MULTIPLY(e, 0x0e) ^ SSE2NEON_MULTIPLY(f, 0x0b) ^
++                  SSE2NEON_MULTIPLY(g, 0x0d) ^ SSE2NEON_MULTIPLY(h, 0x09);
++        v[i][1] = SSE2NEON_MULTIPLY(e, 0x09) ^ SSE2NEON_MULTIPLY(f, 0x0e) ^
++                  SSE2NEON_MULTIPLY(g, 0x0b) ^ SSE2NEON_MULTIPLY(h, 0x0d);
++        v[i][2] = SSE2NEON_MULTIPLY(e, 0x0d) ^ SSE2NEON_MULTIPLY(f, 0x09) ^
++                  SSE2NEON_MULTIPLY(g, 0x0e) ^ SSE2NEON_MULTIPLY(h, 0x0b);
++        v[i][3] = SSE2NEON_MULTIPLY(e, 0x0b) ^ SSE2NEON_MULTIPLY(f, 0x0d) ^
++                  SSE2NEON_MULTIPLY(g, 0x09) ^ SSE2NEON_MULTIPLY(h, 0x0e);
++    }
++
++    return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v));
++#endif
++}
++
++// Assist in expanding the AES cipher key by computing steps towards generating
++// a round key for encryption cipher using data from a and an 8-bit round
++// constant specified in imm8, and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aeskeygenassist_si128
++//
+ // Emits the Advanced Encryption Standard (AES) instruction aeskeygenassist.
+ // This instruction generates a round key for AES encryption. See
+ // https://kazakov.life/2017/11/01/cryptocurrency-mining-on-ios-devices/
+ // for details.
+-//
+-// https://msdn.microsoft.com/en-us/library/cc714138(v=vs.120).aspx
+-FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i key, const int rcon)
++FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon)
+ {
+-    uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0x55));
+-    uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0xFF));
++#if defined(__aarch64__)
++    uint8x16_t _a = vreinterpretq_u8_m128i(a);
++    uint8x16_t v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), _a);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), _a - 0x40);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), _a - 0x80);
++    v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), _a - 0xc0);
++
++    uint32x4_t v_u32 = vreinterpretq_u32_u8(v);
++    uint32x4_t ror_v = vorrq_u32(vshrq_n_u32(v_u32, 8), vshlq_n_u32(v_u32, 24));
++    uint32x4_t ror_xor_v = veorq_u32(ror_v, vdupq_n_u32(rcon));
++
++    return vreinterpretq_m128i_u32(vtrn2q_u32(v_u32, ror_xor_v));
++
++#else /* ARMv7-A NEON implementation */
++    uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(a, 0x55));
++    uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(a, 0xFF));
+     for (int i = 0; i < 4; ++i) {
+-        ((uint8_t *) &X1)[i] = SSE2NEON_sbox[((uint8_t *) &X1)[i]];
+-        ((uint8_t *) &X3)[i] = SSE2NEON_sbox[((uint8_t *) &X3)[i]];
++        ((uint8_t *) &X1)[i] = _sse2neon_sbox[((uint8_t *) &X1)[i]];
++        ((uint8_t *) &X3)[i] = _sse2neon_sbox[((uint8_t *) &X3)[i]];
+     }
+     return _mm_set_epi32(((X3 >> 8) | (X3 << 24)) ^ rcon, X3,
+                          ((X1 >> 8) | (X1 << 24)) ^ rcon, X1);
++#endif
+ }
+ #undef SSE2NEON_AES_SBOX
+ #undef SSE2NEON_AES_RSBOX
+ 
++#if defined(__aarch64__)
++#undef SSE2NEON_XT
++#undef SSE2NEON_MULTIPLY
++#endif
++
+ #else /* __ARM_FEATURE_CRYPTO */
+ // Implements equivalent of 'aesenc' by combining AESE (with an empty key) and
+ // AESMC and then manually applying the real key as an xor operation. This
+@@ -9750,7 +8843,9 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey)
+         vreinterpretq_u8_m128i(RoundKey)));
+ }
+ 
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128
++// Perform the last round of an AES encryption flow on data (state) in a using
++// the round key in RoundKey, and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128
+ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey)
+ {
+     return _mm_xor_si128(vreinterpretq_m128i_u8(vaeseq_u8(
+@@ -9758,6 +8853,23 @@ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey)
+                          RoundKey);
+ }
+ 
++// Perform the last round of an AES decryption flow on data (state) in a using
++// the round key in RoundKey, and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128
++FORCE_INLINE __m128i _mm_aesdeclast_si128(__m128i a, __m128i RoundKey)
++{
++    return vreinterpretq_m128i_u8(
++        vaesdq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)) ^
++        vreinterpretq_u8_m128i(RoundKey));
++}
++
++// Perform the InvMixColumns transformation on a and store the result in dst.
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesimc_si128
++FORCE_INLINE __m128i _mm_aesimc_si128(__m128i a)
++{
++    return vreinterpretq_m128i_u8(vaesimcq_u8(vreinterpretq_u8_m128i(a)));
++}
++
+ // Assist in expanding the AES cipher key by computing steps towards generating
+ // a round key for encryption cipher using data from a and an 8-bit round
+ // constant specified in imm8, and store the result in dst."
+@@ -9783,7 +8895,7 @@ FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon)
+ 
+ // Perform a carry-less multiplication of two 64-bit integers, selected from a
+ // and b according to imm8, and store the results in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_clmulepi64_si128
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_clmulepi64_si128
+ FORCE_INLINE __m128i _mm_clmulepi64_si128(__m128i _a, __m128i _b, const int imm)
+ {
+     uint64x2_t a = vreinterpretq_u64_m128i(_a);
+@@ -9828,7 +8940,7 @@ FORCE_INLINE unsigned int _sse2neon_mm_get_denormals_zero_mode()
+ 
+ // Count the number of bits set to 1 in unsigned 32-bit integer a, and
+ // return that count in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u32
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_popcnt_u32
+ FORCE_INLINE int _mm_popcnt_u32(unsigned int a)
+ {
+ #if defined(__aarch64__)
+@@ -9855,7 +8967,7 @@ FORCE_INLINE int _mm_popcnt_u32(unsigned int a)
+ 
+ // Count the number of bits set to 1 in unsigned 64-bit integer a, and
+ // return that count in dst.
+-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u64
++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_popcnt_u64
+ FORCE_INLINE int64_t _mm_popcnt_u64(uint64_t a)
+ {
+ #if defined(__aarch64__)
+@@ -9911,7 +9023,6 @@ FORCE_INLINE void _sse2neon_mm_set_denormals_zero_mode(unsigned int flag)
+ 
+ // Return the current 64-bit value of the processor's time-stamp counter.
+ // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=rdtsc
+-
+ FORCE_INLINE uint64_t _rdtsc(void)
+ {
+ #if defined(__aarch64__)
diff --git a/bazel/patches/emp-tool.patch b/bazel/patches/emp-tool.patch
new file mode 100644
index 00000000..48220f89
--- /dev/null
+++ b/bazel/patches/emp-tool.patch
@@ -0,0 +1,163 @@
+diff --git a/emp-tool/utils/aes.h b/emp-tool/utils/aes.h
+index 0235544..75a8486 100644
+--- a/emp-tool/utils/aes.h
++++ b/emp-tool/utils/aes.h
+@@ -54,6 +54,10 @@
+ 
+ #include "emp-tool/utils/block.h"
+ 
++#ifdef __aarch64__
++#include "emp-tool/utils/sse2neon.h"
++#endif
++
+ namespace emp {
+ 
+ typedef struct { block rd_key[11]; unsigned int rounds; } AES_KEY;
+@@ -103,6 +107,7 @@ AES_set_encrypt_key(const block userkey, AES_KEY *key) {
+ 
+ #ifdef __x86_64__
+ __attribute__((target("aes,sse2")))
++#endif
+ inline void AES_ecb_encrypt_blks(block *blks, unsigned int nblks, const AES_KEY *key) {
+    for (unsigned int i = 0; i < nblks; ++i)
+       blks[i] = _mm_xor_si128(blks[i], key->rd_key[0]);
+@@ -112,22 +117,6 @@ inline void AES_ecb_encrypt_blks(block *blks, unsigned int nblks, const AES_KEY
+    for (unsigned int i = 0; i < nblks; ++i)
+       blks[i] = _mm_aesenclast_si128(blks[i], key->rd_key[key->rounds]);
+ }
+-#elif __aarch64__
+-inline void AES_ecb_encrypt_blks(block *_blks, unsigned int nblks, const AES_KEY *key) {
+-   uint8x16_t * blks = (uint8x16_t*)(_blks);
+-   uint8x16_t * keys = (uint8x16_t*)(key->rd_key);
+-   auto * first = blks;
+-   for (unsigned int j = 0; j < key->rounds-1; ++j) {
+-		uint8x16_t key_j = (uint8x16_t)keys[j];
+-      blks = first;
+-      for (unsigned int i = 0; i < nblks; ++i, ++blks)
+-	       *blks = vaesmcq_u8(vaeseq_u8(*blks, key_j));
+-   }
+-	uint8x16_t last_key = (uint8x16_t)keys[key->rounds-1];
+-	for (unsigned int i = 0; i < nblks; ++i, ++first)
+-		 *first = vaeseq_u8(*first, last_key) ^ (uint8x16_t)keys[key->rounds];
+-}
+-#endif
+ 
+ #ifdef __GNUC__
+ 	#ifndef __clang__
+diff --git a/emp-tool/utils/aes_opt.h b/emp-tool/utils/aes_opt.h
+index 2594e32..6a78b75 100644
+--- a/emp-tool/utils/aes_opt.h
++++ b/emp-tool/utils/aes_opt.h
+@@ -58,7 +58,6 @@ static inline void AES_opt_key_schedule(block* user_key, AES_KEY *keys) {
+ /*
+  * With numKeys keys, use each key to encrypt numEncs blocks.
+  */
+-#ifdef __x86_64__
+ template<int numKeys, int numEncs>
+ static inline void ParaEnc(block *blks, AES_KEY *keys) {
+ 	block * first = blks;
+@@ -90,29 +89,6 @@ static inline void ParaEnc(block *blks, AES_KEY *keys) {
+ 		}
+ 	}
+ }
+-#elif __aarch64__
+-template<int numKeys, int numEncs>
+-static inline void ParaEnc(block *_blks, AES_KEY *keys) {
+-	uint8x16_t * first = (uint8x16_t*)(_blks);
+-
+-	for (unsigned int r = 0; r < 9; ++r) { 
+-		auto blks = first;
+-		for(size_t i = 0; i < numKeys; ++i) {
+-			uint8x16_t K = vreinterpretq_u8_m128i(keys[i].rd_key[r]);
+-			for(size_t j = 0; j < numEncs; ++j, ++blks)
+-			   *blks = vaesmcq_u8(vaeseq_u8(*blks, K));
+-		}
+-	}
+-	
+-	auto blks = first;
+-	for(size_t i = 0; i < numKeys; ++i) {
+-		uint8x16_t K = vreinterpretq_u8_m128i(keys[i].rd_key[9]);
+-		uint8x16_t K2 = vreinterpretq_u8_m128i(keys[i].rd_key[10]);
+-		for(size_t j = 0; j < numEncs; ++j, ++blks)
+-			*blks = vaeseq_u8(*blks, K) ^ K2;
+-	}
+-}
+-#endif
+ 
+ }
+ #endif
+diff --git a/emp-tool/utils/block.h b/emp-tool/utils/block.h
+index f7d3d34..fcc21c1 100644
+--- a/emp-tool/utils/block.h
++++ b/emp-tool/utils/block.h
+@@ -5,16 +5,7 @@
+ #include <immintrin.h>
+ #elif __aarch64__
+ #include "sse2neon.h"
+-inline __m128i _mm_aesimc_si128(__m128i a) {
+-	return vreinterpretq_m128i_u8(vaesimcq_u8(vreinterpretq_u8_m128i(a)));
+-}
+-
+-inline __m128i _mm_aesdeclast_si128 (__m128i a, __m128i RoundKey)
+-{
+-    return vreinterpretq_m128i_u8(vaesdq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)) ^ vreinterpretq_u8_m128i(RoundKey));
+-}
+ #endif
+-
+ #include <assert.h>
+ #include <cstring>
+ #include <iostream>
+diff --git a/emp-tool/utils/f2k.h b/emp-tool/utils/f2k.h
+index 7fe1b1b..f6186a1 100644
+--- a/emp-tool/utils/f2k.h
++++ b/emp-tool/utils/f2k.h
+@@ -6,6 +6,7 @@ namespace emp {
+ 	/* multiplication in galois field without reduction */
+ 	#ifdef __x86_64__
+ 	__attribute__((target("sse2,pclmul")))
++	#endif
+ 	inline void mul128(__m128i a, __m128i b, __m128i *res1, __m128i *res2) {
+ 		__m128i tmp3, tmp4, tmp5, tmp6;
+ 		tmp3 = _mm_clmulepi64_si128(a, b, 0x00);
+@@ -22,28 +23,6 @@ namespace emp {
+ 		*res1 = tmp3;
+ 		*res2 = tmp6;
+ 	}
+-	#elif __aarch64__
+-	inline void mul128(__m128i a, __m128i b, __m128i *res1, __m128i *res2) {
+-		__m128i tmp3, tmp4, tmp5, tmp6;
+-		poly64_t a_lo = (poly64_t)vget_low_u64(vreinterpretq_u64_m128i(a));
+-		poly64_t a_hi = (poly64_t)vget_high_u64(vreinterpretq_u64_m128i(a));
+-		poly64_t b_lo = (poly64_t)vget_low_u64(vreinterpretq_u64_m128i(b));
+-		poly64_t b_hi = (poly64_t)vget_high_u64(vreinterpretq_u64_m128i(b));
+-		tmp3 = (__m128i)vmull_p64(a_lo, b_lo);
+-		tmp4 = (__m128i)vmull_p64(a_hi, b_lo);
+-		tmp5 = (__m128i)vmull_p64(a_lo, b_hi);
+-		tmp6 = (__m128i)vmull_p64(a_hi, b_hi);
+-
+-		tmp4 = _mm_xor_si128(tmp4, tmp5);
+-		tmp5 = _mm_slli_si128(tmp4, 8);
+-		tmp4 = _mm_srli_si128(tmp4, 8);
+-		tmp3 = _mm_xor_si128(tmp3, tmp5);
+-		tmp6 = _mm_xor_si128(tmp6, tmp4);
+-		// initial mul now in tmp3, tmp6
+-		*res1 = tmp3;
+-		*res2 = tmp6;
+-	}
+-	#endif
+ 
+ 	/* multiplication in galois field with reduction */
+ 	#ifdef __x86_64__
+diff --git a/emp-tool/utils/prg.h b/emp-tool/utils/prg.h
+index 23bbf42..5101d7e 100644
+--- a/emp-tool/utils/prg.h
++++ b/emp-tool/utils/prg.h
+@@ -82,7 +82,7 @@ class PRG { public:
+ 		} else {
+ 			block tmp[2];
+ 			random_block(tmp, 2);
+-			memcpy(data, tmp, nbytes);
++			memcpy(data, tmp, nbytes <= 32? nbytes : 32);
+ 		}
+ 	}
+ 
diff --git a/bazel/patches/emp-zk.patch b/bazel/patches/emp-zk.patch
index 0d268506..9c6e566d 100644
--- a/bazel/patches/emp-zk.patch
+++ b/bazel/patches/emp-zk.patch
@@ -1,16 +1,15 @@
 diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 3a533d2..31baa67 100644
+index 3a533d2..be0e490 100644
 --- a/CMakeLists.txt
 +++ b/CMakeLists.txt
-@@ -17,5 +17,12 @@ install(DIRECTORY emp-zk DESTINATION include/)
+@@ -17,5 +17,11 @@ install(DIRECTORY emp-zk DESTINATION include/)
  install(FILES cmake/emp-zk-config.cmake DESTINATION cmake/)
  install(TARGETS ${NAME} DESTINATION lib)
  
 -ENABLE_TESTING()
 -ADD_SUBDIRECTORY(test)
-+# ENABLE_TESTING()
-+# ADD_SUBDIRECTORY(test)
-+
++#ENABLE_TESTING()
++#ADD_SUBDIRECTORY(test)
 +# add enable testing option, default OFF
 +option(ENABLE_TESTS "Enable tests" OFF)
 +if (${ENABLE_TESTS})
@@ -18,16 +17,14 @@ index 3a533d2..31baa67 100644
 +    ADD_SUBDIRECTORY(test)
 +endif()
 diff --git a/emp-zk/emp-vole/vole_triple.h b/emp-zk/emp-vole/vole_triple.h
-index f299669..fb4ccac 100644
+index 3be3377..6759497 100644
 --- a/emp-zk/emp-vole/vole_triple.h
 +++ b/emp-zk/emp-vole/vole_triple.h
-@@ -58,6 +58,7 @@ public:
+@@ -80,6 +80,7 @@ public:
  		if(mpfss != nullptr) delete mpfss;
  		if(vole_triples != nullptr) delete[] vole_triples;
  		if(vole_x != nullptr) delete[] vole_x;
 +		if(cot != nullptr) delete cot;
  	}
  
- 	void set_param() {
-
-
+ 	void setup(__uint128_t delta) {
diff --git a/bazel/patches/fourq.patch b/bazel/patches/fourq.patch
deleted file mode 100644
index f4a3b9ea..00000000
--- a/bazel/patches/fourq.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-diff --git a/FourQ_64bit_and_portable/makefile b/FourQ_64bit_and_portable/makefile
-index 7b6fd07..0826ab6 100644
---- a/FourQ_64bit_and_portable/makefile
-+++ b/FourQ_64bit_and_portable/makefile
-@@ -77,7 +77,7 @@ else ifeq "$(ARCH)" "ARM"
- endif
- endif
- 
--ADDITIONAL_SETTINGS=-fwrapv -fomit-frame-pointer -march=native
-+ADDITIONAL_SETTINGS=-fwrapv -fomit-frame-pointer -O3 -fPIC
- ifeq "$(EXTENDED_SET)" "FALSE"
-     ADDITIONAL_SETTINGS=
- endif
-@@ -98,8 +98,8 @@ ifeq "$(SHARED_LIB)" "TRUE"
- endif
- 
- cc=$(COMPILER)
--CFLAGS=-c $(OPT) $(ADDITIONAL_SETTINGS) $(SIMD) -D $(ARCHITECTURE) -D __LINUX__ $(USE_AVX) $(USE_AVX2) $(USE_ASM) $(USE_GENERIC) $(USE_ENDOMORPHISMS) $(USE_SERIAL_PUSH) $(DO_MAKE_SHARED_LIB)
--LDFLAGS=
-+CFLAGS+=-c $(OPT) $(ADDITIONAL_SETTINGS) $(SIMD) -D $(ARCHITECTURE) -D __LINUX__ $(USE_AVX) $(USE_AVX2) $(USE_ASM) $(USE_GENERIC) $(USE_ENDOMORPHISMS) $(USE_SERIAL_PUSH) $(DO_MAKE_SHARED_LIB)
-+LDFLAGS+=
- ifdef ASM_var
- ifdef AVX2_var
-     ASM_OBJECTS=fp2_1271_AVX2.o
-@@ -115,6 +115,12 @@ OBJECTS_ALL=$(OBJECTS) $(OBJECTS_FP_TEST) $(OBJECTS_ECC_TEST) $(OBJECTS_CRYPTO_T
- 
- all: crypto_test ecc_test fp_test $(SHARED_LIB_O)
- 
-+install: libfourq.a 
-+	cp libfourq.a $(PREFIX)/lib
-+	cp FourQ.h $(PREFIX)/include
-+	cp FourQ_api.h $(PREFIX)/include
-+	cp FourQ_internal.h $(PREFIX)/include
-+
- ifeq "$(SHARED_LIB)" "TRUE"
-     $(SHARED_LIB_O): $(OBJECTS)
- 	    $(CC) -shared -o $(SHARED_LIB_O) $(OBJECTS)
-@@ -181,6 +187,9 @@ ecc_tests.o: tests/ecc_tests.c
- fp_tests.o: tests/fp_tests.c
- 	$(CC) $(CFLAGS) tests/fp_tests.c
- 
-+libfourq.a: $(OBJECTS)
-+	$(AR) rcs libfourq.a  $(OBJECTS)
-+
- .PHONY: clean
- 
- clean:
-diff --git a/FourQ_64bit_and_portable/schnorrq.c b/FourQ_64bit_and_portable/schnorrq.c
-index da89f86..099f4eb 100644
---- a/FourQ_64bit_and_portable/schnorrq.c
-+++ b/FourQ_64bit_and_portable/schnorrq.c
-@@ -14,7 +14,7 @@
- #include "FourQ_params.h"
- #include "../random/random.h"
- #include "../sha512/sha512.h"
--#include <malloc.h>
-+#include <stdlib.h>
- #include <string.h>
- 
- 
diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl
index e6e93ee0..e934bed4 100644
--- a/bazel/repositories.bzl
+++ b/bazel/repositories.bzl
@@ -18,7 +18,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
 
 SECRETFLOW_GIT = "https://github.com/secretflow"
 
-YACL_COMMIT_ID = "fac5d9f3b2419f45cacc42fd6ba7f482c19e5318"
+YACL_COMMIT_ID = "3ad8dc2c4e00b0e7a2873b96501e1a6f096efd64"
 
 def spu_deps():
     _bazel_platform()
@@ -30,16 +30,15 @@ def spu_deps():
     _com_github_pybind11_bazel()
     _com_github_pybind11()
     _com_intel_hexl()
-    _com_github_amrayn_easyloggingpp()
+    _com_github_emptoolkit_emp_tool()
     _com_github_emptoolkit_emp_ot()
+    _com_github_emptoolkit_emp_zk()
     _com_github_facebook_zstd()
     _com_github_microsoft_seal()
-    _com_github_microsoft_fourqlib()
     _com_github_eigenteam_eigen()
     _com_github_microsoft_apsi()
     _com_github_microsoft_gsl()
     _com_github_microsoft_kuku()
-    _com_github_emptoolkit_emp_zk()
     _com_google_flatbuffers()
 
     maybe(
@@ -139,8 +138,8 @@ def _com_github_xtensor_xtl():
     )
 
 def _com_github_openxla_xla():
-    OPENXLA_COMMIT = "09878be2d6bc1ad0baf6219d6a996db58d6fff1f"
-    OPENXLA_SHA256 = "9d877b82d8801f00ca136d4c9ad37724236ee921f2a84e7871668915967dfe68"
+    OPENXLA_COMMIT = "6d294f24babbadbe7a42fbd91e9bead178acaa10"
+    OPENXLA_SHA256 = "9ead425b1159d32c42b4f5c3cbf48b9ec6331a288ceab6a034e419d0204a6229"
 
     SKYLIB_VERSION = "1.3.0"
 
@@ -202,62 +201,68 @@ def _com_intel_hexl():
         ],
     )
 
-def _com_github_amrayn_easyloggingpp():
+def _com_github_emptoolkit_emp_tool():
     maybe(
         http_archive,
-        name = "com_github_amrayn_easyloggingpp",
+        name = "com_github_emptoolkit_emp_tool",
+        sha256 = "b9ab2380312e78020346b5d2db3d0244c7bd8098cb50f8b3620532ef491808d0",
+        strip_prefix = "emp-tool-0.2.5",
         type = "tar.gz",
-        strip_prefix = "easyloggingpp-9.97.0",
-        sha256 = "9110638e21ef02428254af8688bf9e766483db8cc2624144aa3c59006907ce22",
-        build_file = "@spulib//bazel:easyloggingpp.BUILD",
+        patch_args = ["-p1"],
+        patches = [
+            "@spulib//bazel:patches/emp-tool.patch",
+            "@spulib//bazel:patches/emp-tool-cmake.patch",
+            "@spulib//bazel:patches/emp-tool-sse2neon.patch",
+        ],
         urls = [
-            "https://github.com/amrayn/easyloggingpp/archive/refs/tags/v9.97.0.tar.gz",
+            "https://github.com/emp-toolkit/emp-tool/archive/refs/tags/0.2.5.tar.gz",
         ],
+        build_file = "@spulib//bazel:emp-tool.BUILD",
     )
 
 def _com_github_emptoolkit_emp_ot():
     maybe(
         http_archive,
         name = "com_github_emptoolkit_emp_ot",
-        sha256 = "9c1198e04e2a081386814e9bea672fa6b4513829961c4ee150634354da609a91",
-        strip_prefix = "emp-ot-0.2.2",
+        sha256 = "358036e5d18143720ee17103f8172447de23014bcfc1f8e7d5849c525ca928ac",
+        strip_prefix = "emp-ot-0.2.4",
         type = "tar.gz",
         patch_args = ["-p1"],
         patches = ["@spulib//bazel:patches/emp-ot.patch"],
         urls = [
-            "https://github.com/emp-toolkit/emp-ot/archive/refs/tags/0.2.2.tar.gz",
+            "https://github.com/emp-toolkit/emp-ot/archive/refs/tags/0.2.4.tar.gz",
         ],
         build_file = "@spulib//bazel:emp-ot.BUILD",
     )
 
-def _com_github_microsoft_seal():
+def _com_github_emptoolkit_emp_zk():
     maybe(
         http_archive,
-        name = "com_github_microsoft_seal",
-        sha256 = "616653498ba8f3e0cd23abef1d451c6e161a63bd88922f43de4b3595348b5c7e",
-        strip_prefix = "SEAL-4.0.0",
+        name = "com_github_emptoolkit_emp_zk",
+        sha256 = "e02e6abc6ee14ca0e69e6f5f0efe24cab7da1bc905fc7c86a3e5a529114e489a",
+        strip_prefix = "emp-zk-0.2.1",
         type = "tar.gz",
         patch_args = ["-p1"],
-        patches = ["@spulib//bazel:patches/seal.patch"],
+        patches = ["@spulib//bazel:patches/emp-zk.patch"],
         urls = [
-            "https://github.com/microsoft/SEAL/archive/refs/tags/v4.0.0.tar.gz",
+            "https://github.com/emp-toolkit/emp-zk/archive/refs/tags/0.2.1.tar.gz",
         ],
-        build_file = "@spulib//bazel:seal.BUILD",
+        build_file = "@spulib//bazel:emp-zk.BUILD",
     )
 
-def _com_github_microsoft_fourqlib():
+def _com_github_microsoft_seal():
     maybe(
         http_archive,
-        name = "com_github_microsoft_fourqlib",
-        type = "zip",
-        strip_prefix = "FourQlib-ff61f680505c98c98e33387962223ce0b5e620bc",
-        sha256 = "59f1ebc35735217fc8c8f02c41765560ce3c5a8abd3937b0e2f4db45c49b6e73",
-        build_file = "@spulib//bazel:microsoft_fourqlib.BUILD",
+        name = "com_github_microsoft_seal",
+        sha256 = "616653498ba8f3e0cd23abef1d451c6e161a63bd88922f43de4b3595348b5c7e",
+        strip_prefix = "SEAL-4.0.0",
+        type = "tar.gz",
         patch_args = ["-p1"],
-        patches = ["@spulib//bazel:patches/fourq.patch"],
+        patches = ["@spulib//bazel:patches/seal.patch"],
         urls = [
-            "https://github.com/microsoft/FourQlib/archive/ff61f680505c98c98e33387962223ce0b5e620bc.zip",
+            "https://github.com/microsoft/SEAL/archive/refs/tags/v4.0.0.tar.gz",
         ],
+        build_file = "@spulib//bazel:seal.BUILD",
     )
 
 def _com_github_eigenteam_eigen():
@@ -283,7 +288,10 @@ def _com_github_microsoft_apsi():
         ],
         build_file = "@spulib//bazel:microsoft_apsi.BUILD",
         patch_args = ["-p1"],
-        patches = ["@spulib//bazel:patches/apsi.patch"],
+        patches = [
+            "@spulib//bazel:patches/apsi.patch",
+            "@spulib//bazel:patches/apsi-gen.patch",
+        ],
     )
 
 def _com_github_microsoft_gsl():
@@ -312,21 +320,6 @@ def _com_github_microsoft_kuku():
         build_file = "@spulib//bazel:microsoft_kuku.BUILD",
     )
 
-def _com_github_emptoolkit_emp_zk():
-    maybe(
-        http_archive,
-        name = "com_github_emptoolkit_emp_zk",
-        sha256 = "c03508b653fdc4b1251231c4c99c7240b858c598f228d66e85d61a5ba7fad39e",
-        strip_prefix = "emp-zk-208195554595603c6a3f922e8318bc5b0fa67d82",
-        type = "zip",
-        patch_args = ["-p1"],
-        patches = ["@spulib//bazel:patches/emp-zk.patch"],
-        urls = [
-            "https://github.com/emp-toolkit/emp-zk/archive/208195554595603c6a3f922e8318bc5b0fa67d82.zip",
-        ],
-        build_file = "@spulib//bazel:emp-zk.BUILD",
-    )
-
 def _com_google_flatbuffers():
     maybe(
         http_archive,
diff --git a/bazel/seal.BUILD b/bazel/seal.BUILD
index 62788067..77da8143 100644
--- a/bazel/seal.BUILD
+++ b/bazel/seal.BUILD
@@ -28,12 +28,17 @@ spu_cmake_external(
         "SEAL_BUILD_DEPS": "OFF",
         "SEAL_USE_ZSTD": "ON",
         "SEAL_USE_ZLIB": "OFF",
-        "SEAL_INTEL_HEXL": "ON",
+        "SEAL_USE_INTEL_HEXL": "ON",
         "SEAL_THROW_ON_TRANSPARENT_CIPHERTEXT": "OFF",  #NOTE(juhou) required by apsi
         "CMAKE_INSTALL_LIBDIR": "lib",
+        "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/",
+        "EXT_BUILD_DEPS": "$EXT_BUILD_DEPS",
     },
     lib_source = "@com_github_microsoft_seal//:all",
     out_include_dir = "include/SEAL-4.0",
     out_static_libs = ["libseal-4.0.a"],
-    deps = ["@com_github_facebook_zstd//:zstd"],
+    deps = [
+        "@com_github_facebook_zstd//:zstd",
+        "@com_intel_hexl//:hexl",
+    ],
 )
diff --git a/build_wheel_entrypoint.sh b/build_wheel_entrypoint.sh
index a28cd269..17d70cf4 100755
--- a/build_wheel_entrypoint.sh
+++ b/build_wheel_entrypoint.sh
@@ -17,8 +17,6 @@
 
 pip install numpy
 
-pip install numpy
-
 python setup.py bdist_wheel
 
 # Ensure binary safety
diff --git a/docs/reference/gen_np_op_status_doc.py b/docs/reference/gen_np_op_status_doc.py
index 0efb0a79..3a3808a3 100644
--- a/docs/reference/gen_np_op_status_doc.py
+++ b/docs/reference/gen_np_op_status_doc.py
@@ -63,12 +63,15 @@ def main():
 
     for op in status_doc:
         name = op['name']
+        status = op['status']
+        if status == "Status.PassNoGen":
+            continue
+
         mdFile.new_header(level=2, title=name)
         mdFile.new_line(
             f"JAX NumPy Document link: https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.{name}.html"
         )
         mdFile.new_header(level=3, title='Status')
-        status = op['status']
 
         if status == "Status.Pass":
             mdFile.new_line("**PASS**")
diff --git a/examples/python/ml/flax_vae/flax_vae.py b/examples/python/ml/flax_vae/flax_vae.py
index c82e8a38..beeb07fa 100644
--- a/examples/python/ml/flax_vae/flax_vae.py
+++ b/examples/python/ml/flax_vae/flax_vae.py
@@ -220,7 +220,7 @@ def train(run_on_spu: bool = True):
     train_ds = train_ds.map(prepare_image)
     train_ds = train_ds.cache()
     train_ds = train_ds.repeat()
-    train_ds = train_ds.shuffle(50000)
+    train_ds = train_ds.shuffle(50000, seed=10)
     train_ds = train_ds.batch(args.batch_size)
     train_ds = iter(tfds.as_numpy(train_ds))
 
diff --git a/libspu/compiler/front_end/hlo_importer.cc b/libspu/compiler/front_end/hlo_importer.cc
index 6daa5527..5c5c8a47 100644
--- a/libspu/compiler/front_end/hlo_importer.cc
+++ b/libspu/compiler/front_end/hlo_importer.cc
@@ -157,12 +157,12 @@ HloImporter::parseXlaModuleFromString(const std::string &content) {
   auto module_config =
       xla::HloModule::CreateModuleConfigFromProto(hlo_module, debug_options);
   if (!module_config.status().ok()) {
-    SPU_THROW(module_config.status().error_message());
+    SPU_THROW(module_config.status().message());
   }
 
   auto module = xla::HloModule::CreateFromProto(hlo_module, *module_config);
   if (!module.status().ok()) {
-    SPU_THROW(module.status().error_message());
+    SPU_THROW(module.status().message());
   }
 
   xla::runHloPasses((*module).get());
@@ -175,7 +175,7 @@ HloImporter::parseXlaModuleFromString(const std::string &content) {
 
   auto status = importer.Import(**module);
   if (!status.ok()) {
-    SPU_THROW(status.error_message());
+    SPU_THROW(status.message());
   }
 
   return mlir_hlo;
diff --git a/libspu/compiler/passes/optimize_sqrt_plus_eps.cc b/libspu/compiler/passes/optimize_sqrt_plus_eps.cc
index 92437433..86c874e8 100644
--- a/libspu/compiler/passes/optimize_sqrt_plus_eps.cc
+++ b/libspu/compiler/passes/optimize_sqrt_plus_eps.cc
@@ -41,7 +41,7 @@ struct SqrtRewriter : public OpRewritePattern<AddOp> {
     // Pattern
     // (sqrt(x) + small_const)
     // Into
-    // sqrt(x + small_const)
+    // sqrt(x + eps)
     auto added_const = op.getRhs().getDefiningOp<ConstantOp>();
     if (!added_const) {
       return failure();
diff --git a/libspu/compiler/passes/rewrite_div_sqrt_patterns.cc b/libspu/compiler/passes/rewrite_div_sqrt_patterns.cc
index cac9ee5f..3c732384 100644
--- a/libspu/compiler/passes/rewrite_div_sqrt_patterns.cc
+++ b/libspu/compiler/passes/rewrite_div_sqrt_patterns.cc
@@ -34,37 +34,38 @@ struct DivRewriter : public OpRewritePattern<DivOp> {
   LogicalResult matchAndRewrite(DivOp op,
                                 PatternRewriter &rewriter) const override {
     // Pattern 1:
-    // y/sqrt(x)
+    // y/sqrt(x + eps)
     auto denominator = op.getRhs();
     if (auto sqrt = denominator.getDefiningOp<SqrtOp>()) {
-      auto rsqrt = rewriter.create<RsqrtOp>(
+      auto newRsqrt = rewriter.create<RsqrtOp>(
           denominator.getLoc(), denominator.getType(), sqrt.getOperand());
-      rewriter.replaceOpWithNewOp<MulOp>(op, op.getType(), op.getLhs(), rsqrt);
+      rewriter.replaceOpWithNewOp<MulOp>(op, op.getType(), op.getLhs(),
+                                         newRsqrt);
       return success();
-    }
-    // Pattern 2:
-    // y/(k*sqrt(x))
-    if (auto mul = denominator.getDefiningOp<MulOp>()) {
-      auto sqrt = mul.getLhs().getDefiningOp<SqrtOp>();
-      auto k = mul.getRhs();
-      // Try rhs
-      if (sqrt == nullptr) {
-        sqrt = mul.getRhs().getDefiningOp<SqrtOp>();
-        k = mul.getLhs();
-      }
-      // Still not...bailout
-      if (sqrt == nullptr) {
-        return failure();
+    } else {
+      // Pattern 2:
+      // y/(k*sqrt(x + eps)) -> y/k*rsqrt(x+eps)
+      if (auto mulOp = denominator.getDefiningOp<MulOp>()) {
+        auto sqrtOp = mulOp.getRhs().getDefiningOp<SqrtOp>();
+        auto k = mulOp.getLhs();
+        if (sqrtOp == nullptr) {
+          sqrtOp = mulOp.getLhs().getDefiningOp<SqrtOp>();
+          k = mulOp.getRhs();
+        }
+        if (sqrtOp) {
+          // y/k
+          auto newDiv = rewriter.create<DivOp>(
+              op.getLoc(), op->getResultTypes(), op.getLhs(), k);
+          // rsqrt(x+eps)
+          auto newRsqrt = rewriter.create<RsqrtOp>(
+              op->getLoc(), sqrtOp->getResultTypes(), sqrtOp->getOperand(0));
+          // y/k*rsqrt(x+eps)
+          rewriter.replaceOpWithNewOp<MulOp>(op, op.getType(), newDiv,
+                                             newRsqrt);
+          return success();
+        }
       }
-
-      auto div =
-          rewriter.create<DivOp>(op.getLoc(), op.getType(), op.getLhs(), k);
-      auto rsqrt = rewriter.create<RsqrtOp>(sqrt->getLoc(), sqrt.getType(),
-                                            sqrt->getOperands()[0]);
-      rewriter.replaceOpWithNewOp<MulOp>(op, op.getType(), div, rsqrt);
-      return success();
     }
-
     return failure();
   }
 };
diff --git a/libspu/compiler/tools/mlir-pphlo-opt.cc b/libspu/compiler/tools/mlir-pphlo-opt.cc
index 1f415295..07460637 100644
--- a/libspu/compiler/tools/mlir-pphlo-opt.cc
+++ b/libspu/compiler/tools/mlir-pphlo-opt.cc
@@ -32,7 +32,6 @@ int main(int argc, char **argv) {
   registry.insert<mlir::mhlo::MhloDialect, mlir::stablehlo::StablehloDialect,
                   mlir::pphlo::PPHloDialect, mlir::func::FuncDialect>();
 
-  return static_cast<int>(
-      failed(mlir::MlirOptMain(argc, argv, "MLIR pphlo pass driver\n", registry,
-                               /*preloadDialectsInContext=*/false)));
+  return static_cast<int>(failed(
+      mlir::MlirOptMain(argc, argv, "MLIR pphlo pass driver\n", registry)));
 }
diff --git a/libspu/core/BUILD.bazel b/libspu/core/BUILD.bazel
index b3f9d8b6..e880a7df 100644
--- a/libspu/core/BUILD.bazel
+++ b/libspu/core/BUILD.bazel
@@ -20,6 +20,7 @@ spu_cc_library(
     name = "core",
     deps = [
         ":array_ref",
+        ":config",
         ":encoding",
         ":ndarray_ref",
         ":shape_util",
@@ -86,10 +87,21 @@ spu_cc_library(
     ],
 )
 
+spu_cc_library(
+    name = "config",
+    srcs = ["config.cc"],
+    hdrs = ["config.h"],
+    deps = [
+        "//libspu:spu_cc_proto",
+        "//libspu/core:prelude",
+    ],
+)
+
 spu_cc_test(
     name = "encoding_test",
     srcs = ["encoding_test.cc"],
     deps = [
+        ":config",
         ":encoding",
         ":xt_helper",
     ],
@@ -271,6 +283,15 @@ spu_cc_library(
     }),
 )
 
+spu_cc_test(
+    name = "platform_utils_test",
+    srcs = ["platform_utils_test.cc"],
+    linkstatic = True,
+    deps = [
+        ":platform_utils",
+    ],
+)
+
 spu_cc_library(
     name = "bit_utils",
     srcs = ["bit_utils.cc"],
diff --git a/libspu/core/array_ref.cc b/libspu/core/array_ref.cc
index bb5fd925..c4f4b14e 100644
--- a/libspu/core/array_ref.cc
+++ b/libspu/core/array_ref.cc
@@ -103,7 +103,7 @@ ArrayRef ArrayRef::as(const Type& new_ty, bool force) const {
                 "viewed type={} not equal to origin type={}", new_ty, eltype());
   }
 
-  return {buf(), new_ty, numel(), stride(), offset()};
+  return ArrayRef(buf(), new_ty, numel(), stride(), offset());
 }
 
 ArrayRef ArrayRef::slice(int64_t start, int64_t stop, int64_t stride) const {
@@ -124,7 +124,7 @@ ArrayRef ArrayRef::slice(int64_t start, int64_t stop, int64_t stride) const {
   const int64_t n_stride = stride_ * stride;
   const int64_t n_offset = offset_ + start * stride_ * elsize();
 
-  return {buf(), eltype_, m, n_stride, n_offset};
+  return ArrayRef(buf(), eltype_, m, n_stride, n_offset);
 }
 
 bool ArrayRef::operator==(const ArrayRef& other) const {
diff --git a/libspu/core/array_ref.h b/libspu/core/array_ref.h
index 366e55bb..48f6e747 100644
--- a/libspu/core/array_ref.h
+++ b/libspu/core/array_ref.h
@@ -52,11 +52,11 @@ class ArrayRef {
   ArrayRef() = default;
 
   // full constructor
-  ArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype, int64_t numel,
-           int64_t stride, int64_t offset);
+  explicit ArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
+                    int64_t numel, int64_t stride, int64_t offset);
 
   // create a new buffer of uninitialized elements and ref to it.
-  ArrayRef(const Type& eltype, size_t numel);
+  explicit ArrayRef(const Type& eltype, size_t numel);
 
   // Return total number of elements.
   int64_t numel() const { return numel_; }
diff --git a/libspu/core/config.cc b/libspu/core/config.cc
new file mode 100644
index 00000000..f792d075
--- /dev/null
+++ b/libspu/core/config.cc
@@ -0,0 +1,92 @@
+// Copyright 2023 Ant Group Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "libspu/core/config.h"
+
+#include "prelude.h"
+
+namespace spu {
+namespace {
+
+size_t defaultFxpBits(FieldType field) {
+  switch (field) {
+    case FieldType::FM32: {
+      return 8;
+    }
+    case FieldType::FM64: {
+      return 18;
+    }
+    case FieldType::FM128: {
+      return 26;
+    }
+    default: {
+      SPU_THROW("unsupported field={}", field);
+    }
+  }
+}
+
+}  // namespace
+
+void populateRuntimecConfig(RuntimeConfig& cfg) {
+  // mandatory fields.
+  SPU_ENFORCE(cfg.protocol() != ProtocolKind::PROT_INVALID);
+  SPU_ENFORCE(cfg.field() != FieldType::FT_INVALID);
+
+  //
+  if (cfg.fxp_fraction_bits() == 0) {
+    cfg.set_fxp_fraction_bits(defaultFxpBits(cfg.field()));
+  }
+
+  if (cfg.fxp_div_goldschmidt_iters() == 0) {
+    cfg.set_fxp_div_goldschmidt_iters(2);
+  }
+
+  // fxp exponent config
+  {
+    if (cfg.fxp_exp_mode() == RuntimeConfig::EXP_DEFAULT) {
+      cfg.set_fxp_exp_mode(RuntimeConfig::EXP_TAYLOR);
+    }
+
+    if (cfg.fxp_exp_iters() == 0) {
+      cfg.set_fxp_exp_iters(8);
+    }
+  }
+
+  // fxp log config
+  {
+    if (cfg.fxp_log_mode() == RuntimeConfig::LOG_DEFAULT) {
+      cfg.set_fxp_log_mode(RuntimeConfig::LOG_PADE);
+    }
+
+    if (cfg.fxp_log_iters() == 0) {
+      cfg.set_fxp_log_iters(3);
+    }
+
+    if (cfg.fxp_log_orders() == 0) {
+      cfg.set_fxp_log_orders(8);
+    }
+  }
+
+  if (cfg.sigmoid_mode() == RuntimeConfig::SIGMOID_DEFAULT) {
+    cfg.set_sigmoid_mode(RuntimeConfig::SIGMOID_MM1);
+  }
+}
+
+RuntimeConfig makeFullRuntimeConfig(const RuntimeConfig& cfg) {
+  RuntimeConfig copy(cfg);
+  populateRuntimecConfig(copy);
+  return copy;
+}
+
+}  // namespace spu
diff --git a/libspu/core/config.h b/libspu/core/config.h
new file mode 100644
index 00000000..db55d5a5
--- /dev/null
+++ b/libspu/core/config.h
@@ -0,0 +1,30 @@
+// Copyright 2023 Ant Group Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "libspu/spu.pb.h"
+
+namespace spu {
+
+// Fill attribute with default value with a implementation defined value.
+//
+// Required field must be filled, or exception will be raised.
+// Optional field will not be touched.
+// Field with (implementation defined) default value will be changed
+void populateRuntimecConfig(RuntimeConfig& cfg);
+
+RuntimeConfig makeFullRuntimeConfig(const RuntimeConfig& cfg);
+
+}  // namespace spu
diff --git a/libspu/core/ndarray_ref.cc b/libspu/core/ndarray_ref.cc
index 74541179..e4745fdb 100644
--- a/libspu/core/ndarray_ref.cc
+++ b/libspu/core/ndarray_ref.cc
@@ -25,7 +25,7 @@
 #include "libspu/core/parallel_utils.h"
 
 namespace spu {
-namespace detail {
+namespace {
 
 std::vector<int64_t> deducePadShape(
     absl::Span<const int64_t> input_shape,
@@ -121,7 +121,7 @@ static bool attempt_nocopy_reshape(const NdArrayRef& old,
   return true;
 }
 
-}  // namespace detail
+}  // namespace
 
 // full constructor
 NdArrayRef::NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
@@ -162,7 +162,7 @@ NdArrayRef NdArrayRef::as(const Type& new_ty, bool force) const {
   if (!force) {
     SPU_ENFORCE(elsize() == new_ty.size(),
                 "viewed type={} not equal to origin type={}", new_ty, eltype());
-    return {buf(), new_ty, shape(), strides(), offset()};
+    return NdArrayRef(buf(), new_ty, shape(), strides(), offset());
   }
   // Force view, we need to adjust strides
   auto distance = ((strides().empty() ? 1 : strides().back()) * elsize());
@@ -172,7 +172,7 @@ NdArrayRef NdArrayRef::as(const Type& new_ty, bool force) const {
   std::transform(new_strides.begin(), new_strides.end(), new_strides.begin(),
                  [&](int64_t s) { return (elsize() * s) / new_ty.size(); });
 
-  return {buf(), new_ty, shape(), new_strides, offset()};
+  return NdArrayRef(buf(), new_ty, shape(), new_strides, offset());
 }
 
 int64_t NdArrayRef::numel() const { return calcNumel(shape()); }
@@ -238,7 +238,7 @@ NdArrayRef NdArrayRef::broadcast_to(absl::Span<const int64_t> to_shape,
     }
   }
 
-  return {buf(), eltype(), to_shape, new_strides, offset()};
+  return NdArrayRef(buf(), eltype(), to_shape, new_strides, offset());
 }
 
 NdArrayRef NdArrayRef::reshape(absl::Span<const int64_t> to_shape) const {
@@ -251,13 +251,13 @@ NdArrayRef NdArrayRef::reshape(absl::Span<const int64_t> to_shape) const {
               "reshape from {} to {} is changing numel", shape(), to_shape);
 
   std::vector<int64_t> new_strides(to_shape.size(), 0);
-  if (detail::attempt_nocopy_reshape(*this, to_shape, new_strides)) {
+  if (attempt_nocopy_reshape(*this, to_shape, new_strides)) {
     // No copy reshape
-    return {buf(), eltype(), to_shape, new_strides, offset()};
+    return NdArrayRef(buf(), eltype(), to_shape, new_strides, offset());
   }
 
   auto compact_clone = clone();
-  return {compact_clone.buf(), compact_clone.eltype(), to_shape};
+  return NdArrayRef(compact_clone.buf(), compact_clone.eltype(), to_shape);
 }
 
 NdArrayRef NdArrayRef::slice(absl::Span<const int64_t> start_indices,
@@ -287,13 +287,14 @@ NdArrayRef NdArrayRef::slice(absl::Span<const int64_t> start_indices,
     }
   }
 
-  return {buf(), eltype(), new_shape, new_strides,
-          &at(start_indices) - buf()->data<std::byte>()};
+  return NdArrayRef(buf(), eltype(), new_shape, new_strides,
+                    &at(start_indices) - buf()->data<std::byte>());
 }
 
 NdArrayRef NdArrayRef::slice_scalar_at(
     absl::Span<const int64_t> indices) const {
-  return {buf(), eltype(), {}, {}, &at(indices) - buf()->data<std::byte>()};
+  return NdArrayRef(buf(), eltype(), {}, {},
+                    &at(indices) - buf()->data<std::byte>());
 }
 
 NdArrayRef NdArrayRef::transpose(absl::Span<const int64_t> permutation) const {
@@ -400,7 +401,7 @@ NdArrayRef NdArrayRef::pad(const NdArrayRef& padding_value,
                            absl::Span<const int64_t> edge_padding_low,
                            absl::Span<const int64_t> edge_padding_high,
                            absl::Span<const int64_t> interior_padding) const {
-  auto result = padding_value.expand(detail::deducePadShape(
+  auto result = padding_value.expand(deducePadShape(
       shape(), edge_padding_low, edge_padding_high, interior_padding));
 
   const auto& result_shape = result.shape();
@@ -543,8 +544,8 @@ NdArrayRef unflatten(const ArrayRef& arr, absl::Span<const int64_t> shape) {
               arr.numel());
 
   if (arr.stride() == 0) {
-    return {arr.buf(), arr.eltype(), shape,
-            std::vector<int64_t>(shape.size(), 0), arr.offset()};
+    return NdArrayRef(arr.buf(), arr.eltype(), shape,
+                      std::vector<int64_t>(shape.size(), 0), arr.offset());
   }
 
   // FIXME: due to the current implementation,
@@ -552,7 +553,8 @@ NdArrayRef unflatten(const ArrayRef& arr, absl::Span<const int64_t> shape) {
               arr);
 
   auto strides = makeCompactStrides(shape);
-  return {arr.buf(), arr.eltype(), shape, std::move(strides), arr.offset()};
+  return NdArrayRef(arr.buf(), arr.eltype(), shape, std::move(strides),
+                    arr.offset());
 }
 
 namespace {
@@ -596,7 +598,8 @@ ArrayRef flatten(const NdArrayRef& ndarr) {
   // create a compact clone, it's save here since underline layer will never
   // modify inplace.
   auto compact = ndarr.clone();
-  return {compact.buf(), ndarr.eltype(), ndarr.numel(), 1, compact.offset()};
+  return ArrayRef(compact.buf(), ndarr.eltype(), ndarr.numel(), 1,
+                  compact.offset());
 }
 
 NdArrayRef::Iterator& NdArrayRef::Iterator::operator++() {
diff --git a/libspu/core/ndarray_ref.h b/libspu/core/ndarray_ref.h
index 7b93ecd6..e744381a 100644
--- a/libspu/core/ndarray_ref.h
+++ b/libspu/core/ndarray_ref.h
@@ -51,21 +51,21 @@ class NdArrayRef {
   NdArrayRef() = default;
 
   // full constructor
-  NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
-             std::vector<int64_t> shape, std::vector<int64_t> strides,
-             int64_t offset);
+  explicit NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
+                      std::vector<int64_t> shape, std::vector<int64_t> strides,
+                      int64_t offset);
 
   // constructor, view buf as a compact buffer with given shape.
-  NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
-             absl::Span<const int64_t> shape);
+  explicit NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
+                      absl::Span<const int64_t> shape);
 
   // constructor, create a new buffer of elements and ref to it.
-  NdArrayRef(const Type& eltype, absl::Span<const int64_t> shape);
+  explicit NdArrayRef(const Type& eltype, absl::Span<const int64_t> shape);
 
   // convenient constructor to accept shape/strides from xtensor.
   template <typename ShapeT, typename StridesT>
-  NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype, ShapeT&& shape,
-             StridesT&& strides, int64_t offset)
+  explicit NdArrayRef(std::shared_ptr<yacl::Buffer> buf, Type eltype,
+                      ShapeT&& shape, StridesT&& strides, int64_t offset)
       : NdArrayRef(std::move(buf), std::move(eltype),
                    {shape.begin(), shape.end()},
                    {strides.begin(), strides.end()}, offset) {}
diff --git a/libspu/core/platform_utils.cc b/libspu/core/platform_utils.cc
index 4b4cb0f6..670078d0 100644
--- a/libspu/core/platform_utils.cc
+++ b/libspu/core/platform_utils.cc
@@ -14,8 +14,6 @@
 
 #include "libspu/core/platform_utils.h"
 
-#include "spdlog/spdlog.h"
-
 #ifdef __x86_64__
 #include <immintrin.h>
 
@@ -30,7 +28,11 @@ namespace impl {
 // https://gcc.gnu.org/pipermail/gcc-patches/2017-June/475893.html
 
 // Reference pdep_u64 impl
-inline uint64_t pdep_u64_impl(uint64_t x, uint64_t m) {
+inline uint64_t
+#if defined(__x86_64__) && defined(__GNUC__)
+    __attribute__((target("arch=sandybridge")))
+#endif
+    pdep_u64_impl(uint64_t x, uint64_t m) {
   uint64_t result = 0x0UL;
   const uint64_t mask = 0x8000000000000000UL;
   uint64_t c;
@@ -55,7 +57,11 @@ inline uint64_t pdep_u64_impl(uint64_t x, uint64_t m) {
 }
 
 // Reference pext_u64 impl
-inline uint64_t pext_u64_impl(uint64_t x, uint64_t m) {
+inline uint64_t
+#if defined(__x86_64__) && defined(__GNUC__)
+    __attribute__((target("arch=sandybridge")))
+#endif
+    pext_u64_impl(uint64_t x, uint64_t m) {
   // initial bit permute control
   uint64_t p = 0x4040404040404040UL;
   const uint64_t mask = 0x8000000000000000UL;
@@ -84,32 +90,32 @@ inline uint64_t pext_u64_impl(uint64_t x, uint64_t m) {
 
 #ifdef __x86_64__
 static const auto kCpuFeatures = cpu_features::GetX86Info().features;
-
-bool hasAVX2() { return kCpuFeatures.avx2; }
-bool hasAVX512ifma() { return kCpuFeatures.avx512ifma; }
-
+static const bool kHasBMI2 = kCpuFeatures.bmi2;
+static const bool kHasAVX512 = kCpuFeatures.avx512ifma;
+static const bool kHasAVX2 = kCpuFeatures.avx2;
 #else
-bool hasAVX2() { return false; }
-bool hasAVX512ifma() { return false; }
+static const bool kHasBMI2 = false;
+static const bool kHasAVX512 = false;
+static const bool kHasAVX2 = false;
 #endif
 
+bool hasAVX2() { return kHasAVX2; }
+bool hasBMI2() { return kHasBMI2; }
+bool hasAVX512ifma() { return kHasAVX512; }
+
 // There are no bmi2 intrinsics on platforms other than x86, so directly
 // redirect them to ref implementations
 uint64_t pdep_u64(uint64_t a, uint64_t b) {
-#ifdef __x86_64__
-  if (kCpuFeatures.avx2) {
+  if (hasBMI2()) {
     return _pdep_u64(a, b);
   }
-#endif
   return impl::pdep_u64_impl(a, b);
 }
 
 uint64_t pext_u64(uint64_t a, uint64_t b) {
-#ifdef __x86_64__
-  if (kCpuFeatures.avx2) {
+  if (hasBMI2()) {
     return _pext_u64(a, b);
   }
-#endif
   return impl::pext_u64_impl(a, b);
 }
 
diff --git a/libspu/core/platform_utils.h b/libspu/core/platform_utils.h
index 0ca34fda..aff09d47 100644
--- a/libspu/core/platform_utils.h
+++ b/libspu/core/platform_utils.h
@@ -18,8 +18,9 @@
 
 namespace spu {
 
-bool hasAVX2();
-bool hasAVX512ifma();
+extern bool hasAVX2();
+extern bool hasBMI2();
+extern bool hasAVX512ifma();
 
 // bmi2 wrapper
 uint64_t pdep_u64(uint64_t a, uint64_t b);
diff --git a/libspu/core/type_util.cc b/libspu/core/type_util.cc
index fcba919c..7fb78887 100644
--- a/libspu/core/type_util.cc
+++ b/libspu/core/type_util.cc
@@ -154,28 +154,4 @@ FieldType PtTypeToField(PtType pt_type) {
 #undef CASE
 }
 
-static size_t makeDefaultFractionalBits(FieldType field) {
-  switch (field) {
-    case FieldType::FM32: {
-      return 8;
-    }
-    case FieldType::FM64: {
-      return 18;
-    }
-    case FieldType::FM128: {
-      return 26;
-    }
-    default: {
-      SPU_THROW("unsupported field={}", field);
-    }
-  }
-}
-
-size_t getDefaultFxpBits(const RuntimeConfig& config) {
-  if (config.fxp_fraction_bits() == 0) {
-    return makeDefaultFractionalBits(config.field());
-  }
-  return config.fxp_fraction_bits();
-}
-
 }  // namespace spu
diff --git a/libspu/core/type_util.h b/libspu/core/type_util.h
index c0515198..c9da4ba8 100644
--- a/libspu/core/type_util.h
+++ b/libspu/core/type_util.h
@@ -250,7 +250,4 @@ inline size_t SizeOf(FieldType field) { return SizeOf(GetStorageType(field)); }
     }                                                               \
   }()
 
-// Return the default number of fractional bits.
-size_t getDefaultFxpBits(const RuntimeConfig& config);
-
 }  // namespace spu
diff --git a/libspu/device/io.cc b/libspu/device/io.cc
index c0606c6b..a1f2a4b5 100644
--- a/libspu/device/io.cc
+++ b/libspu/device/io.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "libspu/core/config.h"
 #include "libspu/core/encoding.h"
 #include "libspu/core/pt_buffer_view.h"
 #include "libspu/kernel/hal/constants.h"
@@ -24,15 +25,14 @@
 
 namespace spu::device {
 
-IoClient::IoClient(size_t world_size, RuntimeConfig config)
-    : world_size_(world_size), config_(std::move(config)) {
+IoClient::IoClient(size_t world_size, const RuntimeConfig &config)
+    : world_size_(world_size), config_(makeFullRuntimeConfig(config)) {
   base_io_ = mpc::Factory::CreateIO(config_, world_size_);
 }
 
 std::vector<spu::Value> IoClient::makeShares(const PtBufferView &bv,
                                              Visibility vtype, int owner_rank) {
-  // FIXME(jint), this should be in the io context.
-  const size_t fxp_bits = getDefaultFxpBits(config_);
+  const size_t fxp_bits = config_.fxp_fraction_bits();
   SPU_ENFORCE(fxp_bits != 0, "fxp should never be zero, please check default");
 
   if (bv.pt_type == PT_BOOL && vtype == VIS_SECRET &&
@@ -81,8 +81,7 @@ NdArrayRef IoClient::combineShares(absl::Span<spu::Value const> values) {
               "wrong number of shares, got={}, expect={}", values.size(),
               world_size_);
 
-  // FIXME(jint), this should be in the io context.
-  const size_t fxp_bits = getDefaultFxpBits(config_);
+  const size_t fxp_bits = config_.fxp_fraction_bits();
   SPU_ENFORCE(fxp_bits != 0, "fxp should never be zero, please check default");
 
   // reconstruct to ring buffer.
diff --git a/libspu/device/io.h b/libspu/device/io.h
index f6180ccf..d0a7b841 100644
--- a/libspu/device/io.h
+++ b/libspu/device/io.h
@@ -102,7 +102,7 @@ class IoClient {
   std::unique_ptr<mpc::IoInterface> base_io_;
 
  public:
-  explicit IoClient(size_t world_size, RuntimeConfig config);
+  explicit IoClient(size_t world_size, const RuntimeConfig &config);
 
   // Make shares from plaintext buffer view.
   // Valid owner must be >= 0, and -1 indicates outsourcing model.
diff --git a/libspu/device/pphlo/pphlo_executor.cc b/libspu/device/pphlo/pphlo_executor.cc
index 36447ebc..588c6ac9 100644
--- a/libspu/device/pphlo/pphlo_executor.cc
+++ b/libspu/device/pphlo/pphlo_executor.cc
@@ -20,6 +20,7 @@
 #include "libspu/core/encoding.h"
 #include "libspu/dialect/pphlo_base_enums.h"
 #include "libspu/dialect/pphlo_ops.h"
+#include "libspu/kernel/hal/ring.h"
 #include "libspu/kernel/hlo/basic_binary.h"
 #include "libspu/kernel/hlo/basic_ternary.h"
 #include "libspu/kernel/hlo/basic_unary.h"
@@ -125,6 +126,16 @@ spu::Visibility convertVisibility(mlir::pphlo::Visibility vis) {
   SPU_THROW("Should not hit");
 }
 
+int64_t findTwoK(double in) {
+  uint64_t N = 1;
+  int64_t count = 0;
+  while (N < in) {
+    N <<= 1;
+    ++count;
+  }
+  return --count;
+}
+
 }  // namespace
 
 namespace spu::device::pphlo {
@@ -231,7 +242,6 @@ STANDARD_BINARY_OP_EXEC_IMPL(GreaterEqualOp, GreaterEqual)
 STANDARD_BINARY_OP_EXEC_IMPL(SubtractOp, Sub)
 STANDARD_BINARY_OP_EXEC_IMPL(LessOp, Less)
 STANDARD_BINARY_OP_EXEC_IMPL(GreaterOp, Greater)
-STANDARD_BINARY_OP_EXEC_IMPL(MulOp, Mul)
 STANDARD_BINARY_OP_EXEC_IMPL(PowOp, Power)
 STANDARD_BINARY_OP_EXEC_IMPL(MaxOp, Max)
 STANDARD_BINARY_OP_EXEC_IMPL(MinOp, Min)
@@ -245,6 +255,61 @@ STANDARD_BINARY_OP_EXEC_IMPL(ShiftRightLogicalOp, Rshift)
 
 #undef STANDARD_BINARY_OP_EXEC_IMPL
 
+void execute(OpExecutor *executor, HalContext *hctx, SymbolScope *sscope,
+             mlir::pphlo::MulOp &op, const ExecutionOptions &opts) {
+  auto smallConst = op.getRhs().getDefiningOp<mlir::pphlo::ConstantOp>();
+  auto multiplier = op.getLhs();
+  if (!smallConst) {
+    // Try lhs
+    smallConst = op.getLhs().getDefiningOp<mlir::pphlo::ConstantOp>();
+    multiplier = op.getRhs();
+  }
+
+  if (smallConst && smallConst.getValue().isSplat()) {
+    auto elType = smallConst.getValue().getElementType();
+    if (elType.isF32() || elType.isF64()) {
+      auto fValue = std::abs(smallConst.getValue()
+                                 .getSplatValue<mlir::APFloat>()
+                                 .convertToFloat());
+      auto eps = kernel::hal::dump_public_as<float>(
+          hctx, kernel::hlo::Epsilon(hctx))[0];
+
+      // Amplify eps to 1/(2^(fxp_bits-2))
+      // TODO: Maybe make it configurable?
+      eps = eps * 4;
+
+      if (fValue < eps && fValue > 0) {
+        // Handle x * (very_small_const)
+        // return truncate(x * n/N, k); n = 2^k
+        // Compute N -> 1/fValue
+        auto N = 1 / fValue;
+        auto k = findTwoK(N);
+        auto n = std::pow(2, k);
+
+        // n/N
+        auto newRhs = kernel::hlo::Constant(hctx, static_cast<float>(n) / N,
+                                            smallConst.getType().getShape());
+        // x*n/N
+        auto kv = lookupValue(sscope, multiplier, opts);
+        // To merge truncation in multiply with next k-bits one, we
+        // deliberately pick the ring mul to do a mul *without* truncation
+        auto mulRet = kernel::hal::_mul(hctx, kv, newRhs).setDtype(kv.dtype());
+        // truncate(x*n/N, k)
+        addValue(sscope, op.getResult(),
+                 kernel::hal::_trunc(hctx, mulRet, hctx->getFxpBits() + k)
+                     .setDtype(mulRet.dtype()),
+                 opts);
+        return;
+      }
+    }
+  }
+
+  addValue(sscope, op.getResult(),
+           kernel::hlo::Mul(hctx, lookupValue(sscope, op.getLhs(), opts),
+                            lookupValue(sscope, op.getRhs(), opts)),
+           opts);
+}
+
 void execute(OpExecutor *executor, HalContext *hctx, SymbolScope *sscope,
              mlir::pphlo::DotOp &op, const ExecutionOptions &opts) {
   auto ret = kernel::hlo::Dot(hctx, lookupValue(sscope, op.getLhs(), opts),
diff --git a/libspu/device/pphlo/pphlo_verifier.cc b/libspu/device/pphlo/pphlo_verifier.cc
index 0035ccd5..bd125d10 100644
--- a/libspu/device/pphlo/pphlo_verifier.cc
+++ b/libspu/device/pphlo/pphlo_verifier.cc
@@ -151,100 +151,89 @@ mlir::stablehlo::Tensor convertToStablehloTensor(mlir::MLIRContext *mlir_ctx,
       mlir::DenseElementsAttr::get(shaped_type, buf));
 }
 
-bool verifyScalar(const mlir::stablehlo::Element &hlo_e,
-                  const std::byte *expected) {
+#define PRINT_DETAIL_LIMIT 10
+bool verifyScalar(const mlir::stablehlo::Element &xla_el,
+                  const mlir::stablehlo::Element &spu_el,
+                  int64_t mismatch_counter) {
   // return false;
-  auto t = hlo_e.getType();
+  auto t = xla_el.getType();
   if (t.isF32()) {
-    auto hlo_f = hlo_e.getFloatValue().convertToFloat();
-    auto exp_f = *reinterpret_cast<const float *>(expected);
-    return std::abs(hlo_f - exp_f) <= 1e-2;
+    auto xla_f = xla_el.getFloatValue().convertToFloat();
+    auto spu_f = spu_el.getFloatValue().convertToFloat();
+    bool ret = std::abs(xla_f - spu_f) <= 1e-2;
+    if (!ret && mismatch_counter < PRINT_DETAIL_LIMIT) {
+      SPDLOG_INFO("fxp mismatch xla_value = {}, spu_value = {}", xla_f, spu_f);
+    }
+    return ret;
   }
 
   if (t.isInteger(1)) {
-    return hlo_e.getBooleanValue() == *reinterpret_cast<const bool *>(expected);
+    auto ret = xla_el.getBooleanValue() == spu_el.getBooleanValue();
+    if (!ret && mismatch_counter < PRINT_DETAIL_LIMIT) {
+      SPDLOG_INFO("boolean mismatch xla_value = {}, spu_value = {}",
+                  xla_el.getBooleanValue(), spu_el.getBooleanValue());
+    }
+    return ret;
   }
 
-  auto hlo_i = hlo_e.getIntegerValue().getLimitedValue();
-  if (t.isSignlessInteger(8)) {
-    return static_cast<int8_t>(hlo_i) ==
-           *reinterpret_cast<const int8_t *>(expected);
+  auto ret = xla_el.getIntegerValue() == spu_el.getIntegerValue();
+  if (!ret && mismatch_counter < PRINT_DETAIL_LIMIT) {
+    SPDLOG_INFO("int mismatch xla_value = {}, spu_value = {}",
+                xla_el.getIntegerValue().getLimitedValue(),
+                spu_el.getIntegerValue().getLimitedValue());
   }
+  return ret;
+}
 
-  if (t.isUnsignedInteger(8)) {
-    return static_cast<uint8_t>(hlo_i) ==
-           *reinterpret_cast<const uint8_t *>(expected);
-  }
+std::string mlirTypeToString(mlir::Type type) {
+  std::string buf;
+  llvm::raw_string_ostream os(buf);
+  type.print(os);
+  return os.str();
+}
 
-  if (t.isSignlessInteger(16)) {
-    return static_cast<int16_t>(hlo_i) ==
-           *reinterpret_cast<const int16_t *>(expected);
+bool verifyEqual(HalContext *ctx, const mlir::stablehlo::Tensor &xla_ret,
+                 const mlir::stablehlo::Tensor &spu_ret) {
+  if (xla_ret.getType() != spu_ret.getType()) {
+    SPDLOG_INFO("Answer has a type mismatch, xla type = {}, spu type = {}",
+                mlirTypeToString(xla_ret.getType()),
+                mlirTypeToString(spu_ret.getType()));
+    return false;
   }
 
-  if (t.isUnsignedInteger(16)) {
-    return static_cast<uint16_t>(hlo_i) ==
-           *reinterpret_cast<const uint16_t *>(expected);
+  if (xla_ret.getNumElements() != spu_ret.getNumElements()) {
+    SPDLOG_ERROR("Number of element mismatch, xla numel = {}, spu numel = {}",
+                 xla_ret.getNumElements(), spu_ret.getNumElements());
+    return false;
   }
 
-  if (t.isSignlessInteger(32)) {
-    return static_cast<int32_t>(hlo_i) ==
-           *reinterpret_cast<const int32_t *>(expected);
+  if (xla_ret.getShape() != spu_ret.getShape()) {
+    SPDLOG_ERROR("Number of element mismatch, xla numel = {}, spu numel = {}",
+                 fmt::join(xla_ret.getShape(), "x"),
+                 fmt::join(spu_ret.getShape(), "x"));
+    return false;
   }
 
-  if (t.isUnsignedInteger(32)) {
-    return static_cast<uint32_t>(hlo_i) ==
-           *reinterpret_cast<const uint32_t *>(expected);
-  }
+  bool pass = true;
+  auto numel = xla_ret.getNumElements();
+  int64_t mismatch = 0;
 
-  if (t.isSignlessInteger(64)) {
-    return static_cast<int64_t>(hlo_i) ==
-           *reinterpret_cast<const int64_t *>(expected);
-  }
+  auto iter = spu_ret.index_begin();
+  auto iter_end = spu_ret.index_end();
 
-  if (t.isUnsignedInteger(64)) {
-    return static_cast<uint64_t>(hlo_i) ==
-           *reinterpret_cast<const uint64_t *>(expected);
+  while (iter != iter_end) {
+    bool equal = verifyScalar(xla_ret.get(*iter), spu_ret.get(*iter), mismatch);
+    if (!equal) {
+      pass = false;
+      ++mismatch;
+    }
+    ++iter;
   }
 
-  return false;
-}
-
-bool verifyEqual(const mlir::stablehlo::Tensor &hlo_ret,
-                 const NdArrayRef &expected) {
-  bool pass = true;
-  auto numel = expected.numel();
-  size_t mismatch = 0;
-
-  spu::kernel::forEachIndex(
-      expected.shape(), [&](absl::Span<const int64_t> output_index) {
-        mlir::stablehlo::Index idx{
-            llvm::ArrayRef<int64_t>(output_index.begin(), output_index.end())};
-        auto xla_value = hlo_ret.get(idx);
-        const auto *spu_value = &expected.at(output_index);
-        bool equal = verifyScalar(xla_value, spu_value);
-        if (!equal) {
-          // SPDLOG_INFO(
-          //     "Equal check failed at ({}), xla_value = {}, spu_value= {}",
-          //     fmt::join(output_index, ","), xla_value, spu_value);
-          pass = false;
-          ++mismatch;
-        }
-      });
-
-  SPDLOG_INFO("Answer has {} elements, {} mismatch found", numel, mismatch);
-  return pass;
-#undef CASE
-}
-
-bool verifyEqual(HalContext *ctx, const mlir::stablehlo::Tensor &ret,
-                 const spu::Value &expected) {
-  NdArrayRef arr;
-  if (expected.isSecret()) {
-    arr = kernel::hal::dump_public(ctx, kernel::hal::reveal(ctx, expected));
-  } else {
-    arr = kernel::hal::dump_public(ctx, expected);
+  if (!pass) {
+    SPDLOG_INFO("Answer has {} elements, {} mismatch found", numel, mismatch);
   }
-  return verifyEqual(ret, arr);
+  return pass;
 }
 
 }  // namespace
@@ -253,304 +242,82 @@ PPHloVerifier::PPHloVerifier(HalContext *ctx) : ctx_(ctx) {
   mlir_ctx_.getOrLoadDialect<mlir::stablehlo::StablehloDialect>();
 }
 
-void PPHloVerifier::verify(mlir::pphlo::AbsOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalAbsOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::ReciprocalOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::NegOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalNegOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::LogOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalLogOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::Log1pOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::FloorOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalFloorOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::CeilOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalCeilOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::LogisticOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalLogisticOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::TanhOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalTanhOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::NotOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalNotOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::ExpOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalExponentialOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::Expm1Op op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::RsqrtOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalRsqrtOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::SqrtOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto ret = mlir::stablehlo::evalSqrtOp(t1, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::SignOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::RoundOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::AddOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalAddOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::SubtractOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalSubtractOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::MulOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalMultiplyOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::PowOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalPowerOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::MaxOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalMaxOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::MinOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalMinOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::AndOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalAndOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::OrOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalOrOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::XorOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalXorOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::DivOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalDivideOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::RemOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalRemOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::DotOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::DotGeneralOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {}
-
-void PPHloVerifier::verify(mlir::pphlo::EqualOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::EQ, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::NotEqualOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::NE, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
+#define UNARY_VERIFIER(OP_TYPE, FCN_NAME)                                   \
+  void PPHloVerifier::verify(mlir::pphlo::OP_TYPE op,                       \
+                             absl::Span<const spu::Value> operands,         \
+                             absl::Span<const spu::Value> expected) {       \
+    auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);      \
+    auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]); \
+    auto xla_ret = mlir::stablehlo::FCN_NAME(t1, t1.getType());             \
+    mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));                 \
+  }
 
-void PPHloVerifier::verify(mlir::pphlo::LessOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::LT, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
+UNARY_VERIFIER(AbsOp, evalAbsOp)
+UNARY_VERIFIER(NegOp, evalNegOp)
+UNARY_VERIFIER(LogOp, evalLogOp)
+UNARY_VERIFIER(FloorOp, evalFloorOp)
+UNARY_VERIFIER(CeilOp, evalCeilOp)
+UNARY_VERIFIER(LogisticOp, evalLogisticOp)
+UNARY_VERIFIER(TanhOp, evalTanhOp)
+UNARY_VERIFIER(NotOp, evalNotOp)
+UNARY_VERIFIER(ExpOp, evalExponentialOp)
+UNARY_VERIFIER(RsqrtOp, evalRsqrtOp)
+UNARY_VERIFIER(SqrtOp, evalSqrtOp)
+UNARY_VERIFIER(RoundOp, evalRoundOp)
+UNARY_VERIFIER(SignOp, evalSignOp)
+
+#undef UNARY_VERIFIER
+
+#define BINARY_VERIFIER(OP_TYPE, FCN_NAME)                                  \
+  void PPHloVerifier::verify(mlir::pphlo::OP_TYPE op,                       \
+                             absl::Span<const spu::Value> operands,         \
+                             absl::Span<const spu::Value> expected) {       \
+    auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);      \
+    auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);      \
+    auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]); \
+    auto xla_ret = mlir::stablehlo::FCN_NAME(t1, t2, t1.getType());         \
+    mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));                 \
+  }
 
-void PPHloVerifier::verify(mlir::pphlo::LessEqualOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::LE, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
+BINARY_VERIFIER(AddOp, evalAddOp)
+BINARY_VERIFIER(SubtractOp, evalSubtractOp)
+BINARY_VERIFIER(MulOp, evalMultiplyOp)
+BINARY_VERIFIER(PowOp, evalPowerOp)
+BINARY_VERIFIER(MaxOp, evalMaxOp)
+BINARY_VERIFIER(MinOp, evalMinOp)
+BINARY_VERIFIER(AndOp, evalAndOp)
+BINARY_VERIFIER(OrOp, evalOrOp)
+BINARY_VERIFIER(XorOp, evalXorOp)
+BINARY_VERIFIER(DivOp, evalDivideOp)
+BINARY_VERIFIER(RemOp, evalRemOp)
+BINARY_VERIFIER(ShiftLeftOp, evalShiftLeftOp)
+BINARY_VERIFIER(ShiftRightLogicalOp, evalShiftRightLogicalOp)
+BINARY_VERIFIER(ShiftRightArithmeticOp, evalShiftRightArithmeticOp)
+
+#undef BINARY_VERIFIER
+
+#define COMPARISON_VERIFIER(COMP_OP, KIND)                                  \
+  void PPHloVerifier::verify(mlir::pphlo::COMP_OP op,                       \
+                             absl::Span<const spu::Value> operands,         \
+                             absl::Span<const spu::Value> expected) {       \
+    auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);      \
+    auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);      \
+    auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]); \
+    auto ret_type = mlir::RankedTensorType::get(                            \
+        t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));              \
+    auto xla_ret = mlir::stablehlo::evalCompareOp(                          \
+        t1, t2, mlir::stablehlo::ComparisonDirection::KIND, ret_type);      \
+    mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));                 \
+  }
 
-void PPHloVerifier::verify(mlir::pphlo::GreaterOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::GT, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
+COMPARISON_VERIFIER(EqualOp, EQ)
+COMPARISON_VERIFIER(NotEqualOp, NE)
+COMPARISON_VERIFIER(LessOp, LT)
+COMPARISON_VERIFIER(LessEqualOp, LE)
+COMPARISON_VERIFIER(GreaterOp, GT)
+COMPARISON_VERIFIER(GreaterEqualOp, GE)
 
-void PPHloVerifier::verify(mlir::pphlo::GreaterEqualOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret_type = mlir::RankedTensorType::get(
-      t1.getShape(), mlir::IntegerType::get(&mlir_ctx_, 1));
-  auto ret = mlir::stablehlo::evalCompareOp(
-      t1, t2, mlir::stablehlo::ComparisonDirection::GE, ret_type);
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
+#undef COMPARISON_VERIFIER
 
 void PPHloVerifier::verify(mlir::pphlo::SelectOp op,
                            absl::Span<const spu::Value> operands,
@@ -558,8 +325,9 @@ void PPHloVerifier::verify(mlir::pphlo::SelectOp op,
   auto pred = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
   auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[2]);
-  auto ret = mlir::stablehlo::evalSelectOp(pred, t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalSelectOp(pred, t1, t2, t1.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::ClampOp op,
@@ -568,42 +336,25 @@ void PPHloVerifier::verify(mlir::pphlo::ClampOp op,
   auto min_ = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
   auto op_ = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
   auto max_ = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[2]);
-  auto ret = mlir::stablehlo::evalClampOp(min_, op_, max_, op_.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::BitcastConvertOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::ConvertOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_INFO("Missing stablehlo interpreter support");
-}
-
-void PPHloVerifier::verify(mlir::pphlo::ConvolutionOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalClampOp(min_, op_, max_, op_.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::DynamicSliceOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
   llvm::SmallVector<mlir::stablehlo::Tensor> start_indices;
   for (size_t idx = 1; idx < operands.size(); ++idx) {
-    SPDLOG_INFO(operands[idx]);
     start_indices.emplace_back(
         convertToStablehloTensor(&mlir_ctx_, ctx_, operands[idx]));
   }
-  auto ret = mlir::stablehlo::evalDynamicSliceOp(
+  auto xla_ret = mlir::stablehlo::evalDynamicSliceOp(
       t1, start_indices, mlir::stablehlo::Sizes(op.getSliceSizes()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+      spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::DynamicUpdateSliceOp op,
@@ -611,20 +362,15 @@ void PPHloVerifier::verify(mlir::pphlo::DynamicUpdateSliceOp op,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
   auto update = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
   llvm::SmallVector<mlir::stablehlo::Tensor> start_indices;
   for (size_t idx = 2; idx < operands.size(); ++idx) {
     start_indices.emplace_back(
         convertToStablehloTensor(&mlir_ctx_, ctx_, operands[idx]));
   }
-  auto ret = mlir::stablehlo::evalDynamicUpdateSliceOp(
-      t1, update, start_indices, buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
-}
-
-void PPHloVerifier::verify(mlir::pphlo::GatherOp op,
-                           absl::Span<const spu::Value> operands,
-                           absl::Span<const spu::Value> expected) {
-  SPDLOG_WARN("Missing stablehlo interpreter support");
+  auto xla_ret = mlir::stablehlo::evalDynamicUpdateSliceOp(
+      t1, update, start_indices, spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::PadOp op,
@@ -632,23 +378,24 @@ void PPHloVerifier::verify(mlir::pphlo::PadOp op,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
   auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
 
-  auto ret = mlir::stablehlo::evalPadOp(
+  auto xla_ret = mlir::stablehlo::evalPadOp(
       t1, t2, mlir::stablehlo::Sizes(op.getEdgePaddingLow()),
-      mlir::stablehlo::Sizes(op.getInteriorPadding()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+      mlir::stablehlo::Sizes(op.getInteriorPadding()), spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::BroadcastOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
 
-  auto ret = mlir::stablehlo::evalBroadcastInDimOp(
+  auto xla_ret = mlir::stablehlo::evalBroadcastInDimOp(
       t1, mlir::stablehlo::Axes(op.getBroadcastDimensionsAttr()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+      spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::ConcatenateOp op,
@@ -658,61 +405,59 @@ void PPHloVerifier::verify(mlir::pphlo::ConcatenateOp op,
   for (const auto &operand : operands) {
     vals.emplace_back(convertToStablehloTensor(&mlir_ctx_, ctx_, operand));
   }
-  auto ret = mlir::stablehlo::evalConcatenateOp(
-      vals, op.getDimension(), buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalConcatenateOp(vals, op.getDimension(),
+                                                    spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::ReshapeOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-
-  auto ret = mlir::stablehlo::evalReshapeOp(
-      t1, buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalReshapeOp(t1, spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::ReverseOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-
-  auto ret = mlir::stablehlo::evalReverseOp(
-      t1, mlir::stablehlo::Axes(op.getDimensions()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalReverseOp(
+      t1, mlir::stablehlo::Axes(op.getDimensions()), spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::SliceOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-
-  auto ret = mlir::stablehlo::evalSliceOp(
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalSliceOp(
       t1, mlir::stablehlo::Index(op.getStartIndices()),
-      mlir::stablehlo::Sizes(op.getStrides()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+      mlir::stablehlo::Sizes(op.getStrides()), spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::TransposeOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-
-  auto ret = mlir::stablehlo::evalTransposeOp(
-      t1, mlir::stablehlo::Axes(op.getPermutation()),
-      buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret = mlir::stablehlo::evalTransposeOp(
+      t1, mlir::stablehlo::Axes(op.getPermutation()), spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::IotaOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
-  auto ret = mlir::stablehlo::evalIotaOp(
-      op.getIotaDimension(), buildMLIRType(&mlir_ctx_, expected[0]));
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  auto spu_ret = convertToStablehloTensor(&mlir_ctx_, ctx_, expected[0]);
+  auto xla_ret =
+      mlir::stablehlo::evalIotaOp(op.getIotaDimension(), spu_ret.getType());
+  mismatch_handler_(verifyEqual(ctx_, xla_ret, spu_ret));
 }
 
 void PPHloVerifier::verify(mlir::pphlo::ReduceOp op,
@@ -733,31 +478,62 @@ void PPHloVerifier::verify(mlir::pphlo::SelectAndScatterOp op,
   SPDLOG_WARN("Missing stablehlo interpreter support");
 }
 
-void PPHloVerifier::verify(mlir::pphlo::ShiftLeftOp op,
+void PPHloVerifier::verify(mlir::pphlo::SortOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
-  auto t1 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[0]);
-  auto t2 = convertToStablehloTensor(&mlir_ctx_, ctx_, operands[1]);
-  auto ret = mlir::stablehlo::evalShiftLeftOp(t1, t2, t1.getType());
-  mismatch_handler_(verifyEqual(ctx_, ret, expected[0]));
+  SPDLOG_WARN("Missing stablehlo interpreter support");
 }
 
-void PPHloVerifier::verify(mlir::pphlo::ShiftRightArithmeticOp op,
+void PPHloVerifier::verify(mlir::pphlo::GatherOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   SPDLOG_WARN("Missing stablehlo interpreter support");
 }
 
-void PPHloVerifier::verify(mlir::pphlo::ShiftRightLogicalOp op,
+void PPHloVerifier::verify(mlir::pphlo::BitcastConvertOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   SPDLOG_WARN("Missing stablehlo interpreter support");
 }
 
-void PPHloVerifier::verify(mlir::pphlo::SortOp op,
+void PPHloVerifier::verify(mlir::pphlo::ConvertOp op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {
+  SPDLOG_INFO("Missing stablehlo interpreter support");
+}
+
+void PPHloVerifier::verify(mlir::pphlo::ConvolutionOp op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {
+  SPDLOG_WARN("Missing stablehlo interpreter support");
+}
+
+void PPHloVerifier::verify(mlir::pphlo::ReciprocalOp op,
                            absl::Span<const spu::Value> operands,
                            absl::Span<const spu::Value> expected) {
   SPDLOG_WARN("Missing stablehlo interpreter support");
 }
 
+void PPHloVerifier::verify(mlir::pphlo::Log1pOp op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {
+  SPDLOG_WARN("Missing stablehlo interpreter support");
+}
+
+void PPHloVerifier::verify(mlir::pphlo::Expm1Op op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {
+  SPDLOG_WARN("Missing stablehlo interpreter support");
+}
+
+void PPHloVerifier::verify(mlir::pphlo::DotOp op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {
+  SPDLOG_WARN("Missing stablehlo interpreter support");
+}
+
+void PPHloVerifier::verify(mlir::pphlo::DotGeneralOp op,
+                           absl::Span<const spu::Value> operands,
+                           absl::Span<const spu::Value> expected) {}
+
 }  // namespace spu::device::pphlo
diff --git a/libspu/dialect/pphlo_ops.cc b/libspu/dialect/pphlo_ops.cc
index f2a7a1db..8e95ab18 100644
--- a/libspu/dialect/pphlo_ops.cc
+++ b/libspu/dialect/pphlo_ops.cc
@@ -814,9 +814,10 @@ LogicalResult TransposeOp::verify() {
 
 LogicalResult PadOp::inferReturnTypeComponents(
     MLIRContext* context, std::optional<Location> location,
-    ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
+    ValueShapeRange operands, DictionaryAttr attributes,
+    OpaqueProperties properties, RegionRange regions,
     SmallVectorImpl<ShapedTypeComponents>& inferredReturnShapes) {
-  PadOp::Adaptor adaptor(operands, attributes, regions);
+  PadOp::Adaptor adaptor(operands, attributes, {}, regions);
   SmallVector<Type> types;
   auto status = hlo::inferPadOp(
       location, adaptor.getOperand().getType(),
@@ -833,11 +834,11 @@ LogicalResult PadOp::inferReturnTypeComponents(
 }
 
 LogicalResult PadOp::inferReturnTypes(
-    ::mlir::MLIRContext* context, ::std::optional<::mlir::Location> location,
-    ::mlir::ValueRange operands, ::mlir::DictionaryAttr attributes,
-    ::mlir::RegionRange regions,
+    MLIRContext* context, std::optional<::mlir::Location> location,
+    ValueRange operands, DictionaryAttr attributes, OpaqueProperties properties,
+    RegionRange regions,
     ::llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes) {
-  PadOp::Adaptor adaptor(operands, attributes, regions);
+  PadOp::Adaptor adaptor(operands, attributes, {}, regions);
   return hlo::inferPadOp(location, adaptor.getOperand().getType(),
                          adaptor.getPaddingValue().getType(),
                          adaptor.getEdgePaddingLow(),
@@ -900,9 +901,9 @@ LogicalResult ConcatenateOp::verify() {
 
 LogicalResult ConcatenateOp::inferReturnTypes(
     MLIRContext*, std::optional<Location> location, ValueRange operands,
-    DictionaryAttr attributes, RegionRange regions,
+    DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions,
     SmallVectorImpl<Type>& inferredReturnTypes) {
-  ConcatenateOp::Adaptor adaptor(operands, attributes, regions);
+  ConcatenateOp::Adaptor adaptor(operands, attributes, {}, regions);
   return hlo::inferConcatenateOp(location, adaptor.getVal().getTypes(),
                                  adaptor.getDimension(), inferredReturnTypes);
 }
@@ -1040,9 +1041,9 @@ LogicalResult inferDynamicSliceOp(std::optional<Location> location,
 
 LogicalResult DynamicSliceOp::inferReturnTypes(
     MLIRContext* context, std::optional<Location> location, ValueRange operands,
-    DictionaryAttr attributes, RegionRange regions,
+    DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions,
     SmallVectorImpl<Type>& inferredReturnTypes) {
-  DynamicSliceOp::Adaptor adaptor(operands, attributes, regions);
+  DynamicSliceOp::Adaptor adaptor(operands, attributes, {}, regions);
   return inferDynamicSliceOp(location, adaptor.getOperand().getType(),
                              adaptor.getStartIndices().getTypes(),
                              adaptor.getSliceSizes(), inferredReturnTypes);
@@ -1092,9 +1093,9 @@ LogicalResult inferDynamicUpdateSliceOp(
 
 LogicalResult DynamicUpdateSliceOp::inferReturnTypes(
     MLIRContext* context, std::optional<Location> location, ValueRange operands,
-    DictionaryAttr attributes, RegionRange regions,
+    DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions,
     SmallVectorImpl<Type>& inferredReturnTypes) {
-  DynamicUpdateSliceOp::Adaptor adaptor(operands, attributes, regions);
+  DynamicUpdateSliceOp::Adaptor adaptor(operands, attributes, {}, regions);
 
   return inferDynamicUpdateSliceOp(
       location, adaptor.getOperand(), adaptor.getUpdate(),
diff --git a/libspu/kernel/context.cc b/libspu/kernel/context.cc
index a7196e7b..c10998fd 100644
--- a/libspu/kernel/context.cc
+++ b/libspu/kernel/context.cc
@@ -14,16 +14,16 @@
 
 #include "libspu/kernel/context.h"
 
+#include "libspu/core/config.h"
 #include "libspu/mpc/factory.h"
 
 namespace spu {
 
 HalContext::HalContext(const RuntimeConfig& config,
                        const std::shared_ptr<yacl::link::Context>& lctx)
-    : rt_config_(config),
+    : rt_config_(makeFullRuntimeConfig(config)),
       lctx_(lctx),
-      prot_(mpc::Factory::CreateCompute(config, lctx)),
-      rand_engine_(config.public_random_seed()) {}
+      prot_(mpc::Factory::CreateCompute(rt_config_, lctx)) {}
 
 std::unique_ptr<HalContext> HalContext::fork() {
   auto new_hctx = std::unique_ptr<HalContext>(new HalContext);
@@ -33,7 +33,6 @@ std::unique_ptr<HalContext> HalContext::fork() {
     new_hctx->lctx_ = lctx_->Spawn();
   }
   new_hctx->prot_ = prot_->fork();
-  new_hctx->rand_engine_.seed(rand_engine_());
 
   return new_hctx;
 }
diff --git a/libspu/kernel/context.h b/libspu/kernel/context.h
index 22dcae9b..05f3a328 100644
--- a/libspu/kernel/context.h
+++ b/libspu/kernel/context.h
@@ -19,6 +19,7 @@
 
 #include "yacl/link/link.h"
 
+#include "libspu/core/prelude.h"
 #include "libspu/core/trace.h"
 #include "libspu/mpc/object.h"
 
@@ -34,8 +35,6 @@ class HalContext final {
 
   std::unique_ptr<mpc::Object> prot_;
 
-  std::default_random_engine rand_engine_;
-
   HalContext() = default;
 
  public:
@@ -59,16 +58,17 @@ class HalContext final {
   mpc::Object* prot() const { return prot_.get(); }
 
   // Return current working fixed point fractional bits.
-  size_t getFxpBits() const { return getDefaultFxpBits(rt_config_); }
+  size_t getFxpBits() const {
+    auto fbits = rt_config_.fxp_fraction_bits();
+    SPU_ENFORCE(fbits != 0);
+    return fbits;
+  }
 
   // Return current working field of MPC engine.
   FieldType getField() const { return rt_config_.field(); }
 
   // Return current working runtime config.
   const RuntimeConfig& rt_config() const { return rt_config_; }
-
-  //
-  std::default_random_engine& rand_engine() { return rand_engine_; }
 };
 
 }  // namespace spu
diff --git a/libspu/kernel/hal/fxp_approx.cc b/libspu/kernel/hal/fxp_approx.cc
index 88ca1380..606a7b9a 100644
--- a/libspu/kernel/hal/fxp_approx.cc
+++ b/libspu/kernel/hal/fxp_approx.cc
@@ -123,14 +123,15 @@ Value log_householder_approx(HalContext* ctx, const Value& x) {
       f_add(ctx, f_sub(ctx, term_1, term_2), f_constant(ctx, 3.0, x.shape()));
 
   std::vector<Value> coeffs;
-  const size_t config_orders = ctx->rt_config().fxp_log_orders();
-  const size_t num_order = config_orders == 0 ? 8 : config_orders;
-  for (size_t i = 0; i < num_order; i++) {
+  const size_t fxp_log_orders = ctx->rt_config().fxp_log_orders();
+  SPU_ENFORCE(fxp_log_orders != 0, "fxp_log_orders should not be {}",
+              fxp_log_orders);
+  for (size_t i = 0; i < fxp_log_orders; i++) {
     coeffs.emplace_back(f_constant(ctx, 1.0 / (1.0 + i), x.shape()));
   }
 
-  const size_t config_iters = ctx->rt_config().fxp_log_iters();
-  const size_t num_iters = config_iters == 0 ? 3 : config_iters;
+  const size_t num_iters = ctx->rt_config().fxp_log_iters();
+  SPU_ENFORCE(num_iters != 0, "fxp_log_iters should not be {}", num_iters);
   for (size_t i = 0; i < num_iters; i++) {
     Value h = f_sub(ctx, f_constant(ctx, 1.0, x.shape()),
                     f_mul(ctx, x, f_exp(ctx, f_negate(ctx, y))));
@@ -143,13 +144,14 @@ Value log_householder_approx(HalContext* ctx, const Value& x) {
 // see https://lvdmaaten.github.io/publications/papers/crypten.pdf
 //   exp(x) = (1 + x / n) ^ n, when n is infinite large.
 Value exp_taylor_series(HalContext* ctx, const Value& x) {
-  const size_t config_iters = ctx->rt_config().fxp_exp_iters();
-  const size_t num_iters = config_iters == 0 ? 8 : config_iters;
+  const size_t fxp_exp_iters = ctx->rt_config().fxp_exp_iters();
+  SPU_ENFORCE(fxp_exp_iters != 0, "fxp_exp_iters should not be {}",
+              fxp_exp_iters);
 
-  Value res = f_add(ctx, _trunc(ctx, x, num_iters).asFxp(),
+  Value res = f_add(ctx, _trunc(ctx, x, fxp_exp_iters).asFxp(),
                     f_constant(ctx, 1.0, x.shape()));
 
-  for (size_t i = 0; i < num_iters; i++) {
+  for (size_t i = 0; i < fxp_exp_iters; i++) {
     res = f_square(ctx, res);
   }
 
@@ -273,8 +275,16 @@ Value f_exp(HalContext* ctx, const Value& x) {
     case RuntimeConfig::EXP_DEFAULT:
     case RuntimeConfig::EXP_TAYLOR:
       return detail::exp_taylor_series(ctx, x);
-    case RuntimeConfig::EXP_PADE:
-      return detail::exp_pade_approx(ctx, x);
+    case RuntimeConfig::EXP_PADE: {
+      // The valid input for exp_pade_approx is [-kInputLimit, kInputLimit].
+      // TODO(junfeng): should merge clamp into exp_pade_approx to save msb ops.
+      const float kInputLimit = 32 / std::log2(std::exp(1));
+      const auto clampped_x =
+          _clamp(ctx, x, f_constant(ctx, -kInputLimit, x.shape()),
+                 f_constant(ctx, kInputLimit, x.shape()))
+              .setDtype(x.dtype());
+      return detail::exp_pade_approx(ctx, clampped_x);
+    }
     default:
       SPU_THROW("unexpected exp approxmation method {}",
                 ctx->rt_config().fxp_exp_mode());
@@ -475,4 +485,59 @@ Value f_sqrt(HalContext* ctx, const Value& x) {
   return g;
 }
 
+namespace {
+
+Value sigmiod_real(HalContext* ctx, const Value& x) {
+  // f(x) = 1/(1+exp(-x))
+  const auto c1 = f_constant(ctx, 1.0F, x.shape());
+  return f_reciprocal(ctx, f_add(ctx, c1, f_exp(ctx, f_negate(ctx, x))));
+}
+
+Value sigmiod_mm1(HalContext* ctx, const Value& x) {
+  // SigmoidMM1: f(x) = 0.5 + 0.125 * x
+  const auto c1 = f_constant(ctx, 0.5, x.shape());
+  const auto c2 = f_constant(ctx, 0.125, x.shape());
+  return f_add(ctx, c1, f_mul(ctx, c2, x));
+}
+
+Value sigmiod_seg3(HalContext* ctx, const Value& x) {
+  // f(x) = 0.5 + 0.125x if -4 <= x <= 4
+  //        1            if       x > 4
+  //        0            if  -4 > x
+  // Rounds = Gt + Mux*2 = 4 + Log(K)
+  auto upper = f_constant(ctx, 1.0F, x.shape());
+  auto lower = f_constant(ctx, 0.0F, x.shape());
+  auto middle = sigmiod_mm1(ctx, x);
+
+  auto upper_bound = f_constant(ctx, 4.0F, x.shape());
+  auto lower_bound = f_constant(ctx, -4.0F, x.shape());
+
+  auto ret = _mux(ctx, f_less(ctx, upper_bound, x), upper, middle);
+  return _mux(ctx, f_less(ctx, x, lower_bound), lower, ret).asFxp();
+}
+
+}  // namespace
+
+Value f_sigmoid(HalContext* ctx, const Value& x) {
+  SPU_TRACE_HAL_DISP(ctx, x);
+
+  SPU_ENFORCE(x.isFxp());
+
+  switch (ctx->rt_config().sigmoid_mode()) {
+    case RuntimeConfig::SIGMOID_DEFAULT:
+    case RuntimeConfig::SIGMOID_MM1: {
+      return sigmiod_mm1(ctx, x);
+    }
+    case RuntimeConfig::SIGMOID_SEG3: {
+      return sigmiod_seg3(ctx, x);
+    }
+    case RuntimeConfig::SIGMOID_REAL: {
+      return sigmiod_real(ctx, x);
+    }
+    default: {
+      SPU_THROW("Should not hit");
+    }
+  }
+}
+
 }  // namespace spu::kernel::hal
diff --git a/libspu/kernel/hal/fxp_approx.h b/libspu/kernel/hal/fxp_approx.h
index 38613179..9b72ed34 100644
--- a/libspu/kernel/hal/fxp_approx.h
+++ b/libspu/kernel/hal/fxp_approx.h
@@ -51,4 +51,6 @@ Value f_rsqrt(HalContext* ctx, const Value& x);
 
 Value f_sqrt(HalContext* ctx, const Value& x);
 
+Value f_sigmoid(HalContext* ctx, const Value& x);
+
 }  // namespace spu::kernel::hal
diff --git a/libspu/kernel/hal/fxp_base.cc b/libspu/kernel/hal/fxp_base.cc
index 876e579c..9ab31b6f 100644
--- a/libspu/kernel/hal/fxp_base.cc
+++ b/libspu/kernel/hal/fxp_base.cc
@@ -16,6 +16,7 @@
 
 #include <cmath>
 
+#include "libspu/core/prelude.h"
 #include "libspu/kernel/hal/constants.h"
 #include "libspu/kernel/hal/fxp_cleartext.h"
 #include "libspu/kernel/hal/ring.h"
@@ -124,8 +125,9 @@ Value div_goldschmidt(HalContext* ctx, const Value& a, const Value& b) {
   auto r = w;
   auto e = f_sub(ctx, k1_, f_mul_with_sign(ctx, c, w, SignType::POSITIVE));
 
-  const size_t config_num_iters = ctx->rt_config().fxp_div_goldschmidt_iters();
-  const size_t num_iters = config_num_iters == 0 ? 2 : config_num_iters;
+  const size_t num_iters = ctx->rt_config().fxp_div_goldschmidt_iters();
+  SPU_ENFORCE(num_iters != 0, "fxp_div_goldschmidt_iters should not be {}",
+              num_iters);
 
   // iterate, r=r(1+e), e=e*e
   for (size_t itr = 0; itr < num_iters; itr++) {
@@ -164,8 +166,9 @@ Value reciprocal_goldschmidt_positive(HalContext* ctx, const Value& b_abs) {
   auto r = w;
   auto e = f_sub(ctx, k1_, f_mul_with_sign(ctx, b_abs, w, SignType::POSITIVE));
 
-  const size_t config_num_iters = ctx->rt_config().fxp_div_goldschmidt_iters();
-  const size_t num_iters = config_num_iters == 0 ? 2 : config_num_iters;
+  const size_t num_iters = ctx->rt_config().fxp_div_goldschmidt_iters();
+  SPU_ENFORCE(num_iters != 0, "fxp_div_goldschmidt_iters should not be {}",
+              num_iters);
 
   // iterate, r=r(1+e), e=e*e
   for (size_t itr = 0; itr < num_iters; itr++) {
diff --git a/libspu/kernel/hal/polymorphic.cc b/libspu/kernel/hal/polymorphic.cc
index 6c0b3a28..b4021d3f 100644
--- a/libspu/kernel/hal/polymorphic.cc
+++ b/libspu/kernel/hal/polymorphic.cc
@@ -78,41 +78,6 @@ bool isCrossIntFxp(const Value& x, const Value& y) {
   return (x.isFxp() && y.isInt()) || (x.isInt() && y.isFxp());
 }
 
-Value logisticMM1(HalContext* ctx, const Value& x) {
-  SPU_TRACE_HAL_DISP(ctx, x);
-
-  // SigmoidMM1: f(x) = 0.5 + 0.125 * x
-  const auto c1 = constant(ctx, 0.5, DT_FXP, x.shape());
-  const auto c2 = constant(ctx, 0.125, DT_FXP, x.shape());
-  return add(ctx, c1, mul(ctx, c2, x));
-}
-
-Value logisticReal(HalContext* ctx, const Value& x) {
-  SPU_TRACE_HAL_DISP(ctx, x);
-
-  // f(x) = 1/(1+exp(-x))
-  const auto c1 = constant(ctx, 1.0F, DT_FXP, x.shape());
-  return reciprocal(ctx, add(ctx, c1, exp(ctx, negate(ctx, x))));
-}
-
-Value logisticSEG3(HalContext* ctx, const Value& x) {
-  SPU_TRACE_HAL_DISP(ctx, x);
-
-  // f(x) = 0.5 + 0.125x if -4 <= x <= 4
-  //        1            if       x > 4
-  //        0            if  -4 > x
-  // Rounds = Gt + Mux*2 = 4 + Log(K)
-  auto upper = constant(ctx, 1.0F, DT_FXP, x.shape());
-  auto lower = constant(ctx, 0.0F, DT_FXP, x.shape());
-  auto middle = logisticMM1(ctx, x);
-
-  auto upper_bound = constant(ctx, 4.0F, DT_FXP, x.shape());
-  auto lower_bound = constant(ctx, -4.0F, DT_FXP, x.shape());
-
-  auto ret = select(ctx, greater(ctx, x, upper_bound), upper, middle);
-  return select(ctx, less(ctx, x, lower_bound), lower, ret);
-}
-
 }  // namespace
 
 Value add(HalContext* ctx, const Value& x, const Value& y) {
@@ -172,19 +137,7 @@ Value equal(HalContext* ctx, const Value& x, const Value& y) {
   SPU_TRACE_HAL_DISP(ctx, x, y);
   SPU_ENFORCE(x.shape() == y.shape(), "x = {}, y = {}", x, y);
 
-  if (ctx->rt_config().protocol() == ProtocolKind::CHEETAH &&
-      (x.isSecret() || y.isSecret())) {
-    // For 2PC, equal can be done with the same cost of half MSB.
-    //      x0 + x1 = y0 + y1 mod 2^k
-    // <=>  x0 - y0 = y1 - x1 mod 2^k
-    // <=>  [1{x = y}]_B <- EQ(x0 - y0, y1 - x1) where EQ is a 2PC protocol.
-    return dtypeBinaryDispatch<f_equal, i_equal>("equal", ctx, x, y);
-  }
-
-  // Note: following method does work, but slower ...
-  // With optimized msb kernel, A2B+PreOr is slower than 2*MSB
-  return bitwise_and(ctx, logical_not(ctx, less(ctx, x, y)),
-                     logical_not(ctx, less(ctx, y, x)));
+  return dtypeBinaryDispatch<f_equal, i_equal>("equal", ctx, x, y);
 }
 
 Value not_equal(HalContext* ctx, const Value& x, const Value& y) {
@@ -239,23 +192,7 @@ Value abs(HalContext* ctx, const Value& x) {
 Value exp(HalContext* ctx, const Value& in) {
   SPU_TRACE_HAL_DISP(ctx, in);
 
-  switch (ctx->rt_config().fxp_exp_mode()) {
-    case RuntimeConfig::EXP_DEFAULT:
-    case RuntimeConfig::EXP_TAYLOR:
-      return f_exp(ctx, dtype_cast(ctx, in, DT_FXP));
-    case RuntimeConfig::EXP_PADE: {
-      // The valid input for exp_pade_approx is [-kInputLimit, kInputLimit].
-      // TODO(junfeng): should merge clamp into exp_pade_approx to save msb ops.
-      const float kInputLimit = 32 / std::log2(std::exp(1));
-      const auto x = clamp(ctx, dtype_cast(ctx, in, DT_FXP),
-                           constant(ctx, -kInputLimit, DT_FXP, in.shape()),
-                           constant(ctx, kInputLimit, DT_FXP, in.shape()));
-      return f_exp(ctx, x);
-    }
-    default:
-      SPU_THROW("unexpected exp approximation method {}",
-                ctx->rt_config().fxp_exp_mode());
-  }
+  return f_exp(ctx, dtype_cast(ctx, in, DT_FXP));
 }
 
 Value select(HalContext* ctx, const Value& pred, const Value& a,
@@ -307,22 +244,7 @@ Value logistic(HalContext* ctx, const Value& in) {
   SPU_TRACE_HAL_DISP(ctx, in);
 
   SPU_ENFORCE(in.isFxp());
-
-  switch (ctx->rt_config().sigmoid_mode()) {
-    case RuntimeConfig::SIGMOID_DEFAULT:
-    case RuntimeConfig::SIGMOID_MM1: {
-      return logisticMM1(ctx, in);
-    }
-    case RuntimeConfig::SIGMOID_SEG3: {
-      return logisticSEG3(ctx, in);
-    }
-    case RuntimeConfig::SIGMOID_REAL: {
-      return logisticReal(ctx, in);
-    }
-    default: {
-      SPU_THROW("Should not hit");
-    }
-  }
+  return f_sigmoid(ctx, in);
 }
 
 Value log(HalContext* ctx, const Value& in) {
@@ -437,6 +359,7 @@ Value clamp(HalContext* ctx, const Value& x, const Value& minv,
             const Value& maxv) {
   SPU_TRACE_HAL_DISP(ctx, x, minv, maxv);
 
+  // TODO(jint) are these type contraint required?
   SPU_ENFORCE(minv.dtype() == maxv.dtype());
   SPU_ENFORCE(minv.dtype() == x.dtype());
 
diff --git a/libspu/kernel/hal/prot_wrapper.cc b/libspu/kernel/hal/prot_wrapper.cc
index f0200fb2..b6f11ad2 100644
--- a/libspu/kernel/hal/prot_wrapper.cc
+++ b/libspu/kernel/hal/prot_wrapper.cc
@@ -222,11 +222,25 @@ MAP_BINARY_OP(and_ss)
 MAP_BINARY_OP(xor_pp)
 MAP_BINARY_OP(xor_sp)
 MAP_BINARY_OP(xor_ss)
-MAP_BINARY_OP(equal_ss)
-MAP_BINARY_OP(equal_sp)
-MAP_BINARY_OP(equal_pp)
 MAP_MMUL_OP(mmul_pp)
 MAP_MMUL_OP(mmul_sp)
 MAP_MMUL_OP(mmul_ss)
 
+#define MAP_OPTIONAL_BINARY_OP(NAME)                                     \
+  std::optional<Value> _##NAME(HalContext* ctx, const Value& x,          \
+                               const Value& y) {                         \
+    SPU_TRACE_HAL_DISP(ctx, x, y);                                       \
+    SPU_ENFORCE(x.shape() == y.shape(), "shape mismatch: x={}, y={}",    \
+                x.shape(), y.shape());                                   \
+    auto ret = mpc::NAME(ctx->prot(), flattenValue(x), flattenValue(y)); \
+    if (!ret.has_value()) {                                              \
+      return std::nullopt;                                               \
+    }                                                                    \
+    return unflattenValue(ret.value(), x.shape());                       \
+  }
+
+MAP_OPTIONAL_BINARY_OP(equal_ss)
+MAP_OPTIONAL_BINARY_OP(equal_sp)
+MAP_BINARY_OP(equal_pp)
+
 }  // namespace spu::kernel::hal
diff --git a/libspu/kernel/hal/prot_wrapper.h b/libspu/kernel/hal/prot_wrapper.h
index cea016a5..977889bb 100644
--- a/libspu/kernel/hal/prot_wrapper.h
+++ b/libspu/kernel/hal/prot_wrapper.h
@@ -33,8 +33,8 @@ Value _msb_p(HalContext* ctx, const Value& in);
 Value _msb_s(HalContext* ctx, const Value& in);
 
 Value _equal_pp(HalContext* ctx, const Value& x, const Value& y);
-Value _equal_sp(HalContext* ctx, const Value& x, const Value& y);
-Value _equal_ss(HalContext* ctx, const Value& x, const Value& y);
+std::optional<Value> _equal_sp(HalContext* ctx, const Value& x, const Value& y);
+std::optional<Value> _equal_ss(HalContext* ctx, const Value& x, const Value& y);
 
 Value _lshift_p(HalContext* ctx, const Value& in, size_t bits);
 Value _lshift_s(HalContext* ctx, const Value& in, size_t bits);
diff --git a/libspu/kernel/hal/public_helper.h b/libspu/kernel/hal/public_helper.h
index a61ba46e..4201ea78 100644
--- a/libspu/kernel/hal/public_helper.h
+++ b/libspu/kernel/hal/public_helper.h
@@ -43,4 +43,13 @@ xt::xarray<T> dump_public_as(HalContext* ctx, const Value& in) {
 #undef CASE
 }
 
+template <typename T>
+T getScalarValue(HalContext* ctx, const spu::Value& value) {
+  SPU_ENFORCE(value.numel() == 1, "{} is not a scalar tensor.", value);
+  SPU_ENFORCE(value.isPublic(), "{} is not a public value", value);
+
+  const auto pvar = kernel::hal::dump_public_as<T>(ctx, value);
+  return pvar.front();
+}
+
 }  // namespace spu::kernel::hal
diff --git a/libspu/kernel/hal/random.cc b/libspu/kernel/hal/random.cc
index b41b1cc9..21e07879 100644
--- a/libspu/kernel/hal/random.cc
+++ b/libspu/kernel/hal/random.cc
@@ -20,28 +20,40 @@
 #include "libspu/kernel/hal/prot_wrapper.h"
 #include "libspu/kernel/hal/public_helper.h"
 
+//
+#include "libspu/mpc/common/prg_state.h"  // TODO: bad reference.
+
 namespace spu::kernel::hal {
+namespace {
+
+uint64_t genPublicRandSeed(HalContext* hctx) {
+  auto* prg_state = hctx->prot()->getState<mpc::PrgState>();
+  uint64_t seed;
+  prg_state->fillPubl(absl::MakeSpan(&seed, 1));
+  return seed;
+}
 
-Value rng_uniform(HalContext* ctx, const Value& a, const Value& b,
+}  // namespace
+
+Value rng_uniform(HalContext* ctx, const Value& lo, const Value& hi,
                   absl::Span<const int64_t> to_shape) {
-  SPU_TRACE_HAL_LEAF(ctx, a, b, to_shape);
-  SPU_ENFORCE(a.isPublic() && b.isPublic());
-  SPU_ENFORCE(a.dtype() == b.dtype());
-  // FIXME: This is a hacky ref impl, fill a real proper impl later.
-  if (a.isFxp()) {
-    auto pa = dump_public_as<float>(ctx, a);
-    auto pb = dump_public_as<float>(ctx, b);
-    xt::xarray<float> randv =
-        xt::random::rand(to_shape, pa[0], pb[0], ctx->rand_engine());
-    return constant(ctx, randv, DT_FXP);
+  SPU_TRACE_HAL_LEAF(ctx, lo, hi, to_shape);
+  SPU_ENFORCE(lo.isPublic() && hi.isPublic());
+  SPU_ENFORCE(lo.numel() == 1 && hi.numel() == 1);
+
+  const auto f_lo = getScalarValue<float>(ctx, lo);
+  const auto f_hi = getScalarValue<float>(ctx, hi);
+
+  // TODO: support more random generator.
+  std::mt19937 gen(genPublicRandSeed(ctx));
+  std::uniform_real_distribution<> dist(f_lo, f_hi);
+
+  std::vector<float> buffer(calcNumel(to_shape));
+  for (float& ele : buffer) {
+    ele = dist(gen);
   }
 
-  SPU_ENFORCE(a.isInt());
-  auto pa = dump_public_as<int>(ctx, a);
-  auto pb = dump_public_as<int>(ctx, b);
-  xt::xarray<int> randv =
-      xt::random::randint(to_shape, pa[0], pb[0], ctx->rand_engine());
-  return constant(ctx, randv, DT_FXP);
+  return constant(ctx, buffer, DT_FXP);
 }
 
 Value random(HalContext* ctx, Visibility vis, DataType dtype,
diff --git a/libspu/kernel/hal/random.h b/libspu/kernel/hal/random.h
index 7893a975..b2415bcc 100644
--- a/libspu/kernel/hal/random.h
+++ b/libspu/kernel/hal/random.h
@@ -20,10 +20,10 @@
 namespace spu::kernel::hal {
 
 /// Uniform rand
-// @param a, lower limit (include)
-// @param b, upper limit (exclude)
+// @param lo, lower limit (include)
+// @param hi, upper limit (exclude)
 // @param to_shape, the target shape
-Value rng_uniform(HalContext* ctx, const Value& a, const Value& b,
+Value rng_uniform(HalContext* ctx, const Value& lo, const Value& hi,
                   absl::Span<const int64_t> to_shape);
 
 /// Make a random value.
diff --git a/libspu/kernel/hal/ring.cc b/libspu/kernel/hal/ring.cc
index e139558d..fed0e389 100644
--- a/libspu/kernel/hal/ring.cc
+++ b/libspu/kernel/hal/ring.cc
@@ -170,7 +170,6 @@ IMPL_COMMUTATIVE_BINARY_OP(_add, _add_pp, _add_sp, _add_ss)
 IMPL_COMMUTATIVE_BINARY_OP(_mul, _mul_pp, _mul_sp, _mul_ss)
 IMPL_COMMUTATIVE_BINARY_OP(_and, _and_pp, _and_sp, _and_ss)
 IMPL_COMMUTATIVE_BINARY_OP(_xor, _xor_pp, _xor_sp, _xor_ss)
-IMPL_COMMUTATIVE_BINARY_OP(_equal, _equal_pp, _equal_sp, _equal_ss)
 
 Value _sub(HalContext* ctx, const Value& x, const Value& y) {
   SPU_TRACE_HAL_LEAF(ctx, x, y);
@@ -308,6 +307,42 @@ Value _or(HalContext* ctx, const Value& x, const Value& y) {
   return _xor(ctx, x, _xor(ctx, y, _and(ctx, x, y)));
 }
 
+static std::optional<Value> _equal_impl(HalContext* ctx, const Value& x,
+                                        const Value& y) {
+  SPU_TRACE_HAL_LEAF(ctx, x, y);
+
+  if (x.isPublic() && y.isPublic()) {
+    return _equal_pp(ctx, x, y);
+  } else if (x.isSecret() && y.isPublic()) {
+    return _equal_sp(ctx, x, y);
+  } else if (x.isPublic() && y.isSecret()) { /* commutative, swap args */
+    return _equal_sp(ctx, y, x);
+  } else if (x.isSecret() && y.isSecret()) {
+    return _equal_ss(ctx, y, x);
+  }
+
+  return std::nullopt;
+}
+
+Value _equal(HalContext* ctx, const Value& x, const Value& y) {
+  // First try use equal kernel, i.e. for 2PC , equal can be done with the same
+  // cost of half MSB.
+  //      x0 + x1 = y0 + y1 mod 2^k
+  // <=>  x0 - y0 = y1 - x1 mod 2^k
+  // <=>  [1{x = y}]_B <- EQ(x0 - y0, y1 - x1) where EQ is a 2PC protocol.
+  auto z = _equal_impl(ctx, x, y);
+  if (z.has_value()) {
+    return z.value();
+  }
+
+  // Note: With optimized msb kernel, A2B+PreOr is slower than 2*MSB
+  // eq(x, y) = !lt(x, y) & !lt(y, x)
+  //          = xor(a, 1) & xor(b, 1)  // let a = lt(x, y), b = lt(y, x)
+  const auto _k1 = _constant(ctx, 1, x.shape());
+  return _and(ctx, _xor(ctx, _less(ctx, x, y), _k1),
+              _xor(ctx, _less(ctx, y, x), _k1));
+}
+
 Value _trunc_with_sign(HalContext* ctx, const Value& x, size_t bits,
                        bool is_positive) {
   SPU_TRACE_HAL_LEAF(ctx, x, bits);
@@ -361,6 +396,7 @@ Value _sign(HalContext* ctx, const Value& x) {
 Value _less(HalContext* ctx, const Value& x, const Value& y) {
   SPU_TRACE_HAL_LEAF(ctx, x, y);
 
+  // Note: the impl assume inputs are signed with two's complement encoding.
   // test msb(x-y) == 1
   return _msb(ctx, _sub(ctx, x, y));
 }
@@ -385,6 +421,17 @@ Value _mux(HalContext* ctx, const Value& pred, const Value& a, const Value& b) {
   return _add(ctx, b, _mul(ctx, pred, _sub(ctx, a, b)));
 }
 
+Value _clamp(HalContext* ctx, const Value& x, const Value& minv,
+             const Value& maxv) {
+  SPU_TRACE_HAL_LEAF(ctx, x, minv, maxv);
+
+  // clamp lower bound, res = x < minv ? minv : x
+  auto res = _mux(ctx, _less(ctx, x, minv), minv, x);
+
+  // clamp upper bound, res = res < maxv ? res, maxv
+  return _mux(ctx, _less(ctx, res, maxv), res, maxv);
+}
+
 Value _constant(HalContext* ctx, uint128_t init,
                 absl::Span<const int64_t> shape) {
   return _make_p(ctx, init, shape);
@@ -506,7 +553,7 @@ Value _prefer_a(HalContext* ctx, const Value& x) {
 Value _prefer_b(HalContext* ctx, const Value& x) {
   if (x.storage_type().isa<AShare>()) {
     const auto k0 = _constant(ctx, 0U, x.shape());
-    return _or(ctx, x, k0).setDtype(x.dtype());  // noop, to bshare
+    return _xor(ctx, x, k0).setDtype(x.dtype());  // noop, to bshare
   }
 
   return x;
diff --git a/libspu/kernel/hal/ring.h b/libspu/kernel/hal/ring.h
index ac1682d3..ae010bc3 100644
--- a/libspu/kernel/hal/ring.h
+++ b/libspu/kernel/hal/ring.h
@@ -81,6 +81,9 @@ Value _bitrev(HalContext* ctx, const Value&, size_t start_idx, size_t end_idx);
 // Expect pred is either {0, 1}.
 Value _mux(HalContext* ctx, const Value& pred, const Value& a, const Value& b);
 
+// TODO: test me
+Value _clamp(HalContext* ctx, const Value& x, const Value& minv,
+             const Value& maxv);
 // Make a public value from uint128_t init value.
 //
 // If the current working field has less than 128bit, the lower sizeof(field)
diff --git a/libspu/kernel/hal/shape_ops.cc b/libspu/kernel/hal/shape_ops.cc
index 05ff74f6..6e1dbf8a 100644
--- a/libspu/kernel/hal/shape_ops.cc
+++ b/libspu/kernel/hal/shape_ops.cc
@@ -127,6 +127,7 @@ Value concatenate(HalContext* ctx, absl::Span<const Value> values,
       std::all_of(values.begin() + 1, values.end(), [&](const Value& v) {
         return v.storage_type() == values.begin()->storage_type();
       });
+
   if (!all_same_stype) {
     Type common_type = values[0].storage_type();
     for (size_t idx = 1; idx < values.size(); idx++) {
diff --git a/libspu/kernel/hlo/reduce.cc b/libspu/kernel/hlo/reduce.cc
index 21575672..305d4604 100644
--- a/libspu/kernel/hlo/reduce.cc
+++ b/libspu/kernel/hlo/reduce.cc
@@ -160,11 +160,13 @@ spu::Value ExpandStridedWindow(
               window_shape, window_strides, window_dilations, padding,
               base_shape, base_dilations, window_count_index, window_index,
               absl::MakeSpan(base_index));
+
           if (!out_of_bound) {
             // TODO: anti-pattern, do not use .data(), use ops instead.
             expanded.data().update_slice(
                 base.data().slice_scalar_at(base_index), expanded_index);
           }
+
           if (!bumpIndices<int64_t>(expanded_shape,
                                     absl::MakeSpan(expanded_index))) {
             break;
@@ -312,7 +314,7 @@ std::vector<spu::Value> ReduceWindowImpl(
 
   // For each resulting dimension, calculate and assign computed value.
   auto evaluate_impl =
-      [&](absl::Span<int64_t> output_index) -> std::vector<spu::Value> {
+      [&](absl::Span<int64_t const> output_index) -> std::vector<spu::Value> {
     std::vector<spu::Value> ret;
     RunOnWindowIndex(
         config.window_shape, config.window_strides, config.window_dilations,
@@ -329,9 +331,9 @@ std::vector<spu::Value> ReduceWindowImpl(
   };
 
   // For each window index
-  std::vector<spu::Value> batchs(nargs);
+  std::vector<spu::Value> batches(nargs);
   for (int64_t idx = 0; idx < nargs; ++idx) {
-    batchs[idx] =
+    batches[idx] =
         hal::expand(ctx, hal::slice_scalar_at(ctx, inputs[idx], {}), ret_shape);
   }
 
@@ -339,20 +341,18 @@ std::vector<spu::Value> ReduceWindowImpl(
     // Collect one element from each window
     std::vector<int64_t> output_index(ret_shape.size(), 0);
     do {
-      auto r = evaluate_impl(absl::MakeSpan(output_index));
+      auto r = evaluate_impl(output_index);
       if (!r.empty()) {
         for (int64_t idx = 0; idx < nargs; ++idx) {
-          batchs[idx].data().update_slice(r[idx].data(), output_index);
+          batches[idx].data().update_slice(r[idx].data(), output_index);
         }
       }
-    } while (
-        bumpIndices(absl::MakeSpan(ret_shape), absl::MakeSpan(output_index)));
+    } while (bumpIndices(ret_shape, absl::MakeSpan(output_index)));
 
     // Now run the batch
-    rets = reducer(rets, batchs);
+    rets = reducer(rets, batches);
 
-  } while (
-      bumpIndices<int64_t>(config.window_shape, absl::MakeSpan(window_index)));
+  } while (bumpIndices(config.window_shape, absl::MakeSpan(window_index)));
 
   return rets;
 }
diff --git a/libspu/mpc/aby3/io.cc b/libspu/mpc/aby3/io.cc
index fe145d3c..a869975d 100644
--- a/libspu/mpc/aby3/io.cc
+++ b/libspu/mpc/aby3/io.cc
@@ -77,8 +77,9 @@ std::vector<ArrayRef> Aby3Io::makeBitSecret(const ArrayRef& in) const {
   const auto out_type = makeType<BShrTy>(PT_U8, /* out_nbits */ 1);
   const size_t numel = in.numel();
 
-  std::vector<ArrayRef> shares{
-      {out_type, numel}, {out_type, numel}, {out_type, numel}};
+  std::vector<ArrayRef> shares = {ArrayRef(out_type, numel),
+                                  ArrayRef(out_type, numel),
+                                  ArrayRef(out_type, numel)};
   return DISPATCH_UINT_PT_TYPES(in_pt_type, "_", [&]() {
     using InT = ScalarT;
     using BShrT = uint8_t;
diff --git a/libspu/mpc/aby3/value.cc b/libspu/mpc/aby3/value.cc
index bd21eff3..845742f2 100644
--- a/libspu/mpc/aby3/value.cc
+++ b/libspu/mpc/aby3/value.cc
@@ -27,13 +27,13 @@ ArrayRef getShare(const ArrayRef& in, int64_t share_idx) {
   if (in.eltype().isa<AShrTy>()) {
     const auto field = in.eltype().as<AShrTy>()->field();
     const auto ty = makeType<RingTy>(field);
-    return {in.buf(), ty, in.numel(), in.stride() * 2,
-            in.offset() + share_idx * static_cast<int64_t>(ty.size())};
+    return ArrayRef(in.buf(), ty, in.numel(), in.stride() * 2,
+                    in.offset() + share_idx * static_cast<int64_t>(ty.size()));
   } else if (in.eltype().isa<BShrTy>()) {
     const auto stype = in.eltype().as<BShrTy>()->getBacktype();
     const auto ty = makeType<PtTy>(stype);
-    return {in.buf(), ty, in.numel(), in.stride() * 2,
-            in.offset() + share_idx * static_cast<int64_t>(ty.size())};
+    return ArrayRef(in.buf(), ty, in.numel(), in.stride() * 2,
+                    in.offset() + share_idx * static_cast<int64_t>(ty.size()));
   } else {
     SPU_THROW("unsupported type {}", in.eltype());
   }
diff --git a/libspu/mpc/api.cc b/libspu/mpc/api.cc
index 7ba21ce4..7d2be256 100644
--- a/libspu/mpc/api.cc
+++ b/libspu/mpc/api.cc
@@ -64,11 +64,11 @@ SPU_MPC_DEF_BINARY_OP(and_ss)
 SPU_MPC_DEF_BINARY_OP(xor_pp)
 SPU_MPC_DEF_BINARY_OP(xor_sp)
 SPU_MPC_DEF_BINARY_OP(xor_ss)
-SPU_MPC_DEF_BINARY_OP(equal_pp)
-SPU_MPC_DEF_BINARY_OP(equal_sp)
-SPU_MPC_DEF_BINARY_OP(equal_ss)
 SPU_MPC_DEF_MMUL(mmul_pp)
 SPU_MPC_DEF_MMUL(mmul_sp)
 SPU_MPC_DEF_MMUL(mmul_ss)
+SPU_MPC_DEF_BINARY_OP(equal_pp);
+SPU_MPC_DEF_OPTIONAL_BINARY_OP(equal_sp);
+SPU_MPC_DEF_OPTIONAL_BINARY_OP(equal_ss);
 
 }  // namespace spu::mpc
diff --git a/libspu/mpc/api.h b/libspu/mpc/api.h
index 651c1b7a..86f1f0c4 100644
--- a/libspu/mpc/api.h
+++ b/libspu/mpc/api.h
@@ -14,6 +14,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include "libspu/core/array_ref.h"
 #include "libspu/mpc/object.h"
 
@@ -80,8 +82,9 @@ ArrayRef msb_p(Object* ctx, const ArrayRef&);
 ArrayRef msb_s(Object* ctx, const ArrayRef&);
 
 ArrayRef equal_pp(Object* ctx, const ArrayRef&, const ArrayRef&);
-ArrayRef equal_sp(Object* ctx, const ArrayRef&, const ArrayRef&);
-ArrayRef equal_ss(Object* ctx, const ArrayRef&, const ArrayRef&);
+// Optional API, return nullopt if no valid kernel found.
+std::optional<ArrayRef> equal_sp(Object* ctx, const ArrayRef&, const ArrayRef&);
+std::optional<ArrayRef> equal_ss(Object* ctx, const ArrayRef&, const ArrayRef&);
 
 ArrayRef lshift_p(Object* ctx, const ArrayRef&, size_t);
 ArrayRef lshift_s(Object* ctx, const ArrayRef&, size_t);
@@ -143,6 +146,15 @@ ArrayRef mmul_ss(Object* ctx, const ArrayRef&, const ArrayRef&, size_t, size_t,
     return ctx->call(#NAME, x, y);                                   \
   }
 
+#define SPU_MPC_DEF_OPTIONAL_BINARY_OP(NAME)                   \
+  std::optional<ArrayRef> NAME(Object* ctx, const ArrayRef& x, \
+                               const ArrayRef& y) {            \
+    if (!ctx->hasKernel(#NAME)) {                              \
+      return std::nullopt;                                     \
+    }                                                          \
+    return ctx->call<std::optional<ArrayRef>>(#NAME, x, y);    \
+  }
+
 #define SPU_MPC_DEF_MMUL(NAME)                                                 \
   ArrayRef NAME(Object* ctx, const ArrayRef& x, const ArrayRef& y, size_t sz1, \
                 size_t sz2, size_t sz3) {                                      \
diff --git a/libspu/mpc/cheetah/arith/cheetah_mul.cc b/libspu/mpc/cheetah/arith/cheetah_mul.cc
index bd295a7e..6b306a59 100644
--- a/libspu/mpc/cheetah/arith/cheetah_mul.cc
+++ b/libspu/mpc/cheetah/arith/cheetah_mul.cc
@@ -45,9 +45,15 @@ namespace spu::mpc::cheetah {
 
 struct CheetahMul::Impl : public EnableCPRNG {
  public:
-  static constexpr uint32_t kSmallPrimeBitLen = 36;
+  // RLWE parameters: N = 8192, Q \approx 2^{109}, t \approx 2^{40}
+  // NOTE(juhou): Under this parameters, the Mul() might introduce 1-bit error
+  // within a small chance Pr = 2^{-32}.
   static constexpr size_t kPolyDegree = 8192;
+  static constexpr size_t kCipherModulusBits = 109;
+  static constexpr uint32_t kSmallPrimeBitLen = 40;
+
   static constexpr int kNoiseFloodRandomBits = 50;
+
   static constexpr size_t kParallelGrain = 1;
 
   explicit Impl(std::shared_ptr<yacl::link::Context> lctx)
@@ -60,14 +66,14 @@ struct CheetahMul::Impl : public EnableCPRNG {
   constexpr size_t OLEBatchSize() const { return kPolyDegree; }
 
   int Rank() const { return lctx_->Rank(); }
-
   static seal::EncryptionParameters DecideSEALParameters(uint32_t ring_bitlen) {
     size_t poly_deg = kPolyDegree;
     auto scheme_type = seal::scheme_type::bfv;
     auto parms = seal::EncryptionParameters(scheme_type);
     std::vector<int> modulus_bits;
-    // NOTE(juhou): 109bit should be enough for one multiplication under the
-    // `kSmallPrimeBitLen`
+    // NOTE(juhou): We set the 2nd modulus a bit larger than
+    // `kSmallPrimeBitLen`. We will drop the 2nd modulus during the H2A step.
+    // Also, it helps reducing the noise in the BFV ciphertext.
     modulus_bits = {60, 49};
     parms.set_use_special_prime(false);
     parms.set_poly_modulus_degree(poly_deg);
@@ -111,8 +117,20 @@ struct CheetahMul::Impl : public EnableCPRNG {
   static inline uint32_t FieldBitLen(FieldType f) { return 8 * SizeOf(f); }
 
   static inline uint32_t TotalCRTBitLen(uint32_t field_bitlen) {
-    const int margins_for_full_random = 15;
-    return 2 * field_bitlen + margins_for_full_random;
+    if (field_bitlen < 26) {
+      // 1 <= k < 26 uses P = 80bit
+      return 2 * kSmallPrimeBitLen;
+    } else if (field_bitlen < 64) {
+      // 26 <= k < 64 uses P = 120bit
+      return 3 * kSmallPrimeBitLen;
+    } else if (field_bitlen < 128) {
+      // 64 <= k < 128 uses P = 160bit
+      // The Pr(1bit error) < 2^{-32}
+      return 4 * kSmallPrimeBitLen;
+    }
+    // k == 128 uses P = 280bit
+    SPU_ENFORCE_EQ(field_bitlen, 128U);
+    return 7 * kSmallPrimeBitLen;
   }
 
   void LazyInitModSwitchHelper(uint32_t field_bitlen);
@@ -565,11 +583,13 @@ ArrayRef CheetahMul::Impl::MuThenResponse(
             // Multiply step
             CATCH_SEAL_ERROR(
                 evaluator.multiply_plain_inplace(ct, plains[offset + idx]));
+            // re-randomize the ciphertext (e.g., noise flood)
+            RandomizeCipherForDecryption(ct, cidx);
             // H2A
             CATCH_SEAL_ERROR(
                 evaluator.sub_plain_inplace(ct, ecd_random[offset + idx]));
-            // re-randomize the ciphertext (e.g., noise flood)
-            RandomizeCipherForDecryption(ct, cidx);
+            // Truncate for a smaller communication
+            TruncateBFVForDecryption(ct, seal_cntxt);
             response[offset + idx] = EncodeSEALObject(ct);
           }
         }
@@ -698,9 +718,6 @@ void CheetahMul::Impl::RandomizeCipherForDecryption(RLWECt &ct, size_t cidx) {
   RLWECt zero_enc;
   CATCH_SEAL_ERROR(pk_encryptors_[cidx]->encrypt_zero(ct.parms_id(), zero_enc));
   CATCH_SEAL_ERROR(evaluator.add_inplace(ct, zero_enc));
-
-  // 4. Truncate for smaller communication
-  TruncateBFVForDecryption(ct, seal_cntxt);
 }
 
 CheetahMul::CheetahMul(std::shared_ptr<yacl::link::Context> lctx) {
diff --git a/libspu/mpc/cheetah/arith/conv2d_helper.h b/libspu/mpc/cheetah/arith/conv2d_helper.h
index 8cd34f1e..e3342f05 100644
--- a/libspu/mpc/cheetah/arith/conv2d_helper.h
+++ b/libspu/mpc/cheetah/arith/conv2d_helper.h
@@ -11,9 +11,9 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
 #pragma once
+
 #include "libspu/core/array_ref.h"
 #include "libspu/core/ndarray_ref.h"
 #include "libspu/core/shape_util.h"
@@ -36,7 +36,7 @@ struct SlicedTensor {
 
   SlicedTensor(const SlicedTensor<Dim> &oth) = default;
 
-  SlicedTensor(SlicedTensor<Dim> &&oth) = default;
+  SlicedTensor(SlicedTensor<Dim> &&oth) noexcept = default;
 
   SlicedTensor<Dim> &operator=(const SlicedTensor<Dim> &oth) = delete;
 
diff --git a/libspu/mpc/cheetah/ot/ferret.cc b/libspu/mpc/cheetah/ot/ferret.cc
index 3ad8f71f..e301da41 100644
--- a/libspu/mpc/cheetah/ot/ferret.cc
+++ b/libspu/mpc/cheetah/ot/ferret.cc
@@ -219,7 +219,7 @@ struct FerretOT::Impl {
     }
 
     ferret_ = std::make_shared<emp::FerretCOT<CheetahIO>>(
-        role, thread, io_holder_.data(), malicious, run_setup, save_file);
+        role, thread, io_holder_.data(), malicious, run_setup);
 
     OtBaseTyp seed;
     if (is_sender_) {
diff --git a/libspu/mpc/cheetah/rlwe/utils.cc b/libspu/mpc/cheetah/rlwe/utils.cc
index 708fd71b..6f4dfea2 100644
--- a/libspu/mpc/cheetah/rlwe/utils.cc
+++ b/libspu/mpc/cheetah/rlwe/utils.cc
@@ -21,6 +21,8 @@
 
 namespace spu::mpc::cheetah {
 
+#define ALLOW_BFV_DECRYPTION_FAIL 1
+
 void RemoveCoefficientsInplace(RLWECt& ciphertext,
                                const std::set<size_t>& to_remove) {
   SPU_ENFORCE(!ciphertext.is_ntt_form());
@@ -119,6 +121,10 @@ void TruncateBFVForDecryption(seal::Ciphertext& ct,
   std::transform(ct.data(0), ct.data(0) + poly_n, ct.data(0),
                  [mask0](uint64_t u) { return u & mask0; });
 
+  // NOTE(juhou): The following truncation might lead to a decryption failure.
+  // For for error sensitive computation (eg Beaver's triples), we just skip
+  // this truncation.
+#if ALLOW_BFV_DECRYPTION_FAIL
   // Norm |c1 * s|_infty < |c1|_infty * |s|_infty.
   // The value of |c1|_infty * |s|_infty is heuristically bounded by 12. *
   // Std(|c1|_infty) * Std(|s|_infty) Assume |c1| < B is B-bounded uniform. Then
@@ -132,6 +138,7 @@ void TruncateBFVForDecryption(seal::Ciphertext& ct,
   const uint64_t mask1 = make_bits_mask(n_delta_bits - n_var_bits);
   std::transform(ct.data(1), ct.data(1) + poly_n, ct.data(1),
                  [mask1](uint64_t u) { return u & mask1; });
+#endif
 }
 
 void NttInplace(seal::Plaintext& pt, const seal::SEALContext& context) {
diff --git a/libspu/mpc/common/ab_api.cc b/libspu/mpc/common/ab_api.cc
index 80eeda46..13b04d41 100644
--- a/libspu/mpc/common/ab_api.cc
+++ b/libspu/mpc/common/ab_api.cc
@@ -428,7 +428,7 @@ class ABProtMulSP : public BinaryKernel {
 
 class ABProtMulSS : public BinaryKernel {
   static bool hasMulA1B(KernelEvalContext* ctx) {
-    return ctx->caller()->hasKernel("mul_a1b");
+    return ctx->hasKernel("mul_a1b");
   }
 
  public:
@@ -553,36 +553,72 @@ class ABProtXorSS : public BinaryKernel {
   }
 };
 
-class ABProtEqualSS : public BinaryKernel {
+class ABProtEqualSS : public Kernel {
  public:
   static constexpr char kBindName[] = "equal_ss";
 
   Kind kind() const override { return Kind::Dynamic; }
 
-  ArrayRef proc(KernelEvalContext* ctx, const ArrayRef& x,
-                const ArrayRef& y) const override {
+  void evaluate(KernelEvalContext* ctx) const override {
+    const auto& x = ctx->getParam<ArrayRef>(0);
+    const auto& y = ctx->getParam<ArrayRef>(1);
+
     SPU_TRACE_MPC_DISP(ctx, x, y);
-    if (_LAZY_AB) {
-      return _EqualAA(_2A(x), _2A(y));
+
+    // TODO: use mandatory lazyAB pattern.
+    SPU_ENFORCE(_LAZY_AB);
+
+    ctx->setOutput(std::nullopt);
+
+    // try fast path
+    // TODO: use cost model instead of hand-coded priority.
+    if (_IsA(x) && _IsA(y)) {
+      if (ctx->hasKernel("equal_aa")) {
+        ctx->setOutput(ctx->call("equal_aa", x, y));
+      }
+    } else if (_IsB(x) && _IsB(y)) {
+      if (ctx->hasKernel("equal_bb")) {
+        ctx->setOutput(ctx->call("equal_bb", x, y));
+      }
+    } else {
+      // mixed A and B
+      SPU_ENFORCE((_IsA(x) && _IsB(y)) || (_IsB(x) && _IsA(y)));
+
+      if (ctx->hasKernel("equal_aa")) {
+        ctx->setOutput(ctx->call("equal_aa", _2A(x), _2A(y)));
+      }
+
+      if (ctx->hasKernel("equal_bb")) {
+        ctx->setOutput(ctx->call("equal_bb", _2B(x), _2B(y)));
+      }
     }
-    // TODO: equal could be done in boolean domain.
-    return _EqualAA(x, y);
+
+    // fall through, no kernel found.
   }
 };
 
-class ABProtEqualSP : public BinaryKernel {
+class ABProtEqualSP : public Kernel {
  public:
   static constexpr char kBindName[] = "equal_sp";
 
   Kind kind() const override { return Kind::Dynamic; }
 
-  ArrayRef proc(KernelEvalContext* ctx, const ArrayRef& x,
-                const ArrayRef& y) const override {
+  void evaluate(KernelEvalContext* ctx) const override {
+    const auto& x = ctx->getParam<ArrayRef>(0);
+    const auto& y = ctx->getParam<ArrayRef>(1);
+
     SPU_TRACE_MPC_DISP(ctx, x, y);
-    if (_LAZY_AB) {
-      return _EqualAP(_2A(x), y);
+
+    // TODO: use mandatory lazyAB.
+    SPU_ENFORCE(_LAZY_AB);
+
+    ctx->setOutput(std::nullopt);
+
+    if (_IsA(x) && ctx->hasKernel("equal_ap")) {
+      ctx->setOutput(ctx->call("equal_ap", x, y));
+    } else if (_IsB(x) && ctx->hasKernel("equal_bp")) {
+      ctx->setOutput(ctx->call("equal_bp", x, y));
     }
-    return _EqualAP(x, y);
   }
 };
 
@@ -686,7 +722,7 @@ class ABProtMsbS : public UnaryKernel {
   ArrayRef proc(KernelEvalContext* ctx, const ArrayRef& in) const override {
     SPU_TRACE_MPC_DISP(ctx, in);
     const auto field = in.eltype().as<Ring2k>()->field();
-    if (ctx->caller()->hasKernel("msb_a2b")) {
+    if (ctx->hasKernel("msb_a2b")) {
       if (_LAZY_AB) {
         if (in.eltype().isa<BShare>()) {
           return _RShiftB(in, SizeOf(field) * 8 - 1);
diff --git a/libspu/mpc/common/ab_api.h b/libspu/mpc/common/ab_api.h
index baafb1d3..a5875ab6 100644
--- a/libspu/mpc/common/ab_api.h
+++ b/libspu/mpc/common/ab_api.h
@@ -30,10 +30,6 @@ ArrayRef rand_b(Object* ctx, size_t);
 
 ArrayRef not_a(Object* ctx, const ArrayRef&);
 
-ArrayRef equal_pp(Object* ctx, const ArrayRef&, const ArrayRef&);
-ArrayRef equal_sp(Object* ctx, const ArrayRef&, const ArrayRef&);
-ArrayRef equal_ss(Object* ctx, const ArrayRef&, const ArrayRef&);
-
 ArrayRef add_ap(Object* ctx, const ArrayRef&, const ArrayRef&);
 ArrayRef add_aa(Object* ctx, const ArrayRef&, const ArrayRef&);
 
diff --git a/libspu/mpc/object.h b/libspu/mpc/object.h
index 7eed92f8..0bfde276 100644
--- a/libspu/mpc/object.h
+++ b/libspu/mpc/object.h
@@ -16,6 +16,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <variant>
 
@@ -36,11 +37,13 @@ template <typename CallerT>
 class EvaluationContext final {
   // Please keep param types as less as possible.
   using ParamType = std::variant<  //
-      ArrayRef,                    // value type
-      size_t,                      // represent size(mmul), shift_bits(shift)
-      bool,                        // binary flag
-      Type,                        // type of type
-      uint128_t                    // ring constant
+      std::optional<ArrayRef>,  // for kernel evaluation failure, give a change
+                                // for the caller to handle failed case.
+      ArrayRef,                 // value type
+      size_t,                   // represent size(mmul), shift_bits(shift)
+      bool,                     // binary flag
+      Type,                     // type of type
+      uint128_t                 // ring constant
       >;
 
   CallerT* caller_;
@@ -71,18 +74,13 @@ class EvaluationContext final {
   // Get the caller's pointer.
   //
   // * usually called by kernel callee.
-  CallerT* caller() {
+  CallerT* caller() const {
     if (auto caller = dynamic_cast<CallerT*>(caller_)) {
       return caller;
     }
     SPU_THROW("cast failed");
   }
 
-  template <typename StateT>
-  StateT* getState() {
-    return caller()->template getState<StateT>();
-  }
-
   // Get the i'th parameter.
   //
   // * usually called by kernel callee.
@@ -100,6 +98,24 @@ class EvaluationContext final {
   void setOutput(T&& out) {
     output_ = std::forward<T>(out);
   }
+
+  // helper function, forward to caller.
+  template <typename StateT>
+  StateT* getState() {
+    return caller()->template getState<StateT>();
+  }
+
+  // helper function, forward to caller
+  bool hasKernel(std::string_view name) const {
+    return caller()->hasKernel(name);
+  }
+
+  // helper function, forward to caller
+  template <typename Ret = ArrayRef, typename... Args>
+  Ret call(std::string_view name, Args&&... args) {
+    return caller()->template call<Ret, Args...>(name,
+                                                 std::forward<Args>(args)...);
+  }
 };
 
 class Object;
@@ -220,13 +236,13 @@ class Object final {
     return names;
   }
 
-  template <typename Ret = ArrayRef>
+  template <typename Ret>
   Ret callImpl(Kernel* kernel, EvaluationContext<Object>* ctx) {
     kernel->evaluate(ctx);
     return ctx->stealOutput<Ret>();
   }
 
-  template <typename Ret = ArrayRef, typename First, typename... Args>
+  template <typename Ret, typename First, typename... Args>
   Ret callImpl(Kernel* kernel, EvaluationContext<Object>* ctx, First&& head,
                Args&&... tail) {
     ctx->bindParam(std::forward<First>(head));
diff --git a/libspu/mpc/spdz2k/value.cc b/libspu/mpc/spdz2k/value.cc
index ae9793da..87fb8d07 100644
--- a/libspu/mpc/spdz2k/value.cc
+++ b/libspu/mpc/spdz2k/value.cc
@@ -46,7 +46,7 @@ ArrayRef getValueShare(const ArrayRef& in) {
   auto ty = makeType<RingTy>(field);
 
   SPU_ENFORCE(in.stride() != 0);
-  return {in.buf(), ty, in.numel(), in.stride() * 2, in.offset()};
+  return ArrayRef(in.buf(), ty, in.numel(), in.stride() * 2, in.offset());
 }
 
 ArrayRef getMacShare(const ArrayRef& in) {
@@ -54,8 +54,8 @@ ArrayRef getMacShare(const ArrayRef& in) {
   auto ty = makeType<RingTy>(field);
 
   SPU_ENFORCE(in.stride() != 0);
-  return {in.buf(), ty, in.numel(), in.stride() * 2,
-          in.offset() + static_cast<int64_t>(ty.size())};
+  return ArrayRef(in.buf(), ty, in.numel(), in.stride() * 2,
+                  in.offset() + static_cast<int64_t>(ty.size()));
 }
 
 ArrayRef makeAShare(const ArrayRef& s1, const ArrayRef& s2, FieldType field) {
diff --git a/libspu/psi/core/bc22_psi/emp_vole.h b/libspu/psi/core/bc22_psi/emp_vole.h
index 174bacf9..e4d49d65 100644
--- a/libspu/psi/core/bc22_psi/emp_vole.h
+++ b/libspu/psi/core/bc22_psi/emp_vole.h
@@ -21,7 +21,6 @@
 #include "absl/types/span.h"
 #include "emp-tool/utils/block.h"
 #include "emp-tool/utils/f2k.h"
-#include "emp-zk/emp-vole/constants.h"
 #include "emp-zk/emp-vole/emp-vole.h"
 #include "yacl/link/link.h"
 
diff --git a/libspu/psi/core/ecdh_oprf/BUILD.bazel b/libspu/psi/core/ecdh_oprf/BUILD.bazel
index 10bccb04..4ac1a91b 100644
--- a/libspu/psi/core/ecdh_oprf/BUILD.bazel
+++ b/libspu/psi/core/ecdh_oprf/BUILD.bazel
@@ -36,12 +36,26 @@ spu_cc_library(
     name = "basic_ecdh_oprf",
     srcs = ["basic_ecdh_oprf.cc"],
     hdrs = ["basic_ecdh_oprf.h"],
+    defines = [
+        "__LINUX__",
+    ] + select({
+        "@bazel_tools//src/conditions:linux_x86_64": [
+            "_AMD64_",
+            "_ASM_",
+        ],
+        "@bazel_tools//src/conditions:darwin_arm64": [
+            "_ARM64_",
+        ],
+        "//conditions:default": [
+            "_AMD64_",
+        ],
+    }),
     deps = [
         ":ecdh_oprf",
         "//libspu/core:prelude",
         "//libspu/psi/cryptor:ecc_utils",
         "//libspu/psi/cryptor:sm2_cryptor",
-        "@com_github_microsoft_fourqlib//:fourqlib",
+        "@com_github_microsoft_apsi//:apsi",
         "@com_google_absl//absl/types:span",
         "@yacl//yacl/crypto/base/hash:blake3",
         "@yacl//yacl/crypto/base/hash:hash_utils",
diff --git a/libspu/psi/core/ecdh_oprf/basic_ecdh_oprf.cc b/libspu/psi/core/ecdh_oprf/basic_ecdh_oprf.cc
index 8b7d25b2..41971f33 100644
--- a/libspu/psi/core/ecdh_oprf/basic_ecdh_oprf.cc
+++ b/libspu/psi/core/ecdh_oprf/basic_ecdh_oprf.cc
@@ -18,16 +18,13 @@
 #include <vector>
 
 #include "absl/strings/escaping.h"
+#include "apsi/fourq/FourQ_api.h"
+#include "apsi/fourq/FourQ_internal.h"
 #include "yacl/crypto/base/hash/blake3.h"
 #include "yacl/crypto/base/hash/hash_utils.h"
 
 #include "libspu/psi/cryptor/ecc_utils.h"
 
-extern "C" {
-#include "FourQ_api.h"
-#include "FourQ_internal.h"
-}
-
 namespace spu::psi {
 
 namespace {
diff --git a/libspu/psi/core/ecdh_oprf_psi.cc b/libspu/psi/core/ecdh_oprf_psi.cc
index bf561106..a696c031 100644
--- a/libspu/psi/core/ecdh_oprf_psi.cc
+++ b/libspu/psi/core/ecdh_oprf_psi.cc
@@ -106,10 +106,10 @@ size_t EcdhOprfPsiServer::FullEvaluate(
   SPDLOG_INFO("omp_get_num_threads:{} cpus:{}", nthreads, mcpus);
   omp_set_num_threads(mcpus);
 
-#pragma omp parallel private(tid, nthreads, i, batch_items, batch_indices, \
-                             shuffle_indices, batch, local_batch_count)    \
-    shared(lck_read, lck_send, batch_count, items_count, compare_length,   \
-           stop_flag)
+#pragma omp parallel private(tid, nthreads, i, batch_items, batch_indices,  \
+                                 shuffle_indices, batch, local_batch_count) \
+    shared(lck_read, lck_send, batch_count, items_count, compare_length,    \
+               stop_flag)
   {
     tid = omp_get_thread_num();
     if ((tid == 0) && (batch_count == 0)) {
@@ -440,7 +440,7 @@ void EcdhOprfPsiClient::RecvFinalEvaluatedItems(
     std::vector<std::string> evaluated_items(num_items);
     for (size_t idx = 0; idx < num_items; ++idx) {
       evaluated_items[idx] =
-          absl::BytesToHexString(masked_batch.flatten_bytes.substr(
+          absl::Base64Escape(masked_batch.flatten_bytes.substr(
               idx * compare_length_, compare_length_));
     }
     cipher_store->SavePeer(evaluated_items);
@@ -571,7 +571,7 @@ void EcdhOprfPsiClient::RecvEvaluatedItems(
 
     yacl::parallel_for(0, num_items, 1, [&](int64_t begin, int64_t end) {
       for (int64_t idx = begin; idx < end; ++idx) {
-        oprf_items[idx] = absl::BytesToHexString(
+        oprf_items[idx] = absl::Base64Escape(
             oprf_clients[idx]->Finalize(evaluate_items[idx]));
       }
     });
@@ -610,7 +610,10 @@ void EcdhOprfPsiClient::SendIntersectionMaskedItems(
     blinded_batch.flatten_bytes.reserve(items.size() * compare_length_);
 
     for (uint64_t idx = 0; idx < items.size(); ++idx) {
-      blinded_batch.flatten_bytes.append(absl::HexStringToBytes(items[idx]));
+      std::string b64_dest;
+      absl::Base64Unescape(items[idx], &b64_dest);
+
+      blinded_batch.flatten_bytes.append(b64_dest);
     }
 
     options_.link1->SendAsync(options_.link1->NextRank(),
diff --git a/libspu/psi/core/kkrt_psi.cc b/libspu/psi/core/kkrt_psi.cc
index b1b9e04a..245a9c10 100644
--- a/libspu/psi/core/kkrt_psi.cc
+++ b/libspu/psi/core/kkrt_psi.cc
@@ -86,7 +86,7 @@ KkrtPsiOptions GetDefaultKkrtPsiOptions() {
 
 // first use baseOT get 128 ots
 // then, use iknpOtExtension get 512 ots
-std::shared_ptr<yacl::crypto::OtRecvStore> GetKkrtOtSenderOptions(
+yacl::crypto::OtRecvStore GetKkrtOtSenderOptions(
     const std::shared_ptr<yacl::link::Context>& link_ctx, const size_t num_ot) {
   // use base ot get 128 ots
   auto base_ot = yacl::crypto::BaseOtSend(link_ctx, 128);
@@ -95,7 +95,7 @@ std::shared_ptr<yacl::crypto::OtRecvStore> GetKkrtOtSenderOptions(
   return yacl::crypto::IknpOtExtRecv(link_ctx, base_ot, choice, num_ot);
 }
 
-std::shared_ptr<yacl::crypto::OtSendStore> GetKkrtOtReceiverOptions(
+yacl::crypto::OtSendStore GetKkrtOtReceiverOptions(
     const std::shared_ptr<yacl::link::Context>& link_ctx, const size_t num_ot) {
   // use base ot get 128 ots
   size_t base_ot_num = 128;
@@ -107,12 +107,12 @@ std::shared_ptr<yacl::crypto::OtSendStore> GetKkrtOtReceiverOptions(
 
 void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx,
                  const KkrtPsiOptions& kkrt_psi_options,  // with kkrt options
-                 const std::shared_ptr<yacl::crypto::OtRecvStore>& ot_recv,
+                 const yacl::crypto::OtRecvStore& ot_recv,
                  const std::vector<uint128_t>& items_hash) {
   SPU_ENFORCE((kkrt_psi_options.cuckoo_hash_num == 3) &&
                   (kkrt_psi_options.stash_size == 0),
               "now only support cuckoo HashNum = 3 , stash size = 0");
-  SPU_ENFORCE(ot_recv->Size() == 512,
+  SPU_ENFORCE(ot_recv.Size() == 512,
               "now only support baseRecvOption block size 512");
 
   size_t self_size = items_hash.size();
@@ -301,13 +301,13 @@ void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx,
 std::vector<std::size_t> KkrtPsiRecv(
     const std::shared_ptr<yacl::link::Context>& link_ctx,
     const KkrtPsiOptions& kkrt_psi_options,  // with kkrt options
-    const std::shared_ptr<yacl::crypto::OtSendStore>& ot_send,
+    const yacl::crypto::OtSendStore& ot_send,
     const std::vector<uint128_t>& items_hash) {
   SPU_ENFORCE((kkrt_psi_options.cuckoo_hash_num == 3) &&
                   (kkrt_psi_options.stash_size == 0),
               "now only support cuckoo HashNum = 3 , stash size = 0");
 
-  SPU_ENFORCE(ot_send->Size() == 512,
+  SPU_ENFORCE(ot_send.Size() == 512,
               "now only support yacl::OtSendStore block size 512");
 
   std::vector<std::size_t> ret_intersection;
diff --git a/libspu/psi/core/kkrt_psi.h b/libspu/psi/core/kkrt_psi.h
index 37a107d0..a16b8db9 100644
--- a/libspu/psi/core/kkrt_psi.h
+++ b/libspu/psi/core/kkrt_psi.h
@@ -51,10 +51,10 @@ struct KkrtPsiOptions {
   size_t stat_sec_param = 40;
 };
 
-std::shared_ptr<yacl::crypto::OtRecvStore> GetKkrtOtSenderOptions(
+yacl::crypto::OtRecvStore GetKkrtOtSenderOptions(
     const std::shared_ptr<yacl::link::Context>& link_ctx, size_t num_ot);
 
-std::shared_ptr<yacl::crypto::OtSendStore> GetKkrtOtReceiverOptions(
+yacl::crypto::OtSendStore GetKkrtOtReceiverOptions(
     const std::shared_ptr<yacl::link::Context>& link_ctx, size_t num_ot);
 
 KkrtPsiOptions GetDefaultKkrtPsiOptions();
@@ -65,27 +65,26 @@ KkrtPsiOptions GetDefaultKkrtPsiOptions();
 //
 void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx,
                  const KkrtPsiOptions& kkrt_psi_options,  // with kkrt options
-                 const std::shared_ptr<yacl::crypto::OtRecvStore>& ot_recv,
+                 const yacl::crypto::OtRecvStore& ot_recv,
                  const std::vector<uint128_t>& items_hash);
 
 std::vector<std::size_t> KkrtPsiRecv(
     const std::shared_ptr<yacl::link::Context>& link_ctx,
     const KkrtPsiOptions& kkrt_psi_options,  // with kkrt options
-    const std::shared_ptr<yacl::crypto::OtSendStore>& ot_send,
+    const yacl::crypto::OtSendStore& ot_send,
     const std::vector<uint128_t>& items_hash);
 
 // inline functions
-inline void KkrtPsiSend(
-    const std::shared_ptr<yacl::link::Context>& link_ctx,
-    const std::shared_ptr<yacl::crypto::OtRecvStore>& ot_recv,
-    const std::vector<uint128_t>& items_hash) {
+inline void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx,
+                        const yacl::crypto::OtRecvStore& ot_recv,
+                        const std::vector<uint128_t>& items_hash) {
   KkrtPsiOptions kkrt_psi_options = GetDefaultKkrtPsiOptions();
   return KkrtPsiSend(link_ctx, kkrt_psi_options, ot_recv, items_hash);
 }
 
 inline std::vector<std::size_t> KkrtPsiRecv(
     const std::shared_ptr<yacl::link::Context>& link_ctx,
-    const std::shared_ptr<yacl::crypto::OtSendStore>& ot_send,
+    const yacl::crypto::OtSendStore& ot_send,
     const std::vector<uint128_t>& items_hash) {
   KkrtPsiOptions kkrt_psi_options = GetDefaultKkrtPsiOptions();
   return KkrtPsiRecv(link_ctx, kkrt_psi_options, ot_send, items_hash);
diff --git a/libspu/psi/core/labeled_psi/sender_db.cc b/libspu/psi/core/labeled_psi/sender_db.cc
index 960d4bcf..941e7e21 100644
--- a/libspu/psi/core/labeled_psi/sender_db.cc
+++ b/libspu/psi/core/labeled_psi/sender_db.cc
@@ -1145,7 +1145,7 @@ void SenderDB::InsertOrAssign(const std::vector<apsi::Item> &data) {
 
 void SenderDB::InsertOrAssign(
     const std::shared_ptr<IBatchProvider> &batch_provider, size_t batch_size) {
-  size_t batch_count = 0;
+  [[maybe_unused]] size_t batch_count = 0;
   size_t indices_count = 0;
 
   std::shared_ptr<yacl::io::KVStore> kv_store =
diff --git a/libspu/psi/cryptor/BUILD.bazel b/libspu/psi/cryptor/BUILD.bazel
index 40929b99..37eb4c9d 100644
--- a/libspu/psi/cryptor/BUILD.bazel
+++ b/libspu/psi/cryptor/BUILD.bazel
@@ -64,10 +64,24 @@ spu_cc_library(
     name = "fourq_cryptor",
     srcs = ["fourq_cryptor.cc"],
     hdrs = ["fourq_cryptor.h"],
+    defines = [
+        "__LINUX__",
+    ] + select({
+        "@bazel_tools//src/conditions:linux_x86_64": [
+            "_AMD64_",
+            "_ASM_",
+        ],
+        "@bazel_tools//src/conditions:darwin_arm64": [
+            "_ARM64_",
+        ],
+        "//conditions:default": [
+            "_AMD64_",
+        ],
+    }),
     deps = [
         ":ecc_cryptor",
         "//libspu/core:prelude",
-        "@com_github_microsoft_fourqlib//:fourqlib",
+        "@com_github_microsoft_apsi//:apsi",
         "@com_github_openssl_openssl//:openssl",
         "@yacl//yacl/utils:parallel",
     ],
diff --git a/libspu/psi/cryptor/fourq_cryptor.cc b/libspu/psi/cryptor/fourq_cryptor.cc
index 41f03fd2..d848d07e 100644
--- a/libspu/psi/cryptor/fourq_cryptor.cc
+++ b/libspu/psi/cryptor/fourq_cryptor.cc
@@ -14,13 +14,10 @@
 
 #include "libspu/psi/cryptor/fourq_cryptor.h"
 
-extern "C" {
-#include "FourQ_api.h"
-#include "FourQ_internal.h"
-}
-
 #include <vector>
 
+#include "apsi/fourq/FourQ_api.h"
+#include "apsi/fourq/FourQ_internal.h"
 #include "yacl/crypto/base/hash/hash_utils.h"
 #include "yacl/utils/parallel.h"
 
diff --git a/libspu/psi/utils/batch_provider.cc b/libspu/psi/utils/batch_provider.cc
index 8cd79708..bd3aaf71 100644
--- a/libspu/psi/utils/batch_provider.cc
+++ b/libspu/psi/utils/batch_provider.cc
@@ -206,6 +206,8 @@ CachedCsvBatchProvider::CachedCsvBatchProvider(
 
 std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>>
 CachedCsvBatchProvider::ReadNextBatchWithIndex(size_t batch_size) {
+  std::unique_lock lk(read_mutex_);
+
   std::vector<std::string> batch_data;
   std::vector<size_t> batch_indices;
   std::vector<size_t> shuffle_indices;
@@ -242,41 +244,47 @@ CachedCsvBatchProvider::ReadNextBatchWithIndex(size_t batch_size) {
   }
 
   size_t next_index = 1 - bucket_index_;
-  if (bucket_items_[next_index].size() > 0) {
+  {
+    // get next_index lock
     std::unique_lock lk(bucket_mutex_[next_index]);
 
-    SPDLOG_INFO("lock idx:{}", next_index);
-
-    size_t left_size = batch_size - n_items;
+    if (bucket_items_[next_index].size() > 0) {
+      SPDLOG_INFO("lock idx:{}", next_index);
 
-    cursor_index_ = 0;
-    bucket_count_++;
-    size_t m_items = std::min(left_size, bucket_items_[next_index].size());
-
-    for (size_t i = 0; i < m_items; ++i) {
-      size_t shuffled_index = shuffled_indices_[next_index][cursor_index_ + i];
-      batch_data.push_back(bucket_items_[next_index][shuffled_index]);
-      batch_indices.push_back(bucket_count_ * bucket_size_ + cursor_index_ + i);
-      shuffle_indices.push_back(bucket_count_ * bucket_size_ + shuffled_index);
-    }
+      size_t left_size = batch_size - n_items;
 
-    if (m_items == bucket_items_[next_index].size()) {
       cursor_index_ = 0;
-      bucket_items_[next_index].resize(0);
-    } else {
-      cursor_index_ += m_items;
-    }
-    n_items += m_items;
+      bucket_count_++;
+      size_t m_items = std::min(left_size, bucket_items_[next_index].size());
+
+      for (size_t i = 0; i < m_items; ++i) {
+        size_t shuffled_index =
+            shuffled_indices_[next_index][cursor_index_ + i];
+        batch_data.push_back(bucket_items_[next_index][shuffled_index]);
+        batch_indices.push_back(bucket_count_ * bucket_size_ + cursor_index_ +
+                                i);
+        shuffle_indices.push_back(bucket_count_ * bucket_size_ +
+                                  shuffled_index);
+      }
 
-    // read
-    SPDLOG_INFO("read next bucket, n_items:{} m_items:{}", n_items, m_items);
-    if (!file_end_flag_) {
-      ReadAndShuffle(bucket_index_, true);
-    }
+      if (m_items == bucket_items_[next_index].size()) {
+        cursor_index_ = 0;
+        bucket_items_[next_index].resize(0);
+      } else {
+        cursor_index_ += m_items;
+      }
+      n_items += m_items;
 
-    bucket_index_ = next_index;
+      // read
+      SPDLOG_INFO("read next bucket, n_items:{} m_items:{}", n_items, m_items);
+      if (!file_end_flag_) {
+        ReadAndShuffle(bucket_index_, true);
+      }
 
-    SPDLOG_INFO("unlock idx:{}", next_index);
+      bucket_index_ = next_index;
+
+      SPDLOG_INFO("unlock idx:{}", next_index);
+    }
   }
 
   return std::make_tuple(batch_data, batch_indices, shuffle_indices);
diff --git a/libspu/psi/utils/batch_provider.h b/libspu/psi/utils/batch_provider.h
index 160e7c65..0fa16c63 100644
--- a/libspu/psi/utils/batch_provider.h
+++ b/libspu/psi/utils/batch_provider.h
@@ -147,6 +147,7 @@ class CachedCsvBatchProvider : public IShuffleBatchProvider {
   std::array<std::future<void>, 2> f_read_;
 
   std::array<std::mutex, 2> bucket_mutex_;
+  std::mutex read_mutex_;
   std::mutex file_mutex_;
   bool file_end_flag_ = false;
 };
diff --git a/libspu/psi/utils/serializable.proto b/libspu/psi/utils/serializable.proto
index 5a41c8be..900c5e2f 100644
--- a/libspu/psi/utils/serializable.proto
+++ b/libspu/psi/utils/serializable.proto
@@ -31,8 +31,3 @@ message PsiDataBatchProto {
 message StrItemsProto {
   repeated string items = 1;
 }
-
-message DataPaddingProto {
-  bytes data = 1;
-  bytes padding = 2;
-}
diff --git a/libspu/psi/utils/utils.cc b/libspu/psi/utils/utils.cc
index 12f64086..fb99de4e 100644
--- a/libspu/psi/utils/utils.cc
+++ b/libspu/psi/utils/utils.cc
@@ -156,29 +156,29 @@ std::vector<size_t> GetShuffledIdx(size_t items_size) {
   return shuffled_idx_vec;
 }
 
+// pad data to max_len bytes
+// format len(32bit)||data||00..00
 std::vector<uint8_t> PaddingData(yacl::ByteContainerView data, size_t max_len) {
-  SPU_ENFORCE((data.size() + 4) < max_len, "data_size:{} max_len:{}",
+  SPU_ENFORCE((data.size() + 4) <= max_len, "data_size:{} max_len:{}",
               data.size(), max_len);
-  std::string padding(max_len - data.size() - 4, '\0');
-  proto::DataPaddingProto proto;
 
-  proto.set_data(data.data(), data.length());
-  proto.set_padding(padding.data(), padding.length());
+  std::vector<uint8_t> data_with_padding(max_len);
+  uint32_t data_size = data.size();
 
-  std::vector<uint8_t> data_with_padding(proto.ByteSizeLong());
-
-  proto.SerializePartialToArray(&data_with_padding[0],
-                                data_with_padding.size());
+  std::memcpy(&data_with_padding[0], &data_size, sizeof(uint32_t));
+  std::memcpy(&data_with_padding[4], data.data(), data.size());
 
   return data_with_padding;
 }
 
 std::string UnPaddingData(yacl::ByteContainerView data) {
-  proto::DataPaddingProto proto;
+  uint32_t data_len;
+  std::memcpy(&data_len, data.data(), sizeof(data_len));
 
-  SPU_ENFORCE(proto.ParseFromArray(data.data(), data.size()));
+  SPU_ENFORCE((data_len + sizeof(uint32_t)) <= data.size());
 
-  std::string ret(proto.data());
+  std::string ret(data_len, '\0');
+  std::memcpy(ret.data(), data.data() + 4, data_len);
 
   return ret;
 }
diff --git a/libspu/spu.proto b/libspu/spu.proto
index a72e0f4b..a2b3511d 100644
--- a/libspu/spu.proto
+++ b/libspu/spu.proto
@@ -164,6 +164,7 @@ message RuntimeConfig {
   FieldType field = 2;
 
   // Number of fraction bits of fixed-point number.
+  // 0(default) indicates implementation defined.
   int64 fxp_fraction_bits = 3;
 
   ///////////////////////////////////////
diff --git a/spu/version.py b/spu/version.py
index 9d0c1111..0a6a2730 100644
--- a/spu/version.py
+++ b/spu/version.py
@@ -13,4 +13,4 @@
 # limitations under the License.
 
 
-__version__ = "0.3.3b2"
+__version__ = "0.3.3b3"


Test patch:
diff --git a/examples/python/ml/ml_test.py b/examples/python/ml/ml_test.py
index 2d058a2f..da2e1609 100644
--- a/examples/python/ml/ml_test.py
+++ b/examples/python/ml/ml_test.py
@@ -135,13 +135,13 @@ def test_flax_vae(self):
         from examples.python.ml.flax_vae import flax_vae
 
         flax_vae.args.num_epochs = 1
-        flax_vae.args.num_steps = 1
+        flax_vae.args.num_steps = 10
         metrics = profile_test_point(flax_vae.train)
 
-        self.assertTrue(530 < metrics['loss'] < 540)
-        self.assertTrue(530 < metrics['bce'] < 540)
-        self.assertTrue(0.8 < metrics['kld'] < 1)
-    
+        self.assertTrue(260 < metrics['loss'] < 270)
+        self.assertTrue(240 < metrics['bce'] < 247)
+        self.assertTrue(20 < metrics['kld'] < 23)
+
     def test_flax_resnet(self):
         from examples.python.ml.flax_resnet import flax_resnet
 
diff --git a/libspu/core/platform_utils_test.cc b/libspu/core/platform_utils_test.cc
new file mode 100644
index 00000000..1c30bc55
--- /dev/null
+++ b/libspu/core/platform_utils_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 Ant Group Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "libspu/core/platform_utils.h"
+
+#include <array>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace spu {
+
+TEST(PlatformUtils, pdep_u64) {
+  auto x = pdep_u64(19, 19);
+
+  GTEST_ASSERT_GT(x, 0);
+}
+
+TEST(PlatformUtils, pext_u64) {
+  auto x = pext_u64(19, 19);
+
+  GTEST_ASSERT_GT(x, 0);
+}
+
+}  // namespace spu
diff --git a/libspu/device/pphlo/pphlo_executor_test.cc b/libspu/device/pphlo/pphlo_executor_test.cc
index 87b25012..a78d07dd 100644
--- a/libspu/device/pphlo/pphlo_executor_test.cc
+++ b/libspu/device/pphlo/pphlo_executor_test.cc
@@ -936,11 +936,11 @@ TEST_P(ExecutorTest, Simple4x4Conv2DWith2x2Kernel) {
 
   auto ir = spu::device::Runner::compileMHlo(R"(
 func.func @main(%arg0: tensor<1x1x4x4xf32>, %arg1: tensor<1x1x2x2xf32>) -> (tensor<1x1x4x4xf32>) {
-    %0 = mhlo.convolution(%arg0, %arg1) 
-            dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1], 
-            window = {stride = [1, 1], pad = [[0, 1], [0, 1]], lhs_dilate = [1, 1], rhs_dilate = [1, 1]} 
+    %0 = mhlo.convolution(%arg0, %arg1)
+            dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1],
+            window = {stride = [1, 1], pad = [[0, 1], [0, 1]], lhs_dilate = [1, 1], rhs_dilate = [1, 1]}
             {
-              batch_group_count = 1 : i64, 
+              batch_group_count = 1 : i64,
               feature_group_count = 1 : i64
             } : (tensor<1x1x4x4xf32>, tensor<1x1x2x2xf32>) -> tensor<1x1x4x4xf32>
     return %0 : tensor<1x1x4x4xf32>
@@ -974,9 +974,9 @@ TEST_P(ExecutorTest, Conv2DGeneralDimensions) {
 
   auto ir = spu::device::Runner::compileMHlo(R"(
 func.func @main(%arg0: tensor<2x3x1x4xf32>, %arg1:tensor<1x3x2x3xf32>) -> (tensor<1x1x1x2xf32>) {
-    %0 = mhlo.convolution(%arg0, %arg1) 
-          dim_numbers = [f, 0, b, 1]x[o, 1, i,0]->[f, 0, b, 1], 
-          window = {stride = [1, 1], pad = [[0, 0], [0, 0]], lhs_dilate = [1, 1], rhs_dilate = [1, 1]} 
+    %0 = mhlo.convolution(%arg0, %arg1)
+          dim_numbers = [f, 0, b, 1]x[o, 1, i,0]->[f, 0, b, 1],
+          window = {stride = [1, 1], pad = [[0, 0], [0, 0]], lhs_dilate = [1, 1], rhs_dilate = [1, 1]}
           {
             batch_group_count = 1 : i64,
             feature_group_count = 1 : i64
@@ -1011,11 +1011,11 @@ TEST_P(ExecutorTest, DilatedBaseConv2DWithHighPadding) {
 
   auto ir = spu::device::Runner::compileMHlo(R"(
 func.func @main(%arg0: tensor<1x1x4x4xf32>, %arg1: tensor<1x1x2x2xf32>) -> (tensor<1x1x7x7xf32>) {
-    %0 = mhlo.convolution(%arg0, %arg1) 
-          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1], 
-          window = {stride = [1, 1], pad = [[0, 1], [0, 1]], lhs_dilate = [2, 2], rhs_dilate = [1, 1]} 
+    %0 = mhlo.convolution(%arg0, %arg1)
+          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1],
+          window = {stride = [1, 1], pad = [[0, 1], [0, 1]], lhs_dilate = [2, 2], rhs_dilate = [1, 1]}
           {
-            batch_group_count = 1 : i64, 
+            batch_group_count = 1 : i64,
             feature_group_count = 1 : i64
           } : (tensor<1x1x4x4xf32>, tensor<1x1x2x2xf32>) -> tensor<1x1x7x7xf32>
     return %0 : tensor<1x1x7x7xf32>
@@ -1054,11 +1054,11 @@ TEST_P(ExecutorTest, DilatedBaseConv2DWithLowAndHighPadding) {
 
   auto ir = spu::device::Runner::compileMHlo(R"(
 func.func @main(%arg0: tensor<1x1x4x4xf32>, %arg1: tensor<1x1x2x2xf32>) -> (tensor<1x1x8x8xf32>) {
-    %0 = mhlo.convolution(%arg0, %arg1) 
-          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1], 
-          window = {stride = [1, 1], pad = [[1, 1], [1, 1]], lhs_dilate = [2, 2], rhs_dilate = [1, 1]} 
+    %0 = mhlo.convolution(%arg0, %arg1)
+          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1],
+          window = {stride = [1, 1], pad = [[1, 1], [1, 1]], lhs_dilate = [2, 2], rhs_dilate = [1, 1]}
           {
-            batch_group_count = 1 : i64, 
+            batch_group_count = 1 : i64,
             feature_group_count = 1 : i64
           } : (tensor<1x1x4x4xf32>, tensor<1x1x2x2xf32>) -> tensor<1x1x8x8xf32>
     return %0 : tensor<1x1x8x8xf32>
@@ -1099,11 +1099,11 @@ TEST_P(ExecutorTest, FlatRhsDilation) {
 
   auto ir = spu::device::Runner::compileMHlo(R"(
 func.func @main(%arg0: tensor<1x1x4x6xf32>, %arg1: tensor<1x1x2x3xf32>) -> (tensor<1x1x2x2xf32>) {
-    %0 = mhlo.convolution(%arg0, %arg1) 
-          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1], 
-          window = {stride = [1, 1], pad = [[0, 0], [0, 0]], lhs_dilate = [1, 1], rhs_dilate = [2, 2]} 
+    %0 = mhlo.convolution(%arg0, %arg1)
+          dim_numbers = [b, f, 0, 1]x[o, i, 0, 1]->[b, f, 0, 1],
+          window = {stride = [1, 1], pad = [[0, 0], [0, 0]], lhs_dilate = [1, 1], rhs_dilate = [2, 2]}
           {
-            batch_group_count = 1 : i64, 
+            batch_group_count = 1 : i64,
             feature_group_count = 1 : i64
           } : (tensor<1x1x4x6xf32>, tensor<1x1x2x3xf32>) -> tensor<1x1x2x2xf32>
     return %0 : tensor<1x1x2x2xf32>
@@ -1544,7 +1544,7 @@ TEST_P(ExecutorTest, RemainderFxp) {
 
   r.run(R"(
 func.func @main(%arg0: tensor<3x!pphlo.pub<f32>>, %arg1: tensor<3x!pphlo.pub<f32>>) -> (tensor<3x!pphlo.pub<f32>>) {
-  %0 = "pphlo.remainder"(%arg0, %arg1) : (tensor<3x!pphlo.pub<f32>>, tensor<3x!pphlo.pub<f32>>) -> tensor<3x!pphlo.pub<f32>> 
+  %0 = "pphlo.remainder"(%arg0, %arg1) : (tensor<3x!pphlo.pub<f32>>, tensor<3x!pphlo.pub<f32>>) -> tensor<3x!pphlo.pub<f32>>
   return %0 : tensor<3x!pphlo.pub<f32>>
 })");
 
@@ -1952,7 +1952,7 @@ func.func @main(%arg0: tensor<4x6xi32>, %arg1: tensor<2x2xi32>) -> (tensor<2x2xi
     ^bb0(%arg2: tensor<i32>, %arg3: tensor<i32>):
       %3 = mhlo.maximum %arg2, %arg3 : tensor<i32>
       "mhlo.return"(%3) : (tensor<i32>) -> ()
-    }) {base_dilations = dense<1> : tensor<2xi64>, padding = dense<0> : tensor<2x2xi64>, window_dilations = dense<1> : tensor<2xi64>, 
+    }) {base_dilations = dense<1> : tensor<2xi64>, padding = dense<0> : tensor<2x2xi64>, window_dilations = dense<1> : tensor<2xi64>,
         window_dimensions = dense<[2,3]> : tensor<2xi64>, window_strides = dense<[2, 3]> : tensor<2xi64>} : (tensor<4x6xi32>, tensor<i32>) -> tensor<2x2xi32>
   %2 = "mhlo.select_and_scatter"(%arg0, %arg1, %0) ({
     ^bb0(%arg3: tensor<i32>, %arg4: tensor<i32>):
@@ -2057,7 +2057,7 @@ func.func @main() -> (tensor<!pphlo.pub<i32>>, tensor<!pphlo.pub<i1>>) {
 
 TEST_P(ExecutorTest, Case) {
   const auto *prog = R"(
- func.func @main(%arg0: tensor<!pphlo.pub<i32>>) -> (tensor<!pphlo.pub<i32>>,tensor<!pphlo.pub<i32>>) {  
+ func.func @main(%arg0: tensor<!pphlo.pub<i32>>) -> (tensor<!pphlo.pub<i32>>,tensor<!pphlo.pub<i32>>) {
   %0:2 = "pphlo.case"(%arg0) ({
     %1 = "pphlo.constant"() {value = dense<1> : tensor<i32>} : () -> tensor<!pphlo.pub<i32>>
     %2 = "pphlo.constant"() {value = dense<11> : tensor<i32>} : () -> tensor<!pphlo.pub<i32>>
@@ -2116,7 +2116,7 @@ TEST_P(ExecutorTest, Case) {
 
 TEST_P(ExecutorTest, CasePrivate) {
   const auto *prog = R"(
- func.func @main(%arg0: tensor<!pphlo.sec<i32>>) -> (tensor<!pphlo.sec<i32>>, tensor<!pphlo.sec<i32>>) {  
+ func.func @main(%arg0: tensor<!pphlo.sec<i32>>) -> (tensor<!pphlo.sec<i32>>, tensor<!pphlo.sec<i32>>) {
   %0:2 = "pphlo.case"(%arg0) ({
     %1 = "pphlo.constant"() {value = dense<1> : tensor<i32>} : () -> tensor<!pphlo.pub<i32>>
     %2 = "pphlo.convert"(%1) : (tensor<!pphlo.pub<i32>>) -> tensor<!pphlo.sec<i32>>
diff --git a/libspu/device/test_utils.h b/libspu/device/test_utils.h
index 9ec74d07..b632a0b3 100644
--- a/libspu/device/test_utils.h
+++ b/libspu/device/test_utils.h
@@ -17,6 +17,7 @@
 #include <future>
 
 #include "libspu/core/array_ref.h"
+#include "libspu/core/config.h"
 #include "libspu/device/io.h"
 #include "libspu/device/symbol_table.h"
 
@@ -28,7 +29,8 @@ namespace spu::device {
 class LocalIo {
  public:
   LocalIo(size_t world_size, const RuntimeConfig &config)
-      : symbol_tables_(world_size), io_client_(world_size, config) {
+      : symbol_tables_(world_size),
+        io_client_(world_size, makeFullRuntimeConfig(config)) {
     //
   }
 
@@ -43,6 +45,7 @@ class LocalIo {
 
   NdArrayRef OutFeed(const std::string &name) {
     std::vector<spu::Value> shares;
+    shares.reserve(symbol_tables_.size());
     for (auto &st : symbol_tables_) {
       shares.push_back(st.getVar(name));
     }
diff --git a/libspu/kernel/hlo/const_test.cc b/libspu/kernel/hlo/const_test.cc
index baad4573..ab90fa14 100644
--- a/libspu/kernel/hlo/const_test.cc
+++ b/libspu/kernel/hlo/const_test.cc
@@ -41,7 +41,8 @@ TEST(ConstTest, Epsilon) {
 
   auto v = hal::dump_public_as<float>(&hctx, eps);
 
-  EXPECT_FLOAT_EQ(v[0], 1 / (std::pow(2, getDefaultFxpBits(hctx.rt_config()))));
+  EXPECT_FLOAT_EQ(v[0],
+                  1 / (std::pow(2, hctx.rt_config().fxp_fraction_bits())));
 }
 
 }  // namespace spu::kernel::hlo
diff --git a/libspu/mpc/cheetah/arith/cheetah_mul_test.cc b/libspu/mpc/cheetah/arith/cheetah_mul_test.cc
index e1b8f210..5bc09e93 100644
--- a/libspu/mpc/cheetah/arith/cheetah_mul_test.cc
+++ b/libspu/mpc/cheetah/arith/cheetah_mul_test.cc
@@ -29,7 +29,7 @@ INSTANTIATE_TEST_SUITE_P(
     Cheetah, CheetahMulTest,
     testing::Combine(testing::Values(FieldType::FM32, FieldType::FM64,
                                      FieldType::FM128),
-                     testing::Values(15, 1023)),
+                     testing::Values(1024, 10000)),
     [](const testing::TestParamInfo<CheetahMulTest::ParamType>& p) {
       return fmt::format("{}x{}", std::get<0>(p.param), std::get<1>(p.param));
     });
@@ -39,78 +39,97 @@ TEST_P(CheetahMulTest, Basic) {
   auto field = std::get<0>(GetParam());
   auto n = std::get<1>(GetParam());
 
-  std::vector<ArrayRef> arr(kWorldSize);
-  // NOTE(juhou): now Cheetah supports strided ArrayRef
-  for (size_t stride : {1, 2, 3}) {
-    for (size_t i = 0; i < kWorldSize; ++i) {
-      arr[i] = ring_rand(field, n);
-      arr[i] = arr[i].slice(1, n, stride);
+  ArrayRef a_bits = ring_rand(field, n);
+  ArrayRef b_bits = ring_rand(field, n);
+
+  std::vector<ArrayRef> a_shr(kWorldSize);
+  std::vector<ArrayRef> b_shr(kWorldSize);
+  a_shr[0] = ring_rand(field, n);
+  b_shr[0] = ring_rand(field, n);
+  a_shr[1] = ring_sub(a_bits, a_shr[0]);
+  b_shr[1] = ring_sub(b_bits, b_shr[0]);
+
+  std::vector<ArrayRef> result(kWorldSize);
+  utils::simulate(kWorldSize, [&](std::shared_ptr<yacl::link::Context> lctx) {
+    int rank = lctx->Rank();
+    // (a0 + a1) * (b0 + b1)
+    // a0*b0 + a0*b1 + a1*b0 + a1*b1
+    auto mul = std::make_shared<CheetahMul>(lctx);
+
+    ArrayRef cross0, cross1;
+    if (rank == 0) {
+      cross0 = mul->MulOLE(a_shr[0], true);
+      cross1 = mul->MulOLE(b_shr[0], true);
+    } else {
+      cross0 = mul->MulOLE(b_shr[1], false);
+      cross1 = mul->MulOLE(a_shr[1], false);
     }
 
-    std::vector<ArrayRef> result(kWorldSize);
-    utils::simulate(kWorldSize, [&](std::shared_ptr<yacl::link::Context> lctx) {
-      int rank = lctx->Rank();
-      auto mul = std::make_shared<CheetahMul>(lctx);
-      result[rank] = mul->MulOLE(arr[rank], rank == 0);
-    });
+    result[rank] = ring_mul(a_shr[rank], b_shr[rank]);
+    ring_add_(result[rank], cross0);
+    ring_add_(result[rank], cross1);
+  });
 
-    auto expected = ring_mul(arr[0], arr[1]);
-    auto computed = ring_add(result[0], result[1]);
+  auto expected = ring_mul(a_bits, b_bits);
+  auto computed = ring_add(result[0], result[1]);
 
-    const int64_t kMaxDiff = 1;
-    DISPATCH_ALL_FIELDS(field, "_", [&]() {
-      auto e = ArrayView<ring2k_t>(expected);
-      auto c = ArrayView<ring2k_t>(computed);
+  const int64_t kMaxDiff = 0;
+  DISPATCH_ALL_FIELDS(field, "_", [&]() {
+    auto e = ArrayView<ring2k_t>(expected);
+    auto c = ArrayView<ring2k_t>(computed);
 
-      for (auto idx = 0; idx < expected.numel(); idx++) {
-        EXPECT_NEAR(e[idx], c[idx], kMaxDiff);
-      }
-    });
-  }
+    for (auto idx = 0; idx < expected.numel(); idx++) {
+      EXPECT_NEAR(e[idx], c[idx], kMaxDiff);
+    }
+  });
 }
 
-TEST_P(CheetahMulTest, Fork) {
+TEST_P(CheetahMulTest, BasicBinary) {
   size_t kWorldSize = 2;
   auto field = std::get<0>(GetParam());
   auto n = std::get<1>(GetParam());
 
-  std::vector<ArrayRef> arr(kWorldSize);
-  for (size_t i = 0; i < kWorldSize; ++i) {
-    arr[i] = ring_rand(field, n);
-  }
+  ArrayRef a_bits = ring_rand_range(field, n, 0, 1);
+  ArrayRef b_bits = ring_rand_range(field, n, 0, 1);
 
-  std::vector<ArrayRef> result0(kWorldSize);
-  std::vector<ArrayRef> result1(kWorldSize);
-  std::vector<ArrayRef> result2(kWorldSize);
+  std::vector<ArrayRef> a_shr(kWorldSize);
+  std::vector<ArrayRef> b_shr(kWorldSize);
+  a_shr[0] = ring_rand(field, n);
+  b_shr[0] = ring_rand(field, n);
+  a_shr[1] = ring_sub(a_bits, a_shr[0]);
+  b_shr[1] = ring_sub(b_bits, b_shr[0]);
 
+  std::vector<ArrayRef> result(kWorldSize);
   utils::simulate(kWorldSize, [&](std::shared_ptr<yacl::link::Context> lctx) {
     int rank = lctx->Rank();
+    // (a0 + a1) * (b0 + b1)
+    // a0*b0 + a0*b1 + a1*b0 + a1*b1
     auto mul = std::make_shared<CheetahMul>(lctx);
-    auto fork0 = mul->Fork();  // fork before warm up
 
-    result0[rank] = mul->MulOLE(arr[rank], rank == 0);
-    result1[rank] = fork0->MulOLE(arr[rank], rank == 1);
+    ArrayRef cross0, cross1;
+    if (rank == 0) {
+      cross0 = mul->MulOLE(a_shr[0], true);
+      cross1 = mul->MulOLE(b_shr[0], true);
+    } else {
+      cross0 = mul->MulOLE(b_shr[1], false);
+      cross1 = mul->MulOLE(a_shr[1], false);
+    }
 
-    auto fork1 = mul->Fork();  // fork after warm up
-    result2[rank] = fork1->MulOLE(arr[rank], rank == 0);
+    result[rank] = ring_mul(a_shr[rank], b_shr[rank]);
+    ring_add_(result[rank], cross0);
+    ring_add_(result[rank], cross1);
   });
 
-  auto expected = ring_mul(arr[0], arr[1]);
-  auto computed0 = ring_add(result0[0], result0[1]);
-  auto computed1 = ring_add(result1[0], result1[1]);
-  auto computed2 = ring_add(result2[0], result2[1]);
+  auto expected = ring_mul(a_bits, b_bits);
+  auto computed = ring_add(result[0], result[1]);
 
-  const int64_t kMaxDiff = 1;
+  const int64_t kMaxDiff = 0;
   DISPATCH_ALL_FIELDS(field, "_", [&]() {
     auto e = ArrayView<ring2k_t>(expected);
-    auto c0 = ArrayView<ring2k_t>(computed0);
-    auto c1 = ArrayView<ring2k_t>(computed1);
-    auto c2 = ArrayView<ring2k_t>(computed2);
-
-    for (auto idx = 1; idx < expected.numel(); idx++) {
-      EXPECT_NEAR(c0[idx], e[idx], kMaxDiff);
-      EXPECT_NEAR(c1[idx], e[idx], kMaxDiff);
-      EXPECT_NEAR(c2[idx], e[idx], kMaxDiff);
+    auto c = ArrayView<ring2k_t>(computed);
+
+    for (auto idx = 0; idx < expected.numel(); idx++) {
+      EXPECT_NEAR(e[idx], c[idx], kMaxDiff);
     }
   });
 }
diff --git a/libspu/psi/core/labeled_psi/kv_test.cc b/libspu/psi/core/labeled_psi/kv_test.cc
index 6e354787..a0592111 100644
--- a/libspu/psi/core/labeled_psi/kv_test.cc
+++ b/libspu/psi/core/labeled_psi/kv_test.cc
@@ -324,8 +324,9 @@ TEST_P(LabelPsiTest, Works) {
 #endif
 }
 
-INSTANTIATE_TEST_SUITE_P(Works_Instances, LabelPsiTest,
-                         testing::Values(  //
+INSTANTIATE_TEST_SUITE_P(
+    Works_Instances, LabelPsiTest,
+    testing::Values(  //
 #if 0
                              TestParams{1, 10000, 0},         // 1-10K
                              TestParams{1, 100000, 0},        // 1-100K
@@ -349,11 +350,11 @@ INSTANTIATE_TEST_SUITE_P(Works_Instances, LabelPsiTest,
                              TestParams{10000, 100000, 32},   // 10000-100K-32
                              TestParams{10000, 1000000, 32})  // 10000-1M-32
 #else
-                             // TestParams{1, 10000, 0},       // 1-10K
-                             // TestParams{1, 100000, 0},      // 1-100K
-                             // TestParams{256, 100000, 0},    // 256-100K
-                             // TestParams{2048, 100000, 32},  // 2048-100K-32
-                             TestParams{1, 100000, 32})  // 1-10K
+                      // TestParams{1, 10000, 0},       // 1-10K
+                      // TestParams{1, 100000, 0},      // 1-100K
+                      // TestParams{256, 100000, 0},    // 256-100K
+                      // TestParams{2048, 100000, 32},  // 2048-100K-32
+        TestParams{1, 100000, 32})  // 1-10K
 #endif
 );
 
diff --git a/spu/tests/BUILD.bazel b/spu/tests/BUILD.bazel
index ad0b6482..16f4b145 100644
--- a/spu/tests/BUILD.bazel
+++ b/spu/tests/BUILD.bazel
@@ -36,6 +36,7 @@ py_binary(
 
 py_test(
     name = "jnp_aby3_r128_test",
+    timeout = "long",
     srcs = ["jnp_aby3_r128_test.py"],
     deps = [
         ":jnp_testbase",
@@ -44,6 +45,7 @@ py_test(
 
 py_test(
     name = "jnp_aby3_r64_test",
+    timeout = "long",
     srcs = ["jnp_aby3_r64_test.py"],
     deps = [
         ":jnp_testbase",
@@ -52,6 +54,7 @@ py_test(
 
 py_test(
     name = "jnp_cheetah_r64_test",
+    timeout = "long",
     srcs = ["jnp_cheetah_r64_test.py"],
     deps = [
         ":jnp_testbase",
@@ -60,6 +63,7 @@ py_test(
 
 py_test(
     name = "jnp_semi2k_r128_test",
+    timeout = "long",
     srcs = ["jnp_semi2k_r128_test.py"],
     deps = [
         ":jnp_testbase",
@@ -68,6 +72,7 @@ py_test(
 
 py_test(
     name = "jnp_semi2k_r64_test",
+    timeout = "long",
     srcs = ["jnp_semi2k_r64_test.py"],
     deps = [
         ":jnp_testbase",
@@ -76,6 +81,7 @@ py_test(
 
 py_test(
     name = "jnp_ref2k_r64_test",
+    timeout = "long",
     srcs = ["jnp_ref2k_r64_test.py"],
     deps = [
         ":jnp_testbase",
diff --git a/spu/tests/jnp_debug.py b/spu/tests/jnp_debug.py
index 616928dd..0e7e6dde 100644
--- a/spu/tests/jnp_debug.py
+++ b/spu/tests/jnp_debug.py
@@ -29,10 +29,12 @@
 
     sim = ppsim.Simulator.simple(3, spu_pb2.ProtocolKind.ABY3, spu_pb2.FieldType.FM64)
 
-    x = np.random.randint(low=-100, high=100, size=(3, 5))
-    fn = lambda x: jnp.argmax(x, axis=0)
+    x = np.random.randn(3, 4)
+    fn = lambda x: jnp.mean(x)
     spu_fn = ppsim.sim_jax(sim, fn)
     z = spu_fn(x)
 
+    print(spu_fn.pphlo)
+
     print(f'spu out = {z}')
     print(f'cpu out = {fn(x)}')
diff --git a/spu/tests/jnp_testbase.py b/spu/tests/jnp_testbase.py
index 8d0325f7..7621db38 100644
--- a/spu/tests/jnp_testbase.py
+++ b/spu/tests/jnp_testbase.py
@@ -27,6 +27,7 @@
 from spu.utils.simulation import sim_jax
 
 all_shapes = [(4,), (2, 3, 4)]
+extra_large_shapes = [(100000000,)]
 
 float_dtypes = [np.float32]
 int32_dtypes = [np.int16, np.uint16, np.int32, np.uint32]
@@ -46,6 +47,8 @@ class Status(Enum):
     SysError = 3
     """ Result error """
     Failed = 4
+    """ DO NOT generate report """
+    PassNoGen = 5
 
 
 OpRecord = collections.namedtuple(
@@ -366,6 +369,7 @@ def post(x):
 
 REDUCER_RECORDS = [
     REC("mean", 1, number_dtypes, all_shapes, rand_default),
+    REC("mean", 1, float_dtypes, extra_large_shapes, jtu.rand_small, Status.PassNoGen),
     REC("prod", 1, all_dtypes, all_shapes, jtu.rand_small_positive),
     REC("sum", 1, int_dtypes, all_shapes, rand_default),
     REC(
@@ -491,7 +495,7 @@ def test_ops(self, name, status, nargs, dtype, shape, rnd_factory):
             for keepdims in [False, True]
         )
         def test_reducer(self, name, status, dtype, shape, rnd_factory, axis, keepdims):
-            if status != Status.Pass:
+            if status != Status.Pass or status != Status.PassNoGen:
                 return
             jnp_op = getattr(jnp, name)
             jnp_fn = lambda x: jnp_op(x, axis=axis, keepdims=keepdims)