Skip to content

Commit c1e4124

Browse files
containers: add typed int_hash / float_hash / bitvec_hash
R13 audit identified the missing hash callbacks for Int/Float/BitVec -- all three ship a typed compare but no hash, so they can't drop into a Map without the caller rolling their own helper. Following the Vec/Map callback pattern (typed signature -- callers cast to GenericHash at the use site, as VecFind / MapInitFull_9 already do for compare), each new hash function keeps the type-safe shape: u64 int_hash(Int *value, u32 size); u64 float_hash(Float *value, u32 size); u64 bitvec_hash(BitVec *bv, u32 size); Bodies are FNV-1a: - Int: magnitude bytes only (Int is unsigned by design). - Float: significand magnitude + i64 exponent bytes + sign bit, so +1.5e3 / -1.5e3 / 1.5e2 all land in different buckets. - BitVec: live bit bytes + a length tail-mix, so bitvectors that share a byte prefix but differ in length still distinguish. The 'size' parameter is the GenericHash callback's value-slot size -- ignored here because the real length lives inside the value itself. Drops .audit-questions.md item 1 (the hash gap) -- only the Debug.c pre-existing warnings carve-out remains parked.
1 parent 0bde0dd commit c1e4124

6 files changed

Lines changed: 128 additions & 0 deletions

File tree

Include/Misra/Std/Container/BitVec/Compare.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ extern "C" {
144144
///
145145
int BitVecCompare(BitVec *bv1, BitVec *bv2);
146146

147+
///
148+
/// Hash a `BitVec` for use as a map key. FNV-1a over the live bits
149+
/// with the bit length mixed in at the tail, so two bitvectors that
150+
/// share a byte prefix but differ in length still land in different
151+
/// buckets. Typed signature; cast to `GenericHash` at the `Map` /
152+
/// `Vec` callback site.
153+
///
154+
/// bv[in] : BitVec to hash.
155+
/// size[in] : Ignored. Included for `GenericHash`-cast compatibility.
156+
///
157+
/// SUCCESS : Returns a stable hash of the bit pattern + length.
158+
///
159+
/// USAGE:
160+
/// Map(BitVec, u64) counts = MapInit(bitvec_hash, BitVecCompare, alloc);
161+
///
162+
/// TAGS: BitVec, Hash, GenericHash
163+
///
164+
u64 bitvec_hash(BitVec *bv, u32 size);
165+
147166
///
148167
/// Compare two bitvectors as unsigned integers.
149168
/// Treats bitvectors as unsigned binary numbers (LSB first).

Include/Misra/Std/Container/Float/Compare.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ extern "C" {
2828
///
2929
int float_compare_with_error(Float *lhs, Float *rhs, bool *error);
3030
int float_compare(Float *lhs, Float *rhs);
31+
32+
///
33+
/// Hash a `Float` for use as a map key. FNV-1a over the significand
34+
/// magnitude bytes, the exponent, and the sign so `+1.5e3`,
35+
/// `-1.5e3`, and `1.5e2` land in different buckets. Typed signature;
36+
/// cast to `GenericHash` at the `Map` / `Vec` callback site.
37+
///
38+
/// value[in] : Float to hash.
39+
/// size[in] : Ignored. Included for `GenericHash`-cast compatibility.
40+
///
41+
/// SUCCESS : Returns a stable hash of the float's representation.
42+
///
43+
/// USAGE:
44+
/// Map(Float, u64) counts = MapInit(float_hash, float_compare, alloc);
45+
///
46+
/// TAGS: Float, Hash, GenericHash
47+
///
48+
u64 float_hash(Float *value, u32 size);
49+
3150
#ifndef __cplusplus
3251
# define FLOAT_COMPARE_DISPATCH(rhs) \
3352
_Generic( \

Include/Misra/Std/Container/Int/Compare.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ extern "C" {
2828
///
2929
int int_compare(Int *lhs, Int *rhs);
3030

31+
///
32+
/// Hash an `Int` for use as a map key. FNV-1a over the magnitude
33+
/// bytes. Typed signature; cast to `GenericHash` at the
34+
/// `Map` / `Vec` callback site (the standard pattern -- see
35+
/// `MapInitFull_9` and `VecFind`).
36+
///
37+
/// value[in] : Int to hash.
38+
/// size[in] : Ignored. Included for `GenericHash`-cast compatibility.
39+
///
40+
/// SUCCESS : Returns a stable hash of the integer's magnitude.
41+
///
42+
/// USAGE:
43+
/// Map(Int, u64) counts = MapInit(int_hash, int_compare, alloc);
44+
///
45+
/// TAGS: Int, Hash, GenericHash
46+
///
47+
u64 int_hash(Int *value, u32 size);
48+
3149
#ifndef __cplusplus
3250
# define INT_COMPARE_DISPATCH(rhs) \
3351
_Generic( \

Source/Misra/Std/Container/BitVec.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,30 @@ bool BitVecEqualsRange(BitVec *bv1, u64 start1, BitVec *bv2, u64 start2, u64 len
654654
return true;
655655
}
656656

657+
// FNV-1a over the live bit-vector bytes plus a length tail-mix so two
658+
// bitvectors that share a byte prefix but differ in length still hash
659+
// to distinct buckets. Typed signature -- callers cast to GenericHash
660+
// at the Map / Vec callback site.
661+
u64 bitvec_hash(BitVec *bv, u32 size) {
662+
u64 hash = 1469598103934665603ULL;
663+
664+
(void)size;
665+
ValidateBitVec(bv);
666+
667+
u64 bit_count = bv->length;
668+
u64 byte_count = bit_count == 0 ? 0 : BYTES_FOR_BITS(bit_count);
669+
const u8 *bytes = (const u8 *)bv->data;
670+
for (u64 i = 0; i < byte_count; i++) {
671+
hash ^= (u64)bytes[i];
672+
hash *= 1099511628211ULL;
673+
}
674+
for (u64 i = 0; i < sizeof(bit_count); i++) {
675+
hash ^= (bit_count >> (i * 8u)) & 0xFFu;
676+
hash *= 1099511628211ULL;
677+
}
678+
return hash;
679+
}
680+
657681
int BitVecCompare(BitVec *bv1, BitVec *bv2) {
658682
ValidateBitVec(bv1);
659683
ValidateBitVec(bv2);

Source/Misra/Std/Container/Float.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,34 @@ int float_compare(Float *lhs, Float *rhs) {
759759
return float_compare_with_error(lhs, rhs, NULL);
760760
}
761761

762+
// FNV-1a over significand magnitude bytes, exponent, and sign. Typed
763+
// signature -- callers cast to GenericHash at the Map/Vec callback
764+
// site (see MapInitFull_9 / VecFind for the pattern).
765+
u64 float_hash(Float *value, u32 size) {
766+
u64 hash = 1469598103934665603ULL;
767+
768+
(void)size;
769+
ValidateFloat(value);
770+
771+
u64 bits = IntBitLength(&value->significand);
772+
u64 bytes = bits == 0 ? 0 : CEIL_DIV(bits, 8u);
773+
const u8 *magnitude = (const u8 *)BitVecData(&value->significand.bits);
774+
for (u64 i = 0; i < bytes; i++) {
775+
hash ^= (u64)magnitude[i];
776+
hash *= 1099511628211ULL;
777+
}
778+
// Mix exponent bytes (little-endian view of the signed i64).
779+
i64 exp = value->exponent;
780+
for (u64 i = 0; i < sizeof(exp); i++) {
781+
hash ^= ((u64)exp >> (i * 8u)) & 0xFFu;
782+
hash *= 1099511628211ULL;
783+
}
784+
// Sign byte so +N and -N hash differently.
785+
hash ^= (u64)(value->negative ? 1u : 0u);
786+
hash *= 1099511628211ULL;
787+
return hash;
788+
}
789+
762790
int float_compare_int_with_error(Float *lhs, Int *rhs, bool *error) {
763791
Float rhs_value = FloatInit(lhs->significand.bits.allocator);
764792
int cmp = 0;

Source/Misra/Std/Container/Int.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,26 @@ Str IntToHexStr(Int *value) {
919919
return IntToStrRadix(value, 16, false);
920920
}
921921

922+
// FNV-1a over the significant magnitude bytes. `size` is the
923+
// GenericHash callback's value-slot size; ignored since Int's real
924+
// length lives inside the value itself. `Int` is unsigned by design,
925+
// so there's no sign byte to mix in.
926+
u64 int_hash(Int *value, u32 size) {
927+
u64 hash = 1469598103934665603ULL;
928+
929+
(void)size;
930+
ValidateInt(value);
931+
932+
u64 bits = IntBitLength(value);
933+
u64 bytes = bits == 0 ? 0 : CEIL_DIV(bits, 8u);
934+
const u8 *magnitude = (const u8 *)BitVecData(INT_BITS(value));
935+
for (u64 i = 0; i < bytes; i++) {
936+
hash ^= (u64)magnitude[i];
937+
hash *= 1099511628211ULL;
938+
}
939+
return hash;
940+
}
941+
922942
int int_compare(Int *lhs, Int *rhs) {
923943
ValidateInt(lhs);
924944
ValidateInt(rhs);

0 commit comments

Comments
 (0)