Copilot commented on code in PR #2421:
URL: https://github.com/apache/age/pull/2421#discussion_r3171526315


##########
src/backend/utils/cache/agehash.c:
##########
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * agehash.c - Robin Hood open-addressing hashtable for AGE.
+ *
+ * See agehash.h for the public contract. This file implements the INLINE
+ * mode only.
+ *
+ * Internal slot layout (INLINE):
+ *
+ *   bytes 0..1     uint16 probe_dist  (AGEHASH_EMPTY = 0xFFFF marks empty)
+ *   bytes 2..3     uint16 reserved    (future tombstone / flag bits)
+ *   bytes 4..7     uint32 pad         (forces key to 8-byte alignment)
+ *   bytes 8..K+7   key
+ *   bytes K+8..    payload
+ *
+ * slot_size = MAXALIGN(8 + key_size + payload_size).
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/agehash.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+
+/* ------------------------------------------------------------------------- */
+
+struct AgeHashTable
+{
+    /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */
+    char            *slots;
+    uint32           capacity;       /* always a power of two */
+    uint32           capacity_mask;  /* capacity - 1 */
+    uint32           size;           /* live entries */
+    uint32           max_size;       /* size at which we grow */
+    uint32           slot_size;      /* total bytes per slot */
+    uint32           key_size;
+    uint32           payload_size;
+    uint32           payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */
+    AgeHashMode      mode;
+    bool             frozen;
+    agehash_hash_fn  hash_fn;
+    agehash_keyeq_fn keyeq_fn;
+    MemoryContext    mcxt;
+};
+
+/* ------------------------------------------------------------------------- */
+/* Slot accessors. */
+
+static inline char *
+slot_at(AgeHashTable *t, uint32 idx)
+{
+    return t->slots + (Size) idx * t->slot_size;
+}
+
+static inline uint16
+slot_probe_dist(const char *slot)
+{
+    uint16 d;
+    memcpy(&d, slot, sizeof(uint16));
+    return d;
+}
+
+static inline void
+slot_set_probe_dist(char *slot, uint16 d)
+{
+    memcpy(slot, &d, sizeof(uint16));
+}
+
+static inline char *
+slot_key_ptr(AgeHashTable *t, char *slot)
+{
+    (void) t;
+    return slot + AGEHASH_SLOT_KEY_OFFSET;
+}
+
+static inline char *
+slot_payload_ptr(AgeHashTable *t, char *slot)
+{
+    return slot + t->payload_offset;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Construction. */
+
+static uint32
+next_pow2(uint32 v)
+{
+    uint32 p = 1;
+    while (p < v)
+        p <<= 1;
+    return p;
+}
+
+AgeHashTable *
+agehash_create_inline(MemoryContext mcxt,
+                      Size key_size,
+                      Size payload_size,
+                      uint32 capacity_hint,
+                      agehash_hash_fn hash_fn,
+                      agehash_keyeq_fn keyeq_fn)
+{
+    AgeHashTable  *t;
+    MemoryContext  oldctx;
+    uint32         min_cap;
+    uint32         cap;
+
+    Assert(mcxt != NULL);
+    Assert(key_size > 0 && key_size <= 64);
+    Assert(payload_size > 0 && payload_size <= 4096);
+    Assert(hash_fn != NULL);
+    Assert(keyeq_fn != NULL);
+
+    oldctx = MemoryContextSwitchTo(mcxt);
+
+    t = palloc0(sizeof(AgeHashTable));
+    t->mcxt = mcxt;
+    t->mode = AGEHASH_INLINE;
+    t->frozen = false;
+    t->hash_fn = hash_fn;
+    t->keyeq_fn = keyeq_fn;
+    t->key_size = (uint32) key_size;
+    t->payload_size = (uint32) payload_size;
+    t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size;
+    t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size);

Review Comment:
   INLINE slot layout sets payload_offset = 8 + key_size without any additional 
alignment, yet callers treat the returned payload pointer as a typed struct 
pointer. If key_size is not a multiple of MAXIMUM_ALIGNOF, the payload pointer 
can become misaligned on architectures with strict alignment, causing undefined 
behavior. Either (a) document/enforce an alignment requirement on key_size, or 
(b) add padding so payload_offset is MAXALIGN'd and adjust the key-from-payload 
recovery API accordingly.



##########
src/backend/utils/adt/age_vle.c:
##########
@@ -900,23 +901,24 @@ static VLE_local_context 
*build_local_vle_context(FunctionCallInfo fcinfo,
 }
 
 /*
- * Helper function to get the specified edge's state. If it does not find it, 
it
- * creates and initializes it.
+ * Helper function to get the specified edge's state, using a precomputed hash
+ * value. The dynahash table keyed on graphid is shared with edge_hashtable
+ * elsewhere, so callers can compute graphid_hash() once and reuse it for
+ * lookups in both tables.

Review Comment:
   This comment still refers to sharing a dynahash table with the 
"edge_hashtable", but the global edge structure is now agehash-backed 
(edge_table). Consider updating the wording to reflect that the precomputed 
graphid_hash is reused across the dynahash edge_state_hashtable and the agehash 
edge_table lookup, to avoid confusion when maintaining this hot-path code.
   



##########
src/backend/utils/cache/agehash.c:
##########
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * agehash.c - Robin Hood open-addressing hashtable for AGE.
+ *
+ * See agehash.h for the public contract. This file implements the INLINE
+ * mode only.
+ *
+ * Internal slot layout (INLINE):
+ *
+ *   bytes 0..1     uint16 probe_dist  (AGEHASH_EMPTY = 0xFFFF marks empty)
+ *   bytes 2..3     uint16 reserved    (future tombstone / flag bits)
+ *   bytes 4..7     uint32 pad         (forces key to 8-byte alignment)
+ *   bytes 8..K+7   key
+ *   bytes K+8..    payload
+ *
+ * slot_size = MAXALIGN(8 + key_size + payload_size).
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/agehash.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+
+/* ------------------------------------------------------------------------- */
+
+struct AgeHashTable
+{
+    /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */
+    char            *slots;
+    uint32           capacity;       /* always a power of two */
+    uint32           capacity_mask;  /* capacity - 1 */
+    uint32           size;           /* live entries */
+    uint32           max_size;       /* size at which we grow */
+    uint32           slot_size;      /* total bytes per slot */
+    uint32           key_size;
+    uint32           payload_size;
+    uint32           payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */
+    AgeHashMode      mode;
+    bool             frozen;
+    agehash_hash_fn  hash_fn;
+    agehash_keyeq_fn keyeq_fn;
+    MemoryContext    mcxt;
+};
+
+/* ------------------------------------------------------------------------- */
+/* Slot accessors. */
+
+static inline char *
+slot_at(AgeHashTable *t, uint32 idx)
+{
+    return t->slots + (Size) idx * t->slot_size;
+}
+
+static inline uint16
+slot_probe_dist(const char *slot)
+{
+    uint16 d;
+    memcpy(&d, slot, sizeof(uint16));
+    return d;
+}
+
+static inline void
+slot_set_probe_dist(char *slot, uint16 d)
+{
+    memcpy(slot, &d, sizeof(uint16));
+}
+
+static inline char *
+slot_key_ptr(AgeHashTable *t, char *slot)
+{
+    (void) t;
+    return slot + AGEHASH_SLOT_KEY_OFFSET;
+}
+
+static inline char *
+slot_payload_ptr(AgeHashTable *t, char *slot)
+{
+    return slot + t->payload_offset;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Construction. */
+
+static uint32
+next_pow2(uint32 v)
+{
+    uint32 p = 1;
+    while (p < v)
+        p <<= 1;
+    return p;
+}
+
+AgeHashTable *
+agehash_create_inline(MemoryContext mcxt,
+                      Size key_size,
+                      Size payload_size,
+                      uint32 capacity_hint,
+                      agehash_hash_fn hash_fn,
+                      agehash_keyeq_fn keyeq_fn)
+{
+    AgeHashTable  *t;
+    MemoryContext  oldctx;
+    uint32         min_cap;
+    uint32         cap;
+
+    Assert(mcxt != NULL);
+    Assert(key_size > 0 && key_size <= 64);
+    Assert(payload_size > 0 && payload_size <= 4096);
+    Assert(hash_fn != NULL);
+    Assert(keyeq_fn != NULL);
+

Review Comment:
   agehash_create_inline() only Assert()s key_size/payload_size bounds, but the 
implementation relies on fixed-size on-stack buffers (carry_key[64], 
carry_payload[4096]) during inserts. In non-assert builds, a caller can create 
a table with larger sizes and trigger a stack buffer overflow/memory corruption 
on insert. Please enforce these limits with a runtime check (elog/ereport) or 
remove the fixed-size buffers in favor of size-based allocation.
   



##########
src/backend/utils/cache/agehash.c:
##########
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * agehash.c - Robin Hood open-addressing hashtable for AGE.
+ *
+ * See agehash.h for the public contract. This file implements the INLINE
+ * mode only.
+ *
+ * Internal slot layout (INLINE):
+ *
+ *   bytes 0..1     uint16 probe_dist  (AGEHASH_EMPTY = 0xFFFF marks empty)
+ *   bytes 2..3     uint16 reserved    (future tombstone / flag bits)
+ *   bytes 4..7     uint32 pad         (forces key to 8-byte alignment)
+ *   bytes 8..K+7   key
+ *   bytes K+8..    payload
+ *
+ * slot_size = MAXALIGN(8 + key_size + payload_size).
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/agehash.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+
+/* ------------------------------------------------------------------------- */
+
+struct AgeHashTable
+{
+    /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */
+    char            *slots;
+    uint32           capacity;       /* always a power of two */
+    uint32           capacity_mask;  /* capacity - 1 */
+    uint32           size;           /* live entries */
+    uint32           max_size;       /* size at which we grow */
+    uint32           slot_size;      /* total bytes per slot */
+    uint32           key_size;
+    uint32           payload_size;
+    uint32           payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */
+    AgeHashMode      mode;
+    bool             frozen;
+    agehash_hash_fn  hash_fn;
+    agehash_keyeq_fn keyeq_fn;
+    MemoryContext    mcxt;
+};
+
+/* ------------------------------------------------------------------------- */
+/* Slot accessors. */
+
+static inline char *
+slot_at(AgeHashTable *t, uint32 idx)
+{
+    return t->slots + (Size) idx * t->slot_size;
+}
+
+static inline uint16
+slot_probe_dist(const char *slot)
+{
+    uint16 d;
+    memcpy(&d, slot, sizeof(uint16));
+    return d;
+}
+
+static inline void
+slot_set_probe_dist(char *slot, uint16 d)
+{
+    memcpy(slot, &d, sizeof(uint16));
+}
+
+static inline char *
+slot_key_ptr(AgeHashTable *t, char *slot)
+{
+    (void) t;
+    return slot + AGEHASH_SLOT_KEY_OFFSET;
+}
+
+static inline char *
+slot_payload_ptr(AgeHashTable *t, char *slot)
+{
+    return slot + t->payload_offset;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Construction. */
+
+static uint32
+next_pow2(uint32 v)
+{
+    uint32 p = 1;
+    while (p < v)
+        p <<= 1;
+    return p;
+}
+
+AgeHashTable *
+agehash_create_inline(MemoryContext mcxt,
+                      Size key_size,
+                      Size payload_size,
+                      uint32 capacity_hint,
+                      agehash_hash_fn hash_fn,
+                      agehash_keyeq_fn keyeq_fn)
+{
+    AgeHashTable  *t;
+    MemoryContext  oldctx;
+    uint32         min_cap;
+    uint32         cap;
+
+    Assert(mcxt != NULL);
+    Assert(key_size > 0 && key_size <= 64);
+    Assert(payload_size > 0 && payload_size <= 4096);
+    Assert(hash_fn != NULL);
+    Assert(keyeq_fn != NULL);
+
+    oldctx = MemoryContextSwitchTo(mcxt);
+
+    t = palloc0(sizeof(AgeHashTable));
+    t->mcxt = mcxt;
+    t->mode = AGEHASH_INLINE;
+    t->frozen = false;
+    t->hash_fn = hash_fn;
+    t->keyeq_fn = keyeq_fn;
+    t->key_size = (uint32) key_size;
+    t->payload_size = (uint32) payload_size;
+    t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size;
+    t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size);
+
+    /*
+     * Capacity floor of 64 keeps tiny tables out of degenerate-load territory
+     * and avoids a flurry of grows on the first few inserts.
+     */
+    if (capacity_hint == 0)
+        min_cap = 64;
+    else
+    {
+        /* size capacity_hint at MAX_LOAD so we don't immediately grow */
+        min_cap = (uint32) ((double) capacity_hint / AGEHASH_MAX_LOAD) + 1;
+        if (min_cap < 64)
+            min_cap = 64;
+    }
+    cap = next_pow2(min_cap);
+    Assert((cap & (cap - 1)) == 0);
+
+    t->capacity = cap;
+    t->capacity_mask = cap - 1;
+    t->size = 0;
+    t->max_size = (uint32) ((double) cap * AGEHASH_MAX_LOAD);
+    /*
+     * The slot array can comfortably exceed 1 GiB on production graphs
+     * (SF3 ldbc_snb edge_table is ~3 GiB at 0.7 load). Use the HUGE
+     * allocator to bypass the standard MaxAllocSize check.
+     */
+    t->slots = (char *) MemoryContextAllocHuge(mcxt,
+                                               (Size) cap * t->slot_size);
+
+    /* Mark every slot empty. */
+    {
+        uint32 i;
+        for (i = 0; i < cap; i++)
+            slot_set_probe_dist(slot_at(t, i), AGEHASH_EMPTY);
+    }
+
+    MemoryContextSwitchTo(oldctx);
+    return t;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Insert. Robin Hood with rich-poor swap. */
+
+static void agehash_grow(AgeHashTable *t);
+
+static void *
+agehash_insert_internal(AgeHashTable *t, const void *key, uint32 hashvalue,
+                        bool *found)
+{
+    uint32 i;
+    uint16 d;
+    /*
+     * Carrier for the entry currently being placed. Starts as the caller's
+     * key with a fresh, zero-filled payload; gets overwritten on each
+     * Robin Hood swap.
+     */
+    char   carry_key[64];
+    char   carry_payload[4096];
+    void  *result_payload = NULL;
+    bool   placed_caller = false;
+
+    Assert(!t->frozen);
+    Assert(t->key_size <= sizeof(carry_key));
+    Assert(t->payload_size <= sizeof(carry_payload));
+
+    /* Grow before insert if at threshold. */
+    if (t->size >= t->max_size)
+        agehash_grow(t);
+
+    /* Initialize carry buffers with the caller's key and an empty payload. */
+    memcpy(carry_key, key, t->key_size);
+    memset(carry_payload, 0, t->payload_size);
+
+    i = hashvalue & t->capacity_mask;
+    d = 0;
+
+    for (;;)
+    {
+        char  *slot = slot_at(t, i);
+        uint16 sd   = slot_probe_dist(slot);
+
+        if (sd == AGEHASH_EMPTY)
+        {
+            /* Place the carrier here and we're done. */
+            slot_set_probe_dist(slot, d);
+            memcpy(slot_key_ptr(t, slot), carry_key, t->key_size);
+            memcpy(slot_payload_ptr(t, slot), carry_payload, t->payload_size);
+            t->size++;
+            if (!placed_caller)
+            {
+                /* The caller's slot landed here. */
+                if (found != NULL)
+                    *found = false;
+                return slot_payload_ptr(t, slot);
+            }
+            /*
+             * The caller was placed earlier via a swap; result_payload
+             * already points at their final slot.
+             */
+            Assert(result_payload != NULL);
+            return result_payload;
+        }
+
+        if (sd == d &&
+            !placed_caller &&
+            t->keyeq_fn(slot_key_ptr(t, slot), carry_key, t->key_size))
+        {
+            /*
+             * Existing entry with the caller's key. Note: this match check
+             * is only relevant before we've performed a swap; once we've
+             * placed the caller into a slot, the key in `carry` is some
+             * displaced entry that, by RH invariant on insert from a fresh
+             * key, cannot already exist in the table.
+             */
+            if (found != NULL)
+                *found = true;
+            return slot_payload_ptr(t, slot);
+        }
+
+        if (sd < d)
+        {
+            /*
+             * Rich-poor swap: this slot's owner is closer to its ideal
+             * bucket than we are. Take its place and continue with the
+             * displaced entry. If we have not yet placed the caller, this
+             * is where they end up; remember the pointer so we can return
+             * it once the displaced chain finishes.
+             */
+            char   tmp_key[64];
+            char   tmp_payload[4096];
+            uint16 tmp_d = sd;
+
+            memcpy(tmp_key,     slot_key_ptr(t, slot),     t->key_size);
+            memcpy(tmp_payload, slot_payload_ptr(t, slot), t->payload_size);
+
+            slot_set_probe_dist(slot, d);
+            memcpy(slot_key_ptr(t, slot),     carry_key,     t->key_size);
+            memcpy(slot_payload_ptr(t, slot), carry_payload, t->payload_size);
+
+            if (!placed_caller)
+            {
+                placed_caller = true;
+                result_payload = slot_payload_ptr(t, slot);
+                /* Notify caller: this insert is a fresh entry. */
+                if (found != NULL)
+                {
+                    *found = false;
+                    found = NULL; /* don't write again */
+                }
+            }
+
+            /* Continue with the displaced entry as the new carrier. */
+            memcpy(carry_key,     tmp_key,     t->key_size);
+            memcpy(carry_payload, tmp_payload, t->payload_size);
+            d = tmp_d;
+        }
+
+        i = (i + 1) & t->capacity_mask;
+        d++;
+
+        /*
+         * Probe distance overflow guard. With AGEHASH_MAX_LOAD = 0.7 and a
+         * non-degenerate hash function, max probe is empirically <= 32.
+         * The 0xFE00 ceiling reserves headroom while leaving probe_dist
+         * well clear of the AGEHASH_EMPTY sentinel.

Review Comment:
   The probe-distance overflow guard comment references AGEHASH_MAX_LOAD = 0.7, 
but AGEHASH_MAX_LOAD is defined as 0.85 in agehash.h. Please update this 
comment (and any other related constants/assumptions) so the documented 
invariants match the compiled behavior.
   



##########
src/backend/utils/cache/agehash.c:
##########
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * agehash.c - Robin Hood open-addressing hashtable for AGE.
+ *
+ * See agehash.h for the public contract. This file implements the INLINE
+ * mode only.
+ *
+ * Internal slot layout (INLINE):
+ *
+ *   bytes 0..1     uint16 probe_dist  (AGEHASH_EMPTY = 0xFFFF marks empty)
+ *   bytes 2..3     uint16 reserved    (future tombstone / flag bits)
+ *   bytes 4..7     uint32 pad         (forces key to 8-byte alignment)
+ *   bytes 8..K+7   key
+ *   bytes K+8..    payload
+ *
+ * slot_size = MAXALIGN(8 + key_size + payload_size).
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/agehash.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+
+/* ------------------------------------------------------------------------- */
+
+struct AgeHashTable
+{
+    /* Slot array: capacity * slot_size bytes, palloc'd in mcxt. */
+    char            *slots;
+    uint32           capacity;       /* always a power of two */
+    uint32           capacity_mask;  /* capacity - 1 */
+    uint32           size;           /* live entries */
+    uint32           max_size;       /* size at which we grow */
+    uint32           slot_size;      /* total bytes per slot */
+    uint32           key_size;
+    uint32           payload_size;
+    uint32           payload_offset; /* AGEHASH_SLOT_KEY_OFFSET + key_size */
+    AgeHashMode      mode;
+    bool             frozen;
+    agehash_hash_fn  hash_fn;
+    agehash_keyeq_fn keyeq_fn;
+    MemoryContext    mcxt;
+};
+
+/* ------------------------------------------------------------------------- */
+/* Slot accessors. */
+
+static inline char *
+slot_at(AgeHashTable *t, uint32 idx)
+{
+    return t->slots + (Size) idx * t->slot_size;
+}
+
+static inline uint16
+slot_probe_dist(const char *slot)
+{
+    uint16 d;
+    memcpy(&d, slot, sizeof(uint16));
+    return d;
+}
+
+static inline void
+slot_set_probe_dist(char *slot, uint16 d)
+{
+    memcpy(slot, &d, sizeof(uint16));
+}
+
+static inline char *
+slot_key_ptr(AgeHashTable *t, char *slot)
+{
+    (void) t;
+    return slot + AGEHASH_SLOT_KEY_OFFSET;
+}
+
+static inline char *
+slot_payload_ptr(AgeHashTable *t, char *slot)
+{
+    return slot + t->payload_offset;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Construction. */
+
+static uint32
+next_pow2(uint32 v)
+{
+    uint32 p = 1;
+    while (p < v)
+        p <<= 1;
+    return p;
+}
+
+AgeHashTable *
+agehash_create_inline(MemoryContext mcxt,
+                      Size key_size,
+                      Size payload_size,
+                      uint32 capacity_hint,
+                      agehash_hash_fn hash_fn,
+                      agehash_keyeq_fn keyeq_fn)
+{
+    AgeHashTable  *t;
+    MemoryContext  oldctx;
+    uint32         min_cap;
+    uint32         cap;
+
+    Assert(mcxt != NULL);
+    Assert(key_size > 0 && key_size <= 64);
+    Assert(payload_size > 0 && payload_size <= 4096);
+    Assert(hash_fn != NULL);
+    Assert(keyeq_fn != NULL);
+
+    oldctx = MemoryContextSwitchTo(mcxt);
+
+    t = palloc0(sizeof(AgeHashTable));
+    t->mcxt = mcxt;
+    t->mode = AGEHASH_INLINE;
+    t->frozen = false;
+    t->hash_fn = hash_fn;
+    t->keyeq_fn = keyeq_fn;
+    t->key_size = (uint32) key_size;
+    t->payload_size = (uint32) payload_size;
+    t->payload_offset = AGEHASH_SLOT_KEY_OFFSET + (uint32) key_size;
+    t->slot_size = MAXALIGN(t->payload_offset + (uint32) payload_size);
+
+    /*
+     * Capacity floor of 64 keeps tiny tables out of degenerate-load territory
+     * and avoids a flurry of grows on the first few inserts.
+     */
+    if (capacity_hint == 0)
+        min_cap = 64;
+    else
+    {
+        /* size capacity_hint at MAX_LOAD so we don't immediately grow */
+        min_cap = (uint32) ((double) capacity_hint / AGEHASH_MAX_LOAD) + 1;
+        if (min_cap < 64)
+            min_cap = 64;
+    }
+    cap = next_pow2(min_cap);
+    Assert((cap & (cap - 1)) == 0);
+
+    t->capacity = cap;
+    t->capacity_mask = cap - 1;
+    t->size = 0;
+    t->max_size = (uint32) ((double) cap * AGEHASH_MAX_LOAD);
+    /*
+     * The slot array can comfortably exceed 1 GiB on production graphs
+     * (SF3 ldbc_snb edge_table is ~3 GiB at 0.7 load). Use the HUGE
+     * allocator to bypass the standard MaxAllocSize check.
+     */
+    t->slots = (char *) MemoryContextAllocHuge(mcxt,
+                                               (Size) cap * t->slot_size);
+
+    /* Mark every slot empty. */
+    {
+        uint32 i;
+        for (i = 0; i < cap; i++)
+            slot_set_probe_dist(slot_at(t, i), AGEHASH_EMPTY);
+    }
+
+    MemoryContextSwitchTo(oldctx);
+    return t;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Insert. Robin Hood with rich-poor swap. */
+
+static void agehash_grow(AgeHashTable *t);
+
+static void *
+agehash_insert_internal(AgeHashTable *t, const void *key, uint32 hashvalue,
+                        bool *found)
+{
+    uint32 i;
+    uint16 d;
+    /*
+     * Carrier for the entry currently being placed. Starts as the caller's
+     * key with a fresh, zero-filled payload; gets overwritten on each
+     * Robin Hood swap.
+     */
+    char   carry_key[64];
+    char   carry_payload[4096];
+    void  *result_payload = NULL;
+    bool   placed_caller = false;
+

Review Comment:
   agehash_freeze() is documented to make subsequent inserts/grows fail with 
elog(ERROR) in production, but agehash_insert_internal() only has 
Assert(!t->frozen). In non-assert builds this allows inserts into a frozen 
table, which can silently reorder slots and invalidate any payload pointers 
callers consider stable after freeze. Add a non-assert runtime check (and 
similarly in grow paths) that errors out when t->frozen is true to match the 
API contract and prevent hard-to-debug corruption.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to