Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions cl/_testgo/mapindirect/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,45 +33,47 @@ func main() {
// CHECK-LABEL: define void @main.main(){{.*}} {
// CHECK-NEXT: _llgo_[[BB0:[0-9]+]]:
// CHECK-NEXT: %[[TMP0:[0-9]+]] = alloca [256 x i8], align 1
// CHECK-NEXT: %[[TMP1:[0-9]+]] = alloca [256 x i8], align 1
// CHECK-NEXT: %[[TMP1:[0-9]+]] = alloca %"{{.*}}/runtime/internal/runtime.llgoMapIter", align 8
// CHECK-NEXT: %[[TMP2:[0-9]+]] = alloca [256 x i8], align 1
// CHECK-NEXT: %[[TMP3:[0-9]+]] = alloca { [8 x i8], [8 x ptr], [8 x i64], ptr }, align 8
// CHECK-NEXT: %[[TMP4:[0-9]+]] = alloca %"{{.*}}/runtime/internal/runtime.hmap", align 8
// CHECK-NEXT: %[[TMP5:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_main.LargeKey]_llgo_int", i64 0, ptr %[[TMP4]], ptr %[[TMP3]])
// CHECK-NEXT: %[[TMP3:[0-9]+]] = alloca [256 x i8], align 1
// CHECK-NEXT: %[[TMP4:[0-9]+]] = alloca { [8 x i8], [8 x ptr], [8 x i64], ptr }, align 8
// CHECK-NEXT: %[[TMP5:[0-9]+]] = alloca %"{{.*}}/runtime/internal/runtime.hmap", align 8
// CHECK-NEXT: %[[TMP6:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_main.LargeKey]_llgo_int", i64 0, ptr %[[TMP5]], ptr %[[TMP4]])
// CHECK-NEXT: br label %_llgo_[[BB1:[0-9]+]]
// CHECK-EMPTY:
// CHECK-NEXT: _llgo_[[BB1]]:
// CHECK-NEXT: %[[TMP6:[0-9]+]] = phi i64 [ 0, %_llgo_[[BB0]] ], [ %[[TMP13:[0-9]+]], %_llgo_[[BB2:[0-9]+]] ]
// CHECK-NEXT: %[[TMP7:[0-9]+]] = icmp slt i64 %[[TMP6]], 32
// CHECK-NEXT: br i1 %[[TMP7]], label %_llgo_[[BB2]], label %_llgo_[[BB3:[0-9]+]]
// CHECK-NEXT: %[[TMP7:[0-9]+]] = phi i64 [ 0, %_llgo_[[BB0]] ], [ %[[TMP14:[0-9]+]], %_llgo_[[BB2:[0-9]+]] ]
// CHECK-NEXT: %[[TMP8:[0-9]+]] = icmp slt i64 %[[TMP7]], 32
// CHECK-NEXT: br i1 %[[TMP8]], label %_llgo_[[BB2]], label %_llgo_[[BB3:[0-9]+]]
// CHECK-EMPTY:
// CHECK-NEXT: _llgo_[[BB2]]:
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP2]], i8 0, i64 256, i1 false)
// CHECK-NEXT: %[[TMP8:[0-9]+]] = trunc i64 %[[TMP6]] to i8
// CHECK-NEXT: %[[TMP9:[0-9]+]] = getelementptr inbounds i8, ptr %[[TMP2]], i64 0
// CHECK-NEXT: store i8 %[[TMP8]], ptr %[[TMP9]], align 1
// CHECK-NEXT: %[[TMP10:[0-9]+]] = load [256 x i8], ptr %[[TMP2]], align 1
// CHECK-NEXT: %[[TMP11:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocAtomicU"(i64 256) #{{[0-9]+}}
// CHECK-NEXT: store [256 x i8] %[[TMP10]], ptr %[[TMP11]], align 1
// CHECK-NEXT: %[[TMP12:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_main.LargeKey]_llgo_int", ptr %[[TMP5]], ptr %[[TMP11]])
// CHECK-NEXT: store i64 %[[TMP6]], ptr %[[TMP12]], align 8
// CHECK-NEXT: %[[TMP13]] = add i64 %[[TMP6]], 1
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP3]], i8 0, i64 256, i1 false)
// CHECK-NEXT: %[[TMP9:[0-9]+]] = trunc i64 %[[TMP7]] to i8
// CHECK-NEXT: %[[TMP10:[0-9]+]] = getelementptr inbounds i8, ptr %[[TMP3]], i64 0
// CHECK-NEXT: store i8 %[[TMP9]], ptr %[[TMP10]], align 1
// CHECK-NEXT: %[[TMP11:[0-9]+]] = load [256 x i8], ptr %[[TMP3]], align 1
// CHECK-NEXT: %[[TMP12:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocAtomicU"(i64 256) #{{[0-9]+}}
// CHECK-NEXT: store [256 x i8] %[[TMP11]], ptr %[[TMP12]], align 1
// CHECK-NEXT: %[[TMP13:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_main.LargeKey]_llgo_int", ptr %[[TMP6]], ptr %[[TMP12]])
// CHECK-NEXT: store i64 %[[TMP7]], ptr %[[TMP13]], align 8
// CHECK-NEXT: %[[TMP14]] = add i64 %[[TMP7]], 1
// CHECK-NEXT: br label %_llgo_[[BB1]]
// CHECK-EMPTY:
// CHECK-NEXT: _llgo_[[BB3]]:
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP1]], i8 0, i64 256, i1 false)
// CHECK-NEXT: %[[TMP14:[0-9]+]] = getelementptr inbounds i8, ptr %[[TMP1]], i64 0
// CHECK-NEXT: store i8 17, ptr %[[TMP14]], align 1
// CHECK-NEXT: %[[TMP15:[0-9]+]] = load [256 x i8], ptr %[[TMP1]], align 1
// CHECK-NEXT: %[[TMP16:[0-9]+]] = call { i64, i1 } @"main.lookup[main.LargeKey]"(ptr %[[TMP5]], [256 x i8] %[[TMP15]])
// CHECK-NEXT: %[[TMP17:[0-9]+]] = extractvalue { i64, i1 } %[[TMP16]], 0
// CHECK-NEXT: %[[TMP18:[0-9]+]] = extractvalue { i64, i1 } %[[TMP16]], 1
// CHECK-NEXT: %[[TMP19:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.NewMapIter"(ptr @"map[_llgo_main.LargeKey]_llgo_int", ptr %[[TMP5]])
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP2]], i8 0, i64 256, i1 false)
// CHECK-NEXT: %[[TMP15:[0-9]+]] = getelementptr inbounds i8, ptr %[[TMP2]], i64 0
// CHECK-NEXT: store i8 17, ptr %[[TMP15]], align 1
// CHECK-NEXT: %[[TMP16:[0-9]+]] = load [256 x i8], ptr %[[TMP2]], align 1
// CHECK-NEXT: %[[TMP17:[0-9]+]] = call { i64, i1 } @"main.lookup[main.LargeKey]"(ptr %[[TMP6]], [256 x i8] %[[TMP16]])
// CHECK-NEXT: %[[TMP18:[0-9]+]] = extractvalue { i64, i1 } %[[TMP17]], 0
// CHECK-NEXT: %[[TMP19:[0-9]+]] = extractvalue { i64, i1 } %[[TMP17]], 1
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP1]], i8 0, i64 104, i1 false)
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.MapIterInit"(ptr @"map[_llgo_main.LargeKey]_llgo_int", ptr %[[TMP6]], ptr %[[TMP1]])
// CHECK-NEXT: br label %_llgo_[[BB4:[0-9]+]]
// CHECK-EMPTY:
// CHECK-NEXT: _llgo_[[BB4]]:
// CHECK-NEXT: %[[TMP20:[0-9]+]] = phi i64 [ 0, %_llgo_[[BB3]] ], [ %[[TMP29:[0-9]+]], %_llgo_[[BB5:[0-9]+]] ]
// CHECK-NEXT: %[[TMP21:[0-9]+]] = call { i1, ptr, ptr } @"{{.*}}/runtime/internal/runtime.MapIterNext"(ptr %[[TMP19]])
// CHECK-NEXT: %[[TMP21:[0-9]+]] = call { i1, ptr, ptr } @"{{.*}}/runtime/internal/runtime.MapIterNext"(ptr %[[TMP1]])
// CHECK-NEXT: %[[TMP22:[0-9]+]] = extractvalue { i1, ptr, ptr } %[[TMP21]], 0
// CHECK-NEXT: br i1 %[[TMP22]], label %_llgo_[[BB7:[0-9]+]], label %_llgo_[[BB8:[0-9]+]]
// CHECK-EMPTY:
Expand All @@ -88,10 +90,10 @@ func main() {
// CHECK-NEXT: br label %_llgo_[[BB4]]
// CHECK-EMPTY:
// CHECK-NEXT: _llgo_[[BB6:[0-9]+]]:
// CHECK-NEXT: %[[TMP30:[0-9]+]] = call i64 @"{{.*}}/runtime/internal/runtime.MapLen"(ptr %[[TMP5]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %[[TMP17]])
// CHECK-NEXT: %[[TMP30:[0-9]+]] = call i64 @"{{.*}}/runtime/internal/runtime.MapLen"(ptr %[[TMP6]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %[[TMP18]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32)
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %[[TMP18]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %[[TMP19]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32)
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %[[TMP30]])
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32)
Expand Down
6 changes: 3 additions & 3 deletions cl/_testrt/makemap/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ package main
// CHECK: %[[MAP:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_int]_llgo_string", i64 0, ptr %{{[0-9]+}}, ptr %{{[0-9]+}})
// CHECK: %[[ASSIGN:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAssignFast64"(ptr @"map[_llgo_int]_llgo_string", ptr %[[MAP]], i64 1)
// CHECK: %[[VALUE:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAccess1Fast64"(ptr @"map[_llgo_int]_llgo_string", ptr %[[MAP]], i64 1)
// CHECK: %[[ITER:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.NewMapIter"(ptr @"map[_llgo_int]_llgo_string", ptr %[[MAP]])
// CHECK: call void @"{{.*}}/runtime/internal/runtime.MapIterInit"(ptr @"map[_llgo_int]_llgo_string", ptr %[[MAP]], ptr %[[ITER:[0-9]+]])
// CHECK: call { i1, ptr, ptr } @"{{.*}}/runtime/internal/runtime.MapIterNext"(ptr %[[ITER]])
// CHECK: %[[MAP_LEN:[0-9]+]] = call i64 @"{{.*}}/runtime/internal/runtime.MapLen"(ptr %[[MAP]])
// CHECK: %[[REVERSE:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_string]_llgo_int", i64 %[[MAP_LEN]], ptr %{{[0-9]+}}, ptr %{{[0-9]+}})
Expand All @@ -40,7 +40,7 @@ package main
// CHECK: %[[ANY_MAP:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_any]_llgo_int", i64 0, ptr %{{[0-9]+}}, ptr %{{[0-9]+}})
// CHECK: insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_main.N1, ptr undef }, ptr %{{[0-9]+}}, 1
// CHECK: call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_any]_llgo_int", ptr %[[ANY_MAP]], ptr %{{[0-9]+}})
// CHECK: %[[ANY_ITER:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.NewMapIter"(ptr @"map[_llgo_any]_llgo_int", ptr %[[ANY_MAP]])
// CHECK: call void @"{{.*}}/runtime/internal/runtime.MapIterInit"(ptr @"map[_llgo_any]_llgo_int", ptr %[[ANY_MAP]], ptr %[[ANY_ITER:[0-9]+]])
// CHECK: call { i1, ptr, ptr } @"{{.*}}/runtime/internal/runtime.MapIterNext"(ptr %[[ANY_ITER]])

// Array-of-value and array-of-pointer interface keys both go through dynamic
Expand All @@ -67,7 +67,7 @@ package main
// CHECK-LABEL: define void @main.make6(){{.*}} {
// CHECK: %[[NAMED_MAP:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MakeMapBuffer"(ptr @"map[_llgo_int]_llgo_string", i64 0, ptr %{{[0-9]+}}, ptr %{{[0-9]+}})
// CHECK: call ptr @"{{.*}}/runtime/internal/runtime.MapAssignFast64"(ptr @_llgo_main.M, ptr %[[NAMED_MAP]], i64 1)
// CHECK: call ptr @"{{.*}}/runtime/internal/runtime.NewMapIter"(ptr @_llgo_main.M, ptr %[[NAMED_MAP]])
// CHECK: call void @"{{.*}}/runtime/internal/runtime.MapIterInit"(ptr @_llgo_main.M, ptr %[[NAMED_MAP]], ptr %{{[0-9]+}})

// A local named key has its own descriptor and the literal's two entries are
// reflected in the allocation hint.
Expand Down
60 changes: 60 additions & 0 deletions cl/map_iter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cl

import (
"strings"
"testing"

llssa "github.com/xgo-dev/llgo/ssa"
"golang.org/x/tools/go/ssa"
)

func TestMapRangeStackStorage(t *testing.T) {
const src = `package iteration
func sum(m map[int]*int) int {
var n int
for _, p := range m { n += *p }
return n
}
func nested(m map[int]*int) int {
var n int
for _, p := range m { for _, q := range m { n += *p + *q } }
return n
}
func repeated(maps []map[int]*int) int {
var n int
for _, m := range maps { for _, p := range m { n += *p } }
return n
}
`
for _, target := range []llssa.Target{
{GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "linux", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "386"},
{GOOS: "js", GOARCH: "wasm", LLVMTarget: "wasm32-unknown-emscripten", WasmProfile: "j32"},
{GOOS: "js", GOARCH: "wasm", LLVMTarget: "wasm64-unknown-emscripten", WasmProfile: "j64"},
{GOOS: "wasip1", GOARCH: "wasm", LLVMTarget: "wasm32-unknown-wasip1", WasmProfile: "w32"},
} {
t.Run(target.GOOS+"/"+target.GOARCH+"/"+target.WasmProfile, func(t *testing.T) {
roots := target.GOARCH == "wasm"
ir := compileInterfaceBoxIR(t, src, ssa.InstantiateGenerics, &target, roots)
for name, count := range map[string]int{"sum": 1, "nested": 2, "repeated": 1} {
body := llvmFunction(t, ir, "iteration."+name)
if strings.Contains(body, "runtime.Alloc") || strings.Contains(body, "NewMapIter") {
t.Fatalf("%s retains a heap iterator:\n%s", name, body)
}
if got := strings.Count(body, "alloca %\"github.com/xgo-dev/llgo/runtime/internal/runtime.llgoMapIter\""); got != count {
t.Fatalf("%s has %d iterator slots, want %d:\n%s", name, got, count, body)
}
if got := strings.Count(body, "runtime.MapIterInit\""); got != count {
t.Fatalf("%s has %d iterator initializations, want %d:\n%s", name, got, count, body)
}
if roots && !strings.Contains(body, "@llvm_gc_root_chain") {
t.Fatalf("%s lost explicit iterator roots:\n%s", name, body)
}
}
if roots && !strings.Contains(ir, "__llgo_gcroot_stack_pointer_") {
t.Fatal("iterator fields have no typed stack root metadata")
}
})
}
}
7 changes: 4 additions & 3 deletions runtime/internal/runtime/z_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,12 @@ type llgoMapIter struct {
ready bool
}

func NewMapIter(t *maptype, h *hmap) *llgoMapIter {
var it llgoMapIter
// MapIterInit initializes caller-owned range iterator storage.
// The caller must zero *it before every call: mapiterinit leaves fields from
// the previous iteration unchanged when h is nil or empty.
func MapIterInit(t *maptype, h *hmap, it *llgoMapIter) {
mapiterinit(t, h, &it.hiter)
it.ready = true
return &it
}

func MapIterNext(it *llgoMapIter) (ok bool, k unsafe.Pointer, v unsafe.Pointer) {
Expand Down
9 changes: 8 additions & 1 deletion ssa/datastruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,14 @@ func (b Builder) Range(x Expr) Expr {
return b.InlineCall(b.Pkg.rtFunc("NewStringIter"), x)
case vkMap:
typ := b.abiType(x.raw.Type)
return b.InlineCall(b.Pkg.rtFunc("NewMapIter"), typ, x)
init := b.Pkg.rtFunc("MapIterInit")
// Only Next in this invocation uses this iterator. MapIterInit's last
// parameter is *llgoMapIter. Alloc reserves an entry-block slot with
// typed roots, but emits zeroing here on every range entry, clearing
// the previous hiter before MapIterInit, even for nil or empty maps.
iter := b.Alloc(b.Prog.Elem(lastParamType(b.Prog, init)), false)
b.InlineCall(init, typ, x, iter)
return iter
}
panic("unsupport range for " + x.raw.Type.String())
}
Expand Down
108 changes: 108 additions & 0 deletions test/go/map_iter_stack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package gotest

import (
"runtime"
"testing"
)

func TestMapRangeRepeatedInitialization(t *testing.T) {
// The same range instruction must clear its previous hiter even when the
// next map is nil or empty and mapiterinit returns without filling it.
maps := []map[int]int{{1: 11, 2: 22}, nil, {}, {3: 33}, nil, {4: 44}}
for round := 0; round < 3; round++ {
sum, count := 0, 0
for _, m := range maps {
for k, v := range m {
if v != 11*k {
t.Fatalf("stale iterator entry: %d=%d", k, v)
}
sum += v
count++
}
}
if sum != 110 || count != 4 {
t.Fatalf("sum=%d count=%d", sum, count)
}
}
}

func TestMapRangeNestedIterators(t *testing.T) {
m := map[int]int{1: 10, 2: 20, 3: 30}
pairs, sum := 0, 0
for k, v := range m {
for other, value := range m {
if other == k {
continue
}
pairs++
sum += v + value
}
}
if pairs != 6 || sum != 240 {
t.Fatalf("nested iteration: pairs=%d sum=%d", pairs, sum)
}
}

//go:noinline
func mapRangePointerValues() map[int]*[1024]byte {
m := make(map[int]*[1024]byte)
for i := 0; i < 32; i++ {
p := new([1024]byte)
p[0], p[1023] = byte(i), byte(i+1)
m[i] = p
}
return m
}

func TestMapRangeKeepsPointersAcrossGC(t *testing.T) {
// Only the iterator retains the returned map while the loop calls GC.
seen := uint32(0)
for k, p := range mapRangePointerValues() {
if k%8 == 0 {
runtime.GC()
}
if k < 0 || k >= 32 || p[0] != byte(k) || p[1023] != byte(k+1) {
t.Fatalf("collected map entry: %d %v", k, p)
}
seen |= uint32(1) << uint(k)
}
if seen != ^uint32(0) {
t.Fatalf("missing entries after GC: %08x", seen)
}
}

func TestMapRangeGrowAcrossGC(t *testing.T) {
// Pointer-free maps keep overflow buckets alive through iterator-owned
// pointers, including buckets from earlier generations after a grow.
m := make(map[int]int)
for i := 0; i < 64; i++ {
m[i] = i + 1
}
var seen [64]bool
first := true
for k, v := range m {
if first {
for i := 64; i < 1024; i++ {
m[i] = i + 1
}
runtime.GC()
first = false
}
if v != k+1 {
t.Fatalf("entry changed after grow and GC: %d=%d", k, v)
}
if k >= 0 && k < len(seen) {
if seen[k] {
t.Fatalf("original entry yielded twice: %d", k)
}
seen[k] = true
}
}
// Newly inserted entries may be yielded or skipped. Original entries
// were never deleted and must all be yielded exactly once.
for k, ok := range seen {
if !ok {
t.Fatalf("original entry lost after grow and GC: %d", k)
}
}
}
Loading