Remove UB from indeterminate value handling
Move SIMD operations on potentially-indeterminate Node16::index bytes into file-level assembly, where loading and operating on indeterminate values is well-defined (unlike C++). Restructure scalar fallback loops to iterate [0, numChildren) instead of [0, kMaxNodes). Fix TrivialSpan construction from indeterminate pointers in check::Job::init and insertPointWritesOrSorted to only construct when end.len > 0. Add MSan toolchain to the debug CI build to catch these issues going forward.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// SIMD operations on potentially-indeterminate Node16::index[16] bytes.
|
||||
// Written in assembly so msan doesn't track the loads. The caller is
|
||||
// responsible for masking the returned bitfield to [0, numChildren) before
|
||||
// using it.
|
||||
//
|
||||
// All functions return a 16-bit bitmask in %eax (bit i set = condition true
|
||||
// at index i). The upper 16 bits of %eax are zero.
|
||||
//
|
||||
// System V AMD64 ABI:
|
||||
// %rdi = const uint8_t *idx (16 bytes)
|
||||
// %esi = uint8_t key (find_eq_16, find_ge_16)
|
||||
// %sil = uint8_t begin (mask_in_range_16)
|
||||
// %dl = uint8_t end (mask_in_range_16)
|
||||
|
||||
.text
|
||||
|
||||
// uint32_t find_eq_16(const uint8_t idx[16], uint8_t key)
|
||||
// Returns bit i set if idx[i] == key
|
||||
.globl find_eq_16
|
||||
.type find_eq_16, @function
|
||||
find_eq_16:
|
||||
vmovd %esi, %xmm1 // broadcast key
|
||||
vpbroadcastb %xmm1, %xmm1
|
||||
vmovdqu (%rdi), %xmm0 // load 16 bytes (may contain indeterminate data)
|
||||
vpcmpeqb %xmm0, %xmm1, %xmm0 // 0xff for each match
|
||||
vpmovmskb %xmm0, %eax // 16-bit bitmask
|
||||
movzwl %ax, %eax // zero-extend to 32 bits
|
||||
ret
|
||||
.size find_eq_16, .-find_eq_16
|
||||
|
||||
// uint32_t find_ge_16(const uint8_t idx[16], uint8_t child)
|
||||
// Returns bit i set if idx[i] >= child
|
||||
// x86 doesn't have a "compare unsigned >=" for bytes directly, so we use:
|
||||
// min(key, idx[i]) == key iff idx[i] >= key
|
||||
.globl find_ge_16
|
||||
.type find_ge_16, @function
|
||||
find_ge_16:
|
||||
vmovd %esi, %xmm1
|
||||
vpbroadcastb %xmm1, %xmm1 // key broadcast
|
||||
vmovdqu (%rdi), %xmm0 // load 16 bytes
|
||||
vpminub %xmm0, %xmm1, %xmm2 // min(key, idx[i])
|
||||
vpcmpeqb %xmm2, %xmm1, %xmm0 // 0xff where min == key, i.e. idx[i] >= key
|
||||
vpmovmskb %xmm0, %eax
|
||||
movzwl %ax, %eax
|
||||
ret
|
||||
.size find_ge_16, .-find_ge_16
|
||||
|
||||
// uint32_t mask_in_range_16(const uint8_t idx[16], uint8_t begin, uint8_t end)
|
||||
// Returns bit i set if begin <= idx[i] < end
|
||||
// Logic: (idx[i] - begin) < (end - begin) [unsigned wrapping arithmetic]
|
||||
// Equivalently: idx[i] - begin != max(idx[i] - begin, end - begin)
|
||||
// i.e. idx[i] - begin is NOT equal to the saturated value.
|
||||
// We compute: sub = idx - begin; result = (sub < (end-begin)) for each byte.
|
||||
// Using: sub == max(sub, end-begin) means NOT in range.
|
||||
// So: in_range = ~(movemask(cmpeq(sub, max(sub, range_size))))
|
||||
.globl mask_in_range_16
|
||||
.type mask_in_range_16, @function
|
||||
mask_in_range_16:
|
||||
vmovd %esi, %xmm1 // begin
|
||||
vpbroadcastb %xmm1, %xmm1
|
||||
vmovd %edx, %xmm2 // end
|
||||
vpbroadcastb %xmm2, %xmm2
|
||||
vmovdqu (%rdi), %xmm0 // load 16 bytes
|
||||
vpsubb %xmm1, %xmm0, %xmm0 // idx - begin (wrapping)
|
||||
vpsubb %xmm1, %xmm2, %xmm2 // end - begin (range size)
|
||||
vpmaxub %xmm0, %xmm2, %xmm3 // max(idx-begin, range_size)
|
||||
vpcmpeqb %xmm3, %xmm0, %xmm0 // 0xff where NOT in range
|
||||
vpmovmskb %xmm0, %eax
|
||||
not %eax // invert: 1 = in range
|
||||
movzwl %ax, %eax
|
||||
ret
|
||||
.size mask_in_range_16, .-mask_in_range_16
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
Reference in New Issue
Block a user