From 2761414777ab29f1716aa84fa407b5bcca7dbfd1 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 31 Aug 2026 08:31:52 -0700 Subject: [PATCH] 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. --- compiler/map.go | 8 +++++--- compiler/testdata/zeromap.ll | 25 ++++++++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/map.go b/compiler/map.go index 3481ae90a..ea8a49f43 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -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") case *types.Struct: hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) + multiplier := llvm.ConstInt(b.ctx.Int32Type(), 31, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < keyType.NumFields(); i++ { 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) fieldPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") fieldHash := b.generateKeyHash(fieldType, llvmFieldType, fieldPtr, seed) - hash = b.CreateXor(hash, fieldHash, "") + hash = b.CreateXor(b.CreateMul(hash, multiplier, ""), fieldHash, "") } return hash case *types.Array: @@ -445,6 +446,7 @@ func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, key if arrayLen == 0 { return llvm.ConstInt(b.ctx.Int32Type(), 0, false) } + multiplier := llvm.ConstInt(b.ctx.Int32Type(), 31, false) if arrayLen <= hashArrayUnrollLimit { hash := 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) elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed) - hash = b.CreateXor(hash, elemHash, "") + hash = b.CreateXor(b.CreateMul(hash, multiplier, ""), elemHash, "") } 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}, "") 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), "") cond := b.CreateICmp(llvm.IntULT, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "") b.CreateCondBr(cond, loopBody, loopDone) diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index 03f96f2fa..3becf83aa 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -113,8 +113,9 @@ entry: %hash = call i32 @runtime.hashmapStringPtrHash(ptr %0, i32 8, i32 %2, ptr undef) #4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4 - %5 = xor i32 %hash, %hash1 - ret i32 %5 + %5 = mul i32 %hash, 31 + %6 = xor i32 %5, %hash1 + ret i32 %6 } 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 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %hash1 = call i32 @runtime.hashmapStringPtrHash(ptr nonnull %4, i32 8, i32 %2, ptr undef) #4 - %5 = xor i32 %hash, %hash1 - ret i32 %5 + %5 = mul i32 %hash, 31 + %6 = xor i32 %5, %hash1 + ret i32 %6 } ; Function Attrs: nounwind @@ -203,17 +205,18 @@ entry: br label %hash.array.body hash.array.body: ; preds = %hash.array.body, %entry - %i = phi i32 [ 0, %entry ], [ %6, %hash.array.body ] - %hash.acc = phi i32 [ 0, %entry ], [ %5, %hash.array.body ] + %i = phi i32 [ 0, %entry ], [ %7, %hash.array.body ] + %hash.acc = phi i32 [ 0, %entry ], [ %6, %hash.array.body ] %4 = getelementptr inbounds nuw %runtime._string, ptr %0, i32 %i %hash = call i32 @runtime.hashmapStringPtrHash(ptr %4, i32 8, i32 %2, ptr undef) #4 - %5 = xor i32 %hash.acc, %hash - %6 = add nuw nsw i32 %i, 1 - %7 = icmp samesign ult i32 %i, 4 - br i1 %7, label %hash.array.body, label %hash.array.done + %5 = mul i32 %hash.acc, 31 + %6 = xor i32 %5, %hash + %7 = add nuw nsw i32 %i, 1 + %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 - ret i32 %5 + ret i32 %6 } ; Function Attrs: nounwind