Keith Randall would like Arseny Samoylov and Michael Pratt to review this change.
internal/runtime/maps: document hashing of stack pointers
CL 776281 makes it a bit clearer that we're doing pointer to uintptr
conversion on pointer keys. That sent me down something of a rabbit
hole trying to figure out how that actually could work, given stack
copies could happen after hashing but before the core map operation.
Turns out it does work, for subtle reasons. Documenting here.
diff --git a/src/cmd/compile/internal/escape/assign.go b/src/cmd/compile/internal/escape/assign.go
index 6af5388..27f13ca 100644
--- a/src/cmd/compile/internal/escape/assign.go
+++ b/src/cmd/compile/internal/escape/assign.go
@@ -50,6 +50,8 @@
case ir.OINDEXMAP:
n := n.(*ir.IndexExpr)
e.discard(n.X)
+ // Keys used in map assignments must escape.
+ // See "Hashing Pointers" doc in internal/runtime/maps/map.go.
e.assignHeap(n.Index, "key of map put", n)
}
diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go
index 074edd9..9fa7fbc 100644
--- a/src/cmd/compile/internal/escape/call.go
+++ b/src/cmd/compile/internal/escape/call.go
@@ -209,6 +209,8 @@
e.discard(arg)
}
e.discard(call.RType)
+ // Note: keys used in map deletes do not need to escape.
+ // See "Hashing Pointers" doc in internal/runtime/maps/map.go.
case ir.OMIN, ir.OMAX:
call := call.(*ir.CallExpr)
diff --git a/src/cmd/compile/internal/escape/expr.go b/src/cmd/compile/internal/escape/expr.go
index 1521c2e..468c471 100644
--- a/src/cmd/compile/internal/escape/expr.go
+++ b/src/cmd/compile/internal/escape/expr.go
@@ -90,6 +90,8 @@
case ir.OINDEXMAP:
n := n.(*ir.IndexExpr)
e.discard(n.X)
+ // Keys used in map lookups do not need to escape.
+ // See "Hashing Pointers" doc in internal/runtime/maps/map.go.
e.discard(n.Index)
case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR:
n := n.(*ir.SliceExpr)
diff --git a/src/internal/runtime/maps/map.go b/src/internal/runtime/maps/map.go
index 0a0dd359..e6f7a83 100644
--- a/src/internal/runtime/maps/map.go
+++ b/src/internal/runtime/maps/map.go
@@ -177,6 +177,24 @@
// For (b), we must adjust the current directory index when the directory
// grows. This is more straightforward, as the directory orders remains the
// same after grow, so we just double the index if the directory size doubles.
+//
+// Hashing Pointers
+//
+// Keys in Go maps can be pointers, or contain pointers. The hash of
+// a pointer is a somewhat tricky concept, as pointers to stack
+// objects can change during a stack copy. Because we hash a pointer
+// by just hashing its uintptr-converted value, the hash of a key can
+// potentially become stale across any stack copy.
+//
+// For keys that are stored into maps, we must avoid this. All key
+// arguments to map assignments must be marked as escaping so that the
+// hash of the key in the map is stable. This is true even when the
+// map itself does not escape and can live on the stack.
+//
+// For keys that are used for lookup (or delete), it turns out that
+// escaping is not required. If we are looking up a pointer which
+// points to the stack, the hash value is ~irrelevant, as the key is
+// guaranteed to not be in the map (due to the previous paragraph).
// Extracts the H1 portion of a hash: the 57 upper bits.
// TODO(prattmic): what about 32-bit systems?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
This looks great! This very non-trivial detail that definitely worth documenting.
// For keys that are stored into maps, we must avoid this. All key
// arguments to map assignments must be marked as escaping so that the
// hash of the key in the map is stable. This is true even when the
// map itself does not escape and can live on the stack.I think this could be rephrased slightly.
On first read, I interpreted this as saying that the key itself must escape to the heap, even if the map lives on the stack. But actually it the object that the key references must escape to the heap. The key itself doesn't need to escape.
And with that it makes sense that map[ptr] can be on stack and store keys (pointers to heap allocated objects) on the stack.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// For keys that are stored into maps, we must avoid this. All key
// arguments to map assignments must be marked as escaping so that the
// hash of the key in the map is stable. This is true even when the
// map itself does not escape and can live on the stack.I think this could be rephrased slightly.
On first read, I interpreted this as saying that the key itself must escape to the heap, even if the map lives on the stack. But actually it the object that the key references must escape to the heap. The key itself doesn't need to escape.
And with that it makes sense that map[ptr] can be on stack and store keys (pointers to heap allocated objects) on the stack.
I always confuse myself about whether escaping applies to the *allocation*, or to the *pointer* to the allocation.
Wikipedia generally talks like it is the *pointer* that escapes, although parts of that page contradict itself: https://en.wikipedia.org/wiki/Escape_analysis
I think our code generally applies it to the allocation itself.
I'll reword to make it clearer.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
Gentle ping. This CL seems to have slipped out of sight a bit.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +2 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
internal/runtime/maps: document hashing of stack pointers
CL 776281 makes it a bit clearer that we're doing pointer to uintptr
conversion on pointer keys. That sent me down something of a rabbit
hole trying to figure out how that actually could work, given stack
copies could happen after hashing but before the core map operation.
Turns out it does work, for subtle reasons. Documenting here.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |