hashes: process multiple sha256 blocks per call
What changed, and why it matters
This commit is a routine performance optimization for SHA-256 hashing on modern CPUs. It rewrites the inner loop so that multiple 64-byte blocks are processed in one go, keeping the calculation state in CPU registers rather than reloading it each time. The change is purely about speed and does not fix or introduce any security-relevant behavior.
No security action required. Treat as a normal performance refactor; standard code review and regression testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors the SHA-256 hardware-accelerated paths for x86 SHA-NI and ARM SHA2. It renames process_block to process_blocks and moves the per-block loop inside the unsafe intrinsic functions. State vectors (state0/state1) are loaded once before the loop and stored once after, reducing register spills/reloads. The caller in crypto/mod.rs now passes the full blocks slice directly instead of chunking it. No cryptographic constants, round logic, endianness handling, or state accumulation semantics are changed.
Changed components
hashes/src/sha256/crypto/x86_shani.rshashes/src/sha256/crypto/arm_sha2.rshashes/src/sha256/crypto/mod.rsInspect captured patch +164 / −167
diff --git a/hashes/src/sha256/crypto/arm_sha2.rs b/hashes/src/sha256/crypto/arm_sha2.rs
index 0212e116..be57d1b6 100644
--- a/hashes/src/sha256/crypto/arm_sha2.rs
+++ b/hashes/src/sha256/crypto/arm_sha2.rs
@@ -12,9 +12,9 @@ use core::arch::aarch64::{
vsha256hq_u32, vsha256su0q_u32, vsha256su1q_u32, vst1q_u32,
};
-/// Processes a single sha256 block using ARM SHA2 intrinsics.
+/// Processes sha256 blocks using ARM SHA2 intrinsics.
#[target_feature(enable = "sha2")]
-pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
+pub(super) unsafe fn process_blocks(state: &mut [u32; 8], blocks: &[u8]) {
// Code translated and based on from
// https://github.com/noloader/SHA-Intrinsics/blob/4e754bec921a9f281b69bd681ca0065763aa911c/sha256-arm.c
@@ -45,7 +45,7 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
];
let (mut state0, mut state1);
- let (abcd_save, efgh_save);
+ let (mut abcd_save, mut efgh_save);
let (mut msg0, mut msg1, mut msg2, mut msg3);
let (mut tmp0, mut tmp1, mut tmp2);
@@ -54,147 +54,152 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
state0 = vld1q_u32(state.as_ptr().add(0));
state1 = vld1q_u32(state.as_ptr().add(4));
- // Save state
- abcd_save = state0;
- efgh_save = state1;
-
- // Load message
- msg0 = vld1q_u32(block.as_ptr().add(0).cast::<u32>());
- msg1 = vld1q_u32(block.as_ptr().add(16).cast::<u32>());
- msg2 = vld1q_u32(block.as_ptr().add(32).cast::<u32>());
- msg3 = vld1q_u32(block.as_ptr().add(48).cast::<u32>());
-
- // Reverse for little endian
- msg0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg0)));
- msg1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg1)));
- msg2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg2)));
- msg3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg3)));
-
- tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x00)));
-
- // Rounds 1-4
- msg0 = vsha256su0q_u32(msg0, msg1);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x04)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg0 = vsha256su1q_u32(msg0, msg2, msg3);
-
- // Rounds 5-8
- msg1 = vsha256su0q_u32(msg1, msg2);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x08)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg1 = vsha256su1q_u32(msg1, msg3, msg0);
-
- // Rounds 9-12
- msg2 = vsha256su0q_u32(msg2, msg3);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x0c)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg2 = vsha256su1q_u32(msg2, msg0, msg1);
-
- // Rounds 13-16
- msg3 = vsha256su0q_u32(msg3, msg0);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x10)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg3 = vsha256su1q_u32(msg3, msg1, msg2);
-
- // Rounds 17-20
- msg0 = vsha256su0q_u32(msg0, msg1);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x14)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg0 = vsha256su1q_u32(msg0, msg2, msg3);
-
- // Rounds 21-24
- msg1 = vsha256su0q_u32(msg1, msg2);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x18)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg1 = vsha256su1q_u32(msg1, msg3, msg0);
-
- // Rounds 25-28
- msg2 = vsha256su0q_u32(msg2, msg3);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x1c)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg2 = vsha256su1q_u32(msg2, msg0, msg1);
-
- // Rounds 29-32
- msg3 = vsha256su0q_u32(msg3, msg0);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x20)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg3 = vsha256su1q_u32(msg3, msg1, msg2);
-
- // Rounds 33-36
- msg0 = vsha256su0q_u32(msg0, msg1);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x24)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg0 = vsha256su1q_u32(msg0, msg2, msg3);
-
- // Rounds 37-40
- msg1 = vsha256su0q_u32(msg1, msg2);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x28)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg1 = vsha256su1q_u32(msg1, msg3, msg0);
-
- // Rounds 41-44
- msg2 = vsha256su0q_u32(msg2, msg3);
- tmp2 = state0;
- tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x2c)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
- msg2 = vsha256su1q_u32(msg2, msg0, msg1);
-
- // Rounds 45-48
- msg3 = vsha256su0q_u32(msg3, msg0);
- tmp2 = state0;
- tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x30)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
- msg3 = vsha256su1q_u32(msg3, msg1, msg2);
-
- // Rounds 49-52
- tmp2 = state0;
- tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x34)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
-
- // Rounds 53-56
- tmp2 = state0;
- tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x38)));
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
-
- // Rounds 57-60
- tmp2 = state0;
- tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x3c)));
- state0 = vsha256hq_u32(state0, state1, tmp0);
- state1 = vsha256h2q_u32(state1, tmp2, tmp0);
-
- // Rounds 61-64
- tmp2 = state0;
- state0 = vsha256hq_u32(state0, state1, tmp1);
- state1 = vsha256h2q_u32(state1, tmp2, tmp1);
-
- // Combine state
- state0 = vaddq_u32(state0, abcd_save);
- state1 = vaddq_u32(state1, efgh_save);
-
+ let mut block_offset = 0;
+ while block_offset < blocks.len() {
+ // Save state
+ abcd_save = state0;
+ efgh_save = state1;
+
+ // Load message
+ msg0 = vld1q_u32(blocks.as_ptr().add(block_offset).cast::<u32>());
+ msg1 = vld1q_u32(blocks.as_ptr().add(block_offset + 16).cast::<u32>());
+ msg2 = vld1q_u32(blocks.as_ptr().add(block_offset + 32).cast::<u32>());
+ msg3 = vld1q_u32(blocks.as_ptr().add(block_offset + 48).cast::<u32>());
+
+ // Reverse for little endian
+ msg0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg0)));
+ msg1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg1)));
+ msg2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg2)));
+ msg3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg3)));
+
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x00)));
+
+ // Rounds 1-4
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x04)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 5-8
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x08)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 9-12
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x0c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 13-16
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x10)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 17-20
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x14)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 21-24
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x18)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 25-28
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x1c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 29-32
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x20)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 33-36
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x24)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 37-40
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x28)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 41-44
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x2c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 45-48
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x30)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 49-52
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x34)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+
+ // Rounds 53-56
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x38)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+
+ // Rounds 57-60
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x3c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+
+ // Rounds 61-64
+ tmp2 = state0;
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+
+ // Combine state
+ state0 = vaddq_u32(state0, abcd_save);
+ state1 = vaddq_u32(state1, efgh_save);
+
+ block_offset += 64;
+ }
+
// Save state
vst1q_u32(state.as_mut_ptr().add(0), state0);
vst1q_u32(state.as_mut_ptr().add(4), state1);
diff --git a/hashes/src/sha256/crypto/mod.rs b/hashes/src/sha256/crypto/mod.rs
index 3cafc24f..45d10f0d 100644
--- a/hashes/src/sha256/crypto/mod.rs
+++ b/hashes/src/sha256/crypto/mod.rs
@@ -308,9 +308,7 @@ impl HashEngine {
&& std::is_x86_feature_detected!("sse2")
&& std::is_x86_feature_detected!("ssse3")
{
- for block in blocks.chunks_exact(BLOCK_SIZE) {
- unsafe { x86_shani::process_block(state, block) };
- }
+ unsafe { x86_shani::process_blocks(state, blocks) };
return;
}
}
@@ -319,9 +317,7 @@ impl HashEngine {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if cpuid_sha256_x86::get() {
- for block in blocks.chunks_exact(BLOCK_SIZE) {
- unsafe { x86_shani::process_block(state, block) };
- }
+ unsafe { x86_shani::process_blocks(state, blocks) };
return;
}
}
@@ -330,9 +326,7 @@ impl HashEngine {
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("sha2") {
- for block in blocks.chunks_exact(BLOCK_SIZE) {
- unsafe { arm_sha2::process_block(state, block) };
- }
+ unsafe { arm_sha2::process_blocks(state, blocks) };
return;
}
}
@@ -341,9 +335,7 @@ impl HashEngine {
#[cfg(target_arch = "aarch64")]
{
if cpuid_sha256_aarch64::get() {
- for block in blocks.chunks_exact(BLOCK_SIZE) {
- unsafe { arm_sha2::process_block(state, block) };
- }
+ unsafe { arm_sha2::process_blocks(state, blocks) };
return;
}
}
diff --git a/hashes/src/sha256/crypto/x86_shani.rs b/hashes/src/sha256/crypto/x86_shani.rs
index 9936b7d5..f0eb7eb5 100644
--- a/hashes/src/sha256/crypto/x86_shani.rs
+++ b/hashes/src/sha256/crypto/x86_shani.rs
@@ -17,9 +17,9 @@ use core::arch::x86_64::{
_mm_shuffle_epi8, _mm_storeu_si128,
};
-/// Processes a single sha256 block using x86 SHA-NI intrinsics.
+/// Processes sha256 blocks using x86 SHA-NI intrinsics.
#[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
-pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
+pub(super) unsafe fn process_blocks(state: &mut [u32; 8], blocks: &[u8]) {
// Code translated and based on from
// https://github.com/noloader/SHA-Intrinsics/blob/4899efc81d1af159c1fd955936c673139f35aea9/sha256-x86.c
@@ -34,14 +34,12 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
let (mut msg0, mut msg1, mut msg2, mut msg3);
- let (abef_save, cdgh_save);
+ let (mut abef_save, mut cdgh_save);
#[allow(non_snake_case)]
let MASK: __m128i =
_mm_set_epi64x(0x0c0d_0e0f_0809_0a0bu64 as i64, 0x0405_0607_0001_0203u64 as i64);
- let block_offset = 0;
-
// Load initial values
// CAST SAFETY: loadu_si128 documentation states that mem_addr does not
// need to be aligned on any particular boundary.
@@ -53,14 +51,14 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
state0 = _mm_alignr_epi8(tmp, state1, 8); // ABEF
state1 = _mm_blend_epi16(state1, tmp, 0xF0); // CDGH
- // Process a single block
- {
+ let mut block_offset = 0;
+ while block_offset < blocks.len() {
// Save current state
abef_save = state0;
cdgh_save = state1;
// Rounds 1-4
- msg = _mm_loadu_si128(block.as_ptr().add(block_offset).cast::<__m128i>());
+ msg = _mm_loadu_si128(blocks.as_ptr().add(block_offset).cast::<__m128i>());
msg0 = _mm_shuffle_epi8(msg, MASK);
msg = _mm_add_epi32(
msg0,
@@ -71,7 +69,7 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
// Rounds 5-8
- msg1 = _mm_loadu_si128(block.as_ptr().add(block_offset + 16).cast::<__m128i>());
+ msg1 = _mm_loadu_si128(blocks.as_ptr().add(block_offset + 16).cast::<__m128i>());
msg1 = _mm_shuffle_epi8(msg1, MASK);
msg = _mm_add_epi32(
msg1,
@@ -83,7 +81,7 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
msg0 = _mm_sha256msg1_epu32(msg0, msg1);
// Rounds 9-12
- msg2 = _mm_loadu_si128(block.as_ptr().add(block_offset + 32).cast::<__m128i>());
+ msg2 = _mm_loadu_si128(blocks.as_ptr().add(block_offset + 32).cast::<__m128i>());
msg2 = _mm_shuffle_epi8(msg2, MASK);
msg = _mm_add_epi32(
msg2,
@@ -95,7 +93,7 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
msg1 = _mm_sha256msg1_epu32(msg1, msg2);
// Rounds 13-16
- msg3 = _mm_loadu_si128(block.as_ptr().add(block_offset + 48).cast::<__m128i>());
+ msg3 = _mm_loadu_si128(blocks.as_ptr().add(block_offset + 48).cast::<__m128i>());
msg3 = _mm_shuffle_epi8(msg3, MASK);
msg = _mm_add_epi32(
msg3,
@@ -262,6 +260,8 @@ pub(super) unsafe fn process_block(state: &mut [u32; 8], block: &[u8]) {
// Combine state
state0 = _mm_add_epi32(state0, abef_save);
state1 = _mm_add_epi32(state1, cdgh_save);
+
+ block_offset += 64;
}
tmp = _mm_shuffle_epi32(state0, 0x1B); // FEBA
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.