Repository: numba/numba

Issue:
Dicts with tuple keys not working in 0.52.0
I’m using a dict with a tuple as the key which worked fine in 0.51.2, but does not work in 0.52.0:

```
import numpy as np
import numba

@numba.njit
def testdict(tuptype):
    tdict = numba.typed.Dict.empty(tuptype, numba.core.types.float32)
    t1 = np.array([3], dtype=np.uint64)
    t2 = np.array([5.67], dtype=np.float32)
    v1 = np.array([10.23], dtype=np.float32)
    tdict[(t1[0], t2[0])] = v1[0]
    # 0.51.2 prints True as expected; 0.52.0 prints False
    print((t1[0], t2[0]) in tdict)

testdict(
  numba.core.types.Tuple((numba.core.types.uint64, numba.core.types.float32)))
```
When I try to check membership of an element that I just stored, 0.52.0 returns False.

Discussion:

Patch:
diff --git a/numba/core/cgutils.py b/numba/core/cgutils.py
index 45c58e9f2..f62216365 100644
--- a/numba/core/cgutils.py
+++ b/numba/core/cgutils.py
@@ -373,20 +373,28 @@ def alloca_once(builder, ty, size=None, name='', zfill=False):
     with builder.goto_entry_block():
         ptr = builder.alloca(ty, size=size, name=name)
         # Always zero-fill at init-site.  This is safe.
-        builder.store(ty(None), ptr)
+        builder.store(ptr.type.pointee(None), ptr)
     # Also zero-fill at the use-site
     if zfill:
-        builder.store(ty(None), ptr)
+        builder.store(ptr.type.pointee(None), ptr)
     return ptr
 
 
-def alloca_once_value(builder, value, name=''):
+def sizeof(builder, ptr_type):
+    """Compute sizeof using GEP
+    """
+    null = ptr_type(None)
+    offset = null.gep([int32_t(1)])
+    return builder.ptrtoint(offset, intp_t)
+
+
+def alloca_once_value(builder, value, name='', zfill=False):
     """
     Like alloca_once(), but passing a *value* instead of a type.  The
     type is inferred and the allocated slot is also initialized with the
     given value.
     """
-    storage = alloca_once(builder, value.type)
+    storage = alloca_once(builder, value.type, zfill=zfill)
     builder.store(value, storage)
     return storage
 
@@ -907,6 +915,18 @@ def memset(builder, ptr, size, value):
     builder.call(fn, [ptr, value, size, bool_t(0)])
 
 
+def memset_padding(builder, ptr):
+    """
+    Fill padding bytes of the pointee with zeros.
+    """
+    # Load existing value
+    val = builder.load(ptr)
+    # Fill pointee with zeros
+    memset(builder, ptr, sizeof(builder, ptr.type), 0)
+    # Store value back
+    builder.store(val, ptr)
+
+
 def global_constant(builder_or_module, name, value, linkage='internal'):
     """
     Get or create a (LLVM module-)global constant with *name* or *value*.
diff --git a/numba/typed/dictobject.py b/numba/typed/dictobject.py
index 9fd6ec4f1..345216d71 100644
--- a/numba/typed/dictobject.py
+++ b/numba/typed/dictobject.py
@@ -343,6 +343,8 @@ def _dict_insert(typingctx, d, key, hashval, val):
         data_val = dm_val.as_data(builder, val)
 
         ptr_key = cgutils.alloca_once_value(builder, data_key)
+        cgutils.memset_padding(builder, ptr_key)
+
         ptr_val = cgutils.alloca_once_value(builder, data_val)
         # TODO: the ptr_oldval is not used.  needed for refct
         ptr_oldval = cgutils.alloca_once(builder, data_val.type)
@@ -435,6 +437,7 @@ def _dict_lookup(typingctx, d, key, hashval):
 
         data_key = dm_key.as_data(builder, key)
         ptr_key = cgutils.alloca_once_value(builder, data_key)
+        cgutils.memset_padding(builder, ptr_key)
 
         ll_val = context.get_data_type(td.value_type)
         ptr_val = cgutils.alloca_once(builder, ll_val)


Test patch:
diff --git a/numba/tests/test_dictobject.py b/numba/tests/test_dictobject.py
index 6e4fa2e1e..c011f3dcd 100644
--- a/numba/tests/test_dictobject.py
+++ b/numba/tests/test_dictobject.py
@@ -930,6 +930,24 @@ class TestDictObject(MemoryLeakMixin, TestCase):
 
         self.assertEqual(foo(), foo.py_func())
 
+    def test_issue6570_alignment_padding(self):
+        # Create a key type that is 12-bytes long on a 8-byte aligned system
+        # so that the a 4-byte padding is needed.
+        # If the 4-byte padding is not zero-filled, it will have garbage data
+        # that affects key matching in the lookup.
+        keyty = types.Tuple([types.uint64, types.float32])
+
+        @njit
+        def foo():
+            d = dictobject.new_dict(keyty, float64)
+            t1 = np.array([3], dtype=np.uint64)
+            t2 = np.array([5.67], dtype=np.float32)
+            v1 = np.array([10.23], dtype=np.float32)
+            d[(t1[0], t2[0])] = v1[0]
+            return (t1[0], t2[0]) in d
+
+        self.assertTrue(foo())
+
 
 class TestDictTypeCasting(TestCase):
     def check_good(self, fromty, toty):