compiler: make composite map hashes order dependent

Use the same multiply-and-XOR combination as the runtime interface hash for generated struct and array key hashes. This avoids systematic collisions when components are permuted.
This commit is contained in:
Jake Bailey
2026-08-31 08:31:52 -07:00
committed by Ron Evans
parent 7d4b42db83
commit 2761414777
2 changed files with 19 additions and 14 deletions
+5 -3
View File
@@ -417,6 +417,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key
return b.createRuntimeCall("hashmapInterfacePtrHash", []llvm.Value{keyPtr, size, seed}, "hash") return b.createRuntimeCall("hashmapInterfacePtrHash", []llvm.Value{keyPtr, size, seed}, "hash")
case *types.Struct: case *types.Struct:
hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
multiplier := llvm.ConstInt(b.ctx.Int32Type(), 31, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < keyType.NumFields(); i++ { for i := 0; i < keyType.NumFields(); i++ {
if keyType.Field(i).Name() == "_" { if keyType.Field(i).Name() == "_" {
@@ -430,7 +431,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false) idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
fieldPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") fieldPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "")
fieldHash := b.generateKeyHash(fieldType, llvmFieldType, fieldPtr, seed) fieldHash := b.generateKeyHash(fieldType, llvmFieldType, fieldPtr, seed)
hash = b.CreateXor(hash, fieldHash, "") hash = b.CreateXor(b.CreateMul(hash, multiplier, ""), fieldHash, "")
} }
return hash return hash
case *types.Array: case *types.Array:
@@ -445,6 +446,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key
if arrayLen == 0 { if arrayLen == 0 {
return llvm.ConstInt(b.ctx.Int32Type(), 0, false) return llvm.ConstInt(b.ctx.Int32Type(), 0, false)
} }
multiplier := llvm.ConstInt(b.ctx.Int32Type(), 31, false)
if arrayLen <= hashArrayUnrollLimit { if arrayLen <= hashArrayUnrollLimit {
hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
@@ -452,7 +454,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key
idx := llvm.ConstInt(b.uintptrType, uint64(i), false) idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "")
elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed) elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed)
hash = b.CreateXor(hash, elemHash, "") hash = b.CreateXor(b.CreateMul(hash, multiplier, ""), elemHash, "")
} }
return hash return hash
} }
@@ -471,7 +473,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key
elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, phiI}, "") elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, phiI}, "")
elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed) elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed)
newHash := b.CreateXor(phiHash, elemHash, "") newHash := b.CreateXor(b.CreateMul(phiHash, multiplier, ""), elemHash, "")
nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "") nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "")
cond := b.CreateICmp(llvm.IntULT, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "") cond := b.CreateICmp(llvm.IntULT, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "")
b.CreateCondBr(cond, loopBody, loopDone) b.CreateCondBr(cond, loopBody, loopDone)
+14 -11
View File
@@ -113,8 +113,9 @@ entry:
%hash = call i32 @runtime.hashmapStringPtrHash(ptr %0, i32 8, i32 %2, ptr undef) #4 %hash = call i32 @runtime.hashmapStringPtrHash(ptr %0, i32 8, i32 %2, ptr undef) #4
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8
%hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4 %hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4
%5 = xor i32 %hash, %hash1 %5 = mul i32 %hash, 31
ret i32 %5 %6 = xor i32 %5, %hash1
ret i32 %6
} }
declare i32 @runtime.hashmapStringPtrHash(ptr, i32, i32, ptr) #0 declare i32 @runtime.hashmapStringPtrHash(ptr, i32, i32, ptr) #0
@@ -161,8 +162,9 @@ entry:
%hash = call i32 @runtime.hashmapStringPtrHash(ptr %0, i32 8, i32 %2, ptr undef) #4 %hash = call i32 @runtime.hashmapStringPtrHash(ptr %0, i32 8, i32 %2, ptr undef) #4
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8
%hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4 %hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4
%5 = xor i32 %hash, %hash1 %5 = mul i32 %hash, 31
ret i32 %5 %6 = xor i32 %5, %hash1
ret i32 %6
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
@@ -203,17 +205,18 @@ entry:
br label %hash.array.body br label %hash.array.body
hash.array.body: ; preds = %hash.array.body, %entry hash.array.body: ; preds = %hash.array.body, %entry
%i = phi i32 [ 0, %entry ], [ %6, %hash.array.body ] %i = phi i32 [ 0, %entry ], [ %7, %hash.array.body ]
%hash.acc = phi i32 [ 0, %entry ], [ %5, %hash.array.body ] %hash.acc = phi i32 [ 0, %entry ], [ %6, %hash.array.body ]
%4 = getelementptr inbounds nuw %runtime._string, ptr %0, i32 %i %4 = getelementptr inbounds nuw %runtime._string, ptr %0, i32 %i
%hash = call i32 @runtime.hashmapStringPtrHash(ptr %4, i32 8, i32 %2, ptr undef) #4 %hash = call i32 @runtime.hashmapStringPtrHash(ptr %4, i32 8, i32 %2, ptr undef) #4
%5 = xor i32 %hash.acc, %hash %5 = mul i32 %hash.acc, 31
%6 = add nuw nsw i32 %i, 1 %6 = xor i32 %5, %hash
%7 = icmp samesign ult i32 %i, 4 %7 = add nuw nsw i32 %i, 1
br i1 %7, label %hash.array.body, label %hash.array.done %8 = icmp samesign ult i32 %i, 4
br i1 %8, label %hash.array.body, label %hash.array.done
hash.array.done: ; preds = %hash.array.body hash.array.done: ; preds = %hash.array.body
ret i32 %5 ret i32 %6
} }
; Function Attrs: nounwind ; Function Attrs: nounwind