diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e8dfc2ead3..6c16b12f2d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -75,7 +75,7 @@ jobs: - name: Test with coverage # 45m: the caller-info acceptance suite (test/go) legitimately grew # the covered run past the old 30m budget on macOS runners. - run: go test -timeout 45m -coverprofile="coverage.txt" -covermode=atomic ./... + run: go test -timeout 45m -p 2 -coverprofile="coverage.txt" -covermode=atomic ./... - name: Test with embedded emulator env env: diff --git a/cl/_testdata/vargs/in.go b/cl/_testdata/vargs/in.go index 27eef21e99..98ea773fd7 100644 --- a/cl/_testdata/vargs/in.go +++ b/cl/_testdata/vargs/in.go @@ -3,7 +3,6 @@ package main import "github.com/goplus/lib/c" -// CHECK: @0 = private unnamed_addr constant [3 x i8] c"int", align 1 // CHECK: @1 = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 func main() { @@ -88,7 +87,8 @@ func test(a ...any) { // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %12, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 3 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %17 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %12, ptr @_llgo_int, ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %17) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/abimethod/in.go b/cl/_testgo/abimethod/in.go index c72d8dce01..b06ce6fa1f 100644 --- a/cl/_testgo/abimethod/in.go +++ b/cl/_testgo/abimethod/in.go @@ -736,7 +736,8 @@ type I2 interface { // CHECK-NEXT: br i1 %29, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %23, %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 3 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %30 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %23, ptr @_llgo_int, ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %30) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/cgodefer/cgodefer.go b/cl/_testgo/cgodefer/cgodefer.go index 7415613677..179ea33da9 100644 --- a/cl/_testgo/cgodefer/cgodefer.go +++ b/cl/_testgo/cgodefer/cgodefer.go @@ -90,17 +90,20 @@ import "C" // CHECK-NEXT: store ptr %32, ptr %18, align 8 // CHECK-NEXT: %33 = extractvalue { ptr, i64, { ptr, ptr } } %31, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %30) -// CHECK-NEXT: %34 = extractvalue { ptr, ptr } %33, 1 -// CHECK-NEXT: %35 = extractvalue { ptr, ptr } %33, 0 -// CHECK-NEXT: call void %35(ptr %34) +// CHECK-NEXT: %34 = extractvalue { ptr, ptr } %33, 0 +// CHECK-NEXT: %35 = call ptr @"{{.*}}/runtime/internal/runtime.StartRecoverFrame"(ptr %34) +// CHECK-NEXT: %36 = extractvalue { ptr, ptr } %33, 1 +// CHECK-NEXT: %37 = extractvalue { ptr, ptr } %33, 0 +// CHECK-NEXT: call void %37(ptr %36) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.EndRecoverFrame"(ptr %35) // CHECK-NEXT: br label %_llgo_8 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_7, %_llgo_2 -// CHECK-NEXT: %36 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, align 8 -// CHECK-NEXT: %37 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %36, 2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %37) -// CHECK-NEXT: %38 = load ptr, ptr %17, align 8 -// CHECK-NEXT: indirectbr ptr %38, [label %_llgo_3, label %_llgo_6] +// CHECK-NEXT: %38 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, align 8 +// CHECK-NEXT: %39 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %38, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %39) +// CHECK-NEXT: %40 = load ptr, ptr %17, align 8 +// CHECK-NEXT: indirectbr ptr %40, [label %_llgo_3, label %_llgo_6] // CHECK-NEXT: } func main() { // CHECK-LABEL: define { ptr, ptr } @"{{.*}}/cl/_testgo/cgodefer.main$1"(ptr %0){{.*}} { diff --git a/cl/_testgo/closureall/in.go b/cl/_testgo/closureall/in.go index a9dedf95bb..0a0d89d585 100644 --- a/cl/_testgo/closureall/in.go +++ b/cl/_testgo/closureall/in.go @@ -7,8 +7,6 @@ import "github.com/goplus/lib/c" // CHECK: @0 = private unnamed_addr constant [46 x i8] c"{{.*}}/cl/_testgo/closureall.S", align 1 // CHECK: @1 = private unnamed_addr constant [3 x i8] c"Inc", align 1 -// CHECK: @7 = private unnamed_addr constant [3 x i8] c"Add", align 1 -// CHECK: @9 = private unnamed_addr constant [23 x i8] c"interface{Add(int) int}", align 1 //go:linkname cSqrt C.sqrt func cSqrt(x c.Double) c.Double @@ -173,7 +171,8 @@ func makeWithFree(base int) Fn { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %21, %"{{.*}}/runtime/internal/runtime.String" { ptr @9, i64 23 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @7, i64 3 }) +// CHECK-NEXT: %32 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %21, ptr @"_llgo_iface$VdBKYV8-gcMjZtZfcf-u2oKoj9Lu3VXwuG8TGCW2S4A", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %32) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/genericembediface/in.go b/cl/_testgo/genericembediface/in.go index c83bc68087..6c4294bc1b 100644 --- a/cl/_testgo/genericembediface/in.go +++ b/cl/_testgo/genericembediface/in.go @@ -7,10 +7,9 @@ import ( // CHECK: {{^}}@2 = private unnamed_addr constant [20 x i8] c"ServerReflectionInfo", align 1{{$}} // CHECK: {{^}}@5 = private unnamed_addr constant [7 x i8] c"Context", align 1{{$}} -// CHECK: {{^}}@11 = private unnamed_addr constant [68 x i8] c"{{.*}}/cl/_testgo/genericembediface.ReflectionServer", align 1{{$}} -// CHECK: {{^}}@19 = private unnamed_addr constant [4 x i8] c"pass", align 1{{$}} -// CHECK: {{^}}@20 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/genericembediface.server", align 1{{$}} -// CHECK: {{^}}@21 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/genericembediface.stream", align 1{{$}} +// CHECK: {{^}}@18 = private unnamed_addr constant [4 x i8] c"pass", align 1{{$}} +// CHECK: {{^}}@19 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/genericembediface.server", align 1{{$}} +// CHECK: {{^}}@20 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/genericembediface.stream", align 1{{$}} type Request struct{} type Response struct{} @@ -48,7 +47,8 @@ type ReflectionServer interface { // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %21 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @11, i64 68 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 20 }) +// CHECK-NEXT: %22 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %2, ptr @"_llgo_{{.*}}/cl/_testgo/genericembediface.ReflectionServer", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %22) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -93,7 +93,7 @@ func (stream) Context() string { // CHECK-NEXT: %4 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" undef, ptr %3, 0 // CHECK-NEXT: %5 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" %4, ptr %2, 1 // CHECK-NEXT: %6 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.handler"(%"{{.*}}/runtime/internal/runtime.eface" %1, %"{{.*}}/runtime/internal/runtime.iface" %5) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @19, i64 4 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @18, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } @@ -111,7 +111,7 @@ func main() { // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.(*server).ServerReflectionInfo"(ptr %0, %"{{.*}}/runtime/internal/runtime.iface" %1){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %2 = icmp eq ptr %0, null -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @20, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 20 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @19, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 20 }) // CHECK-NEXT: %3 = icmp eq ptr %0, null // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %3) // CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.server.ServerReflectionInfo"(%"{{.*}}/cl/_testgo/genericembediface.server" zeroinitializer, %"{{.*}}/runtime/internal/runtime.iface" %1) @@ -126,7 +126,7 @@ func main() { // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/cl/_testgo/genericembediface.(*stream).Context"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %1 = icmp eq ptr %0, null -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @21, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 7 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @20, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 7 }) // CHECK-NEXT: %2 = icmp eq ptr %0, null // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %2) // CHECK-NEXT: %3 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/cl/_testgo/genericembediface.stream.Context"(%"{{.*}}/cl/_testgo/genericembediface.stream" zeroinitializer) diff --git a/cl/_testgo/ifaceprom/in.go b/cl/_testgo/ifaceprom/in.go index b84e4a3a35..96f73586fd 100644 --- a/cl/_testgo/ifaceprom/in.go +++ b/cl/_testgo/ifaceprom/in.go @@ -8,8 +8,7 @@ package main // CHECK: @0 = private unnamed_addr constant [3 x i8] c"two", align 1 // CHECK: @1 = private unnamed_addr constant [48 x i8] c"{{.*}}/cl/_testgo/ifaceprom.impl", align 1 // CHECK: @2 = private unnamed_addr constant [3 x i8] c"one", align 1 -// CHECK: @13 = private unnamed_addr constant [45 x i8] c"{{.*}}/cl/_testgo/ifaceprom.I", align 1 -// CHECK: @14 = private unnamed_addr constant [4 x i8] c"pass", align 1 +// CHECK: @13 = private unnamed_addr constant [4 x i8] c"pass", align 1 type I interface { one() int @@ -256,7 +255,7 @@ func main() { // CHECK-EMPTY: // CHECK-NEXT: _llgo_7: ; preds = %_llgo_19 // CHECK-NEXT: %44 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) -// CHECK-NEXT: store i64 %100, ptr %44, align 8 +// CHECK-NEXT: store i64 %101, ptr %44, align 8 // CHECK-NEXT: %45 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %44, 1 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %45) // CHECK-NEXT: unreachable @@ -316,7 +315,7 @@ func main() { // CHECK-EMPTY: // CHECK-NEXT: _llgo_13: ; preds = %_llgo_21 // CHECK-NEXT: %80 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" %107, ptr %80, align 8 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" %109, ptr %80, align 8 // CHECK-NEXT: %81 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %80, 1 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %81) // CHECK-NEXT: unreachable @@ -330,13 +329,13 @@ func main() { // CHECK-EMPTY: // CHECK-NEXT: _llgo_15: ; preds = %_llgo_23 // CHECK-NEXT: %86 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" %115, ptr %86, align 8 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" %118, ptr %86, align 8 // CHECK-NEXT: %87 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %86, 1 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %87) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_16: ; preds = %_llgo_23 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @14, i64 4 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @13, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-EMPTY: @@ -352,54 +351,58 @@ func main() { // CHECK-NEXT: br i1 %94, label %_llgo_5, label %_llgo_6 // CHECK-EMPTY: // CHECK-NEXT: _llgo_18: ; preds = %_llgo_4 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %36, %"{{.*}}/runtime/internal/runtime.String" { ptr @13, i64 45 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }) +// CHECK-NEXT: %95 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %36, ptr @"_llgo_{{.*}}/cl/_testgo/ifaceprom.I", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %95) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_19: ; preds = %_llgo_6 -// CHECK-NEXT: %95 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: %96 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %95, i32 0, i32 0 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %41, ptr %96, align 8 -// CHECK-NEXT: %97 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.one$bound", ptr undef }, ptr %95, 1 -// CHECK-NEXT: %98 = extractvalue { ptr, ptr } %97, 1 -// CHECK-NEXT: %99 = extractvalue { ptr, ptr } %97, 0 -// CHECK-NEXT: %100 = call i64 %99(ptr %98) -// CHECK-NEXT: %101 = icmp ne i64 %100, 1 -// CHECK-NEXT: br i1 %101, label %_llgo_7, label %_llgo_8 +// CHECK-NEXT: %96 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// CHECK-NEXT: %97 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %96, i32 0, i32 0 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %41, ptr %97, align 8 +// CHECK-NEXT: %98 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.one$bound", ptr undef }, ptr %96, 1 +// CHECK-NEXT: %99 = extractvalue { ptr, ptr } %98, 1 +// CHECK-NEXT: %100 = extractvalue { ptr, ptr } %98, 0 +// CHECK-NEXT: %101 = call i64 %100(ptr %99) +// CHECK-NEXT: %102 = icmp ne i64 %101, 1 +// CHECK-NEXT: br i1 %102, label %_llgo_7, label %_llgo_8 // CHECK-EMPTY: // CHECK-NEXT: _llgo_20: ; preds = %_llgo_6 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %42, %"{{.*}}/runtime/internal/runtime.String" { ptr @13, i64 45 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }) +// CHECK-NEXT: %103 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %42, ptr @"_llgo_{{.*}}/cl/_testgo/ifaceprom.I", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %103) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_21: ; preds = %_llgo_12 -// CHECK-NEXT: %102 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: %103 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %102, i32 0, i32 0 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %77, ptr %103, align 8 -// CHECK-NEXT: %104 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.two$bound", ptr undef }, ptr %102, 1 -// CHECK-NEXT: %105 = extractvalue { ptr, ptr } %104, 1 -// CHECK-NEXT: %106 = extractvalue { ptr, ptr } %104, 0 -// CHECK-NEXT: %107 = call %"{{.*}}/runtime/internal/runtime.String" %106(ptr %105) -// CHECK-NEXT: %108 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %107, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 3 }) -// CHECK-NEXT: %109 = xor i1 %108, true -// CHECK-NEXT: br i1 %109, label %_llgo_13, label %_llgo_14 +// CHECK-NEXT: %104 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// CHECK-NEXT: %105 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %104, i32 0, i32 0 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %77, ptr %105, align 8 +// CHECK-NEXT: %106 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.two$bound", ptr undef }, ptr %104, 1 +// CHECK-NEXT: %107 = extractvalue { ptr, ptr } %106, 1 +// CHECK-NEXT: %108 = extractvalue { ptr, ptr } %106, 0 +// CHECK-NEXT: %109 = call %"{{.*}}/runtime/internal/runtime.String" %108(ptr %107) +// CHECK-NEXT: %110 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %109, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 3 }) +// CHECK-NEXT: %111 = xor i1 %110, true +// CHECK-NEXT: br i1 %111, label %_llgo_13, label %_llgo_14 // CHECK-EMPTY: // CHECK-NEXT: _llgo_22: ; preds = %_llgo_12 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %78, %"{{.*}}/runtime/internal/runtime.String" { ptr @13, i64 45 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }) +// CHECK-NEXT: %112 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %78, ptr @"_llgo_{{.*}}/cl/_testgo/ifaceprom.I", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %112) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_23: ; preds = %_llgo_14 -// CHECK-NEXT: %110 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: %111 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %110, i32 0, i32 0 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %83, ptr %111, align 8 -// CHECK-NEXT: %112 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.two$bound", ptr undef }, ptr %110, 1 -// CHECK-NEXT: %113 = extractvalue { ptr, ptr } %112, 1 -// CHECK-NEXT: %114 = extractvalue { ptr, ptr } %112, 0 -// CHECK-NEXT: %115 = call %"{{.*}}/runtime/internal/runtime.String" %114(ptr %113) -// CHECK-NEXT: %116 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %115, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 3 }) -// CHECK-NEXT: %117 = xor i1 %116, true -// CHECK-NEXT: br i1 %117, label %_llgo_15, label %_llgo_16 +// CHECK-NEXT: %113 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// CHECK-NEXT: %114 = getelementptr inbounds { %"{{.*}}/runtime/internal/runtime.iface" }, ptr %113, i32 0, i32 0 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.iface" %83, ptr %114, align 8 +// CHECK-NEXT: %115 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testgo/ifaceprom.I.two$bound", ptr undef }, ptr %113, 1 +// CHECK-NEXT: %116 = extractvalue { ptr, ptr } %115, 1 +// CHECK-NEXT: %117 = extractvalue { ptr, ptr } %115, 0 +// CHECK-NEXT: %118 = call %"{{.*}}/runtime/internal/runtime.String" %117(ptr %116) +// CHECK-NEXT: %119 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %118, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 3 }) +// CHECK-NEXT: %120 = xor i1 %119, true +// CHECK-NEXT: br i1 %120, label %_llgo_15, label %_llgo_16 // CHECK-EMPTY: // CHECK-NEXT: _llgo_24: ; preds = %_llgo_14 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %84, %"{{.*}}/runtime/internal/runtime.String" { ptr @13, i64 45 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }) +// CHECK-NEXT: %121 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %84, ptr @"_llgo_{{.*}}/cl/_testgo/ifaceprom.I", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %121) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/invoke/in.go b/cl/_testgo/invoke/in.go index 6c7f8377f7..3d75637de1 100644 --- a/cl/_testgo/invoke/in.go +++ b/cl/_testgo/invoke/in.go @@ -17,9 +17,6 @@ package main // CHECK: {{^}}@13 = private unnamed_addr constant [43 x i8] c"{{.*}}/cl/_testgo/invoke.T6", align 1{{$}} // CHECK: {{^}}@14 = private unnamed_addr constant [5 x i8] c"hello", align 1{{$}} // CHECK: {{^}}@36 = private unnamed_addr constant [5 x i8] c"world", align 1{{$}} -// CHECK: {{^}}@38 = private unnamed_addr constant [42 x i8] c"{{.*}}/cl/_testgo/invoke.I", align 1{{$}} -// CHECK: {{^}}@40 = private unnamed_addr constant [3 x i8] c"any", align 1{{$}} -// CHECK: {{^}}@41 = private unnamed_addr constant [23 x i8] c"interface{Invoke() int}", align 1{{$}} type T struct { s string @@ -445,28 +442,31 @@ type M interface { // CHECK-NEXT: br i1 %85, label %_llgo_3, label %_llgo_4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %78, %"{{.*}}/runtime/internal/runtime.String" { ptr @38, i64 42 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 6 }) +// CHECK-NEXT: %86 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %78, ptr @"_llgo_{{.*}}/cl/_testgo/invoke.I", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %86) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_1 -// CHECK-NEXT: %86 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %77, 0 -// CHECK-NEXT: %87 = call i1 @"{{.*}}/runtime/internal/runtime.Implements"(ptr @"_llgo_iface$uRUteI7wmSy7y7ODhGzk0FdDaxGKMhVSSu6HZEv9aa0", ptr %86) -// CHECK-NEXT: br i1 %87, label %_llgo_5, label %_llgo_6 +// CHECK-NEXT: %87 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %77, 0 +// CHECK-NEXT: %88 = call i1 @"{{.*}}/runtime/internal/runtime.Implements"(ptr @"_llgo_iface$uRUteI7wmSy7y7ODhGzk0FdDaxGKMhVSSu6HZEv9aa0", ptr %87) +// CHECK-NEXT: br i1 %88, label %_llgo_5, label %_llgo_6 // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_1 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %84, %"{{.*}}/runtime/internal/runtime.String" { ptr @40, i64 3 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %89 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %84, ptr @_llgo_any, ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %89) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_3 -// CHECK-NEXT: %88 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %77, 1 -// CHECK-NEXT: %89 = call ptr @"{{.*}}/runtime/internal/runtime.NewItab"(ptr @"_llgo_iface$uRUteI7wmSy7y7ODhGzk0FdDaxGKMhVSSu6HZEv9aa0", ptr %86) -// CHECK-NEXT: %90 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" undef, ptr %89, 0 -// CHECK-NEXT: %91 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" %90, ptr %88, 1 -// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/invoke.invoke"(%"{{.*}}/runtime/internal/runtime.iface" %91) +// CHECK-NEXT: %90 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %77, 1 +// CHECK-NEXT: %91 = call ptr @"{{.*}}/runtime/internal/runtime.NewItab"(ptr @"_llgo_iface$uRUteI7wmSy7y7ODhGzk0FdDaxGKMhVSSu6HZEv9aa0", ptr %87) +// CHECK-NEXT: %92 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" undef, ptr %91, 0 +// CHECK-NEXT: %93 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" %92, ptr %90, 1 +// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/invoke.invoke"(%"{{.*}}/runtime/internal/runtime.iface" %93) // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_3 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %86, %"{{.*}}/runtime/internal/runtime.String" { ptr @41, i64 23 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 6 }) +// CHECK-NEXT: %94 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %87, ptr @"_llgo_iface$uRUteI7wmSy7y7ODhGzk0FdDaxGKMhVSSu6HZEv9aa0", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %94) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/reader/in.go b/cl/_testgo/reader/in.go index 5aa0a21155..b80ee91a76 100644 --- a/cl/_testgo/reader/in.go +++ b/cl/_testgo/reader/in.go @@ -11,15 +11,14 @@ import ( // CHECK: @29 = private unnamed_addr constant [11 x i8] c"short write", align 1 // CHECK: @30 = private unnamed_addr constant [11 x i8] c"hello world", align 1 // CHECK: @53 = private unnamed_addr constant [50 x i8] c"{{.*}}/cl/_testgo/reader.nopCloser", align 1 -// CHECK: @54 = private unnamed_addr constant [49 x i8] c"{{.*}}/cl/_testgo/reader.WriterTo", align 1 -// CHECK: @55 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/reader.nopCloserWriterTo", align 1 -// CHECK: @56 = private unnamed_addr constant [37 x i8] c"stringsReader.ReadAt: negative offset", align 1 -// CHECK: @57 = private unnamed_addr constant [34 x i8] c"stringsReader.Seek: invalid whence", align 1 -// CHECK: @58 = private unnamed_addr constant [37 x i8] c"stringsReader.Seek: negative position", align 1 -// CHECK: @59 = private unnamed_addr constant [48 x i8] c"stringsReader.UnreadByte: at beginning of string", align 1 -// CHECK: @60 = private unnamed_addr constant [49 x i8] c"strings.Reader.UnreadRune: at beginning of string", align 1 -// CHECK: @61 = private unnamed_addr constant [62 x i8] c"strings.Reader.UnreadRune: previous operation was not ReadRune", align 1 -// CHECK: @62 = private unnamed_addr constant [48 x i8] c"stringsReader.WriteTo: invalid WriteString count", align 1 +// CHECK: @54 = private unnamed_addr constant [58 x i8] c"{{.*}}/cl/_testgo/reader.nopCloserWriterTo", align 1 +// CHECK: @55 = private unnamed_addr constant [37 x i8] c"stringsReader.ReadAt: negative offset", align 1 +// CHECK: @56 = private unnamed_addr constant [34 x i8] c"stringsReader.Seek: invalid whence", align 1 +// CHECK: @57 = private unnamed_addr constant [37 x i8] c"stringsReader.Seek: negative position", align 1 +// CHECK: @58 = private unnamed_addr constant [48 x i8] c"stringsReader.UnreadByte: at beginning of string", align 1 +// CHECK: @59 = private unnamed_addr constant [49 x i8] c"strings.Reader.UnreadRune: at beginning of string", align 1 +// CHECK: @60 = private unnamed_addr constant [62 x i8] c"strings.Reader.UnreadRune: previous operation was not ReadRune", align 1 +// CHECK: @61 = private unnamed_addr constant [48 x i8] c"stringsReader.WriteTo: invalid WriteString count", align 1 type Reader interface { Read(p []byte) (n int, err error) @@ -689,14 +688,15 @@ func main() { // CHECK-NEXT: ret { i64, %"{{.*}}/runtime/internal/runtime.iface" } %23 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %5, %"{{.*}}/runtime/internal/runtime.String" { ptr @54, i64 49 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 7 }) +// CHECK-NEXT: %24 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %5, ptr @"_llgo_{{.*}}/cl/_testgo/reader.WriterTo", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %24) // CHECK-NEXT: unreachable // CHECK-NEXT: } // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.(*nopCloserWriterTo).Close"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %1 = icmp eq ptr %0, null -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @55, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @17, i64 5 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @54, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @17, i64 5 }) // CHECK-NEXT: %2 = load %"{{.*}}/cl/_testgo/reader.nopCloserWriterTo", ptr %0, align 8 // CHECK-NEXT: %3 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.nopCloserWriterTo.Close"(%"{{.*}}/cl/_testgo/reader.nopCloserWriterTo" %2) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %3 @@ -725,7 +725,7 @@ func main() { // CHECK-LABEL: define { i64, %"{{.*}}/runtime/internal/runtime.iface" } @"{{.*}}/cl/_testgo/reader.(*nopCloserWriterTo).WriteTo"(ptr %0, %"{{.*}}/runtime/internal/runtime.iface" %1){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %2 = icmp eq ptr %0, null -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @55, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 7 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @54, i64 58 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 7 }) // CHECK-NEXT: %3 = load %"{{.*}}/cl/_testgo/reader.nopCloserWriterTo", ptr %0, align 8 // CHECK-NEXT: %4 = call { i64, %"{{.*}}/runtime/internal/runtime.iface" } @"{{.*}}/cl/_testgo/reader.nopCloserWriterTo.WriteTo"(%"{{.*}}/cl/_testgo/reader.nopCloserWriterTo" %3, %"{{.*}}/runtime/internal/runtime.iface" %1) // CHECK-NEXT: %5 = extractvalue { i64, %"{{.*}}/runtime/internal/runtime.iface" } %4, 0 @@ -801,7 +801,7 @@ func main() { // CHECK-NEXT: br i1 %3, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 -// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @56, i64 37 }) +// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @55, i64 37 }) // CHECK-NEXT: %5 = insertvalue { i64, %"{{.*}}/runtime/internal/runtime.iface" } { i64 0, %"{{.*}}/runtime/internal/runtime.iface" undef }, %"{{.*}}/runtime/internal/runtime.iface" %4, 1 // CHECK-NEXT: ret { i64, %"{{.*}}/runtime/internal/runtime.iface" } %5 // CHECK-EMPTY: @@ -987,12 +987,12 @@ func main() { // CHECK-NEXT: br i1 %15, label %_llgo_5, label %_llgo_7 // CHECK-EMPTY: // CHECK-NEXT: _llgo_7: ; preds = %_llgo_6 -// CHECK-NEXT: %16 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @57, i64 34 }) +// CHECK-NEXT: %16 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @56, i64 34 }) // CHECK-NEXT: %17 = insertvalue { i64, %"{{.*}}/runtime/internal/runtime.iface" } { i64 0, %"{{.*}}/runtime/internal/runtime.iface" undef }, %"{{.*}}/runtime/internal/runtime.iface" %16, 1 // CHECK-NEXT: ret { i64, %"{{.*}}/runtime/internal/runtime.iface" } %17 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_1 -// CHECK-NEXT: %18 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @58, i64 37 }) +// CHECK-NEXT: %18 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @57, i64 37 }) // CHECK-NEXT: %19 = insertvalue { i64, %"{{.*}}/runtime/internal/runtime.iface" } { i64 0, %"{{.*}}/runtime/internal/runtime.iface" undef }, %"{{.*}}/runtime/internal/runtime.iface" %18, 1 // CHECK-NEXT: ret { i64, %"{{.*}}/runtime/internal/runtime.iface" } %19 // CHECK-EMPTY: @@ -1020,7 +1020,7 @@ func main() { // CHECK-NEXT: br i1 %3, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 -// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @59, i64 48 }) +// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @58, i64 48 }) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 @@ -1042,7 +1042,7 @@ func main() { // CHECK-NEXT: br i1 %3, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 -// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @60, i64 49 }) +// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @59, i64 49 }) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 @@ -1052,7 +1052,7 @@ func main() { // CHECK-NEXT: br i1 %7, label %_llgo_3, label %_llgo_4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_2 -// CHECK-NEXT: %8 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @61, i64 62 }) +// CHECK-NEXT: %8 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/reader.newError"(%"{{.*}}/runtime/internal/runtime.String" { ptr @60, i64 62 }) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %8 // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_2 @@ -1096,7 +1096,7 @@ func main() { // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_2 // CHECK-NEXT: %20 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @62, i64 48 }, ptr %20, align 8 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @61, i64 48 }, ptr %20, align 8 // CHECK-NEXT: %21 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %20, 1 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %21) // CHECK-NEXT: unreachable diff --git a/cl/_testgo/recoverthenpanic/in.go b/cl/_testgo/recoverthenpanic/in.go index bee54a59bb..1f5b5e198a 100644 --- a/cl/_testgo/recoverthenpanic/in.go +++ b/cl/_testgo/recoverthenpanic/in.go @@ -7,7 +7,7 @@ package main // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/recoverthenpanic.End"(){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %0 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.Recover"() +// CHECK-NEXT: %0 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.Recover"(ptr @"{{.*}}/cl/_testgo/recoverthenpanic.End") // CHECK-NEXT: %1 = call i1 @"{{.*}}/runtime/internal/runtime.EfaceEqual"(%"{{.*}}/runtime/internal/runtime.eface" %0, %"{{.*}}/runtime/internal/runtime.eface" zeroinitializer) // CHECK-NEXT: %2 = xor i1 %1, true // CHECK-NEXT: %3 = call ptr @"{{.*}}/runtime/internal/runtime.GetThreadDefer"() @@ -161,26 +161,28 @@ func main() { // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5 // CHECK-NEXT: store ptr blockaddress(@"{{.*}}/cl/_testgo/recoverthenpanic.main", %_llgo_3), ptr %8, align 8 // CHECK-NEXT: %13 = load i64, ptr %7, align 8 +// CHECK-NEXT: %14 = call ptr @"{{.*}}/runtime/internal/runtime.StartRecoverFrame"(ptr @"{{.*}}/cl/_testgo/recoverthenpanic.End") // CHECK-NEXT: call void @"{{.*}}/cl/_testgo/recoverthenpanic.End"() -// CHECK-NEXT: %14 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, align 8 -// CHECK-NEXT: %15 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %14, 2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %15) -// CHECK-NEXT: %16 = load ptr, ptr %9, align 8 -// CHECK-NEXT: indirectbr ptr %16, [label %_llgo_3] +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.EndRecoverFrame"(ptr %14) +// CHECK-NEXT: %15 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, align 8 +// CHECK-NEXT: %16 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %15, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %16) +// CHECK-NEXT: %17 = load ptr, ptr %9, align 8 +// CHECK-NEXT: indirectbr ptr %17, [label %_llgo_3] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %0) // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_0 -// CHECK-NEXT: %17 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 13 }, ptr %17, align 8 -// CHECK-NEXT: %18 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %17, 1 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %18) +// CHECK-NEXT: %18 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 13 }, ptr %18, align 8 +// CHECK-NEXT: %19 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %18, 1 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %19) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 // CHECK-NEXT: store ptr blockaddress(@"{{.*}}/cl/_testgo/recoverthenpanic.main", %_llgo_3), ptr %9, align 8 -// CHECK-NEXT: %19 = load ptr, ptr %8, align 8 -// CHECK-NEXT: indirectbr ptr %19, [label %_llgo_3, label %_llgo_2] +// CHECK-NEXT: %20 = load ptr, ptr %8, align 8 +// CHECK-NEXT: indirectbr ptr %20, [label %_llgo_3, label %_llgo_2] // CHECK-NEXT: } diff --git a/cl/_testgo/reflect/in.go b/cl/_testgo/reflect/in.go index fa7b800830..f34ead84dc 100644 --- a/cl/_testgo/reflect/in.go +++ b/cl/_testgo/reflect/in.go @@ -7,7 +7,6 @@ import ( ) // CHECK: @0 = private unnamed_addr constant [11 x i8] c"call.method", align 1 -// CHECK: @2 = private unnamed_addr constant [3 x i8] c"int", align 1 // CHECK: @6 = private unnamed_addr constant [7 x i8] c"closure", align 1 // CHECK: @7 = private unnamed_addr constant [5 x i8] c"error", align 1 // CHECK: @9 = private unnamed_addr constant [12 x i8] c"call.closure", align 1 @@ -724,7 +723,8 @@ func callMethod() { // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %22, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %37 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %22, ptr @_llgo_int, ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %37) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testgo/tpinst/main.go b/cl/_testgo/tpinst/main.go index 7e87b9680e..388900ecd4 100644 --- a/cl/_testgo/tpinst/main.go +++ b/cl/_testgo/tpinst/main.go @@ -4,8 +4,6 @@ package main // CHECK-NOT: @6 = private unnamed_addr constant [5 x i8] c"value", align 1 // CHECK: @6 = private unnamed_addr constant [46 x i8] c"{{.*}}/cl/_testgo/tpinst.value", align 1 // CHECK: {{^}}@8 = private unnamed_addr constant [5 x i8] c"error", align 1{{$}} -// CHECK: {{^}}@15 = private unnamed_addr constant [22 x i8] c"interface{value() int}", align 1{{$}} -// CHECK: {{^}}@16 = private unnamed_addr constant [5 x i8] c"value", align 1{{$}} type M[T interface{}] struct { v T @@ -101,7 +99,8 @@ type I[T interface{}] interface { // CHECK-NEXT: br i1 %51, label %_llgo_5, label %_llgo_6 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_4 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %34, %"{{.*}}/runtime/internal/runtime.String" { ptr @15, i64 22 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @16, i64 5 }) +// CHECK-NEXT: %52 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %34, ptr @"{{.*}}/cl/_testgo/tpinst.iface$2sV9fFeqOv1SzesvwIdhTqCFzDT8ZX5buKUSAoHNSww", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %52) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testrt/any/in.go b/cl/_testrt/any/in.go index c498abc180..c09ae66398 100644 --- a/cl/_testrt/any/in.go +++ b/cl/_testrt/any/in.go @@ -5,10 +5,8 @@ import ( "github.com/goplus/lib/c" ) -// CHECK: @1 = private unnamed_addr constant [29 x i8] c"*github.com/goplus/lib/c.Char", align 1 -// CHECK: @2 = private unnamed_addr constant [3 x i8] c"int", align 1 -// CHECK: @3 = private unnamed_addr constant [7 x i8] c"%s %d\0A\00", align 1 -// CHECK: @4 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1 +// CHECK: @2 = private unnamed_addr constant [7 x i8] c"%s %d\0A\00", align 1 +// CHECK: @3 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1 // CHECK-LABEL: define ptr @"{{.*}}/cl/_testrt/any.hi"(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { // CHECK-NEXT: _llgo_0: @@ -21,7 +19,8 @@ import ( // CHECK-NEXT: ret ptr %3 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 29 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %1, ptr @"*_llgo_int8", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %4) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -42,7 +41,8 @@ func hi(a any) *c.Char { // CHECK-NEXT: ret i64 %5 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 3 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %6 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %1, ptr @_llgo_int, ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %6) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -69,12 +69,12 @@ func main() { // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/any.main"(){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %0 = call ptr @"{{.*}}/cl/_testrt/any.hi"(%"{{.*}}/runtime/internal/runtime.eface" { ptr @"*_llgo_int8", ptr @4 }) +// CHECK-NEXT: %0 = call ptr @"{{.*}}/cl/_testrt/any.hi"(%"{{.*}}/runtime/internal/runtime.eface" { ptr @"*_llgo_int8", ptr @3 }) // CHECK-NEXT: %1 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) // CHECK-NEXT: store i64 100, ptr %1, align 8 // CHECK-NEXT: %2 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %1, 1 // CHECK-NEXT: %3 = call i64 @"{{.*}}/cl/_testrt/any.incVal"(%"{{.*}}/runtime/internal/runtime.eface" %2) -// CHECK-NEXT: %4 = call i32 (ptr, ...) @printf(ptr @3, ptr %0, i64 %3) +// CHECK-NEXT: %4 = call i32 (ptr, ...) @printf(ptr @2, ptr %0, i64 %3) // CHECK-NEXT: ret void // CHECK-NEXT: } diff --git a/cl/_testrt/any/out.ll b/cl/_testrt/any/out.ll new file mode 100644 index 0000000000..3c251678be --- /dev/null +++ b/cl/_testrt/any/out.ll @@ -0,0 +1,136 @@ +; ModuleID = 'github.com/goplus/llgo/cl/_testrt/any' +source_filename = "github.com/goplus/llgo/cl/_testrt/any" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32" +target triple = "arm64-apple-macosx" + +%"github.com/goplus/llgo/runtime/abi.PtrType" = type { %"github.com/goplus/llgo/runtime/abi.Type", ptr } +%"github.com/goplus/llgo/runtime/abi.Type" = type { i64, i64, i32, i8, i8, i8, i8, { ptr, ptr }, ptr, %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr } +%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 } +%"github.com/goplus/llgo/runtime/abi.InterfaceType" = type { %"github.com/goplus/llgo/runtime/abi.Type", %"github.com/goplus/llgo/runtime/internal/runtime.String", %"github.com/goplus/llgo/runtime/internal/runtime.Slice" } +%"github.com/goplus/llgo/runtime/internal/runtime.Slice" = type { ptr, i64, i64 } +%"github.com/goplus/llgo/runtime/internal/runtime.eface" = type { ptr, ptr } + +@"github.com/goplus/llgo/cl/_testrt/any.init$guard" = global i1 false, align 1 +@"*_llgo_int8" = weak_odr constant %"github.com/goplus/llgo/runtime/abi.PtrType" { %"github.com/goplus/llgo/runtime/abi.Type" { i64 8, i64 8, i32 -1399554408, i8 10, i8 8, i8 8, i8 54, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 4 }, ptr null }, ptr @_llgo_int8 }, align 8 +@0 = private unnamed_addr constant [4 x i8] c"int8", align 1 +@_llgo_int8 = weak_odr constant %"github.com/goplus/llgo/runtime/abi.Type" { i64 1, i64 0, i32 1444672578, i8 12, i8 1, i8 1, i8 3, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 4 }, ptr @"*_llgo_int8" }, align 8 +@_llgo_any = weak_odr constant %"github.com/goplus/llgo/runtime/abi.InterfaceType" { %"github.com/goplus/llgo/runtime/abi.Type" { i64 16, i64 16, i32 1376530322, i8 0, i8 8, i8 8, i8 20, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.nilinterequal", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 12 }, ptr @"*_llgo_any" }, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @2, i64 37 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" zeroinitializer }, align 8 +@1 = private unnamed_addr constant [12 x i8] c"interface {}", align 1 +@"*_llgo_any" = weak_odr constant %"github.com/goplus/llgo/runtime/abi.PtrType" { %"github.com/goplus/llgo/runtime/abi.Type" { i64 8, i64 8, i32 1741196194, i8 10, i8 8, i8 8, i8 54, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 12 }, ptr null }, ptr @_llgo_any }, align 8 +@2 = private unnamed_addr constant [37 x i8] c"github.com/goplus/llgo/cl/_testrt/any", align 1 +@_llgo_int = weak_odr constant %"github.com/goplus/llgo/runtime/abi.Type" { i64 8, i64 0, i32 -25294021, i8 12, i8 8, i8 8, i8 2, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @3, i64 3 }, ptr @"*_llgo_int" }, align 8 +@3 = private unnamed_addr constant [3 x i8] c"int", align 1 +@"*_llgo_int" = weak_odr constant %"github.com/goplus/llgo/runtime/abi.PtrType" { %"github.com/goplus/llgo/runtime/abi.Type" { i64 8, i64 8, i32 -939606833, i8 10, i8 8, i8 8, i8 54, { ptr, ptr } { ptr @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr", ptr null }, ptr null, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @3, i64 3 }, ptr null }, ptr @_llgo_int }, align 8 +@4 = private unnamed_addr constant [7 x i8] c"%s %d\0A\00", align 1 +@5 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1 + +; Function Attrs: null_pointer_is_valid +define ptr @"github.com/goplus/llgo/cl/_testrt/any.hi"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %0) #0 { +_llgo_0: + %1 = extractvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %0, 0 + %2 = icmp eq ptr %1, @"*_llgo_int8" + br i1 %2, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %3 = extractvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %0, 1 + ret ptr %3 + +_llgo_2: ; preds = %_llgo_0 + %4 = call %"github.com/goplus/llgo/runtime/internal/runtime.eface" @"github.com/goplus/llgo/runtime/internal/runtime.TypeAssertError"(ptr %1, ptr @"*_llgo_int8", ptr @_llgo_any) + call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %4) + unreachable +} + +; Function Attrs: null_pointer_is_valid +define i64 @"github.com/goplus/llgo/cl/_testrt/any.incVal"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %0) #0 { +_llgo_0: + %1 = extractvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %0, 0 + %2 = icmp eq ptr %1, @_llgo_int + br i1 %2, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %3 = extractvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %0, 1 + %4 = load i64, ptr %3, align 8 + %5 = add i64 %4, 1 + ret i64 %5 + +_llgo_2: ; preds = %_llgo_0 + %6 = call %"github.com/goplus/llgo/runtime/internal/runtime.eface" @"github.com/goplus/llgo/runtime/internal/runtime.TypeAssertError"(ptr %1, ptr @_llgo_int, ptr @_llgo_any) + call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %6) + unreachable +} + +; Function Attrs: null_pointer_is_valid +define void @"github.com/goplus/llgo/cl/_testrt/any.init"() #0 { +_llgo_0: + %0 = load i1, ptr @"github.com/goplus/llgo/cl/_testrt/any.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"github.com/goplus/llgo/cl/_testrt/any.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +; Function Attrs: null_pointer_is_valid +define void @"github.com/goplus/llgo/cl/_testrt/any.main"() #0 { +_llgo_0: + %0 = call ptr @"github.com/goplus/llgo/cl/_testrt/any.hi"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" { ptr @"*_llgo_int8", ptr @5 }) + %1 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i64 100, ptr %1, align 8 + %2 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %1, 1 + %3 = call i64 @"github.com/goplus/llgo/cl/_testrt/any.incVal"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %2) + %4 = call i32 (ptr, ...) @printf(ptr @4, ptr %0, i64 %3) + ret void +} + +; Function Attrs: null_pointer_is_valid +declare i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequalptr"(ptr, ptr) #0 + +define linkonce i1 @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr"(ptr %0, ptr %1, ptr %2) { +_llgo_0: + %3 = tail call i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequalptr"(ptr %1, ptr %2) + ret i1 %3 +} + +; Function Attrs: null_pointer_is_valid +declare i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequal8"(ptr, ptr) #0 + +define linkonce i1 @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8"(ptr %0, ptr %1, ptr %2) { +_llgo_0: + %3 = tail call i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequal8"(ptr %1, ptr %2) + ret i1 %3 +} + +; Function Attrs: null_pointer_is_valid +declare %"github.com/goplus/llgo/runtime/internal/runtime.eface" @"github.com/goplus/llgo/runtime/internal/runtime.TypeAssertError"(ptr, ptr, ptr) #0 + +; Function Attrs: null_pointer_is_valid +declare i1 @"github.com/goplus/llgo/runtime/internal/runtime.nilinterequal"(ptr, ptr) #0 + +define linkonce i1 @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.nilinterequal"(ptr %0, ptr %1, ptr %2) { +_llgo_0: + %3 = tail call i1 @"github.com/goplus/llgo/runtime/internal/runtime.nilinterequal"(ptr %1, ptr %2) + ret i1 %3 +} + +; Function Attrs: null_pointer_is_valid +declare void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface") #0 + +; Function Attrs: null_pointer_is_valid +declare i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequal64"(ptr, ptr) #0 + +define linkonce i1 @"__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64"(ptr %0, ptr %1, ptr %2) { +_llgo_0: + %3 = tail call i1 @"github.com/goplus/llgo/runtime/internal/runtime.memequal64"(ptr %1, ptr %2) + ret i1 %3 +} + +; Function Attrs: null_pointer_is_valid +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) #0 + +declare i32 @printf(ptr, ...) + +attributes #0 = { null_pointer_is_valid "frame-pointer"="non-leaf" } diff --git a/cl/_testrt/funcdecl/in.go b/cl/_testrt/funcdecl/in.go index 45f6f0cc67..d3d7632a27 100644 --- a/cl/_testrt/funcdecl/in.go +++ b/cl/_testrt/funcdecl/in.go @@ -5,9 +5,8 @@ import ( "unsafe" ) -// CHECK: @4 = private unnamed_addr constant [39 x i8] c"struct{$f func(); $data unsafe.Pointer}", align 1 -// CHECK: @5 = private unnamed_addr constant [4 x i8] c"demo", align 1 -// CHECK: @6 = private unnamed_addr constant [5 x i8] c"hello", align 1 +// CHECK: @4 = private unnamed_addr constant [4 x i8] c"demo", align 1 +// CHECK: @5 = private unnamed_addr constant [5 x i8] c"hello", align 1 // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/funcdecl.check"({ ptr, ptr } %0){{.*}} { // CHECK-NEXT: _llgo_0: @@ -29,36 +28,38 @@ import ( // CHECK-NEXT: br i1 %10, label %_llgo_3, label %_llgo_4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %5, %"{{.*}}/runtime/internal/runtime.String" { ptr @4, i64 39 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %11 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %5, ptr @"_llgo_closure$b7Su1hWaFih-M0M9hMk6nO_RD1K_GQu5WjIXQp6Q2e8", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %11) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_1 -// CHECK-NEXT: %11 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %4, 1 -// CHECK-NEXT: %12 = load { ptr, ptr }, ptr %11, align 8 +// CHECK-NEXT: %12 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %4, 1 +// CHECK-NEXT: %13 = load { ptr, ptr }, ptr %12, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintEface"(%"{{.*}}/runtime/internal/runtime.eface" %2) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintEface"(%"{{.*}}/runtime/internal/runtime.eface" %4) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: %13 = extractvalue { ptr, ptr } %0, 0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintPointer"(ptr %13) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: %14 = extractvalue { ptr, ptr } %8, 0 +// CHECK-NEXT: %14 = extractvalue { ptr, ptr } %0, 0 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintPointer"(ptr %14) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: %15 = extractvalue { ptr, ptr } %12, 0 +// CHECK-NEXT: %15 = extractvalue { ptr, ptr } %8, 0 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintPointer"(ptr %15) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// CHECK-NEXT: %16 = extractvalue { ptr, ptr } %13, 0 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintPointer"(ptr %16) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintPointer"(ptr @"{{.*}}/cl/_testrt/funcdecl.demo") // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: %16 = call ptr @"{{.*}}/cl/_testrt/funcdecl.closurePtr"(%"{{.*}}/runtime/internal/runtime.eface" %2) -// CHECK-NEXT: %17 = call ptr @"{{.*}}/cl/_testrt/funcdecl.closurePtr"(%"{{.*}}/runtime/internal/runtime.eface" %4) -// CHECK-NEXT: %18 = icmp eq ptr %16, %17 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %18) +// CHECK-NEXT: %17 = call ptr @"{{.*}}/cl/_testrt/funcdecl.closurePtr"(%"{{.*}}/runtime/internal/runtime.eface" %2) +// CHECK-NEXT: %18 = call ptr @"{{.*}}/cl/_testrt/funcdecl.closurePtr"(%"{{.*}}/runtime/internal/runtime.eface" %4) +// CHECK-NEXT: %19 = icmp eq ptr %17, %18 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %19) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_1 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %9, %"{{.*}}/runtime/internal/runtime.String" { ptr @4, i64 39 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %20 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %9, ptr @"_llgo_closure$b7Su1hWaFih-M0M9hMk6nO_RD1K_GQu5WjIXQp6Q2e8", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %20) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -96,7 +97,7 @@ type rtype struct { // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/funcdecl.demo"(){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 4 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @4, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } @@ -120,7 +121,7 @@ func demo() { // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/funcdecl.main"(){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @6, i64 5 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 5 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: call void @"{{.*}}/cl/_testrt/funcdecl.check"({ ptr, ptr } { ptr @"__llgo_stub.{{.*}}/cl/_testrt/funcdecl.demo", ptr null }) // CHECK-NEXT: ret void diff --git a/cl/_testrt/makemap/in.go b/cl/_testrt/makemap/in.go index 69fce2a770..736591977f 100644 --- a/cl/_testrt/makemap/in.go +++ b/cl/_testrt/makemap/in.go @@ -8,9 +8,6 @@ package main // CHECK: @22 = private unnamed_addr constant [2 x i8] c"go", align 1 // CHECK: @23 = private unnamed_addr constant [7 x i8] c"bad key", align 1 // CHECK: @24 = private unnamed_addr constant [7 x i8] c"bad len", align 1 -// CHECK: @32 = private unnamed_addr constant [44 x i8] c"{{.*}}/cl/_testrt/makemap.N1", align 1 -// CHECK: @39 = private unnamed_addr constant [43 x i8] c"{{.*}}/cl/_testrt/makemap.K", align 1 -// CHECK: @42 = private unnamed_addr constant [44 x i8] c"{{.*}}/cl/_testrt/makemap.K2", align 1 func main() { make1() @@ -377,7 +374,8 @@ type N1 [1]int // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %39, %"{{.*}}/runtime/internal/runtime.String" { ptr @32, i64 44 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %54 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %39, ptr @"_llgo_{{.*}}/cl/_testrt/makemap.N1", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %54) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -514,7 +512,8 @@ type K2 [1]*N // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %39, %"{{.*}}/runtime/internal/runtime.String" { ptr @39, i64 43 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %56 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %39, ptr @"_llgo_{{.*}}/cl/_testrt/makemap.K", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %56) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -646,7 +645,8 @@ func make3() { // CHECK-NEXT: br label %_llgo_1 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %43, %"{{.*}}/runtime/internal/runtime.String" { ptr @42, i64 44 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %61 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %43, ptr @"_llgo_{{.*}}/cl/_testrt/makemap.K2", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %61) // CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/cl/_testrt/slice2array/in.go b/cl/_testrt/slice2array/in.go index 78af725c11..5c2a7b1438 100644 --- a/cl/_testrt/slice2array/in.go +++ b/cl/_testrt/slice2array/in.go @@ -1,6 +1,26 @@ // LITTEST package main +func main() { + array := [4]byte{1, 2, 3, 4} + ptr := (*[4]byte)(array[:]) + println(array == *ptr) + println(*(*[2]byte)(array[:]) == [2]byte{1, 2}) +} + +// CHECK-LABEL: define void @"{{.*}}/cl/_testrt/slice2array.init"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testrt/slice2array.init$guard", align 1 +// CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 +// CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testrt/slice2array.init$guard", align 1 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 +// CHECK-NEXT: ret void +// CHECK-NEXT: } + // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/slice2array.main"(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 4) @@ -80,9 +100,3 @@ package main // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } -func main() { - array := [4]byte{1, 2, 3, 4} - ptr := (*[4]byte)(array[:]) - println(array == *ptr) - println(*(*[2]byte)(array[:]) == [2]byte{1, 2}) -} diff --git a/cl/_testrt/tpabi/in.go b/cl/_testrt/tpabi/in.go index ca04d5219b..cf003eb199 100644 --- a/cl/_testrt/tpabi/in.go +++ b/cl/_testrt/tpabi/in.go @@ -5,8 +5,8 @@ import "github.com/goplus/lib/c" // CHECK: @0 = private unnamed_addr constant [1 x i8] c"a", align 1 // CHECK: @5 = private unnamed_addr constant [4 x i8] c"Info", align 1 -// CHECK: @10 = private unnamed_addr constant [54 x i8] c"{{.*}}/cl/_testrt/tpabi.T[string, int]", align 1 -// CHECK: @11 = private unnamed_addr constant [5 x i8] c"hello", align 1 +// CHECK: @10 = private unnamed_addr constant [5 x i8] c"hello", align 1 +// CHECK: @12 = private unnamed_addr constant [54 x i8] c"{{.*}}/cl/_testrt/tpabi.T[string, int]", align 1 // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/tpabi.init"(){{.*}} { // CHECK-NEXT: _llgo_0: @@ -70,7 +70,7 @@ func (t *K[N]) Advance(n int) *K[N] { // CHECK-NEXT: %11 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/cl/_testrt/tpabi.T[string,int]", ptr %11, i32 0, i32 0 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/cl/_testrt/tpabi.T[string,int]", ptr %11, i32 0, i32 1 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @11, i64 5 }, ptr %12, align 8 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @10, i64 5 }, ptr %12, align 8 // CHECK-NEXT: store i64 100, ptr %13, align 8 // CHECK-NEXT: %14 = call ptr @"{{.*}}/runtime/internal/runtime.NewItab"(ptr @"_llgo_iface$BP0p_lUsEd-IbbtJVukGmgrdQkqzcoYzSiwgUvgFvUs", ptr @"*_llgo_{{.*}}/cl/_testrt/tpabi.T[string,int]") // CHECK-NEXT: %15 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" undef, ptr %14, 0 @@ -102,7 +102,8 @@ func (t *K[N]) Advance(n int) *K[N] { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %6, %"{{.*}}/runtime/internal/runtime.String" { ptr @10, i64 54 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %32 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %6, ptr @"_llgo_{{.*}}/cl/_testrt/tpabi.T[string,int]", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %32) // CHECK-NEXT: unreachable // CHECK-NEXT: } @@ -149,7 +150,7 @@ func main() { // CHECK-LABEL: define linkonce void @"{{.*}}/cl/_testrt/tpabi.(*T[string,int]).Info"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %1 = icmp eq ptr %0, null -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @10, i64 54 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 4 }) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicWrapNilPointer"(i1 %1, %"{{.*}}/runtime/internal/runtime.String" { ptr @12, i64 54 }, %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 4 }) // CHECK-NEXT: %2 = load %"{{.*}}/cl/_testrt/tpabi.T[string,int]", ptr %0, align 8 // CHECK-NEXT: call void @"{{.*}}/cl/_testrt/tpabi.T[string,int].Info"(%"{{.*}}/cl/_testrt/tpabi.T[string,int]" %2) // CHECK-NEXT: ret void diff --git a/cl/_testrt/typed/in.go b/cl/_testrt/typed/in.go index 1444e36c60..49a2468ab6 100644 --- a/cl/_testrt/typed/in.go +++ b/cl/_testrt/typed/in.go @@ -2,7 +2,6 @@ package main // CHECK: @0 = private unnamed_addr constant [5 x i8] c"hello", align 1 -// CHECK: @3 = private unnamed_addr constant [41 x i8] c"{{.*}}/cl/_testrt/typed.T", align 1 // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/typed.init"(){{.*}} { // CHECK-NEXT: _llgo_0: @@ -39,67 +38,68 @@ type A [2]int // CHECK-NEXT: br i1 %7, label %_llgo_3, label %_llgo_4 // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicTypeAssert"(ptr %2, %"{{.*}}/runtime/internal/runtime.String" { ptr @3, i64 41 }, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// CHECK-NEXT: %8 = call %"{{.*}}/runtime/internal/runtime.eface" @"{{.*}}/runtime/internal/runtime.TypeAssertError"(ptr %2, ptr @"_llgo_{{.*}}/cl/_testrt/typed.T", ptr null) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %8) // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_1 -// CHECK-NEXT: %8 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 1 -// CHECK-NEXT: %9 = load %"{{.*}}/runtime/internal/runtime.String", ptr %8, align 8 -// CHECK-NEXT: %10 = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } undef, %"{{.*}}/runtime/internal/runtime.String" %9, 0 -// CHECK-NEXT: %11 = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %10, i1 true, 1 +// CHECK-NEXT: %9 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 1 +// CHECK-NEXT: %10 = load %"{{.*}}/runtime/internal/runtime.String", ptr %9, align 8 +// CHECK-NEXT: %11 = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } undef, %"{{.*}}/runtime/internal/runtime.String" %10, 0 +// CHECK-NEXT: %12 = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %11, i1 true, 1 // CHECK-NEXT: br label %_llgo_5 // CHECK-EMPTY: // CHECK-NEXT: _llgo_4: ; preds = %_llgo_1 // CHECK-NEXT: br label %_llgo_5 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_4, %_llgo_3 -// CHECK-NEXT: %12 = phi { %"{{.*}}/runtime/internal/runtime.String", i1 } [ %11, %_llgo_3 ], [ zeroinitializer, %_llgo_4 ] -// CHECK-NEXT: %13 = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %12, 0 -// CHECK-NEXT: %14 = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %12, 1 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %13) +// CHECK-NEXT: %13 = phi { %"{{.*}}/runtime/internal/runtime.String", i1 } [ %12, %_llgo_3 ], [ zeroinitializer, %_llgo_4 ] +// CHECK-NEXT: %14 = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %13, 0 +// CHECK-NEXT: %15 = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %13, 1 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %14) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %14) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %15) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: %15 = alloca [2 x i64], align 8 -// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %15, i8 0, i64 16, i1 false) -// CHECK-NEXT: %16 = getelementptr inbounds i64, ptr %15, i64 0 -// CHECK-NEXT: %17 = getelementptr inbounds i64, ptr %15, i64 1 -// CHECK-NEXT: store i64 1, ptr %16, align 8 -// CHECK-NEXT: store i64 2, ptr %17, align 8 -// CHECK-NEXT: %18 = load [2 x i64], ptr %15, align 8 -// CHECK-NEXT: %19 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) -// CHECK-NEXT: store [2 x i64] %18, ptr %19, align 8 -// CHECK-NEXT: %20 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @"_llgo_{{.*}}/cl/_testrt/typed.A", ptr undef }, ptr %19, 1 -// CHECK-NEXT: %21 = alloca [2 x i64], align 8 -// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %21, i8 0, i64 16, i1 false) -// CHECK-NEXT: %22 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %20, 0 -// CHECK-NEXT: %23 = icmp eq ptr %22, @"_llgo_{{.*}}/cl/_testrt/typed.A" -// CHECK-NEXT: br i1 %23, label %_llgo_6, label %_llgo_7 +// CHECK-NEXT: %16 = alloca [2 x i64], align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %16, i8 0, i64 16, i1 false) +// CHECK-NEXT: %17 = getelementptr inbounds i64, ptr %16, i64 0 +// CHECK-NEXT: %18 = getelementptr inbounds i64, ptr %16, i64 1 +// CHECK-NEXT: store i64 1, ptr %17, align 8 +// CHECK-NEXT: store i64 2, ptr %18, align 8 +// CHECK-NEXT: %19 = load [2 x i64], ptr %16, align 8 +// CHECK-NEXT: %20 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// CHECK-NEXT: store [2 x i64] %19, ptr %20, align 8 +// CHECK-NEXT: %21 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @"_llgo_{{.*}}/cl/_testrt/typed.A", ptr undef }, ptr %20, 1 +// CHECK-NEXT: %22 = alloca [2 x i64], align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %22, i8 0, i64 16, i1 false) +// CHECK-NEXT: %23 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %21, 0 +// CHECK-NEXT: %24 = icmp eq ptr %23, @"_llgo_{{.*}}/cl/_testrt/typed.A" +// CHECK-NEXT: br i1 %24, label %_llgo_6, label %_llgo_7 // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_5 -// CHECK-NEXT: %24 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %20, 1 -// CHECK-NEXT: %25 = load [2 x i64], ptr %24, align 8 -// CHECK-NEXT: %26 = insertvalue { [2 x i64], i1 } undef, [2 x i64] %25, 0 -// CHECK-NEXT: %27 = insertvalue { [2 x i64], i1 } %26, i1 true, 1 +// CHECK-NEXT: %25 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %21, 1 +// CHECK-NEXT: %26 = load [2 x i64], ptr %25, align 8 +// CHECK-NEXT: %27 = insertvalue { [2 x i64], i1 } undef, [2 x i64] %26, 0 +// CHECK-NEXT: %28 = insertvalue { [2 x i64], i1 } %27, i1 true, 1 // CHECK-NEXT: br label %_llgo_8 // CHECK-EMPTY: // CHECK-NEXT: _llgo_7: ; preds = %_llgo_5 // CHECK-NEXT: br label %_llgo_8 // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_7, %_llgo_6 -// CHECK-NEXT: %28 = phi { [2 x i64], i1 } [ %27, %_llgo_6 ], [ zeroinitializer, %_llgo_7 ] -// CHECK-NEXT: %29 = extractvalue { [2 x i64], i1 } %28, 0 -// CHECK-NEXT: store [2 x i64] %29, ptr %21, align 8 -// CHECK-NEXT: %30 = extractvalue { [2 x i64], i1 } %28, 1 -// CHECK-NEXT: %31 = getelementptr inbounds i64, ptr %21, i64 0 -// CHECK-NEXT: %32 = load i64, ptr %31, align 8 -// CHECK-NEXT: %33 = getelementptr inbounds i64, ptr %21, i64 1 -// CHECK-NEXT: %34 = load i64, ptr %33, align 8 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %32) +// CHECK-NEXT: %29 = phi { [2 x i64], i1 } [ %28, %_llgo_6 ], [ zeroinitializer, %_llgo_7 ] +// CHECK-NEXT: %30 = extractvalue { [2 x i64], i1 } %29, 0 +// CHECK-NEXT: store [2 x i64] %30, ptr %22, align 8 +// CHECK-NEXT: %31 = extractvalue { [2 x i64], i1 } %29, 1 +// CHECK-NEXT: %32 = getelementptr inbounds i64, ptr %22, i64 0 +// CHECK-NEXT: %33 = load i64, ptr %32, align 8 +// CHECK-NEXT: %34 = getelementptr inbounds i64, ptr %22, i64 1 +// CHECK-NEXT: %35 = load i64, ptr %34, align 8 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %33) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %34) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %35) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %30) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %31) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } diff --git a/cl/caller_frame_test.go b/cl/caller_frame_test.go index 9879a39585..f6e1d2fc40 100644 --- a/cl/caller_frame_test.go +++ b/cl/caller_frame_test.go @@ -1039,3 +1039,37 @@ func TestDirectiveFilename(t *testing.T) { t.Fatal("nil fset must pass through") } } + +// Packages that read the memory profile pin every trackable function: +// per-site heap attribution needs frames and allocation-site anchors. +func TestPackageReadsMemProfilePin(t *testing.T) { + ssapkg, _ := buildCallerFrameSSAPackage(t, "example.com/hp", `package main + +import "runtime" + +func allocLeaf() *[64]byte { return new([64]byte) } + +func plainHelper() int { return 1 } + +func main() { + runtime.MemProfileRate = 1 + _ = allocLeaf() + _ = plainHelper() + var r [4]runtime.MemProfileRecord + runtime.MemProfile(r[:], true) +} +`) + set := runtimeCallerFuncSet(NewCallerTracking(), ssapkg) + for _, name := range []string{"allocLeaf", "plainHelper", "main"} { + if !set[ssapkg.Func(name)] { + t.Fatalf("%s must be pinned in a memprofile-reading package", name) + } + } + quiet, _ := buildCallerFrameSSAPackage(t, "example.com/quiet", `package q + +func Helper() int { return 2 } +`) + if set := runtimeCallerFuncSet(NewCallerTracking(), quiet); set[quiet.Func("Helper")] { + t.Fatal("quiet package must not be pinned") + } +} diff --git a/cl/cgo_test.go b/cl/cgo_test.go index 8ba0838340..289b68a809 100644 --- a/cl/cgo_test.go +++ b/cl/cgo_test.go @@ -207,6 +207,47 @@ func findStaticCall(t *testing.T, fn *gossa.Function, name string) *gossa.Call { return nil } +func TestRecoverCallClassificationHelpers(t *testing.T) { + if functionUsesRecover(nil) { + t.Fatal("nil function should not report recover use") + } + + ssaPkg, _, _ := buildGoSSAPkg(t, ` +package foo + +func usesRecover() { + recover() +} + +func plain() {} +`) + usesRecover := ssaPkg.Members["usesRecover"].(*gossa.Function) + plain := ssaPkg.Members["plain"].(*gossa.Function) + ctx := &context{} + + if !functionUsesRecover(usesRecover) { + t.Fatal("usesRecover should report direct recover use") + } + if functionUsesRecover(plain) { + t.Fatal("plain should not report recover use") + } + if !ctx.callMayRecover(usesRecover) { + t.Fatal("function using recover should be recover-capable") + } + if ctx.callMayRecover(plain) { + t.Fatal("plain static function should not be recover-capable") + } + if !ctx.callMayRecover(&gossa.MakeClosure{}) { + t.Fatal("unknown closure target should conservatively be recover-capable") + } + if !ctx.callMayRecover(&gossa.Call{}) { + t.Fatal("function value returned by a call should conservatively be recover-capable") + } + if !ctx.callMayRecover(nil) { + t.Fatal("unknown call value should conservatively be recover-capable") + } +} + func TestCgoCgocall_InitArgsFromParams(t *testing.T) { ssaPkg, _, _ := buildGoSSAPkg(t, ` package foo diff --git a/cl/compile.go b/cl/compile.go index c38929ba69..96923acb63 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -176,9 +176,16 @@ type context struct { linkOnceFns map[*ssa.Function]none stackDefers map[*ssa.Function]bool anonDefers map[*ssa.Function]bool + stackClears map[ssa.Instruction][]*ssa.Alloc + entryClears map[*ssa.BasicBlock][]*ssa.Alloc + loadClears map[ssa.Instruction]bool + callClobbers map[ssa.Instruction]bool + paramClobbers map[ssa.Instruction]bool + paramScans map[ssa.Instruction][]*ssa.Parameter paramDIVars map[*types.Var]llssa.DIVar runtimeCallerFuncs map[*ssa.Function]bool pcLineSeq uint64 + recoverSlots map[*ssa.Alloc]none patches Patches blkInfos []blocks.Info @@ -557,12 +564,15 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun noInlineDirective := hasNoInlineDirective(f) runtimeStackNoInline := needsRuntimeStackNoInline(pkgTypes, f) pcLineNoInline := p.needsPCLineNoInline(f) - if disableInline || noInlineDirective || runtimeStackNoInline || pcLineNoInline { + if disableInline || noInlineDirective || runtimeStackNoInline || pcLineNoInline || functionUsesRecover(f) { fn.Inline(llssa.NoInline) } if noInlineDirective || runtimeStackNoInline || pcLineNoInline { fn.DisableTailCalls() } + if functionUsesRecover(f) { + fn.Expr = fn.Expr.MarkMayRecover() + } p.funcs[f] = fn isCgo := isCgoExternSymbol(f) if nblk := len(f.Blocks); nblk > 0 { @@ -599,12 +609,19 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun dbgSymsEnabled := enableDbgSyms && (f == nil || f.Origin() == nil) p.inits = append(p.inits, func() { oldFn, oldGoFn, oldMethodNilDerefChecks, oldCallerFrameMark := p.fn, p.goFn, p.methodNilDerefChecks, p.callerFrameMark + oldRecoverSlots := p.recoverSlots p.fn = fn p.goFn = f p.callerFrameMark = llssa.Nil p.state = state // restore pkgState when compiling funcBody + if f.Recover != nil { + p.recoverSlots = make(map[*ssa.Alloc]none) + } else { + p.recoverSlots = nil + } defer func() { p.fn, p.goFn, p.methodNilDerefChecks, p.callerFrameMark = oldFn, oldGoFn, oldMethodNilDerefChecks, oldCallerFrameMark + p.recoverSlots = oldRecoverSlots }() p.phis = nil if dbgSymsEnabled { @@ -622,6 +639,21 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } p.bvals = make(map[ssa.Value]llssa.Expr) p.methodNilDerefChecks = collectMethodNilDerefChecks(f) + if p.enableConservativeLivenessClears(f) { + p.stackClears = p.collectStackClearPlans(f) + p.entryClears = p.collectEntryClearPlans(f) + p.loadClears = make(map[ssa.Instruction]bool) + p.callClobbers = p.collectCallClobberPlans(f) + p.paramClobbers = p.collectParamClobberPlans(f) + p.paramScans = p.collectParamScanPlans(f) + } else { + p.stackClears = nil + p.entryClears = nil + p.loadClears = nil + p.callClobbers = nil + p.paramClobbers = nil + p.paramScans = nil + } off := make([]int, len(f.Blocks)) if isCgo { p.cgoArgs = make([]llssa.Expr, len(f.Params)) @@ -823,6 +855,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do var instrs = block.Instrs[n:] var ret = fn.Block(block.Index) b.SetBlock(ret) + p.clearEntryAllocs(b, block) if block.Index == 0 && p.shouldTrackCallerFrames() { p.pushCallerLocationFrame(b, block.Parent()) } @@ -860,6 +893,9 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC) b.Call(fnOld.Expr) } + if !(isCgoCfunc || isCgoC2 || isCgoCmacro) && p.shouldSkipLateSetFinalizerValue(instr) { + continue + } if isCgoCfunc || isCgoC2 || isCgoCmacro { switch instr := instr.(type) { case *ssa.Alloc: @@ -898,6 +934,17 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do } else { p.compileInstr(b, instr) } + if isTerminatingInstruction(instr) { + continue + } + p.clearDeadAllocs(b, instr) + if p.callClobbers[instr] { + p.clobberPointerRegs(b) + } + p.scanParamPointers(b, instr) + if p.paramClobbers[instr] { + p.clobberPointerRegs(b) + } } // is cgo cfunc but not return yet, some funcs has multiple blocks if (isCgoCfunc || isCgoC2 || isCgoCmacro) && !cgoReturned { @@ -1101,6 +1148,29 @@ func (p *context) syntheticMakeSliceCap(v *ssa.Slice) (llssa.Expr, bool) { return p.prog.IntVal(uint64(arr.Len()), p.prog.Int()), true } +func (p *context) markRecoverSlot(v *ssa.Alloc) { + if p.recoverSlots == nil || v.Heap { + return + } + p.recoverSlots[v] = none{} +} + +func (p *context) isRecoverSlotAddr(v ssa.Value) bool { + if p.recoverSlots == nil { + return false + } + switch v := v.(type) { + case *ssa.Alloc: + _, ok := p.recoverSlots[v] + return ok + case *ssa.FieldAddr: + return p.isRecoverSlotAddr(v.X) + case *ssa.IndexAddr: + return p.isRecoverSlotAddr(v.X) + } + return false +} + func isAllocVargs(ctx *context, v *ssa.Alloc) bool { refs := *v.Referrers() n := len(refs) @@ -1127,6 +1197,623 @@ func isAllocVargs(ctx *context, v *ssa.Alloc) bool { return false } +func (p *context) enableConservativeLivenessClears(fn *ssa.Function) bool { + if fn == nil || fn.Pkg == nil || fn.Pkg.Pkg == nil { + return false + } + path := fn.Pkg.Pkg.Path() + if path == "command-line-arguments" { + return p.packageUsesRuntimeSetFinalizer(fn.Pkg) + } + return false +} + +func (p *context) packageUsesRuntimeSetFinalizer(pkg *ssa.Package) bool { + for _, member := range pkg.Members { + fn, ok := member.(*ssa.Function) + if ok && p.functionUsesRuntimeSetFinalizer(fn, map[*ssa.Function]bool{}) { + return true + } + } + return false +} + +func (p *context) functionUsesRuntimeSetFinalizer(fn *ssa.Function, seen map[*ssa.Function]bool) bool { + if fn == nil || seen[fn] { + return false + } + seen[fn] = true + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.Call: + if p.isRuntimeSetFinalizerCall(&instr.Call) { + return true + } + case *ssa.Defer: + if p.isRuntimeSetFinalizerCall(&instr.Call) { + return true + } + case *ssa.Go: + if p.isRuntimeSetFinalizerCall(&instr.Call) { + return true + } + } + } + } + for _, anon := range fn.AnonFuncs { + if p.functionUsesRuntimeSetFinalizer(anon, seen) { + return true + } + } + return false +} + +func hasConservativeGCPointers(t types.Type, seen map[types.Type]bool) bool { + if t == nil { + return false + } + t = types.Unalias(t) + if seen[t] { + return false + } + seen[t] = true + switch t := t.Underlying().(type) { + case *types.Pointer, *types.Slice, *types.Map, *types.Chan, *types.Signature, *types.Interface: + return true + case *types.Basic: + return t.Kind() == types.String || t.Kind() == types.UnsafePointer + case *types.Array: + return hasConservativeGCPointers(t.Elem(), seen) + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + if hasConservativeGCPointers(t.Field(i).Type(), seen) { + return true + } + } + } + return false +} + +func (p *context) shouldClearAlloc(v *ssa.Alloc) bool { + if v == nil || v.Comment == "varargs" || v.Comment == "makeslice" { + return false + } + ptr, ok := v.Type().Underlying().(*types.Pointer) + return ok && hasConservativeGCPointers(ptr.Elem(), map[types.Type]bool{}) +} + +func blockCanReach(from, to *ssa.BasicBlock, seen map[*ssa.BasicBlock]bool) bool { + if from == nil || to == nil { + return false + } + if from == to { + return true + } + if seen[from] { + return false + } + seen[from] = true + for _, succ := range from.Succs { + if blockCanReach(succ, to, seen) { + return true + } + } + return false +} + +func refBlock(ref ssa.Instruction) *ssa.BasicBlock { + if ref == nil { + return nil + } + return ref.Block() +} + +func instructionUsesValue(instr ssa.Instruction, v ssa.Value) bool { + if instr == nil || v == nil { + return false + } + for _, operand := range instr.Operands(nil) { + if operand != nil && *operand == v { + return true + } + } + return false +} + +func isCallLikeInstruction(instr ssa.Instruction) bool { + switch instr.(type) { + case *ssa.Call, *ssa.Defer, *ssa.Go: + return true + } + return false +} + +func isTerminatingInstruction(instr ssa.Instruction) bool { + switch instr.(type) { + case *ssa.Jump, *ssa.Return, *ssa.If, *ssa.Panic: + return true + } + return false +} + +func (p *context) isRuntimeSetFinalizerCall(call *ssa.CallCommon) bool { + if call == nil { + return false + } + fn, ok := call.Value.(*ssa.Function) + if !ok || fn == nil || fn.Pkg == nil || fn.Pkg.Pkg == nil { + return false + } + return fn.Name() == "SetFinalizer" && + fn.Pkg.Pkg.Path() == "github.com/goplus/llgo/runtime/internal/lib/runtime" +} + +func (p *context) isOnlyRuntimeSetFinalizerArg(v ssa.Value) bool { + refs := v.Referrers() + if refs == nil || len(*refs) != 1 { + return false + } + call, ok := (*refs)[0].(*ssa.Call) + return ok && p.isRuntimeSetFinalizerCall(&call.Call) +} + +func (p *context) shouldSkipLateSetFinalizerValue(instr ssa.Instruction) bool { + switch instr := instr.(type) { + case *ssa.MakeInterface: + return p.isOnlyRuntimeSetFinalizerArg(instr) + case *ssa.UnOp: + if instr.Op != token.MUL { + return false + } + refs := instr.Referrers() + if refs == nil || len(*refs) != 1 { + return false + } + mi, ok := (*refs)[0].(*ssa.MakeInterface) + return ok && p.isOnlyRuntimeSetFinalizerArg(mi) + } + return false +} + +func (p *context) collectValueUseBlocks(v ssa.Value, blocks map[*ssa.BasicBlock]bool, seen map[ssa.Value]bool, followPhi bool) bool { + if v == nil || seen[v] { + return true + } + seen[v] = true + refs := v.Referrers() + if refs == nil { + return true + } + for _, ref := range *refs { + switch ref := ref.(type) { + case *ssa.DebugRef: + continue + case *ssa.FieldAddr, *ssa.IndexAddr, *ssa.ChangeType, *ssa.Convert, *ssa.MakeInterface: + refVal, ok := ref.(ssa.Value) + if !ok { + return false + } + if !p.collectValueUseBlocks(refVal, blocks, seen, followPhi) { + return false + } + case *ssa.UnOp: + if ref.Op != token.MUL || ref.X != v { + blk := refBlock(ref) + if blk == nil { + return false + } + blocks[blk] = true + continue + } + blk := refBlock(ref) + if blk == nil { + return false + } + blocks[blk] = true + if !p.collectValueUseBlocks(ref, blocks, seen, followPhi) { + return false + } + case *ssa.Phi: + if followPhi { + if !p.collectValueUseBlocks(ref, blocks, seen, followPhi) { + return false + } + continue + } + for i, edge := range ref.Edges { + if edge == v && i < len(ref.Block().Preds) { + blocks[ref.Block().Preds[i]] = true + } + } + default: + instr, ok := ref.(ssa.Instruction) + if !ok || !instructionUsesValue(instr, v) { + return false + } + blk := refBlock(instr) + if blk == nil { + return false + } + blocks[blk] = true + } + } + return true +} + +func (p *context) valueLastUseBlock(v ssa.Value) (*ssa.BasicBlock, bool) { + blocks := make(map[*ssa.BasicBlock]bool) + if !p.collectValueUseBlocks(v, blocks, map[ssa.Value]bool{}, true) { + return nil, false + } + if len(blocks) == 0 { + return nil, true + } + for candidate := range blocks { + ok := true + for blk := range blocks { + if blk != candidate && !blockCanReach(blk, candidate, map[*ssa.BasicBlock]bool{}) { + ok = false + break + } + } + if ok { + return candidate, true + } + } + return nil, false +} + +func (p *context) lastUseInBlock(v ssa.Value, blk *ssa.BasicBlock, order map[ssa.Instruction]int, seen map[ssa.Value]bool) (ssa.Instruction, bool) { + if v == nil || seen[v] { + return nil, true + } + seen[v] = true + refs := v.Referrers() + if refs == nil { + return nil, true + } + var last ssa.Instruction + updateLast := func(instr ssa.Instruction) { + if instr == nil { + return + } + if last == nil || order[instr] > order[last] { + last = instr + } + } + refBeforeBlock := func(refBlk *ssa.BasicBlock) bool { + return refBlk != nil && blk != nil && refBlk != blk && blockCanReach(refBlk, blk, map[*ssa.BasicBlock]bool{}) + } + for _, ref := range *refs { + switch ref := ref.(type) { + case *ssa.DebugRef: + continue + case *ssa.FieldAddr, *ssa.IndexAddr, *ssa.ChangeType, *ssa.Convert, *ssa.MakeInterface: + refVal := ref.(ssa.Value) + refInstr := ref.(ssa.Instruction) + if refInstr.Block() != blk { + if refBeforeBlock(refInstr.Block()) { + continue + } + return nil, false + } + use, ok := p.lastUseInBlock(refVal, blk, order, seen) + if !ok { + return nil, false + } + updateLast(use) + case *ssa.UnOp: + if ref.Op != token.MUL || ref.X != v { + if ref.Block() != blk { + if refBeforeBlock(ref.Block()) { + continue + } + return nil, false + } + updateLast(ref) + continue + } + if ref.Block() != blk { + if refBeforeBlock(ref.Block()) { + continue + } + return nil, false + } + use, ok := p.lastUseInBlock(ref, blk, order, seen) + if !ok { + return nil, false + } + if use != nil { + if isCallLikeInstruction(use) { + updateLast(ref) + continue + } + updateLast(use) + } else { + updateLast(ref) + } + case *ssa.Phi: + use, ok := p.lastUseInBlock(ref, blk, order, seen) + if !ok { + return nil, false + } + updateLast(use) + default: + instr, ok := ref.(ssa.Instruction) + if !ok || !instructionUsesValue(instr, v) { + return nil, false + } + if instr.Block() != blk { + if refBeforeBlock(instr.Block()) { + continue + } + return nil, false + } + updateLast(instr) + } + } + return last, true +} + +func (p *context) collectStackClearPlans(fn *ssa.Function) map[ssa.Instruction][]*ssa.Alloc { + plans := make(map[ssa.Instruction][]*ssa.Alloc) + for _, blk := range fn.Blocks { + for _, instr := range blk.Instrs { + alloc, ok := instr.(*ssa.Alloc) + if !ok || !p.shouldClearAlloc(alloc) { + continue + } + useBlk, ok := p.valueLastUseBlock(alloc) + if !ok || useBlk == nil { + continue + } + if useBlk != alloc.Block() && alloc.Block().Index != 0 { + continue + } + order := make(map[ssa.Instruction]int, len(useBlk.Instrs)) + for i, useInstr := range useBlk.Instrs { + order[useInstr] = i + } + last, ok := p.lastUseInBlock(alloc, useBlk, order, map[ssa.Value]bool{}) + if ok && last != nil { + plans[last] = append(plans[last], alloc) + } + } + } + return plans +} + +func (p *context) collectEntryClearPlans(fn *ssa.Function) map[*ssa.BasicBlock][]*ssa.Alloc { + plans := make(map[*ssa.BasicBlock][]*ssa.Alloc) + for _, blk := range fn.Blocks { + if blk == nil || len(blk.Succs) < 2 { + continue + } + for _, instr := range blk.Instrs { + alloc, ok := instr.(*ssa.Alloc) + if !ok || !p.shouldClearAlloc(alloc) { + continue + } + useBlocks := make(map[*ssa.BasicBlock]bool) + if !p.collectValueUseBlocks(alloc, useBlocks, map[ssa.Value]bool{}, false) { + continue + } + liveSucc := make(map[*ssa.BasicBlock]bool, len(blk.Succs)) + for _, succ := range blk.Succs { + for useBlk := range useBlocks { + if useBlk == nil { + continue + } + if succ == useBlk || blockCanReach(succ, useBlk, map[*ssa.BasicBlock]bool{}) { + liveSucc[succ] = true + break + } + } + } + if len(liveSucc) == 0 || len(liveSucc) == len(blk.Succs) { + continue + } + for _, succ := range blk.Succs { + if !liveSucc[succ] && len(succ.Preds) == 1 { + plans[succ] = append(plans[succ], alloc) + } + } + } + } + return plans +} + +func (p *context) collectParamClobberPlans(fn *ssa.Function) map[ssa.Instruction]bool { + plans := make(map[ssa.Instruction]bool) + for _, param := range fn.Params { + if !hasConservativeGCPointers(param.Type(), map[types.Type]bool{}) { + continue + } + useBlk, ok := p.valueLastUseBlock(param) + if !ok || useBlk == nil { + continue + } + order := make(map[ssa.Instruction]int, len(useBlk.Instrs)) + for i, useInstr := range useBlk.Instrs { + order[useInstr] = i + } + last, ok := p.lastUseInBlock(param, useBlk, order, map[ssa.Value]bool{}) + if ok && last != nil { + plans[last] = true + } + } + return plans +} + +func (p *context) collectParamScanPlans(fn *ssa.Function) map[ssa.Instruction][]*ssa.Parameter { + plans := make(map[ssa.Instruction][]*ssa.Parameter) + for _, param := range fn.Params { + if !hasConservativeGCPointers(param.Type(), map[types.Type]bool{}) { + continue + } + useBlk, ok := p.valueLastUseBlock(param) + if !ok || useBlk == nil { + continue + } + order := make(map[ssa.Instruction]int, len(useBlk.Instrs)) + for i, useInstr := range useBlk.Instrs { + order[useInstr] = i + } + last, ok := p.lastUseInBlock(param, useBlk, order, map[ssa.Value]bool{}) + if ok && last != nil { + plans[last] = append(plans[last], param) + } + } + return plans +} + +func (p *context) collectCallClobberPlans(fn *ssa.Function) map[ssa.Instruction]bool { + plans := make(map[ssa.Instruction]bool) + for _, blk := range fn.Blocks { + for _, instr := range blk.Instrs { + call, ok := instr.(*ssa.Call) + if !ok { + continue + } + for _, arg := range call.Common().Args { + if hasConservativeGCPointers(arg.Type(), map[types.Type]bool{}) { + plans[instr] = true + break + } + } + } + } + return plans +} + +func (p *context) compileLateValue(b llssa.Builder, v ssa.Value) llssa.Expr { + switch v := v.(type) { + case *ssa.MakeInterface: + t := p.type_(v.Type(), llssa.InGo) + x := p.compileLateValue(b, v.X) + return b.MakeInterface(t, x) + case *ssa.UnOp: + if v.Op != token.MUL { + return p.compileValue(b, v) + } + x := p.compileLateValue(b, v.X) + return b.UnOp(v.Op, x) + case *ssa.FieldAddr: + x := p.compileLateValue(b, v.X) + return b.FieldAddr(x, v.Field) + case *ssa.IndexAddr: + x := p.compileLateValue(b, v.X) + idx := p.compileLateValue(b, v.Index) + return b.IndexAddr(x, idx) + case *ssa.ChangeType: + t := p.type_(v.Type(), llssa.InGo) + x := p.compileLateValue(b, v.X) + return b.ChangeType(t, x) + case *ssa.Convert: + t := p.type_(v.Type(), llssa.InGo) + x := p.compileLateValue(b, v.X) + return b.Convert(t, x) + } + return p.compileValue(b, v) +} + +func (p *context) scanStackPointer(b llssa.Builder, val llssa.Expr) { + b.Pkg.NeedRuntime = true + t := p.type_(types.Typ[types.Uintptr], llssa.InGo) + if !types.Identical(val.RawType(), t.RawType()) { + val = b.Convert(t, val) + } + fn := b.Pkg.NewFunc("llgo_clear_stack_ptr", + types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewParam(token.NoPos, nil, "target", types.Typ[types.Uintptr])), nil, false), llssa.InC) + b.Call(fn.Expr, val) +} + +func (p *context) scanPointerExpr(b llssa.Builder, val llssa.Expr) { + switch t := types.Unalias(val.RawType()).Underlying().(type) { + case *types.Pointer: + p.scanStackPointer(b, val) + case *types.Struct: + if t.NumFields() == 1 { + if _, ok := types.Unalias(t.Field(0).Type()).Underlying().(*types.Pointer); ok { + p.scanStackPointer(b, b.Field(val, 0)) + } + } + } +} + +func (p *context) scanAllocPointer(b llssa.Builder, ptr llssa.Expr) { + elem := b.Prog.Elem(ptr.Type) + switch t := types.Unalias(elem.RawType()).Underlying().(type) { + case *types.Pointer: + p.scanStackPointer(b, b.Load(ptr)) + case *types.Struct: + if t.NumFields() == 1 { + if _, ok := types.Unalias(t.Field(0).Type()).Underlying().(*types.Pointer); ok { + p.scanStackPointer(b, b.Load(b.FieldAddr(ptr, 0))) + } + } + } +} + +func (p *context) scanParamPointers(b llssa.Builder, instr ssa.Instruction) { + params := p.paramScans[instr] + for _, param := range params { + p.scanPointerExpr(b, p.compileValue(b, param)) + } +} + +func (p *context) clearAlloc(b llssa.Builder, alloc *ssa.Alloc) { + ptr := p.compileValue(b, alloc) + b.IfThen(b.BinOp(token.NEQ, ptr, p.prog.Zero(ptr.Type)), func() { + p.scanAllocPointer(b, ptr) + elem := b.Prog.Elem(ptr.Type) + b.Store(ptr, p.prog.Zero(elem)) + }) +} + +func (p *context) clearDeadAllocs(b llssa.Builder, instr ssa.Instruction) { + if p.loadClears[instr] { + return + } + allocs := p.stackClears[instr] + if len(allocs) == 0 { + return + } + for _, alloc := range allocs { + p.clearAlloc(b, alloc) + } + if unop, ok := instr.(*ssa.UnOp); ok && unop.Op == token.MUL { + return + } + p.clobberPointerRegs(b) +} + +func (p *context) clearEntryAllocs(b llssa.Builder, block *ssa.BasicBlock) { + allocs := p.entryClears[block] + if len(allocs) == 0 { + return + } + for _, alloc := range allocs { + p.clearAlloc(b, alloc) + } + p.clobberPointerRegs(b) +} + +func (p *context) clobberPointerRegs(b llssa.Builder) { + b.Pkg.NeedRuntime = true + uintptrParam := func(name string) *types.Var { + return types.NewParam(token.NoPos, nil, name, types.Typ[types.Uintptr]) + } + fn := b.Pkg.NewFunc("llgo_clobber_pointer_regs", + types.NewSignatureType(nil, nil, nil, types.NewTuple( + uintptrParam("a0"), uintptrParam("a1"), uintptrParam("a2"), uintptrParam("a3"), + uintptrParam("a4"), uintptrParam("a5"), uintptrParam("a6"), uintptrParam("a7"), + ), nil, false), llssa.InC) + zero := b.Prog.IntVal(0, b.Prog.Uintptr()) + b.Call(fn.Expr, zero, zero, zero, zero, zero, zero, zero, zero) +} + func isPhi(i ssa.Instruction) bool { _, ok := i.(*ssa.Phi) return ok @@ -1257,6 +1944,14 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue return ret } } + if len(p.stackClears[v]) > 0 { + x := p.compileValue(b, v.X) + if ret, ok := b.LoadAndClearSinglePointer(x); ok { + p.loadClears[v] = true + p.bvals[iv] = ret + return ret + } + } } x := p.compileValue(b, v.X) if v.Op != token.ARROW { @@ -1278,6 +1973,9 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue } } ret = b.UnOp(v.Op, x) + if v.Op == token.MUL && p.isRecoverSlotAddr(v.X) { + ret = ret.SetVolatile(true) + } } case *ssa.ChangeType: t := v.Type() @@ -1311,7 +2009,18 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue return } elem := p.type_(t.Elem(), llssa.InGo) + if v.Heap { + // Heap allocations are memory-profile sample sites; give each + // one a statement anchor in tracked functions so sampled + // records attribute to the allocating line (heapsampling.go + // keys buckets by the leaf frame's exact line). + p.emitPCLineLabel(b, v.Pos()) + } ret = b.Alloc(elem, v.Heap) + p.markRecoverSlot(v) + if p.isRecoverSlotAddr(v) { + b.Store(ret, p.prog.Zero(elem)).SetVolatile(true) + } case *ssa.IndexAddr: vx := v.X if _, ok := p.isVArgs(vx); ok { // varargs: this is a varargs index @@ -1581,7 +2290,10 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } ptr := p.compileValue(b, va) val := p.compileValue(b, v.Val) - b.Store(ptr, val) + store := b.Store(ptr, val) + if p.isRecoverSlotAddr(va) { + store.SetVolatile(true) + } case *ssa.Jump: jmpb := p.jumpTo(v) b.Jump(jmpb) @@ -1595,6 +2307,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } if p.returnNeedsImplicitRunDefers(v) { p.recordPanicLocation(b, v.Pos()) + p.emitPCLineLabel(b, p.deferRunPos(v.Pos())) b.RunDefers() } if p.shouldTrackCallerFrames() { @@ -1624,10 +2337,16 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { p.call(b, llssa.Go, &v.Call) case *ssa.RunDefers: p.recordPanicLocation(b, v.Pos()) + p.emitPCLineLabel(b, p.deferRunPos(v.Pos())) b.RunDefers() case *ssa.Panic: arg := p.compileValue(b, v.X) p.recordPanicLocation(b, v.Pos()) + // panic is not a Call instruction, so callEx's statement anchor + // does not cover it; the panic snapshot attributes the panicking + // frame to this pc (issue5856 wants the panic line, not the + // nearest call's). + p.emitPCLineLabel(b, v.Pos()) b.Panic(arg) case *ssa.Send: ch := p.compileValue(b, v.Chan) @@ -1655,6 +2374,25 @@ func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.V return b.DIVarAuto(scope, pos, v.Name(), t) } +func functionUsesRecover(fn *ssa.Function) bool { + if fn == nil { + return false + } + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + call, ok := instr.(ssa.CallInstruction) + if !ok { + continue + } + builtin, ok := call.Common().Value.(*ssa.Builtin) + if ok && builtin.Name() == "recover" { + return true + } + } + } + return false +} + func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyObjRef, kind int) { // TODO(xsw) v.Pkg == nil: means auto generated function? if v.Pkg == p.goPkg || v.Pkg == nil { @@ -1783,6 +2521,25 @@ func (p *context) functionHasExplicitStackDeferSeen(fn *ssa.Function, seen map[* return false } +// deferRunPos is where gc attributes a deferred function's caller frame: +// the function's closing brace — defers run at function exit, not at the +// defer statement (goroot issue14646, issue5856). +func (p *context) deferRunPos(fallback token.Pos) token.Pos { + if p.goFn != nil { + switch syntax := p.goFn.Syntax().(type) { + case *ast.FuncDecl: + if syntax.Body != nil && syntax.Body.Rbrace.IsValid() { + return syntax.Body.Rbrace + } + case *ast.FuncLit: + if syntax.Body != nil && syntax.Body.Rbrace.IsValid() { + return syntax.Body.Rbrace + } + } + } + return fallback +} + func (p *context) returnNeedsImplicitRunDefers(ret *ssa.Return) bool { fn := ret.Parent() if fn == nil || fn.Synthetic != "" || ret.Block() == fn.Recover { diff --git a/cl/import.go b/cl/import.go index e13437b693..eb7a0e2815 100644 --- a/cl/import.go +++ b/cl/import.go @@ -180,6 +180,7 @@ func (p *context) initFiles(pkgPath string, files []*ast.File, cPkg bool) { switch decl := decl.(type) { case *ast.FuncDecl: fullName, inPkgName := astFuncName(pkgPath, decl) + p.processNoInterfaceByDoc(decl.Doc, fullName) if !p.processLinknameByDoc(decl.Doc, fullName, inPkgName, false, true) && cPkg { // package C (https://github.com/goplus/llgo/issues/1165) if decl.Recv == nil && token.IsExported(inPkgName) { @@ -312,6 +313,22 @@ func (p *context) processLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgNam return false } +func (p *context) processNoInterfaceByDoc(doc *ast.CommentGroup, fullName string) { + if doc == nil { + return + } + for n := len(doc.List) - 1; n >= 0; n-- { + line := doc.List[n].Text + if line == "//go:nointerface" { + p.prog.SetNoInterfaceMethod(fullName) + return + } + if !strings.HasPrefix(line, "//go:") { + return + } + } +} + const ( noDirective = iota hasLinkname @@ -737,11 +754,16 @@ func (p *context) initPyModule() { } } -// ParsePkgSyntax parses AST of a package to check llgo:type in type declaration. +// ParsePkgSyntax parses AST of a package to check package-level compiler directives. func ParsePkgSyntax(prog llssa.Program, pkg *types.Package, files []*ast.File) { + ctx := &context{prog: prog} + pkgPath := llssa.PathOf(pkg) for _, file := range files { for _, decl := range file.Decls { switch decl := decl.(type) { + case *ast.FuncDecl: + fullName, _ := astFuncName(pkgPath, decl) + ctx.processNoInterfaceByDoc(decl.Doc, fullName) case *ast.GenDecl: switch decl.Tok { case token.TYPE: diff --git a/cl/import_coverage_test.go b/cl/import_coverage_test.go index d5710ea5d3..345693b29c 100644 --- a/cl/import_coverage_test.go +++ b/cl/import_coverage_test.go @@ -47,6 +47,13 @@ type ( B int C int ) + +//go:nointerface +func (A) Hidden() {} + +//go:other +//go:nointerface +func (A) StackedHidden() {} ` fset := token.NewFileSet() file, err := parser.ParseFile(fset, "p.go", src, parser.ParseComments) @@ -56,6 +63,13 @@ type ( prog := llssa.NewProgram(nil) pkg := types.NewPackage("example.com/p", "p") ParsePkgSyntax(prog, pkg, []*ast.File{file}) + + ctx := &context{prog: prog} + ctx.processNoInterfaceByDoc(nil, "example.com/p.NilDoc") + ctx.processNoInterfaceByDoc(&ast.CommentGroup{List: []*ast.Comment{ + {Text: "// not a directive"}, + {Text: "//go:nointerface"}, + }}, "example.com/p.NonDirectiveStops") } func TestPkgSymInfoAddSymAndInitLinknamesCoverage(t *testing.T) { diff --git a/cl/instr.go b/cl/instr.go index 60e83145e7..782c010904 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -922,10 +922,22 @@ func runtimeCallerFuncSet(c *CallerTracking, pkg *ssa.Package) map[*ssa.Function out[fn] = true } _, trackable := collectRuntimeCallerFunctions(pkg) + // Criterion 5: a package that reads the memory profile gets every + // trackable function pinned. Heap records attribute sampled + // allocations to physical frames at exact statement lines; inlining + // any function in such a package would merge its allocation sites + // into the caller and lose per-site attribution (goroot + // heapsampling.go). Profiling packages are rare and accuracy beats + // inlining there — gc keeps per-site attribution via its inline tree. + pinAll := packageReadsMemProfile(trackable) for fn := range trackable { if out[fn] { continue } + if pinAll { + out[fn] = true + continue + } // Criterion 3: pin program-unique frames. main.main and package // init functions run once, so noinline is free — and they are the // bottom frames of almost every panic traceback, where an @@ -986,6 +998,60 @@ func NewCallerTracking() *CallerTracking { } } +// isPublicRuntimePath matches the public runtime package under both +// spellings: go/ssa unit builds see "runtime"; the real pipeline patches +// it to LLGo's implementation package. +func isPublicRuntimePath(path string) bool { + return path == "runtime" || + path == "github.com/goplus/llgo/runtime/internal/lib/runtime" +} + +func packageReadsMemProfile(funcs map[*ssa.Function]bool) bool { + // Cheap import pre-filter: scanning every instruction of every + // function costs real compile time across thousands of small + // packages (goroot shards); a package that never imports the public + // runtime cannot reference MemProfile. + imported := false + for fn := range funcs { + if fn.Pkg == nil || fn.Pkg.Pkg == nil { + continue + } + for _, imp := range fn.Pkg.Pkg.Imports() { + if isPublicRuntimePath(imp.Path()) { + imported = true + } + } + break + } + if !imported { + return false + } + for fn := range funcs { + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + if call, ok := instr.(ssa.CallInstruction); ok { + if callee := call.Common().StaticCallee(); callee != nil && + callee.Pkg != nil && isPublicRuntimePath(callee.Pkg.Pkg.Path()) && + callee.Name() == "MemProfile" { + return true + } + } + rands := instr.Operands(nil) + for _, rand := range rands { + if rand == nil { + continue + } + if g, ok := (*rand).(*ssa.Global); ok && g.Pkg != nil && + isPublicRuntimePath(g.Pkg.Pkg.Path()) && g.Name() == "MemProfileRate" { + return true + } + } + } + } + } + return false +} + func isProgramUniqueFrame(pkg *ssa.Package, fn *ssa.Function) bool { if fn == nil || fn.Parent() != nil { return false @@ -1781,12 +1847,36 @@ func (p *context) deferStackOwner(fn *ssa.Function) llssa.Function { return owner } -func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, ds *explicitDeferStack, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr { +func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, ds *explicitDeferStack, mayRecover bool, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr { if ds != nil { - b.DeferTo(ds.owner, ds.stack, fn, buildCall, args...) + b.DeferToRecover(ds.owner, ds.stack, mayRecover, fn, buildCall, args...) + return llssa.Nil + } + switch act { + case llssa.Call, llssa.Go: + return b.Do(act, fn, buildCall, args...) + default: + b.DeferRecover(act, mayRecover, fn, buildCall, args...) return llssa.Nil } - return b.Do(act, fn, buildCall, args...) +} + +func (p *context) callMayRecover(v ssa.Value) bool { + switch v := v.(type) { + case *ssa.Builtin: + return false + case *ssa.Function: + return functionUsesRecover(v) + case *ssa.MakeClosure: + if fn, ok := v.Fn.(*ssa.Function); ok { + return functionUsesRecover(fn) + } + return true + case *ssa.Call: + // The deferred callee is the call result, not the factory function. + return true + } + return true } func (p *context) staticArrayLenBuiltinArg(b llssa.Builder, arg ssa.Value) (llssa.Expr, bool) { @@ -1941,6 +2031,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm p.recordCallerLocationForCall(b, call) p.emitPCLineLabel(b, call.Pos()) cv := call.Value + mayRecover := p.callMayRecover(cv) if mthd := call.Method; mthd != nil { reflectCheck := p.reflectTypeMethodCheck(call, mthd) o := p.compileValue(b, cv) @@ -1950,7 +2041,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm hasVArg = fnHasVArg } args := p.compileValues(b, call.Args, hasVArg) - ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, true, fn, llssa.Builder.Call, args...) if reflectCheck.Kind&llssa.ReflectTypeMethodByName != 0 && reflectCheck.Name == "" { b.MarkReflectTypeMethodByNameExpr(ret, 1) } @@ -1984,22 +2075,28 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm } } args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Builtin(fn), llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, false, llssa.Builtin(fn), llssa.Builder.Call, args...) case *ssa.Function: aFn, pyFn, ftype := p.compileFunction(cv) + if p.isRuntimeSetFinalizerCall(call) && len(args) == 2 && act == llssa.Call && ds == nil { + finalizer := p.compileLateValue(b, args[1]) + obj := p.compileLateValue(b, args[0]) + ret = p.emitDo(b, act, nil, false, aFn.Expr, llssa.Builder.Call, obj, finalizer) + return + } // TODO(xsw): check ca != llssa.Call switch ftype { case cFunc: p.inCFunc = true args := p.compileValues(b, args, kind) p.inCFunc = false - ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, aFn.Expr, llssa.Builder.Call, args...) case goFunc: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, aFn.Expr, llssa.Builder.Call, args...) case pyFunc: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, pyFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, pyFn.Expr, llssa.Builder.Call, args...) case llgoPyList: args := p.compileValues(b, args, fnHasVArg) ret = b.PyList(args...) @@ -2073,33 +2170,33 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm b.Unreachable() case llgoAtomicLoad: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicLoad(b, args) }, args...) case llgoAtomicStore: args := p.compileValues(b, args, kind) - p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicStore(b, args) }, args...) case llgoAtomicCmpXchg: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicCmpXchg(b, args) }, args...) case llgoAtomicCmpXchgOK: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicCmpXchgOK(b, args) }, args...) case llgoAtomicAddReturnNew: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return b.BinOp(token.ADD, p.atomic(b, llssa.OpAdd, args), args[1]) }, args...) default: if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) }, args...) } else { @@ -2109,7 +2206,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm default: fn := p.compileValue(b, cv) args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, fn, llssa.Builder.Call, args...) } return } diff --git a/cl/liveness_internal_test.go b/cl/liveness_internal_test.go new file mode 100644 index 0000000000..9e142e0d25 --- /dev/null +++ b/cl/liveness_internal_test.go @@ -0,0 +1,860 @@ +//go:build !llgo +// +build !llgo + +package cl + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "strings" + "testing" + + "github.com/goplus/gogen/packages" + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" +) + +func buildSSAPackageWithPath(t *testing.T, pkgPath, pkgName, src string) *ssa.Package { + t.Helper() + ssapkg, _ := buildSSAPackageWithPathAndFiles(t, pkgPath, pkgName, src) + return ssapkg +} + +func buildSSAPackageWithPathAndFiles(t *testing.T, pkgPath, pkgName, src string) (*ssa.Package, []*ast.File) { + t.Helper() + return buildSSAPackageWithPathAndFilesMode(t, pkgPath, pkgName, src, ssa.SanityCheckFunctions|ssa.InstantiateGenerics) +} + +func buildSSAPackageWithPathAndFilesMode(t *testing.T, pkgPath, pkgName, src string, mode ssa.BuilderMode) (*ssa.Package, []*ast.File) { + t.Helper() + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", src, 0) + if err != nil { + t.Fatal(err) + } + files := []*ast.File{f} + pkg := types.NewPackage(pkgPath, pkgName) + imp := packages.NewImporter(fset) + ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: imp}, fset, pkg, files, mode) + if err != nil { + t.Fatal(err) + } + return ssapkg, files +} + +func TestConservativeGCPointerTypeAnalysis(t *testing.T) { + if hasConservativeGCPointers(nil, map[types.Type]bool{}) { + t.Fatal("nil type should not report conservative pointers") + } + if hasConservativeGCPointers(types.Typ[types.Int], map[types.Type]bool{}) { + t.Fatal("int should not report conservative pointers") + } + if hasConservativeGCPointers(types.Typ[types.String], map[types.Type]bool{types.Typ[types.String]: true}) { + t.Fatal("seen type should short-circuit") + } + for _, typ := range []types.Type{ + types.Typ[types.String], + types.Typ[types.UnsafePointer], + types.NewPointer(types.Typ[types.Int]), + types.NewSlice(types.Typ[types.Int]), + types.NewMap(types.Typ[types.String], types.Typ[types.Int]), + types.NewChan(types.SendRecv, types.Typ[types.Int]), + types.NewSignatureType(nil, nil, nil, nil, nil, false), + types.NewInterfaceType(nil, nil), + types.NewArray(types.NewPointer(types.Typ[types.Int]), 2), + types.NewStruct([]*types.Var{types.NewField(token.NoPos, nil, "p", types.NewPointer(types.Typ[types.Int]), false)}, nil), + } { + if !hasConservativeGCPointers(typ, map[types.Type]bool{}) { + t.Fatalf("%v should report conservative pointers", typ) + } + } + if hasConservativeGCPointers(types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, nil, "i", types.Typ[types.Int], false), + }, nil), map[types.Type]bool{}) { + t.Fatal("struct without pointer fields should not report conservative pointers") + } + if hasConservativeGCPointers(types.NewArray(types.Typ[types.Int], 2), map[types.Type]bool{}) { + t.Fatal("array without pointer elements should not report conservative pointers") + } + if !hasConservativeGCPointers(types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, nil, "i", types.Typ[types.Int], false), + types.NewField(token.NoPos, nil, "p", types.NewPointer(types.Typ[types.Int]), false), + }, nil), map[types.Type]bool{}) { + t.Fatal("struct with later pointer field should report conservative pointers") + } +} + +func TestShouldClearAlloc(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "example.com/live", "live", `package live + +type Box struct{ p *int } + +var Sink any + +func allocs(p *int) { + var box Box + var i int + box.p = p + Sink = &box + Sink = &i +} + `) + fn := ssapkg.Func("allocs") + ctx := &context{} + if ctx.shouldClearAlloc(nil) { + t.Fatal("nil alloc should not be cleared") + } + + var boxAlloc, intAlloc *ssa.Alloc + for _, local := range functionAllocs(fn) { + ptr := local.Type().Underlying().(*types.Pointer) + if _, ok := ptr.Elem().Underlying().(*types.Struct); ok { + boxAlloc = local + } + if ptr.Elem() == types.Typ[types.Int] { + intAlloc = local + } + } + if boxAlloc == nil || intAlloc == nil { + var dump strings.Builder + fn.WriteTo(&dump) + t.Fatalf("missing expected allocs: %v\n%s", functionAllocs(fn), dump.String()) + } + if !ctx.shouldClearAlloc(boxAlloc) { + t.Fatal("struct containing a pointer should be cleared") + } + if ctx.shouldClearAlloc(intAlloc) { + t.Fatal("int alloc should not be cleared") + } + + boxAlloc.Comment = "varargs" + if ctx.shouldClearAlloc(boxAlloc) { + t.Fatal("varargs alloc should not be cleared") + } + boxAlloc.Comment = "makeslice" + if ctx.shouldClearAlloc(boxAlloc) { + t.Fatal("synthetic makeslice alloc should not be cleared") + } +} + +func functionAllocs(fn *ssa.Function) []*ssa.Alloc { + seen := make(map[*ssa.Alloc]bool) + var allocs []*ssa.Alloc + add := func(alloc *ssa.Alloc) { + if alloc != nil && !seen[alloc] { + seen[alloc] = true + allocs = append(allocs, alloc) + } + } + for _, local := range fn.Locals { + add(local) + } + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + if alloc, ok := instr.(*ssa.Alloc); ok { + add(alloc) + } + } + } + return allocs +} + +func TestRuntimeSetFinalizerDetection(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "github.com/goplus/llgo/runtime/livetest", "livetest", `package livetest + +import rt "github.com/goplus/llgo/runtime/internal/lib/runtime" + +func direct(p *int) { + rt.SetFinalizer(p, func(*int) {}) +} + +func deferred(p *int) { + defer rt.SetFinalizer(p, nil) +} + +func goroutine(p *int) { + go rt.SetFinalizer(p, nil) +} + +func nested(p *int) { + func() { + rt.SetFinalizer(p, nil) + }() +} + +func none(p *int) {} +`) + ctx := &context{} + if ctx.enableConservativeLivenessClears(nil) { + t.Fatal("nil function should not enable conservative clears") + } + for _, name := range []string{"direct", "deferred", "goroutine", "nested"} { + if !ctx.functionUsesRuntimeSetFinalizer(ssapkg.Func(name), map[*ssa.Function]bool{}) { + t.Fatalf("%s should be detected as SetFinalizer user", name) + } + } + if ctx.functionUsesRuntimeSetFinalizer(nil, map[*ssa.Function]bool{}) { + t.Fatal("nil function should not use SetFinalizer") + } + direct := ssapkg.Func("direct") + if ctx.functionUsesRuntimeSetFinalizer(direct, map[*ssa.Function]bool{direct: true}) { + t.Fatal("seen function should short-circuit") + } + if ctx.functionUsesRuntimeSetFinalizer(ssapkg.Func("none"), map[*ssa.Function]bool{}) { + t.Fatal("none should not use SetFinalizer") + } + if ctx.packageUsesRuntimeSetFinalizer(&ssa.Package{Members: map[string]ssa.Member{"none": ssapkg.Func("none")}}) { + t.Fatal("package without SetFinalizer should not report use") + } + if !ctx.packageUsesRuntimeSetFinalizer(ssapkg) { + t.Fatal("package should report SetFinalizer use") + } + if ctx.enableConservativeLivenessClears(direct) { + t.Fatal("non command-line-arguments package should not enable conservative clears") + } + ssapkg.Pkg = types.NewPackage("command-line-arguments", "main") + if !ctx.enableConservativeLivenessClears(direct) { + t.Fatal("command-line-arguments package with SetFinalizer should enable conservative clears") + } +} + +func TestRuntimeSetFinalizerLateValueSkips(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "github.com/goplus/llgo/runtime/livetest", "livetest", `package livetest + +import rt "github.com/goplus/llgo/runtime/internal/lib/runtime" + +func direct(p *int) { + rt.SetFinalizer(p, func(*int) {}) +} +`) + ctx := &context{} + fn := ssapkg.Func("direct") + var makeIface *ssa.MakeInterface + var deref *ssa.UnOp + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.MakeInterface: + makeIface = instr + case *ssa.UnOp: + if instr.Op == token.MUL { + deref = instr + } + } + } + } + if makeIface == nil { + t.Fatal("missing MakeInterface for SetFinalizer argument") + } + if ctx.isRuntimeSetFinalizerCall(nil) { + t.Fatal("nil call should not be SetFinalizer") + } + if !ctx.shouldSkipLateSetFinalizerValue(makeIface) { + t.Fatal("SetFinalizer-only MakeInterface should be skipped") + } + if deref != nil && !ctx.shouldSkipLateSetFinalizerValue(deref) { + t.Fatal("SetFinalizer-only deref should be skipped") + } + if ctx.shouldSkipLateSetFinalizerValue(&ssa.Return{}) { + t.Fatal("unrelated instruction should not be skipped") + } + if ctx.shouldSkipLateSetFinalizerValue(&ssa.UnOp{Op: token.SUB}) { + t.Fatal("non-deref unary op should not be skipped") + } +} + +func TestConservativeLivenessPlanCollectors(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "example.com/live", "live", `package live + +type Box struct{ p *int } + +var Sink any + +func linear(p *int) { + var box Box + box.p = p + Sink = box.p + Sink = 1 +} + +func branch(p *int, cond bool) { + var box Box + box.p = p + if cond { + Sink = box.p + } else { + Sink = 0 + } + Sink = 1 +} + +func branchBoth(p *int, cond bool) { + var box Box + box.p = p + if cond { + Sink = box.p + } else { + Sink = box.p + } + Sink = 1 +} + +func paramUse(p *int) { + Sink = p + Sink = 1 +} + +func splitParam(p *int, cond bool) { + if cond { + Sink = p + } else { + Sink = p + } + Sink = 1 +} + +func takes(*int) {} + +func callWithPointer(p *int) { + takes(p) + Sink = 1 +} + +func callWithInt(i int) { + Sink = i +} +`) + ctx := &context{} + linear := ssapkg.Func("linear") + stackPlans := ctx.collectStackClearPlans(linear) + if len(stackPlans) == 0 { + t.Fatal("linear should produce stack clear plans") + } + for instr := range stackPlans { + if isTerminatingInstruction(instr) { + t.Fatalf("stack clear should not be scheduled after terminator %T", instr) + } + } + + entryPlans := ctx.collectEntryClearPlans(ssapkg.Func("branch")) + if len(entryPlans) == 0 { + t.Fatal("branch should produce entry clear plans for dead successor") + } + if got := ctx.collectEntryClearPlans(ssapkg.Func("branchBoth")); len(got) != 0 { + t.Fatalf("branchBoth should not clear values live in both successors: %v", got) + } + + paramFn := ssapkg.Func("paramUse") + if len(ctx.collectParamClobberPlans(paramFn)) == 0 { + t.Fatal("paramUse should produce param clobber plans") + } + if len(ctx.collectParamScanPlans(paramFn)) == 0 { + t.Fatal("paramUse should produce param scan plans") + } + splitParam := ssapkg.Func("splitParam") + if got := ctx.collectParamClobberPlans(splitParam); len(got) != 0 { + t.Fatalf("splitParam has no single last-use block, got clobbers: %v", got) + } + if got := ctx.collectParamScanPlans(splitParam); len(got) != 0 { + t.Fatalf("splitParam has no single last-use block, got scans: %v", got) + } + if len(ctx.collectCallClobberPlans(ssapkg.Func("callWithPointer"))) == 0 { + t.Fatal("pointer call should clobber pointer regs") + } + if len(ctx.collectCallClobberPlans(ssapkg.Func("callWithInt"))) != 0 { + t.Fatal("int-only call should not clobber pointer regs") + } +} + +func TestConservativeLivenessGraphHelpers(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "example.com/live", "live", `package live + +import "unsafe" + +var Sink any + +type Box struct{ p *int } + +func flow(p *int, cond bool) { + if cond { + Sink = p + } else { + Sink = 0 + } +} + +func split(p *int, cond bool) { + if cond { + Sink = p + } else { + Sink = p + } +} + +func target(*int) {} + +func withCall(p *int) { + target(p) +} + +func refs(p *int, arr *[2]*int, box *Box, cond bool) *int { + var q *int + if cond { + q = p + } else { + q = box.p + } + Sink = arr[0] + Sink = q + return q +} + +func converted(p *int) unsafe.Pointer { + return unsafe.Pointer(p) +} + `) + fn := ssapkg.Func("flow") + if blockCanReach(nil, fn.Blocks[0], map[*ssa.BasicBlock]bool{}) { + t.Fatal("nil block should not reach anything") + } + if !blockCanReach(fn.Blocks[0], fn.Blocks[0], map[*ssa.BasicBlock]bool{}) { + t.Fatal("block should reach itself") + } + if instructionUsesValue(nil, fn.Params[0]) { + t.Fatal("nil instruction should not use values") + } + if instructionUsesValue(fn.Blocks[0].Instrs[0], nil) { + t.Fatal("nil value should not be used") + } + if isCallLikeInstruction(fn.Blocks[0].Instrs[0]) { + t.Fatal("if instruction should not be call-like") + } + if !isTerminatingInstruction(fn.Blocks[0].Instrs[len(fn.Blocks[0].Instrs)-1]) { + t.Fatal("entry block should end with a terminator") + } + + ctx := &context{} + if blk := refBlock(nil); blk != nil { + t.Fatalf("refBlock(nil) = %v", blk) + } + blocks := make(map[*ssa.BasicBlock]bool) + if !ctx.collectValueUseBlocks(nil, blocks, map[ssa.Value]bool{}, false) { + t.Fatal("nil collectValueUseBlocks should succeed") + } + if !ctx.collectValueUseBlocks(fn.Params[0], blocks, map[ssa.Value]bool{fn.Params[0]: true}, false) { + t.Fatal("seen collectValueUseBlocks should succeed") + } + if !ctx.collectValueUseBlocks(fn.Params[0], blocks, map[ssa.Value]bool{}, false) { + t.Fatal("collectValueUseBlocks failed") + } + if len(blocks) == 0 { + t.Fatal("expected use blocks for parameter") + } + if blk, ok := ctx.valueLastUseBlock(fn.Params[0]); !ok || blk == nil { + t.Fatalf("valueLastUseBlock = %v, %v", blk, ok) + } + if blk, ok := ctx.valueLastUseBlock(nil); !ok || blk != nil { + t.Fatalf("valueLastUseBlock(nil) = %v, %v", blk, ok) + } + if last, ok := ctx.lastUseInBlock(nil, fn.Blocks[0], map[ssa.Instruction]int{}, map[ssa.Value]bool{}); !ok || last != nil { + t.Fatalf("lastUseInBlock(nil) = %v, %v", last, ok) + } + split := ssapkg.Func("split") + if blk, ok := ctx.valueLastUseBlock(split.Params[0]); ok || blk != nil { + t.Fatalf("valueLastUseBlock(split param) = %v, %v; want no single block", blk, ok) + } + entryOrder := make(map[ssa.Instruction]int, len(split.Blocks[0].Instrs)) + for i, instr := range split.Blocks[0].Instrs { + entryOrder[instr] = i + } + if last, ok := ctx.lastUseInBlock(split.Params[0], split.Blocks[0], entryOrder, map[ssa.Value]bool{}); ok || last != nil { + t.Fatalf("lastUseInBlock(split param in entry) = %v, %v; want failure outside block", last, ok) + } + + var callLike int + for _, block := range ssapkg.Func("withCall").Blocks { + for _, instr := range block.Instrs { + if isCallLikeInstruction(instr) { + callLike++ + } + } + } + if callLike == 0 { + t.Fatal("flow should include at least one call-like instruction") + } + + refs := ssapkg.Func("refs") + var lastUseCount int + for _, param := range refs.Params { + blocks := make(map[*ssa.BasicBlock]bool) + if !ctx.collectValueUseBlocks(param, blocks, map[ssa.Value]bool{}, true) { + t.Fatalf("collectValueUseBlocks failed for %s", param.Name()) + } + if len(blocks) == 0 { + t.Fatalf("expected use blocks for %s", param.Name()) + } + blk, ok := ctx.valueLastUseBlock(param) + if !ok || blk == nil { + t.Fatalf("valueLastUseBlock(%s) = %v, %v", param.Name(), blk, ok) + } + order := make(map[ssa.Instruction]int, len(blk.Instrs)) + for i, instr := range blk.Instrs { + order[instr] = i + } + if last, ok := ctx.lastUseInBlock(param, blk, order, map[ssa.Value]bool{}); !ok { + t.Fatalf("lastUseInBlock(%s) = %v, %v", param.Name(), last, ok) + } else if last != nil { + lastUseCount++ + } + } + if lastUseCount == 0 { + t.Fatal("expected at least one parameter with a concrete last use") + } + phiBlocks := make(map[*ssa.BasicBlock]bool) + if !ctx.collectValueUseBlocks(refs.Params[0], phiBlocks, map[ssa.Value]bool{}, false) { + t.Fatal("non-following phi use collection failed") + } + if len(phiBlocks) == 0 { + t.Fatal("non-following phi use collection should record predecessor blocks") + } + converted := ssapkg.Func("converted") + if !ctx.collectValueUseBlocks(converted.Params[0], make(map[*ssa.BasicBlock]bool), map[ssa.Value]bool{}, true) { + t.Fatal("conversion use collection failed") + } +} + +func TestConservativeLivenessHelperFallbacks(t *testing.T) { + ssapkg := buildSSAPackageWithPath(t, "example.com/live", "live", `package live + +var Sink any + +func branch(cond bool) { + if cond { + Sink = 1 + } else { + Sink = 2 + } +} + +func useOne(p, q *int) { + Sink = p +} + +func neg(i int) int { + return -i +} + +func callDeref(f *func()) { + (*f)() +} + +func derefOnly(p **int) { + _ = *p +} + `) + ctx := &context{} + + branch := ssapkg.Func("branch") + if len(branch.Blocks) < 2 { + t.Fatalf("branch should have successors:\n%s", branch.String()) + } + if blockCanReach(branch.Blocks[0], branch.Blocks[1], map[*ssa.BasicBlock]bool{branch.Blocks[0]: true}) { + t.Fatal("seen entry block should stop reachability recursion") + } + + useOne := ssapkg.Func("useOne") + var useP ssa.Instruction + for _, block := range useOne.Blocks { + for _, instr := range block.Instrs { + if instructionUsesValue(instr, useOne.Params[0]) { + useP = instr + break + } + } + if useP != nil { + break + } + } + if useP == nil { + t.Fatalf("missing instruction that uses p:\n%s", useOne.String()) + } + if instructionUsesValue(useP, useOne.Params[1]) { + t.Fatal("instruction using p should not report use of q") + } + if ctx.isOnlyRuntimeSetFinalizerArg(useOne.Params[1]) { + t.Fatal("unused parameter should not be treated as a SetFinalizer-only argument") + } + if ctx.shouldSkipLateSetFinalizerValue(&ssa.UnOp{Op: token.MUL}) { + t.Fatal("deref without a single MakeInterface referrer should not be skipped") + } + + global := ssapkg.Members["Sink"].(*ssa.Global) + if !ctx.collectValueUseBlocks(global, make(map[*ssa.BasicBlock]bool), map[ssa.Value]bool{}, false) { + t.Fatal("global without referrers should be a valid value-use query") + } + if last, ok := ctx.lastUseInBlock(global, useOne.Blocks[0], map[ssa.Instruction]int{}, map[ssa.Value]bool{}); !ok || last != nil { + t.Fatalf("lastUseInBlock(global) = %v, %v", last, ok) + } + + neg := ssapkg.Func("neg") + var negInstr *ssa.UnOp + for _, block := range neg.Blocks { + for _, instr := range block.Instrs { + if unop, ok := instr.(*ssa.UnOp); ok && unop.Op == token.SUB { + negInstr = unop + break + } + } + if negInstr != nil { + break + } + } + if negInstr == nil { + t.Fatalf("missing unary negation:\n%s", neg.String()) + } + blocks := make(map[*ssa.BasicBlock]bool) + if !ctx.collectValueUseBlocks(neg.Params[0], blocks, map[ssa.Value]bool{}, false) { + t.Fatal("non-deref unary use collection failed") + } + if !blocks[negInstr.Block()] { + t.Fatal("non-deref unary use should record its block") + } + order := make(map[ssa.Instruction]int, len(negInstr.Block().Instrs)) + for i, instr := range negInstr.Block().Instrs { + order[instr] = i + } + if last, ok := ctx.lastUseInBlock(neg.Params[0], negInstr.Block(), order, map[ssa.Value]bool{}); !ok || last != negInstr { + t.Fatalf("lastUseInBlock(neg param) = %v, %v; want unary op", last, ok) + } + + callDeref := ssapkg.Func("callDeref") + var deref *ssa.UnOp + for _, block := range callDeref.Blocks { + for _, instr := range block.Instrs { + if unop, ok := instr.(*ssa.UnOp); ok && unop.Op == token.MUL { + deref = unop + break + } + } + if deref != nil { + break + } + } + if deref == nil { + t.Fatalf("missing call dereference:\n%s", callDeref.String()) + } + order = make(map[ssa.Instruction]int, len(deref.Block().Instrs)) + for i, instr := range deref.Block().Instrs { + order[instr] = i + } + if last, ok := ctx.lastUseInBlock(callDeref.Params[0], deref.Block(), order, map[ssa.Value]bool{}); !ok || last != deref { + t.Fatalf("lastUseInBlock(call deref param) = %v, %v; want deref", last, ok) + } + + derefOnly := ssapkg.Func("derefOnly") + var loneDeref *ssa.UnOp + for _, block := range derefOnly.Blocks { + for _, instr := range block.Instrs { + if unop, ok := instr.(*ssa.UnOp); ok && unop.Op == token.MUL { + loneDeref = unop + break + } + } + if loneDeref != nil { + break + } + } + if loneDeref == nil { + t.Fatalf("missing lone dereference:\n%s", derefOnly.String()) + } + if !ctx.collectValueUseBlocks(derefOnly.Params[0], make(map[*ssa.BasicBlock]bool), map[ssa.Value]bool{}, false) { + t.Fatal("lone deref use collection failed") + } + order = make(map[ssa.Instruction]int, len(loneDeref.Block().Instrs)) + for i, instr := range loneDeref.Block().Instrs { + order[instr] = i + } + if last, ok := ctx.lastUseInBlock(derefOnly.Params[0], loneDeref.Block(), order, map[ssa.Value]bool{}); !ok || last != loneDeref { + t.Fatalf("lastUseInBlock(lone deref param) = %v, %v; want deref", last, ok) + } +} + +func TestConservativeLivenessDebugRefs(t *testing.T) { + ssapkg, _ := buildSSAPackageWithPathAndFilesMode(t, "example.com/live", "live", `package live + +var Sink any + +func use(p *int) { + Sink = p +} + `, ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug) + + fn := ssapkg.Func("use") + var debugRefs int + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + if _, ok := instr.(*ssa.DebugRef); ok { + debugRefs++ + } + } + } + if debugRefs == 0 { + t.Fatalf("debug SSA package did not contain DebugRef instructions:\n%s", fn.String()) + } + + ctx := &context{} + if !ctx.collectValueUseBlocks(fn.Params[0], make(map[*ssa.BasicBlock]bool), map[ssa.Value]bool{}, false) { + t.Fatal("DebugRef should be ignored while collecting use blocks") + } + order := make(map[ssa.Instruction]int, len(fn.Blocks[0].Instrs)) + for i, instr := range fn.Blocks[0].Instrs { + order[instr] = i + } + if last, ok := ctx.lastUseInBlock(fn.Params[0], fn.Blocks[0], order, map[ssa.Value]bool{}); !ok || last == nil { + t.Fatalf("lastUseInBlock with DebugRef = %v, %v", last, ok) + } +} + +func TestConservativeLivenessScanAllocPointerSlot(t *testing.T) { + prog := newLLSSAProg(t) + pkg := prog.NewPackage("live", "live") + ptrToInt := types.NewPointer(types.Typ[types.Int]) + slotType := types.NewPointer(ptrToInt) + sig := types.NewSignatureType(nil, nil, nil, + types.NewTuple(types.NewParam(token.NoPos, nil, "slot", slotType)), nil, false) + fn := pkg.NewFunc("scanPointerSlot", sig, llssa.InGo) + b := fn.MakeBody(1) + (&context{prog: prog}).scanAllocPointer(b, fn.Param(0)) + b.Return() + b.EndBuild() + + ir := pkg.String() + if !strings.Contains(ir, "llgo_clear_stack_ptr") { + t.Fatalf("pointer slot scan should emit stack clear helper:\n%s", ir) + } +} + +func TestCompileWithoutConservativeLivenessClears(t *testing.T) { + ssapkg, files := buildSSAPackageWithPathAndFiles(t, "command-line-arguments", "main", `package main + +func main() { + x := 1 + _ = &x +} +`) + + prog := newLLSSAProg(t) + pkg, err := NewPackage(prog, ssapkg, files) + if err != nil { + t.Fatal(err) + } + if strings.Contains(pkg.String(), "llgo_clear_stack_ptr") { + t.Fatalf("package without SetFinalizer should not emit liveness clear helpers:\n%s", pkg.String()) + } +} + +func TestCompileConservativeLivenessClears(t *testing.T) { + ssapkg, files := buildSSAPackageWithPathAndFiles(t, "github.com/goplus/llgo/runtime/livetest", "main", `package main + +import rt "github.com/goplus/llgo/runtime/internal/lib/runtime" + +type Box struct{ p *int } + +var Sink any + +func main() { + x := 1 + var box Box + box.p = &x + Sink = box.p + rt.SetFinalizer(&box, func(*Box) {}) + Sink = 1 +} +`) + ssapkg.Pkg = types.NewPackage("command-line-arguments", "main") + + prog := newLLSSAProg(t) + pkg, err := NewPackage(prog, ssapkg, files) + if err != nil { + t.Fatal(err) + } + ir := pkg.String() + for _, want := range []string{"llgo_clear_stack_ptr", "llgo_clobber_pointer_regs"} { + if !strings.Contains(ir, want) { + t.Fatalf("compiled liveness module missing %s:\n%s", want, ir) + } + } + if !pkg.NeedRuntime { + t.Fatal("liveness clear helpers should mark runtime as needed") + } +} + +func TestCompileConservativeLivenessStructParamScans(t *testing.T) { + ssapkg, files := buildSSAPackageWithPathAndFiles(t, "github.com/goplus/llgo/runtime/livetest", "main", `package main + +import rt "github.com/goplus/llgo/runtime/internal/lib/runtime" +import "unsafe" + +type Cell struct{ p *int } +type Ptr *int + +var Sink any + +func consume(cell Cell) { + Sink = cell.p + Sink = 1 +} + +func consumePtr(p *int) { + Sink = p + Sink = 1 +} + +func branch(cell Cell, cond bool) { + if cond { + Sink = cell.p + } else { + Sink = 0 + } + Sink = 1 +} + +func main() { + x := 1 + y := 2 + arr := [2]*int{&x, &y} + cell := Cell{p: &x} + p := &x + pp := &p + ptr := Ptr(&x) + rt.SetFinalizer(&cell, func(*Cell) {}) + rt.SetFinalizer(&p, func(**int) {}) + rt.SetFinalizer(*pp, nil) + rt.SetFinalizer(&cell.p, func(**int) {}) + rt.SetFinalizer(&arr[0], func(**int) {}) + rt.SetFinalizer(unsafe.Pointer(&x), nil) + rt.SetFinalizer(ptr, nil) + consume(cell) + consumePtr(p) + branch(cell, x == y) +} + `) + ssapkg.Pkg = types.NewPackage("command-line-arguments", "main") + + prog := newLLSSAProg(t) + pkg, err := NewPackage(prog, ssapkg, files) + if err != nil { + t.Fatal(err) + } + ir := pkg.String() + if strings.Count(ir, "llgo_clear_stack_ptr") < 2 { + t.Fatalf("expected stack pointer scans for struct param and local:\n%s", ir) + } + if !strings.Contains(ir, "llgo_clobber_pointer_regs") { + t.Fatalf("compiled liveness module missing clobber helper:\n%s", ir) + } +} diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 15d0a17057..f9c0a23cde 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -699,8 +699,8 @@ func TestEmitDoWithExplicitDeferStack(t *testing.T) { b.SetBlockEx(owner.Block(0), llssa.BeforeLast, true) ctx := &context{} - ctx.emitDo(b, llssa.DeferInLoop, &explicitDeferStack{stack: stack, owner: owner}, callee.Expr, llssa.Builder.Call) - ctx.emitDo(b, llssa.DeferAlways, nil, callee.Expr, llssa.Builder.Call) + ctx.emitDo(b, llssa.DeferInLoop, &explicitDeferStack{stack: stack, owner: owner}, false, callee.Expr, llssa.Builder.Call) + ctx.emitDo(b, llssa.DeferAlways, nil, false, callee.Expr, llssa.Builder.Call) b.DeferStackDrain() b.RunDefers() b.Return() @@ -836,7 +836,7 @@ func TestEmitDoWithoutExplicitDeferStack(t *testing.T) { b := fn.MakeBody(1) ctx := &context{} - got := ctx.emitDo(b, llssa.Call, nil, callee.Expr, llssa.Builder.Call) + got := ctx.emitDo(b, llssa.Call, nil, false, callee.Expr, llssa.Builder.Call) if got.IsNil() { t.Fatal("emitDo without explicit defer stack should return direct call result") } diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index e9657c4d05..3bec7b9b5d 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -258,6 +258,11 @@ func use(goos, goarch string, wasiThreads, forceEspClang bool, level optlevel.Le "-target", targetTriple, "-Qunused-arguments", "-Wno-unused-command-line-argument", + // Keep frame pointers in C code too: the runtime's physical + // unwinder walks fault-site chains through C frames (Go keeps + // them via the "frame-pointer"="non-leaf" attribute; x86-64 C + // would omit them at -O by default). + "-fno-omit-frame-pointer", } if ltoMode.Enabled() { export.CCFLAGS = append(export.CCFLAGS, ltoMode.ClangFlag()) diff --git a/runtime/internal/clite/bdwgc/bdwgc.go b/runtime/internal/clite/bdwgc/bdwgc.go index 9f0bec38e6..4a07b2d44b 100644 --- a/runtime/internal/clite/bdwgc/bdwgc.go +++ b/runtime/internal/clite/bdwgc/bdwgc.go @@ -108,6 +108,9 @@ func GetGCNo() uintptr //go:linkname GetHeapUsageSafe C.GC_get_heap_usage_safe func GetHeapUsageSafe(heapSize, freeBytes, unmappedBytes, bytesSinceGC, totalBytes *uintptr) +//go:linkname ClearStack C.GC_clear_stack +func ClearStack(arg c.Pointer) c.Pointer + //go:linkname GetMemoryUse C.GC_get_memory_use func GetMemoryUse() uintptr diff --git a/runtime/internal/clite/pthread/key_darwin.go b/runtime/internal/clite/pthread/key_darwin.go new file mode 100644 index 0000000000..ee3d87d3c3 --- /dev/null +++ b/runtime/internal/clite/pthread/key_darwin.go @@ -0,0 +1,6 @@ +//go:build darwin + +package pthread + +// Darwin defines pthread_key_t as unsigned long. +type Key uintptr diff --git a/runtime/internal/clite/pthread/key_default.go b/runtime/internal/clite/pthread/key_default.go new file mode 100644 index 0000000000..dc6fb8a572 --- /dev/null +++ b/runtime/internal/clite/pthread/key_default.go @@ -0,0 +1,7 @@ +//go:build !darwin + +package pthread + +import c "github.com/goplus/llgo/runtime/internal/clite" + +type Key c.Uint diff --git a/runtime/internal/clite/pthread/pthread.go b/runtime/internal/clite/pthread/pthread.go index fab5c20c00..af046a82ba 100644 --- a/runtime/internal/clite/pthread/pthread.go +++ b/runtime/internal/clite/pthread/pthread.go @@ -113,8 +113,6 @@ func (attr *Attr) SetStackAddr(stackAddr c.Pointer) c.Int { return 0 } // ----------------------------------------------------------------------------- // Thread Local Storage -type Key c.Uint - //llgo:type C type KeyDestructor func(c.Pointer) diff --git a/runtime/internal/lib/reflect/type.go b/runtime/internal/lib/reflect/type.go index c0898a1aa9..7981e04fed 100644 --- a/runtime/internal/lib/reflect/type.go +++ b/runtime/internal/lib/reflect/type.go @@ -305,16 +305,19 @@ func (t *rtype) Method(i int) (m Method) { } mt := FuncOf(in, out, ft.Variadic()) m.Type = mt - mtfn := (*funcType)(unsafe.Pointer(&mt.(*rtype).t)) - fv := &struct { - fn unsafe.Pointer - env unsafe.Pointer - }{p.Tfn_, nil} - m.Func = Value{closureOf(mtfn), unsafe.Pointer(fv), fl | flagIndir} + m.Func = methodFuncValue(&mt.(*rtype).t, p.Tfn_, fl) m.Index = i return m } +func methodFuncValue(ft *abi.Type, fn unsafe.Pointer, fl flag) Value { + ct := closureOf((*funcType)(unsafe.Pointer(ft))) + c := unsafe_New(ct) + *(*unsafe.Pointer)(c) = fn + *(*unsafe.Pointer)(add(c, goarch.PtrSize, "closure data field")) = nil + return Value{ct, c, fl | flagIndir} +} + func (t *rtype) MethodByName(name string) (m Method, ok bool) { if t.Kind() == Interface { tt := (*interfaceType)(unsafe.Pointer(t)) diff --git a/runtime/internal/lib/runtime/_wrap/runtime.c b/runtime/internal/lib/runtime/_wrap/runtime.c index cd5af9a95e..2cdda12d1a 100644 --- a/runtime/internal/lib/runtime/_wrap/runtime.c +++ b/runtime/internal/lib/runtime/_wrap/runtime.c @@ -1,3 +1,9 @@ +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#include +#include #include int llgo_maxprocs() @@ -9,11 +15,64 @@ int llgo_maxprocs() #endif } -__attribute__((noinline)) void *llgo_framepointer(void) +void llgo_clobber_pointer_regs(uintptr_t a0, uintptr_t a1, uintptr_t a2, uintptr_t a3, + uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7) { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_frame_address(0); -#else - return 0; + volatile uintptr_t sink = a0 | a1 | a2 | a3 | a4 | a5 | a6 | a7; + (void)sink; +} + +void llgo_clear_stack_ptr(uintptr_t target) +{ + if (target == 0) { + return; + } + + volatile uintptr_t marker = 0; + uintptr_t *cur = 0; + uintptr_t *end = 0; + +#if defined(__APPLE__) + void *stackaddr = pthread_get_stackaddr_np(pthread_self()); + size_t stacksize = pthread_get_stacksize_np(pthread_self()); + if (stackaddr != 0 && stacksize != 0) { + uintptr_t *mark = (uintptr_t *)▮ + uintptr_t *lo = (uintptr_t *)((char *)stackaddr - stacksize); + uintptr_t *hi = (uintptr_t *)stackaddr; + if (mark >= lo && mark < hi) { + cur = lo; + end = hi; + } else { + lo = (uintptr_t *)stackaddr; + hi = (uintptr_t *)((char *)stackaddr + stacksize); + if (mark >= lo && mark < hi) { + cur = lo; + end = hi; + } + } + } +#elif defined(__linux__) + pthread_attr_t attr; + void *stackaddr = 0; + size_t stacksize = 0; + if (pthread_getattr_np(pthread_self(), &attr) == 0) { + if (pthread_attr_getstack(&attr, &stackaddr, &stacksize) == 0) { + cur = (uintptr_t *)stackaddr; + end = (uintptr_t *)((char *)stackaddr + stacksize); + } + pthread_attr_destroy(&attr); + } #endif + + if (cur == 0 || end == 0 || end <= cur) { + return; + } + if ((uintptr_t *)target >= cur && (uintptr_t *)target < end) { + return; + } + for (; cur < end; cur++) { + if (*cur == target) { + *cur = 0; + } + } } diff --git a/runtime/internal/lib/runtime/extern.go b/runtime/internal/lib/runtime/extern.go index 5ff8cb5a3b..7fa59cc749 100644 --- a/runtime/internal/lib/runtime/extern.go +++ b/runtime/internal/lib/runtime/extern.go @@ -25,7 +25,7 @@ func callerLocation(file string, line int) (string, int) { func Caller(skip int) (pc uintptr, file string, line int, ok bool) { if fpUnwindAvailable() { var pcs [1]uintptr - if fpCallers(skip+1, pcs[:]) >= 1 { + if callersWithPanicSplice(skip+1, pcs[:]) >= 1 { // pcs hold return addresses; attribute to the call instruction. sym := frameSymbol(pcs[0] - 1) file, line = callerLocation(sym.file, sym.line) @@ -48,7 +48,7 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) { //go:noinline func Callers(skip int, pc []uintptr) int { if fpUnwindAvailable() { - if n := fpCallers(skip, pc); n > 0 { + if n := callersWithPanicSplice(skip, pc); n > 0 { return n } } diff --git a/runtime/internal/lib/runtime/fault_unwind_llgo.go b/runtime/internal/lib/runtime/fault_unwind_llgo.go index 6ee21728d9..a666df0f4e 100644 --- a/runtime/internal/lib/runtime/fault_unwind_llgo.go +++ b/runtime/internal/lib/runtime/fault_unwind_llgo.go @@ -101,6 +101,7 @@ func onFault(pc, fp uintptr, sig int32) { n += m } faultN = int32(n) + rtdebug.StoreFaultPCs(faultPCs[:n]) } // Capture done: re-arm the recursion guard before this fault turns // into an ordinary (recoverable) panic. diff --git a/runtime/internal/lib/runtime/mfinal.go b/runtime/internal/lib/runtime/mfinal.go index 7ed607e65f..b2015bf3c5 100644 --- a/runtime/internal/lib/runtime/mfinal.go +++ b/runtime/internal/lib/runtime/mfinal.go @@ -44,14 +44,14 @@ func initFinalizerState() { } func SetFinalizer(obj any, finalizer any) { - objFace := (*eface)(unsafe.Pointer(&obj)) + objFace := *(*eface)(unsafe.Pointer(&obj)) if objFace._type == nil { throw("runtime.SetFinalizer: first argument is nil") } if objFace._type.Kind() != abi.Pointer { throw("runtime.SetFinalizer: first argument is " + objFace._type.String() + ", not pointer") } - objPtr := ifacePointerData(objFace) + objPtr := ifacePointerData(&objFace) if objPtr == nil { throw("runtime.SetFinalizer: first argument is nil") } @@ -67,7 +67,7 @@ func SetFinalizer(obj any, finalizer any) { } finalizerState.mu.Unlock() - finalizerFace := (*eface)(unsafe.Pointer(&finalizer)) + finalizerFace := *(*eface)(unsafe.Pointer(&finalizer)) if finalizerFace._type == nil { return } diff --git a/runtime/internal/lib/runtime/pprof_memprofile_go123_llgo.go b/runtime/internal/lib/runtime/pprof_memprofile_go123_llgo.go index 172930c92b..0cc01acadc 100644 --- a/runtime/internal/lib/runtime/pprof_memprofile_go123_llgo.go +++ b/runtime/internal/lib/runtime/pprof_memprofile_go123_llgo.go @@ -19,12 +19,17 @@ func pprof_memProfileInternal(p []pprofMemProfileRecord, inuseZero bool) (n int, if n == 0 { return 0, true } - var records [64]MemProfileRecord - if n > len(records) { - return n, false + // Size dynamically with slack and retry: a fixed cap makes pprof's + // retry-until-ok loop spin forever once the bucket set outgrows it. + records := make([]MemProfileRecord, n+n/4+16) + for attempt := 0; ; attempt++ { + n, ok = MemProfile(records, inuseZero) + if ok || attempt >= 3 { + break + } + records = make([]MemProfileRecord, n+n/4+16) } - n, ok = MemProfile(records[:n], inuseZero) - if !ok { + if !ok || len(p) < n { return n, false } for i := 0; i < n; i++ { diff --git a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go index 55d4ffb126..944203b6e0 100644 --- a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go +++ b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go @@ -42,29 +42,66 @@ type BlockProfileRecord struct { Stack []uintptr } +// trimMemProfileStack drops the allocator/runtime plumbing the physical +// capture recorded above the allocation site (AllocZ, the capture path +// itself) so record stacks start at user code like gc's. +func trimMemProfileStack(stk [32]uintptr) [32]uintptr { + i := 0 + for i < len(stk) && stk[i] != 0 { + if !isRuntimePlumbingFrame(stk[i]) { + break + } + i++ + } + if i == 0 { + return stk + } + var out [32]uintptr + copy(out[:], stk[i:]) + return out +} + +// isRuntimePlumbingFrame reports whether pc belongs to LLGo runtime +// plumbing (allocator, capture hooks — including their __llgo_stub. +// wrappers, which is how a hook held in a function variable is entered). +func isRuntimePlumbingFrame(pc uintptr) bool { + name := frameSymbol(pc - 1).function + if name == "" { + return false + } + const stub = "__llgo_stub." + if hasPrefix(name, stub) { + name = name[len(stub):] + } + return hasPrefix(name, "github.com/goplus/llgo/runtime/internal/") || + name == "runtime.captureMemProfileStack" +} + func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) { - n, _ = llrt.MemProfile(nil, inuseZero) - if len(p) < n { + // Size dynamically with slack and retry: sampling between a sizing + // call and its fill call can grow the bucket set, and a fixed cap + // would make callers that retry-until-ok (pprof) loop forever. + records := make([]llrt.MemProfileRecord, 64) + for attempt := 0; ; attempt++ { + n, ok = llrt.MemProfile(records, inuseZero) + if ok || attempt >= 3 { + break + } + records = make([]llrt.MemProfileRecord, n+n/4+16) + } + if !ok || len(p) < n { return n, false } if n == 0 { return 0, true } - var records [64]llrt.MemProfileRecord - if n > len(records) { - return n, false - } - n, ok = llrt.MemProfile(records[:n], inuseZero) - if !ok { - return n, false - } for i := 0; i < n; i++ { p[i] = MemProfileRecord{ AllocBytes: records[i].AllocBytes, FreeBytes: records[i].FreeBytes, AllocObjects: records[i].AllocObjects, FreeObjects: records[i].FreeObjects, - Stack0: records[i].Stack0, + Stack0: trimMemProfileStack(records[i].Stack0), } } return n, true diff --git a/runtime/internal/lib/runtime/runtime_gc.go b/runtime/internal/lib/runtime/runtime_gc.go index d8656f93a4..810d076ebd 100644 --- a/runtime/internal/lib/runtime/runtime_gc.go +++ b/runtime/internal/lib/runtime/runtime_gc.go @@ -36,11 +36,13 @@ func ReadMemStats(m *runtime.MemStats) { } func GC() { + bdwgc.ClearStack(nil) bdwgc.Gcollect() runFinalizers() // BDW finalizers are observed on a subsequent collection cycle. // Run one extra cycle so weak-pointer cleanup hooks (unique/weak) see // finalized state before we trigger map cleanup callbacks. + bdwgc.ClearStack(nil) bdwgc.Gcollect() runFinalizers() unique_runtime_notifyMapCleanup() diff --git a/runtime/internal/lib/runtime/symtab.go b/runtime/internal/lib/runtime/symtab.go index e229577144..f147219702 100644 --- a/runtime/internal/lib/runtime/symtab.go +++ b/runtime/internal/lib/runtime/symtab.go @@ -1605,6 +1605,17 @@ func refinePCSymbolLine(sym pcSymbol, pc uintptr) pcSymbol { lineSym.pc = pc return mergePCLineSymbol(sym, lineSym) } + // No same-function statement anchor covers pc. Mid-function pcs of a + // record with no evidence can actually belong to a foreign (C) + // function linked between Go functions — nearest-below cannot see the + // hole and would misattribute the frame to the preceding Go function. + // One dladdr cross-check on this cold, cache-backed path: a closer + // symbol wins. Exact entries and anchored functions never get here. + if pc != sym.entry { + if ai := addrInfoSymbol(pc); ai.ok && ai.entry > sym.entry { + return ai + } + } return sym } diff --git a/runtime/internal/lib/runtime/unwind_llgo.go b/runtime/internal/lib/runtime/unwind_llgo.go index f4811442e0..8c75b0bad2 100644 --- a/runtime/internal/lib/runtime/unwind_llgo.go +++ b/runtime/internal/lib/runtime/unwind_llgo.go @@ -5,14 +5,233 @@ package runtime import ( "unsafe" + latomic "github.com/goplus/llgo/runtime/internal/lib/sync/atomic" rtdebug "github.com/goplus/llgo/runtime/internal/runtime" ) //go:linkname c_framepointer C.llgo_framepointer func c_framepointer() unsafe.Pointer +//go:linkname c_framepointer2 C.llgo_framepointer +func c_framepointer2() unsafe.Pointer + func init() { rtdebug.PanicTraceback = panicTraceback + rtdebug.PanicPCSnapshot = capturePanicPCs + rtdebug.RecoverMark = recoverMark + rtdebug.PreallocPanicStore() + rtdebug.MemProfileStackCapture = captureMemProfileStack + rtdebug.MemProfileRatePtr = &MemProfileRate +} + +// captureMemProfileStack walks the physical stack at a sampled allocation. +// The leading allocator plumbing (AllocZ/AllocU, this capture path) is +// trimmed at read time by the MemProfile wrapper, where symbolization is +// safe and cached. +func captureMemProfileStack(pcs []uintptr) int { + if !fpUnwindAvailable() { + return 0 + } + // The frame-table init itself allocates; when such an allocation is + // sampled, walking from here would wait on the init latch this very + // thread holds (fpCallers -> init busy -> usleep forever — observed + // hanging whole net/rpc and net/http test binaries at the first + // testing.callerName). Drop the sample instead of waiting. Uninit is + // fine to enter: memProfileInSample is set for the whole sample, so + // the init's own allocations cannot re-sample. + if latomic.LoadUint32(&runtimeFuncPCInitState) == runtimeFuncInfoInitBusy { + return 0 + } + return fpCallers(0, pcs) +} + +// recoverMark records the recovering deferred frame (and one above, for +// wrapper-reached recover) so the panic snapshot stays spliceable while +// that frame is live. After siglongjmp the frame-pointer chain two levels +// up can point into a stale/reused stack region that is sometimes +// unmapped; probe each slot before dereferencing — an unguarded read here +// self-faults ~7% of the time, converting to a nil-deref panic that +// corrupts the value the recover was extracting (goroot reflectmake flake). +func recoverMark() { + // Record the helper's own frame address: it sits just below the + // recovering deferred frame, and the liveness gate tests interval + // containment, so the exact level does not matter — no chain reads, + // nothing that can touch a stale slot. + fp := uintptr(c_framepointer2()) + if fp == 0 { + return + } + rtdebug.MarkPanicRecoverFPs(fp, 0) +} + +// capturePanicPCs runs at panic time, before any longjmp unwinding, and +// stores the physical pc chain for later splicing (see spliceCallers). +func capturePanicPCs() { + if !fpUnwindAvailable() { + return + } + var pcs [64]uintptr + n := fpCallers(0, pcs[:]) + rtdebug.StorePanicPCs(pcs[:n]) +} + +// panicSplicePCs returns the snapshot when it is observable: either the +// panic is still in flight, or the deferred frame that recovered it is +// still live on the physical chain (gc keeps panic frames on the stack +// exactly that long). +func panicSplicePCs() []uintptr { + pcs := rtdebug.PanicPCs() + if len(pcs) == 0 { + return nil + } + if rtdebug.PanicActive() { + return pcs + } + mark, _ := rtdebug.PanicRecoverFPs() + if mark == 0 { + return nil + } + // The mark is a frame address recorded inside Recover's call chain; + // the recovering deferred frame is live iff the mark still lies + // within the current chain's span. Interval containment instead of + // exact equality: the hook's own frame depth differs across + // platforms (stub/wrapper layers), but any frame's [fp, parent fp) + // range straddling the mark proves the region is still stack, not + // reused heap or a dead extent. + fp := uintptr(c_framepointer()) + for i := 0; fp != 0 && i < maxPanicSpliceFrames; i++ { + if !memReadable(fp) { + break + } + prev := *(*uintptr)(unsafe.Pointer(fp)) + if fp <= mark && (prev > mark || prev == 0) { + return pcs + } + if prev <= fp || prev-fp > maxFPStride || prev&(unsafe.Sizeof(uintptr(0))-1) != 0 { + break + } + fp = prev + } + return nil +} + +const maxPanicSpliceFrames = 4096 + +// trimPlumbingPCs drops leading pcs attributed to the LLGo runtime core +// (panic machinery, the capture path) and cuts the tail at the first pc +// outside the program text — fault snapshots are captured without the +// text bound (see fpWalkFrom). +func trimPlumbingPCs(pcs []uintptr) []uintptr { + initRuntimeFuncPCFrames() + head := 0 + for head < len(pcs) { + sym := frameSymbol(pcs[head] - 1) + if sym.function != "" && (hasPrefix(sym.function, "github.com/goplus/llgo/runtime/internal/") || + sym.function == "runtime.capturePanicPCs" || sym.function == "runtime.onFault" || + sym.function == "runtime.fpWalkFrom") { + head++ + continue + } + break + } + // Keep the innermost panic-machinery frame: gc's logical stack has + // runtime.gopanic between the deferred function and the panic site, + // and fixed Caller depths (issue5856's Caller(2)) count it. Fault + // snapshots start at the fault pc and trim nothing — gc's walkers + // skip runtime frames by name there, not by depth. + if head > 0 { + head-- + } + pcs = pcs[head:] + if rtdebug.PanicPCsAreFault() { + // Fault snapshots come from a genuine interrupted context and the + // chain-discipline guards already bounded the walk; keep unnamed + // frames (linux dladdr cannot name non-dynamic C symbols — they + // display as raw pcs, like gc does for unknown frames). + return pcs + } + for i := 0; i < len(pcs); i++ { + if prebuiltTextContains(pcs[i]) { + continue + } + // Outside the Go text range: C frames in this binary (and its + // libraries) still resolve to a symbol via dladdr — keep those; + // cut at the first pc nothing can name (wild slots past the last + // FP-disciplined frame). + if frameSymbol(pcs[i]-1).function == "" { + return pcs[:i] + } + } + return pcs +} + +// spliceCallers rebuilds the caller view a deferred function should see +// during (or right after recovering) a panic: its own live frames, then the +// panic-site chain from the snapshot. The junction is the first live frame +// whose function also appears in the snapshot — the defer owner; the +// snapshot side wins there because the live copy\'s pc points at the +// longjmp resume site, not at the call that panicked. +func spliceCallers(cur []uintptr) []uintptr { + snap := panicSplicePCs() + if len(snap) == 0 { + return cur + } + snap = trimPlumbingPCs(snap) + if len(snap) == 0 { + return cur + } + // The junction is the first live frame whose function also appears in + // the snapshot — the defer owner (or the panicking function itself when + // defer and panic share a frame). Everything from there down is + // replaced by the whole snapshot: it already contains the owner and its + // callers, with the owner's pc on the panic path instead of the longjmp + // resume site. + for i := 0; i < len(cur); i++ { + entry := frameSymbol(cur[i] - 1).entry + if entry == 0 { + continue + } + for j := 0; j < len(snap); j++ { + if frameSymbol(snap[j]-1).entry == entry { + out := make([]uintptr, 0, i+len(snap)) + out = append(out, cur[:i]...) + out = append(out, snap...) + return out + } + } + } + return cur +} + +// callersWithPanicSplice is Callers with panic-frame splicing. With no +// snapshot stored (the overwhelmingly common case) it degrades to the +// plain walk at the cost of one TLS load. Otherwise the raw walk runs +// unskipped so splicing sees the junction frame, then the requested skip +// applies to the spliced view (matching gc, whose skip counts the logical +// panic-inclusive stack). +// +//go:noinline +func callersWithPanicSplice(skip int, pc []uintptr) int { + if len(pc) == 0 { + return 0 + } + if len(rtdebug.PanicPCs()) == 0 { + // One frame deeper than the extern.go call sites used to be. + return fpCallers(skip+1, pc) + } + var raw [128]uintptr + n := fpCallers(1, raw[:]) + if n <= 0 { + return 0 + } + view := spliceCallers(raw[:n]) + if skip < 0 { + skip = 0 + } + if skip >= len(view) { + return 0 + } + return copy(pc, view[skip:]) } func hasPrefix(s, prefix string) bool { @@ -39,8 +258,12 @@ func panicTraceback(skip int) bool { if n <= 0 { return false } + // A stored panic snapshot (Go panic or hardware fault, including + // faults inside C code) carries the frames the longjmp unwinding + // already removed; splice them in like Callers does. + view := spliceCallers(pcs[:n]) print("goroutine 1 [running]:\n") - frames := CallersFrames(pcs[:n]) + frames := CallersFrames(view) skippingPlumbing := true for { frame, more := frames.Next() diff --git a/runtime/internal/runtime/_wrap/fp.c b/runtime/internal/runtime/_wrap/fp.c new file mode 100644 index 0000000000..ac8cff69ce --- /dev/null +++ b/runtime/internal/runtime/_wrap/fp.c @@ -0,0 +1,11 @@ +/* llgo_framepointer lives in the runtime core (not the public runtime + * package): Recover() records the recovering frame through it, and + * programs that never import "runtime" still link the core. */ +__attribute__((noinline)) void *llgo_framepointer(void) +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_frame_address(0); +#else + return 0; +#endif +} diff --git a/runtime/internal/runtime/caller.go b/runtime/internal/runtime/caller.go index 341158e825..ef333ff9a8 100644 --- a/runtime/internal/runtime/caller.go +++ b/runtime/internal/runtime/caller.go @@ -226,7 +226,124 @@ func Callers(skip int, pcs []uintptr) int { return n } +// PreallocPanicStore forces the per-thread snapshot buffer to exist now, +// on the current (main) thread, so the fault handler never mallocs in +// signal context — bdwgc's allocator is not async-signal-safe. +func PreallocPanicStore() { + panicPCStoreFor(true) +} + +// PanicPCSnapshot, set by the public runtime package at init, captures the +// physical pc chain at panic time into the per-thread snapshot below. gc +// runs deferred functions on top of the panicked stack, so runtime.Caller, +// CallersFrames and debug.Stack invoked from a deferred function (before or +// after recover) see the panic-site frames; LLGo's longjmp unwinding +// removes them physically, and this snapshot is what caller-info APIs +// splice back in. +var PanicPCSnapshot func() + func SavePanicCallerFrames() { + // A fault handler stores the fault-site snapshot right before it + // panics; the regular capture here must not overwrite it. + if p := panicPCStoreFor(false); p != nil && p.armed != 0 { + p.armed = 0 + return + } + if PanicPCSnapshot != nil { + PanicPCSnapshot() + } +} + +type panicPCStore struct { + n int32 + armed int32 + fault int32 + recFP1 uintptr + recFP2 uintptr + pcs [64]uintptr +} + +func panicPCStoreFor(create bool) *panicPCStore { + p := (*panicPCStore)(panicPCsKey.Get()) + if p == nil && create { + // AllocRoot (uncollectable): the only reference lives in a pthread + // key bdwgc does not scan, so an AllocU store would be collected + // and its memory reused, making p.n read garbage (observed as a + // wild slice bound in panicSplicePCs). Never freed — one small + // fixed struct per thread. + p = (*panicPCStore)(AllocRoot(unsafe.Sizeof(panicPCStore{}))) + p.n = 0 + p.recFP1 = 0 + p.recFP2 = 0 + panicPCsKey.Set(unsafe.Pointer(p)) + } + return p +} + +// StorePanicPCs replaces the thread's panic snapshot (a new panic +// supersedes the previous one) and resets the recover marks. +func StorePanicPCs(pcs []uintptr) { + storePanicPCs(pcs, 0) +} + +// StoreFaultPCs is StorePanicPCs for fault handlers: the imminent +// panic's own capture is suppressed so the fault-site chain survives. +func StoreFaultPCs(pcs []uintptr) { + storePanicPCs(pcs, 1) +} + +func storePanicPCs(pcs []uintptr, armed int32) { + p := panicPCStoreFor(true) + n := len(pcs) + if n > len(p.pcs) { + n = len(p.pcs) + } + copy(p.pcs[:n], pcs) + p.n = int32(n) + p.armed = armed + p.fault = armed + p.recFP1 = 0 + p.recFP2 = 0 +} + +// PanicPCsAreFault reports whether the stored snapshot came from a +// hardware-fault context (captured without the program-text bound). +func PanicPCsAreFault() bool { + p := panicPCStoreFor(false) + return p != nil && p.fault != 0 +} + +// PanicPCs returns the thread's captured panic pcs (nil when none). +func PanicPCs() []uintptr { + p := panicPCStoreFor(false) + if p == nil || p.n == 0 { + return nil + } + return p.pcs[:p.n] +} + +// MarkPanicRecoverFPs records the frames observing the panic at recover +// time; the snapshot stays spliceable exactly while one of them is live on +// the physical chain (the deferred function has not returned yet). +func MarkPanicRecoverFPs(fp1, fp2 uintptr) { + if p := panicPCStoreFor(false); p != nil { + p.recFP1 = fp1 + p.recFP2 = fp2 + } +} + +// PanicRecoverFPs returns the recover-time frame marks. +func PanicRecoverFPs() (uintptr, uintptr) { + p := panicPCStoreFor(false) + if p == nil { + return 0, 0 + } + return p.recFP1, p.recFP2 +} + +// PanicActive reports whether a panic is in flight (not yet recovered). +func PanicActive() bool { + return panicKey.Get() != nil } func BindCallerLocation(pc uintptr, rawName string) { diff --git a/runtime/internal/runtime/errors.go b/runtime/internal/runtime/errors.go index 06d63ef66c..6fe6436219 100644 --- a/runtime/internal/runtime/errors.go +++ b/runtime/internal/runtime/errors.go @@ -171,7 +171,29 @@ func PanicTypeAssert(concrete *_type, asserted string, missingMethod string) { if missingMethod != "" { panic(errorString("interface conversion: " + concrete.String() + " is not " + asserted + ": missing method " + missingMethod)) } - panic(errorString("interface conversion: interface is " + concrete.String() + ", not " + asserted)) + cs := concrete.String() + msg := "interface conversion: interface is " + cs + ", not " + asserted + if sameTypeAssertName(concrete, cs, asserted) { + msg += " (types from different scopes)" + } + panic(errorString(msg)) +} + +func sameTypeAssertName(concrete *_type, concreteString, asserted string) bool { + if concreteString == asserted { + return true + } + pkg := pkgpath(concrete) + return pkg != "" && hasPrefix(asserted, pkg+".") && typeNameSuffix(concreteString) == typeNameSuffix(asserted) +} + +func typeNameSuffix(name string) string { + for i := len(name) - 1; i >= 0; i-- { + if name[i] == '.' { + return name[i+1:] + } + } + return name } func (e *TypeAssertionError) Error() string { @@ -200,6 +222,52 @@ func (e *TypeAssertionError) Error() string { ": missing method " + e.missingMethod } +func TypeAssertError(have, want, iface *Type) any { + if have == nil { + return &TypeAssertionError{iface, nil, want, ""} + } + missingMethod := "" + if want.Kind() == abi.Interface { + missingMethod = typeAssertMissingMethod((*interfacetype)(unsafe.Pointer(want)), have) + } + return &TypeAssertionError{iface, have, want, missingMethod} +} + +func typeAssertMissingMethod(inter *interfacetype, typ *_type) string { + if len(inter.Methods) == 0 { + return "" + } + if typ.Kind() == abi.Interface { + v := (*interfacetype)(unsafe.Pointer(typ)) + for _, tm := range inter.Methods { + if !ifaceHasMethod(v.Methods, tm) { + return tm.Name() + } + } + return "" + } + u := typ.Uncommon() + if u == nil { + return inter.Methods[0].Name() + } + methods := u.Methods() + for _, tm := range inter.Methods { + if _, ok := findMethod(methods, tm); !ok { + return tm.Name() + } + } + return "" +} + +func ifaceHasMethod(methods []abi.Imethod, target abi.Imethod) bool { + for _, method := range methods { + if method.Name_ == target.Name_ && method.Typ_ == target.Typ_ { + return true + } + } + return false +} + func pkgpath(t *_type) string { if u := t.Uncommon(); u != nil { return u.PkgPath_ diff --git a/runtime/internal/runtime/memprofile.go b/runtime/internal/runtime/memprofile.go index c6221ca1dd..dad06088aa 100644 --- a/runtime/internal/runtime/memprofile.go +++ b/runtime/internal/runtime/memprofile.go @@ -1,5 +1,7 @@ package runtime +import "unsafe" + // MemProfileRecord describes allocations aggregated by size class. type MemProfileRecord struct { AllocBytes, FreeBytes int64 @@ -60,20 +62,186 @@ var memProfileBuckets = [...]memProfileBucket{ {size: 1073741824}, } +// MemProfileStackCapture, set by the public runtime package, walks the +// physical stack at a sampled allocation. When present, MemProfile reports +// stack-keyed buckets (gc semantics: heapsampling.go attributes sampled +// bytes to call stacks); without it the legacy size-class counters remain +// (baremetal, wasm). +var MemProfileStackCapture func(pcs []uintptr) int + +// MemProfileRatePtr points at the public runtime.MemProfileRate variable +// (user-settable at any time, read per sampling decision). +var MemProfileRatePtr *int + +type memStackBucket struct { + next *memStackBucket + hash uintptr + allocBytes memProfileCounter + allocObjects memProfileCounter + nstk int32 + stk [32]uintptr +} + +const memStackTabSize = 512 // power of two + +var memStackTab [memStackTabSize]*memStackBucket + +// memProfileRemaining counts down allocated bytes to the next sample. +// Thresholds are drawn uniformly from [1, 2*rate) — mean rate — because a +// deterministic stride starves small allocation sites when they interleave +// with larger ones (every crossing lands on the big sites; gc randomizes +// for the same reason). Benign races: sampling is statistical. +var ( + memProfileRemaining uintptr + memProfileRandState uint64 = 0x9e3779b97f4a7c15 +) + +func memProfileNextThreshold(rate int) uintptr { + // Exponentially distributed with mean rate, like gc's fastexprand: + // the memoryless property is required — with any bounded-support + // distribution a near-periodic allocation pattern phase-locks the + // sampling points onto the large sites and skews per-site estimates + // (observed 1.6x on goroot heapsampling's interleaved sizes). + x := memProfileRandState + x ^= x >> 12 + x ^= x << 25 + x ^= x >> 27 + memProfileRandState = x + r := (x * 0x2545f4914f6cdd1d) >> 11 // 53 random bits + u := float64(r) / (1 << 53) + if u < 1e-12 { + u = 1e-12 + } + t := -lnApprox(u) * float64(rate) + if t < 1 { + t = 1 + } + if max := float64(rate) * 64; t > max { + t = max + } + return uintptr(t) +} + +// lnApprox computes ln(u) for u in (0,1] via exponent split and an +// atanh series on the mantissa — a few 1e-6s of relative error, far +// below sampling noise. +func lnApprox(u float64) float64 { + const ln2 = 0.6931471805599453 + bits := *(*uint64)(unsafe.Pointer(&u)) + e := int((bits>>52)&0x7ff) - 1023 + mbits := (bits &^ (uint64(0x7ff) << 52)) | (uint64(1023) << 52) + m := *(*float64)(unsafe.Pointer(&mbits)) // in [1, 2) + z := (m - 1) / (m + 1) + z2 := z * z + lnm := 2 * z * (1 + z2/3 + z2*z2/5 + z2*z2*z2/7) + return float64(e)*ln2 + lnm +} + +// memProfileInSample breaks the recursion: allocating a bucket node (and +// anything the capture path allocates) re-enters recordMemProfileAlloc. +// Benign-racy flag — a concurrent thread skipping one sample is fine. +var memProfileInSample bool + func recordMemProfileAlloc(size uintptr) { if size == 0 { return } - size = memProfileSizeClass(size) + if MemProfileStackCapture != nil && MemProfileRatePtr != nil { + // The guard covers the whole decision path: threshold drawing and + // stack capture may themselves allocate (escaping locals, the + // bucket node), and a recursive sample would overflow the stack. + if memProfileInSample { + return + } + memProfileInSample = true + rate := *MemProfileRatePtr + if rate <= 0 { + memProfileInSample = false + return + } + if rate == 1 { + sampleMemProfileStack(size) + memProfileInSample = false + return + } + // Mirror gc's mcache.nextSample: subtract, sample once on + // crossing, redraw. Records hold RAW sampled counts — consumers + // (pprof, goroot heapsampling.go) apply the Poisson correction + // (scaleHeapSample) themselves, exactly like with gc. + if memProfileRemaining == 0 { + memProfileRemaining = memProfileNextThreshold(rate) + } + if size < memProfileRemaining { + memProfileRemaining -= size + memProfileInSample = false + return + } + memProfileRemaining = memProfileNextThreshold(rate) + sampleMemProfileStack(size) + memProfileInSample = false + return + } + sizeClass := memProfileSizeClass(size) for i := range memProfileBuckets { b := &memProfileBuckets[i] - if b.size == size { + if b.size == sizeClass { memProfileAddObject(&b.objects) return } } } +func sampleMemProfileStack(size uintptr) { + // Tiny allocations occupy at least one 16-byte granule (bdwgc's + // minimum on 64-bit, matching gc's tiny size class): report the + // granule so bytes/objects ratios match what the allocator really + // spends — pprof consumers and the tiny-allocation tests key on it. + if size < 16 { + size = 16 + } + var pcs [32]uintptr + n := MemProfileStackCapture(pcs[:]) + if n <= 0 { + return + } + var h uintptr = 5381 + for i := 0; i < n; i++ { + h = h*33 + pcs[i] + } + slot := h & (memStackTabSize - 1) + for b := memStackTab[slot]; b != nil; b = b.next { + if b.hash == h && int(b.nstk) == n && memStackEqual(b, pcs[:n]) { + memStackAdd(b, size) + return + } + } + b := (*memStackBucket)(AllocZ(unsafe.Sizeof(memStackBucket{}))) + b.hash = h + b.nstk = int32(n) + copy(b.stk[:], pcs[:n]) + memStackAdd(b, size) + // Benign-racy publish: a lost insert loses one sample, never corrupts + // (nodes are immutable once linked and the list is prepend-only). + b.next = memStackTab[slot] + memStackTab[slot] = b +} + +func memStackEqual(b *memStackBucket, pcs []uintptr) bool { + for i := range pcs { + if b.stk[i] != pcs[i] { + return false + } + } + return true +} + +// memStackAdd records one raw sampled allocation (gc semantics: no +// scaling here; readers un-bias with the sampling-rate correction). +func memStackAdd(b *memStackBucket, size uintptr) { + memProfileAddN(&b.allocObjects, 1) + memProfileAddN(&b.allocBytes, uint64(size)) +} + func memProfileSizeClass(size uintptr) uintptr { if size <= 16 { return 16 @@ -87,6 +255,9 @@ func memProfileSizeClass(size uintptr) uintptr { } func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) { + if MemProfileStackCapture != nil && MemProfileRatePtr != nil { + return memProfileStacks(p) + } for i := range memProfileBuckets { if memProfileLoadObjects(&memProfileBuckets[i].objects) != 0 { n++ @@ -110,3 +281,41 @@ func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) { } return n, true } + +func memProfileStacks(p []MemProfileRecord) (n int, ok bool) { + // Freeze sampling while enumerating so the count cannot grow between + // a caller's sizing call and its fill call. Explicit reset instead of + // defer: the wasm backend crashes in instruction selection on the + // deferred closure here. + memProfileInSample = true + n, ok = memProfileStacksLocked(p) + memProfileInSample = false + return n, ok +} + +func memProfileStacksLocked(p []MemProfileRecord) (n int, ok bool) { + for i := range memStackTab { + for b := memStackTab[i]; b != nil; b = b.next { + n++ + } + } + if len(p) < n { + return n, false + } + j := 0 + for i := range memStackTab { + for b := memStackTab[i]; b != nil; b = b.next { + if j >= len(p) { + break + } + r := MemProfileRecord{ + AllocBytes: int64(memProfileLoadObjects(&b.allocBytes)), + AllocObjects: int64(memProfileLoadObjects(&b.allocObjects)), + } + copy(r.Stack0[:], b.stk[:b.nstk]) + p[j] = r + j++ + } + } + return n, true +} diff --git a/runtime/internal/runtime/memprofile_atomic.go b/runtime/internal/runtime/memprofile_atomic.go index d31ae8e9f0..995629200c 100644 --- a/runtime/internal/runtime/memprofile_atomic.go +++ b/runtime/internal/runtime/memprofile_atomic.go @@ -10,6 +10,10 @@ func memProfileAddObject(p *memProfileCounter) { atomic.Add(p, memProfileCounter(1)) } +func memProfileAddN(p *memProfileCounter, n uint64) { + atomic.Add(p, memProfileCounter(n)) +} + func memProfileLoadObjects(p *memProfileCounter) memProfileCounter { return atomic.Load(p) } diff --git a/runtime/internal/runtime/memprofile_baremetal.go b/runtime/internal/runtime/memprofile_baremetal.go index 29ba9ab6d9..967288c312 100644 --- a/runtime/internal/runtime/memprofile_baremetal.go +++ b/runtime/internal/runtime/memprofile_baremetal.go @@ -4,6 +4,10 @@ package runtime type memProfileCounter = uintptr +func memProfileAddN(p *memProfileCounter, n uint64) { + *p += memProfileCounter(n) +} + func memProfileAddObject(p *memProfileCounter) { *p = *p + 1 } diff --git a/runtime/internal/runtime/z_baremetal.go b/runtime/internal/runtime/z_baremetal.go index a862225852..7a77953664 100644 --- a/runtime/internal/runtime/z_baremetal.go +++ b/runtime/internal/runtime/z_baremetal.go @@ -21,6 +21,10 @@ func Rethrow(link *Defer) { c.Printf(c.Str("fatal error\n")) c.Exit(2) } else { + if ptr := panicKey.Get(); ptr != nil && link == GetThreadDefer() { + node := (*panicNode)(ptr) + node.defer_ = link + } setjmp.Longjmp((*setjmp.JmpBuf)(link.Addr), 1) } } diff --git a/runtime/internal/runtime/z_default.go b/runtime/internal/runtime/z_default.go index d0757f9cb1..d2d9e994f8 100644 --- a/runtime/internal/runtime/z_default.go +++ b/runtime/internal/runtime/z_default.go @@ -3,6 +3,8 @@ package runtime import ( + "unsafe" + c "github.com/goplus/llgo/runtime/internal/clite" "github.com/goplus/llgo/runtime/internal/clite/debug" "github.com/goplus/llgo/runtime/internal/clite/pthread" @@ -16,15 +18,20 @@ var ( // Rethrow rethrows a panic. func Rethrow(link *Defer) { - if ptr := excepKey.Get(); ptr != nil { + if ptr := panicKey.Get(); ptr != nil { if link == nil { - TracePanic(*(*any)(ptr)) + node := (*panicNode)(ptr) + TracePanic(node.arg) if PanicTraceback == nil || !PanicTraceback(2) { debug.PrintStack(2) } - c.Free(ptr) + c.Free(unsafe.Pointer(node)) c.Exit(2) } else { + node := (*panicNode)(ptr) + if link == (*Defer)(c.GoDeferData()) { + node.defer_ = link + } c.Siglongjmp(link.Addr, 1) } } else if ptr := goexitKey.Get(); ptr != nil { diff --git a/runtime/internal/runtime/z_face.go b/runtime/internal/runtime/z_face.go index f118f90bfb..a578529db7 100644 --- a/runtime/internal/runtime/z_face.go +++ b/runtime/internal/runtime/z_face.go @@ -221,10 +221,16 @@ func DirectIfaceData(typ *abi.Type) bool { func MatchesClosure(T, V *abi.Type) bool { if T == V { return true - } else if V == nil || !V.IsClosure() { + } else if T == nil || V == nil { return false } - return identicalFuncType(T.StructType().Fields[0].Typ, V.StructType().Fields[0].Typ) + if T.IsClosure() { + T = T.StructType().Fields[0].Typ + } + if V.IsClosure() { + V = V.StructType().Fields[0].Typ + } + return identicalFuncType(T, V) } func identicalFuncType(T, V *abi.Type) bool { diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index f5a9e78ff3..406fb2e238 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -36,34 +36,99 @@ type Defer struct { Args unsafe.Pointer // defer func and args links } +type panicNode struct { + prev unsafe.Pointer + arg any + defer_ *Defer +} + // Recover recovers a panic. -func Recover() (ret any) { - ptr := excepKey.Get() +func Recover(token unsafe.Pointer) (ret any) { + if token == nil || token != recoverFrameKey.Get() { + return nil + } + ptr := panicKey.Get() if ptr != nil { - excepKey.Set(nil) - ret = *(*any)(ptr) - c.Free(ptr) + node := (*panicNode)(ptr) + if node.defer_ != (*Defer)(c.GoDeferData()) { + return nil + } + panicKey.Set(node.prev) + recoverFrameKey.Set(nil) + ret = node.arg + c.Free(unsafe.Pointer(node)) + // The deferred function that recovers keeps observing the panic + // stack until it returns (gc runs defers on top of it). The public + // runtime marks its frame so the pc snapshot stays spliceable that + // long; the mark reads the frame-pointer chain, which after + // siglongjmp can reach a stale/unmapped slot, so the guarded read + // lives in the package that has a page probe (RecoverMark). Nil + // when lib/runtime is not linked — no snapshot machinery, nothing + // to mark. + if RecoverMark != nil { + RecoverMark() + } } return } +// RecoverMark, set by the public runtime package, records the recovering +// frame for panic-snapshot splicing. +var RecoverMark func() + +const ( + // LLGoFiles: the frame-pointer helper must live in the runtime core — + // programs that never import "runtime" still link Recover. + LLGoFiles = "_wrap/fp.c" +) + +//go:linkname c_framepointer C.llgo_framepointer +func c_framepointer() unsafe.Pointer + +// StartRecoverFrame enables direct recover calls made by the deferred function +// currently being invoked from frame. +func StartRecoverFrame(frame unsafe.Pointer) unsafe.Pointer { + old := recoverFrameKey.Get() + recoverFrameKey.Set(frame) + return old +} + +// EndRecoverFrame restores direct recover permission after a deferred call. +func EndRecoverFrame(frame unsafe.Pointer) { + recoverFrameKey.Set(frame) +} + +// StartRecoverFrameAlias maps a direct deferred closure wrapper to the wrapped +// function while the wrapper calls into it. +func StartRecoverFrameAlias(from, to unsafe.Pointer) unsafe.Pointer { + old := recoverFrameKey.Get() + if old == from { + recoverFrameKey.Set(to) + } + return old +} + // Panic panics with a value. func Panic(v any) { if v == nil { v = &PanicNilError{} } SavePanicCallerFrames() - ptr := c.Malloc(unsafe.Sizeof(v)) - *(*any)(ptr) = v - excepKey.Set(ptr) + ptr := (*panicNode)(c.Malloc(unsafe.Sizeof(panicNode{}))) + ptr.prev = panicKey.Get() + ptr.arg = v + ptr.defer_ = (*Defer)(c.GoDeferData()) + panicKey.Set(unsafe.Pointer(ptr)) Rethrow((*Defer)(c.GoDeferData())) } var ( - excepKey pthread.Key - goexitKey pthread.Key - mainThread pthread.Thread + panicKey pthread.Key + recoverFrameKey pthread.Key + goexitKey pthread.Key + panicPCsKey pthread.Key + mainThread pthread.Thread ) func Goexit() { @@ -72,8 +137,10 @@ func Goexit() { } func init() { - excepKey.Create(nil) + panicKey.Create(nil) + recoverFrameKey.Create(nil) goexitKey.Create(nil) + panicPCsKey.Create(nil) mainThread = pthread.Self() } @@ -86,9 +153,12 @@ func TracePanic(v any) { println("\n") } -// PanicTraceback, when set by the public runtime package, prints a -// Go-style stack trace for an unrecovered panic and reports whether it -// printed anything; the clite frame dump remains the fallback. +// PanicTraceback, when set by the public runtime package, prints a Go-style +// stack trace (function + file:line per frame) for an unrecovered panic and +// reports whether it printed anything. skip counts physical frames above the +// runtime plumbing frame that invokes the hook, matching clite +// debug.PrintStack's convention. When unset or when it reports false, the +// caller falls back to the clite frame dump. var PanicTraceback func(skip int) bool // PanicSignal converts a hardware signal into the same Go panic the diff --git a/runtime/internal/runtime/z_signal.go b/runtime/internal/runtime/z_signal.go index 1283dff626..e0e98bce9a 100644 --- a/runtime/internal/runtime/z_signal.go +++ b/runtime/internal/runtime/z_signal.go @@ -38,11 +38,15 @@ const ( // For wasm platform compatibility, signal handling is excluded via build tags. // See PR #1059 for wasm platform requirements. func init() { - signal.Signal(SIGSEGV, func(v c.Int) { - if v == SIGSEGV { + handleFault := func(v c.Int) { + if v == SIGSEGV || v == SIGBUS { panic(errorString("invalid memory address or nil pointer dereference")) } var buf [20]byte panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v))))) - }) + } + signal.Signal(SIGSEGV, handleFault) + if SIGBUS != 0 { + signal.Signal(SIGBUS, handleFault) + } } diff --git a/runtime/internal/runtime/z_signal_darwin.go b/runtime/internal/runtime/z_signal_darwin.go new file mode 100644 index 0000000000..d34ee86607 --- /dev/null +++ b/runtime/internal/runtime/z_signal_darwin.go @@ -0,0 +1,7 @@ +//go:build darwin && !wasm && !baremetal + +package runtime + +import c "github.com/goplus/llgo/runtime/internal/clite" + +const SIGBUS = c.Int(0xa) diff --git a/runtime/internal/runtime/z_signal_linux.go b/runtime/internal/runtime/z_signal_linux.go new file mode 100644 index 0000000000..ff74f64db4 --- /dev/null +++ b/runtime/internal/runtime/z_signal_linux.go @@ -0,0 +1,7 @@ +//go:build linux && !wasm && !baremetal + +package runtime + +import c "github.com/goplus/llgo/runtime/internal/clite" + +const SIGBUS = c.Int(0x7) diff --git a/runtime/internal/runtime/z_signal_other.go b/runtime/internal/runtime/z_signal_other.go new file mode 100644 index 0000000000..5be18c7bb6 --- /dev/null +++ b/runtime/internal/runtime/z_signal_other.go @@ -0,0 +1,7 @@ +//go:build !darwin && !linux && !wasm && !baremetal + +package runtime + +import c "github.com/goplus/llgo/runtime/internal/clite" + +const SIGBUS = c.Int(0) diff --git a/ssa/abitype.go b/ssa/abitype.go index 0588a532d5..a42d944a59 100644 --- a/ssa/abitype.go +++ b/ssa/abitype.go @@ -398,16 +398,16 @@ type UncommonType struct { } */ -func (b Builder) abiUncommonType(t types.Type, mset *types.MethodSet) llvm.Value { +func (b Builder) abiUncommonType(t types.Type, methods []*types.Selection) llvm.Value { prog := b.Prog ft := prog.rtType("uncommonType") var fields []llvm.Value _, pkgPath := b.abiUncommonPkg(t) fields = append(fields, b.Str(pkgPath).impl) - mcount := mset.Len() + mcount := len(methods) var xcount int for i := 0; i < mcount; i++ { - if ast.IsExported(mset.At(i).Obj().Name()) { + if ast.IsExported(methods[i].Obj().Name()) { xcount++ } } @@ -427,10 +427,10 @@ type Method struct { } */ -func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Value { +func (b Builder) abiUncommonMethods(t types.Type, methods []*types.Selection) llvm.Value { prog := b.Prog ft := prog.rtType("Method") - n := mset.Len() + n := len(methods) fields := make([]llvm.Value, n) pkg, _ := b.abiUncommonPkg(t) anonymous := pkg == nil @@ -438,7 +438,7 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va pkg = types.NewPackage(b.Pkg.Path(), "") } for i := 0; i < n; i++ { - m := mset.At(i) + m := methods[i] obj := m.Obj() mName := obj.Name() abiName := mName @@ -468,6 +468,20 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va return llvm.ConstArray(ft.ll, fields) } +func (b Builder) abiInterfaceMethods(mset *types.MethodSet) []*types.Selection { + n := mset.Len() + methods := make([]*types.Selection, 0, n) + for i := 0; i < n; i++ { + m := mset.At(i) + fn, _ := m.Obj().(*types.Func) + if b.Prog.isNoInterfaceMethod(fn) { + continue + } + methods = append(methods, m) + } + return methods +} + // closure func type func funcType(prog Program, typ types.Type) types.Type { ftyp := prog.Type(typ, InGo) @@ -524,10 +538,11 @@ func (b Builder) abiType(t types.Type) Expr { t = prog.patchType(t) } mset, hasUncommon := b.abiUncommonMethodSet(t) - methodCount := 0 - if mset != nil { - methodCount = mset.Len() + var methods []*types.Selection + if hasUncommon { + methods = b.abiInterfaceMethods(mset) } + methodCount := len(methods) rt := prog.rtNamed(prog.abi.RuntimeName(t)) var typ types.Type = rt if hasUncommon { @@ -552,15 +567,15 @@ func (b Builder) abiType(t types.Type) Expr { if hasUncommon { fields = []llvm.Value{ llvm.ConstNamedStruct(prog.Type(rt, InGo).ll, fields), - b.abiUncommonType(t, mset), - b.abiUncommonMethods(t, mset), + b.abiUncommonType(t, methods), + b.abiUncommonMethods(t, methods), } } g.impl.SetInitializer(llvm.ConstNamedStruct(g.impl.GlobalValueType(), fields)) g.impl.SetGlobalConstant(true) g.impl.SetLinkage(llvm.WeakODRLinkage) if prog.enableGoGlobalDCE { - prog.addMethodTypeMetadata(g.impl, prog.Type(typ, InGo), mset, methodCount) + prog.addMethodTypeMetadata(g.impl, prog.Type(typ, InGo), methods) } prog.abiSymbol[name] = &AbiSymbol{Name: name, PkgPath: pkg.Path(), Raw: t, Typ: g.Type, MSet: mset} } diff --git a/ssa/closure_wrap.go b/ssa/closure_wrap.go index 470a299caf..abac18e99a 100644 --- a/ssa/closure_wrap.go +++ b/ssa/closure_wrap.go @@ -53,20 +53,38 @@ func closureWrapArgs(fn Function) []Expr { return args } -// closureWrapReturn returns from wrapper, preserving tail-call eligibility. -func closureWrapReturn(b Builder, sig *types.Signature, ret Expr) { +// closureWrapReturn returns from wrapper, preserving tail-call eligibility when +// the wrapper does not need post-call cleanup. +func closureWrapReturn(b Builder, sig *types.Signature, ret Expr, tail bool) { n := sig.Results().Len() if n == 0 { - if !ret.impl.IsNil() { + if tail && !ret.impl.IsNil() { ret.impl.SetTailCall(true) } b.impl.CreateRetVoid() return } - ret.impl.SetTailCall(true) + if tail { + ret.impl.SetTailCall(true) + } b.impl.CreateRet(ret.impl) } +func closureWrapCall(b Builder, wrap Function, fn Expr, args []Expr, aliasRecover bool) (Expr, bool) { + if !aliasRecover || (b.Prog.rt == nil && b.Prog.rtget == nil) { + return b.Call(fn, args...), true + } + prog := b.Prog + prev := b.Call( + b.Pkg.rtFunc("StartRecoverFrameAlias"), + b.PtrCast(prog.VoidPtr(), wrap.Expr), + b.PtrCast(prog.VoidPtr(), fn), + ) + ret := b.Call(fn, args...) + b.Call(b.Pkg.rtFunc("EndRecoverFrame"), prev) + return ret, false +} + // closureWrapDecl wraps a function declaration that lacks __llgo_ctx. // It directly calls the target symbol and ignores the ctx parameter. func (p Package) closureWrapDecl(fn Expr, sig *types.Signature) Function { @@ -80,8 +98,8 @@ func (p Package) closureWrapDecl(fn Expr, sig *types.Signature) Function { wrap.impl.SetLinkage(llvm.LinkOnceAnyLinkage) b := wrap.MakeBody(1) args := closureWrapArgs(wrap) - ret := b.Call(fn, args...) - closureWrapReturn(b, sig, ret) + ret, tail := closureWrapCall(b, wrap, fn, args, fn.mayRecover()) + closureWrapReturn(b, sig, ret, tail) return wrap } @@ -105,7 +123,7 @@ func (p Package) closureWrapPtr(sig *types.Signature) Function { fnPtr := b.Convert(fnPtrType, ctxArg) fnVal := b.Load(fnPtr) args := closureWrapArgs(wrap) - ret := b.Call(fnVal, args...) - closureWrapReturn(b, sig, ret) + ret, tail := closureWrapCall(b, wrap, fnVal, args, true) + closureWrapReturn(b, sig, ret, tail) return wrap } diff --git a/ssa/dispose_test.go b/ssa/dispose_test.go index 1e9b9a94fd..5652cd0cfa 100644 --- a/ssa/dispose_test.go +++ b/ssa/dispose_test.go @@ -30,3 +30,26 @@ func TestProgramDispose(t *testing.T) { } prog.Dispose() // idempotent } + +func TestProgramDisposeDropsMayRecoverMarks(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("foo", "foo") + fn := pkg.NewFunc("recovering", NoArgsNoRet, InGo) + fn.Expr.MarkMayRecover() + if !fn.Expr.mayRecover() { + t.Fatal("marked function should be recover-capable") + } + + ctx := prog.ctx + prog.Dispose() + if _, ok := mayRecoverPrograms.Load(ctx); ok { + t.Fatal("disposed program remains in may-recover registry") + } + + next := NewProgram(nil) + defer next.Dispose() + nextFn := next.NewPackage("bar", "bar").NewFunc("plain", NoArgsNoRet, InGo) + if nextFn.Expr.mayRecover() { + t.Fatal("may-recover mark leaked into a later program") + } +} diff --git a/ssa/eh.go b/ssa/eh.go index b8ead4eb64..74b43a1c97 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -165,11 +165,12 @@ type aDefer struct { // The id uniquely identifies the defer call site for dispatch during drain. // typ is the node struct type needed to decode the linked-list node. type loopDeferCase struct { - id Expr - typ Type - fn Expr - args []Expr - buildCall func(Builder, Expr, ...Expr) Expr + id Expr + typ Type + mayRecover bool + fn Expr + args []Expr + buildCall func(Builder, Expr, ...Expr) Expr } const ( @@ -319,6 +320,11 @@ func (b Builder) DeferStackDrain() { // Defer emits a defer instruction. func (b Builder) Defer(kind DoAction, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { + b.DeferRecover(kind, deferMayRecover(fn), fn, buildCall, args...) +} + +// DeferRecover emits a defer instruction with explicit recover capability. +func (b Builder) DeferRecover(kind DoAction, mayRecover bool, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { dbgInstrCall("Defer", fn, args) var prog Program var nextbit Expr @@ -346,14 +352,20 @@ func (b Builder) Defer(kind DoAction, fn Expr, buildCall func(Builder, Expr, ... } typ := b.saveDeferArgs(self, kind, id, fn, args) if kind == DeferInLoop { - loopCase := loopDeferCase{id: id, typ: typ, fn: fn, args: args, buildCall: buildCall} + loopCase := loopDeferCase{id: id, typ: typ, mayRecover: mayRecover, fn: fn, args: args, buildCall: buildCall} self.loopCases = append(self.loopCases, loopCase) } - b.appendDeferStmt(self, kind, typ, buildCall, fn, args, nextbit) + b.appendDeferStmt(self, kind, typ, mayRecover, buildCall, fn, args, nextbit) } // DeferTo emits a defer instruction into an explicit runtime defer stack. func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { + b.DeferToRecover(owner, stack, deferMayRecover(fn), fn, buildCall, args...) +} + +// DeferToRecover emits a defer instruction into an explicit runtime defer +// stack with explicit recover capability. +func (b Builder) DeferToRecover(owner Function, stack Expr, mayRecover bool, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { if debugInstr { logCall("DeferTo", fn, args) } @@ -367,11 +379,12 @@ func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Bui argsPtr := b.PtrCast(b.Prog.Pointer(b.Prog.VoidPtr()), stack) typ := b.saveDeferArgsTo(argsPtr, DeferInLoop, id, fn, args) loopCase := loopDeferCase{ - id: id, - typ: typ, - fn: fn, - args: args, - buildCall: buildCall, + id: id, + typ: typ, + mayRecover: mayRecover, + fn: fn, + args: args, + buildCall: buildCall, } if self == nil { owner.pendingLoopCases = append(owner.pendingLoopCases, loopCase) @@ -380,7 +393,7 @@ func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Bui self.loopCases = append(self.loopCases, loopCase) } -func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr, nextbit Expr) { +func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, mayRecover bool, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr, nextbit Expr) { self.stmts = append(self.stmts, func(bits Expr) { switch kind { case DeferInCond: @@ -391,13 +404,13 @@ func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCal zero := prog.Val(uintptr(0)) has := b.BinOp(token.NEQ, b.BinOp(token.AND, bits, nextbit), zero) b.IfThen(has, func() { - b.callDefer(self, typ, buildCall, fn, args) + b.callDefer(self, typ, mayRecover, buildCall, fn, args) }) case DeferAlways: // Leaving a run of loop defers; allow the next loop-defer statement // (earlier in source order) to generate its own drainer. self.loopDrainerGenerated = false - b.callDefer(self, typ, buildCall, fn, args) + b.callDefer(self, typ, mayRecover, buildCall, fn, args) case DeferInLoop: b.loopDeferDrainer(self) } @@ -457,7 +470,7 @@ func (b Builder) loopDeferDrainer(self *aDefer) { b.SetBlockEx(caseBlks[i], AtEnd, true) b.Store(self.rethPtr, drainEntryAddr) - b.callDefer(self, c.typ, c.buildCall, c.fn, c.args) + b.callDefer(self, c.typ, c.mayRecover, c.buildCall, c.fn, c.args) b.Jump(condBlk) } @@ -512,9 +525,11 @@ func (b Builder) saveDeferArgsTo(argsPtr Expr, kind DoAction, id Expr, fn Expr, return typ } -func (b Builder) callDefer(self *aDefer, typ Type, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr) { +func (b Builder) callDefer(self *aDefer, typ Type, mayRecover bool, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr) { if typ == nil { - buildCall(b, fn, args...) + b.callRecoverScopedDefer(fn, mayRecover, func() { + buildCall(b, fn, args...) + }) return } prog := b.Prog @@ -537,10 +552,69 @@ func (b Builder) callDefer(self *aDefer, typ Type, buildCall func(Builder, Expr, args[i] = b.getField(data, i+offset) } b.Call(b.Pkg.rtFunc("FreeDeferNode"), ptr) - buildCall(b, fn, args...) + b.callRecoverScopedDefer(fn, mayRecover, func() { + buildCall(b, fn, args...) + }) }) } +func (b Builder) callRecoverScopedDefer(fn Expr, mayRecover bool, call func()) { + if fn.IsNil() || fn.impl.IsNil() || isRecoverBuiltin(fn) { + call() + return + } + token := b.recoverDeferToken(fn, mayRecover) + if token.IsNil() { + call() + return + } + prev := b.Call(b.Pkg.rtFunc("StartRecoverFrame"), token) + call() + b.Call(b.Pkg.rtFunc("EndRecoverFrame"), prev) +} + +func (b Builder) recoverDeferToken(fn Expr, mayRecover bool) Expr { + switch fn.kind { + case vkClosure: + if !mayRecover { + return Nil + } + return b.PtrCast(b.Prog.VoidPtr(), b.Field(fn, 0)) + case vkFuncDecl: + if !mayRecover { + return Nil + } + return b.PtrCast(b.Prog.VoidPtr(), fn) + case vkFuncPtr: + return b.PtrCast(b.Prog.VoidPtr(), fn) + } + return Nil +} + +func deferMayRecover(fn Expr) bool { + if fn.IsNil() || fn.Type == nil { + return false + } + switch fn.kind { + case vkClosure, vkFuncDecl: + return fn.mayRecover() + case vkFuncPtr: + return true + } + return false +} + +func isRecoverBuiltin(fn Expr) bool { + if fn.IsNil() { + return false + } + if fn.kind != vkBuiltin { + return false + } + bi, ok := fn.raw.Type.(*builtinTy) + return ok && bi.name == "recover" +} + // RunDefers emits instructions to run deferred instructions. func (b Builder) RunDefers() { self := b.getDefer(DeferInCond) @@ -610,8 +684,7 @@ func (b Builder) Unreachable() { // Recover emits a recover instruction. func (b Builder) Recover() Expr { dbgInstrln("Recover") - // TODO(xsw): recover can't be a function call in Go - return b.Call(b.Pkg.rtFunc("Recover")) + return b.Call(b.Pkg.rtFunc("Recover"), b.PtrCast(b.Prog.VoidPtr(), b.Func.Expr)) } // Panic emits a panic instruction. diff --git a/ssa/eh_defer_test.go b/ssa/eh_defer_test.go deleted file mode 100644 index 5f99729b1e..0000000000 --- a/ssa/eh_defer_test.go +++ /dev/null @@ -1,158 +0,0 @@ -//go:build !llgo -// +build !llgo - -package ssa_test - -import ( - "strings" - "testing" - - "github.com/goplus/llgo/ssa" - "github.com/goplus/llgo/ssa/ssatest" -) - -func TestExplicitDeferStackIR(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo) - cb := callee.MakeBody(1) - cb.Return() - cb.EndBuild() - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - fn.SetRecover(fn.MakeBlock()) - - stack := b.BuiltinCall("ssa:deferstack") - b.Return() - b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true) - b.DeferTo(fn, stack, callee.Expr, ssa.Builder.Call) - b.DeferStackDrain() - b.RunDefers() - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if !strings.Contains(ir, "FreeDeferNode") { - t.Fatalf("expected explicit defer stack node cleanup in IR, got:\n%s", ir) - } - if !strings.Contains(ir, "sigsetjmp") && !strings.Contains(ir, "setjmp") { - t.Fatalf("expected explicit defer stack setup in IR, got:\n%s", ir) - } -} - -func TestExplicitDeferStackFallbackAndNilBuiltin(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo) - cb := callee.MakeBody(1) - cb.Return() - cb.EndBuild() - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - stack := b.BuiltinCall("ssa:deferstack") - if stack.Type != prog.VoidPtr() { - t.Fatalf("ssa:deferstack without recover returned %v, want %v", stack.Type, prog.VoidPtr()) - } - b.DeferTo(nil, stack, callee.Expr, ssa.Builder.Call) - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if strings.Contains(ir, "sigsetjmp") || strings.Contains(ir, "setjmp") { - t.Fatalf("unexpected defer stack setup without recover, got:\n%s", ir) - } -} - -func TestExplicitDeferStackDrainWithoutLoopCases(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - fn.SetRecover(fn.MakeBlock()) - - _ = b.BuiltinCall("ssa:deferstack") - b.DeferStackDrain() - b.RunDefers() - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if strings.Contains(ir, "FreeDeferNode") { - t.Fatalf("unexpected explicit defer cleanup without loop cases, got:\n%s", ir) - } - if !strings.Contains(ir, "sigsetjmp") && !strings.Contains(ir, "setjmp") { - t.Fatalf("expected defer stack setup with recover, got:\n%s", ir) - } -} - -func TestExplicitDeferStackDrainWithoutRecoverNoop(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - b.DeferStackDrain() - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if strings.Contains(ir, "FreeDeferNode") || strings.Contains(ir, "sigsetjmp") || strings.Contains(ir, "setjmp") { - t.Fatalf("unexpected defer stack machinery without recover, got:\n%s", ir) - } -} - -func TestPlainDeferWithoutSavedArgsIR(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo) - cb := callee.MakeBody(1) - cb.Return() - cb.EndBuild() - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - fn.SetRecover(fn.MakeBlock()) - b.Defer(ssa.DeferAlways, callee.Expr, ssa.Builder.Call) - b.RunDefers() - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if strings.Contains(ir, "FreeDeferNode") { - t.Fatalf("plain zero-arg defer should not allocate defer nodes, got:\n%s", ir) - } - if !strings.Contains(ir, "call void @callee()") { - t.Fatalf("expected direct deferred call in IR, got:\n%s", ir) - } -} - -func TestConditionalDeferIR(t *testing.T) { - prog := ssatest.NewProgram(t, nil) - pkg := prog.NewPackage("foo", "foo") - - callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo) - cb := callee.MakeBody(1) - cb.Return() - cb.EndBuild() - - fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) - b := fn.MakeBody(1) - fn.SetRecover(fn.MakeBlock()) - b.Return() - b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true) - b.Defer(ssa.DeferInCond, callee.Expr, ssa.Builder.Call) - b.RunDefers() - b.Return() - b.EndBuild() - - ir := pkg.Module().String() - if !strings.Contains(ir, "or i64") || !strings.Contains(ir, "and i64") { - t.Fatalf("expected conditional defer bitmask operations in IR, got:\n%s", ir) - } -} diff --git a/ssa/expr.go b/ssa/expr.go index caee097c06..0109d9321d 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -23,6 +23,7 @@ import ( "go/token" "go/types" "log" + "sync" "github.com/xgo-dev/llvm" ) @@ -48,6 +49,29 @@ type Expr struct { var Nil Expr // Zero value is a nil Expr +var mayRecoverPrograms sync.Map // llvm.Context -> Program + +func registerMayRecoverProgram(p Program) { + mayRecoverPrograms.Store(p.ctx, p) +} + +func unregisterMayRecoverProgram(p Program) { + mayRecoverPrograms.Delete(p.ctx) +} + +func (v Expr) mayRecoverProgram() Program { + if v.Type == nil || (v.kind != vkFuncDecl && v.kind != vkFuncPtr) || v.impl.IsNil() { + return nil + } + module := v.impl.GlobalParent() + if module.IsNil() { + return nil + } + p, _ := mayRecoverPrograms.Load(module.Context()) + prog, _ := p.(Program) + return prog +} + // IsNil checks if the expression is nil or not. func (v Expr) IsNil() bool { return v.Type == nil @@ -59,6 +83,29 @@ func (v Expr) SetOrdering(ordering AtomicOrdering) Expr { return v } +// SetVolatile marks a load or store as volatile. +func (v Expr) SetVolatile(volatile bool) Expr { + v.impl.SetVolatile(volatile) + return v +} + +// MarkMayRecover marks a function or closure that may call recover directly. +func (v Expr) MarkMayRecover() Expr { + if p := v.mayRecoverProgram(); p != nil { + p.mayRecoverFuncs[v.impl.Name()] = none{} + } + return v +} + +func (v Expr) mayRecover() bool { + p := v.mayRecoverProgram() + if p == nil { + return false + } + _, ok := p.mayRecoverFuncs[v.impl.Name()] + return ok +} + func (v Expr) SetName(alias string) Expr { v.impl.SetName(alias) return v diff --git a/ssa/globaldce.go b/ssa/globaldce.go index 55f06559cc..cb83c2a2bc 100644 --- a/ssa/globaldce.go +++ b/ssa/globaldce.go @@ -276,8 +276,8 @@ func (p Function) recordFakeUse(v llvm.Value) { p.fakeUses = append(p.fakeUses, v) } -func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, mset *types.MethodSet, methodCount int) { - if methodCount == 0 { +func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, methods []*types.Selection) { + if len(methods) == 0 { return } p.setVCallVisibilityMetadata(global, vcallVisibilityLinkageUnit) @@ -287,8 +287,7 @@ func (p Program) addMethodTypeMetadata(global llvm.Value, fullType Type, mset *t ifnOffset := p.OffsetOf(methodType, abiMethodIFnFieldIndex) tfnOffset := p.OffsetOf(methodType, abiMethodTFnFieldIndex) methodStride := p.SizeOf(methodType) - for i := 0; i < methodCount; i++ { - sel := mset.At(i) + for i, sel := range methods { baseOffset := methodArrayOffset + uint64(i)*methodStride p.addTypeMetadata(global, baseOffset+ifnOffset, methodCapabilityKey(sel.Obj().(*types.Func))) if sel.Obj().Exported() { diff --git a/ssa/interface.go b/ssa/interface.go index 2539c3187e..9d18128be0 100644 --- a/ssa/interface.go +++ b/ssa/interface.go @@ -331,8 +331,15 @@ func (b Builder) TypeAssert(x Expr, assertedTyp Type, commaOk bool) Expr { blks := b.Func.MakeBlocks(2) b.If(eq, blks[0], blks[1]) b.SetBlockEx(blks[1], AtEnd, false) - b.Call(b.Pkg.rtFunc("PanicTypeAssert"), tx, b.Str(assertedTyp.RawType().String()), b.Str(typeAssertMissingMethod(assertedTyp))) - b.Unreachable() + // Panic with a real *runtime.TypeAssertionError (gc semantics: the + // recovered value implements runtime.Error; the missing method is + // computed at runtime from the abi tables). The source-interface + // abi type is deliberately not passed: materializing abiType for + // arbitrary static interface types here can reference another + // package's private local-generic symbols (undefined at link); + // the message's interface name is the documented mdempsky/16 + // residual, pending that abi emission fix. + b.Panic(b.InlineCall(b.Pkg.rtFunc("TypeAssertError"), tx, tabi, b.Prog.Nil(b.Prog.AbiTypePtr()))) b.SetBlockEx(blks[0], AtEnd, false) b.blk.last = blks[0].last return val() diff --git a/ssa/memory.go b/ssa/memory.go index 565e705ba9..d5847f515b 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -63,6 +63,34 @@ func (b Builder) aggregateValue(t Type, flds ...llvm.Value) Expr { return Expr{aggregateValue(b.impl, t.ll, flds...), t} } +// LoadAndClearSinglePointer atomically copies a pointer-sized value out of ptr +// and clears the source slot. It handles either *P or *struct{P}. +func (b Builder) LoadAndClearSinglePointer(ptr Expr) (Expr, bool) { + elem := b.Prog.Elem(ptr.Type) + if elem.ll.TypeKind() == llvm.PointerTypeKind { + old := b.loadAndClearPointerWord(ptr.impl, elem.ll) + return Expr{old, elem}, true + } + + st, ok := types.Unalias(elem.RawType()).Underlying().(*types.Struct) + if !ok || st.NumFields() != 1 { + return Nil, false + } + field := b.Prog.rawType(st.Field(0).Type()) + if field.ll.TypeKind() != llvm.PointerTypeKind { + return Nil, false + } + fieldPtr := llvm.CreateStructGEP(b.impl, elem.ll, ptr.impl, 0) + old := b.loadAndClearPointerWord(fieldPtr, field.ll) + return b.aggregateValue(elem, old), true +} + +func (b Builder) loadAndClearPointerWord(ptr llvm.Value, typ llvm.Type) llvm.Value { + old := llvm.CreateLoad(b.impl, typ, ptr) + b.impl.CreateStore(llvm.ConstNull(typ), ptr) + return old +} + func aggregateValue(b llvm.Builder, tll llvm.Type, flds ...llvm.Value) llvm.Value { agg := llvm.Undef(tll) for i, fld := range flds { diff --git a/ssa/package.go b/ssa/package.go index 5b0e9dd411..eb3f57245f 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -211,6 +211,7 @@ type aProgram struct { memsetInlineTy *types.Signature stackSaveTy *types.Signature stackRestoreTy *types.Signature + frameAddressTy *types.Signature createKeyTy *types.Signature getSpecTy *types.Signature @@ -224,6 +225,7 @@ type aProgram struct { paramObjPtr_ *types.Var linkname map[string]string // pkgPath.nameInPkg => linkname + noInterface map[string]none // pkgPath.T.method or pkgPath.(*T).method abiSymbol map[string]*AbiSymbol // abi symbol name => AbiSymbol ptrSize int @@ -239,6 +241,7 @@ type aProgram struct { enableFuncInfoMetadata bool enableFuncInfoSites bool + mayRecoverFuncs map[string]none } type AbiSymbol struct { @@ -280,6 +283,7 @@ func (p Program) Dispose() { return } p.disposed = true + unregisterMayRecoverProgram(p) p.tm.Dispose() p.td.Dispose() p.ctx.Dispose() @@ -310,8 +314,10 @@ func NewProgram(target *Target) Program { ctx: ctx, gocvt: newGoTypes(), target: target, td: td, tm: tm, is32Bits: is32Bits, ptrSize: td.PointerSize(), named: make(map[string]Type), fnnamed: make(map[string]int), - linkname: make(map[string]string), abiSymbol: make(map[string]*AbiSymbol), + linkname: make(map[string]string), noInterface: make(map[string]none), abiSymbol: make(map[string]*AbiSymbol), + mayRecoverFuncs: make(map[string]none), } + registerMayRecoverProgram(prog) prog.abi.Init(uintptr(prog.ptrSize), (*goProgram)(unsafe.Pointer(prog))) return prog } @@ -359,6 +365,22 @@ func (p Program) EnableLTOPluginMarkers(enable bool) { p.enableLTOPluginMarker = enable } +func (p Program) SetNoInterfaceMethod(fullName string) { + p.noInterface[fullName] = none{} +} + +func (p Program) isNoInterfaceMethod(fn *types.Func) bool { + if fn == nil { + return false + } + sig, ok := fn.Type().(*types.Signature) + if !ok || sig.Recv() == nil { + return false + } + _, ok = p.noInterface[FuncName(fn.Pkg(), fn.Name(), sig.Recv(), true)] + return ok +} + // SetRuntime sets the runtime. // Its type can be *types.Package or func() *types.Package. func (p Program) SetRuntime(runtime any) { diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 6f1365b057..d7012f6b89 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -165,6 +165,68 @@ func TestTooManyConditionalDefers(t *testing.T) { } } +func TestRecoverDeferTokenHelpers(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("foo", "foo") + + callee := pkg.NewFunc("callee", NoArgsNoRet, InGo) + b := callee.MakeBody(1) + + if Nil.mayRecover() { + t.Fatal("nil expression should not be marked recover-capable") + } + if deferMayRecover(callee.Expr) { + t.Fatal("unmarked function declaration should not be recover-capable") + } + if token := b.recoverDeferToken(callee.Expr, false); !token.IsNil() { + t.Fatalf("recover token without mayRecover = %v, want nil", token) + } + + callee.Expr.MarkMayRecover() + if !deferMayRecover(callee.Expr) { + t.Fatal("marked function declaration should be recover-capable") + } + if token := b.recoverDeferToken(callee.Expr, true); token.IsNil() { + t.Fatal("marked function declaration should produce a recover token") + } + + fnPtr := b.ChangeType(prog.rawType(NoArgsNoRet), callee.Expr) + if !fnPtr.mayRecover() { + t.Fatal("marked function should stay recover-capable after pointer retagging") + } + if !deferMayRecover(fnPtr) { + t.Fatal("function pointer should be treated as recover-capable") + } + if token := b.recoverDeferToken(fnPtr, false); token.IsNil() { + t.Fatal("function pointer should produce a recover token") + } + if token := b.recoverDeferToken(prog.Val(1), true); !token.IsNil() { + t.Fatalf("non-function recover token = %v, want nil", token) + } + + if deferMayRecover(Nil) { + t.Fatal("nil expression should not be recover-capable") + } + if deferMayRecover(prog.Val(1)) { + t.Fatal("non-function expression should not be recover-capable") + } + if !isRecoverBuiltin(Builtin("recover")) { + t.Fatal("recover builtin should be recognized") + } + if isRecoverBuiltin(Builtin("panic")) { + t.Fatal("non-recover builtin should not be recognized") + } + if isRecoverBuiltin(Nil) { + t.Fatal("nil expression should not be a recover builtin") + } + if isRecoverBuiltin(callee.Expr) { + t.Fatal("function declaration should not be a recover builtin") + } + + b.Return() + b.EndBuild() +} + func TestPointerSize(t *testing.T) { expected := unsafe.Sizeof(uintptr(0)) if size := NewProgram(nil).PointerSize(); size != int(expected) { @@ -734,9 +796,8 @@ func TestDevLTOGlobalDCEAddMethodTypeMetadataEarlyReturns(t *testing.T) { prog.EnableGoGlobalDCE(true) pkg := prog.NewPackage("main", "main") g := pkg.NewVarEx("g", prog.Pointer(prog.Int())) - mset := types.NewMethodSet(types.Typ[types.Int]) - prog.addMethodTypeMetadata(g.impl, prog.Pointer(prog.Int()), mset, 0) + prog.addMethodTypeMetadata(g.impl, prog.Pointer(prog.Int()), nil) ir := pkg.String() if strings.Contains(ir, "!vcall_visibility") || strings.Contains(ir, "!type !") { @@ -767,7 +828,7 @@ func TestDevLTOGlobalDCEAddMethodTypeMetadataMarksIFnAndTFnForReflectContexts(t methodArray := prog.Type(types.NewArray(prog.rtNamed("Method"), 1), InGo) fullType := prog.Struct(prog.Int(), prog.Int(), methodArray) - prog.addMethodTypeMetadata(g.impl, fullType, mset, mset.Len()) + prog.addMethodTypeMetadata(g.impl, fullType, []*types.Selection{mset.At(0)}) methodType := prog.Type(prog.rtNamed("Method"), InGo) methodArrayOffset := prog.OffsetOf(fullType, 2) @@ -1088,15 +1149,23 @@ _llgo_0: define linkonce i64 @%s(ptr %%0, i64 %%1) { _llgo_0: %%2 = load ptr, ptr %%0, align 8 - %%3 = tail call i64 %%2(i64 %%1) - ret i64 %%3 + %%3 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.StartRecoverFrameAlias"(ptr @%s, ptr %%2) + %%4 = call i64 %%2(i64 %%1) + call void @"github.com/goplus/llgo/runtime/internal/runtime.EndRecoverFrame"(ptr %%3) + ret i64 %%4 } +; Function Attrs: null_pointer_is_valid +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.StartRecoverFrameAlias"(ptr, ptr) #0 + +; Function Attrs: null_pointer_is_valid +declare void @"github.com/goplus/llgo/runtime/internal/runtime.EndRecoverFrame"(ptr) #0 + ; Function Attrs: null_pointer_is_valid declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) #0 attributes #0 = { null_pointer_is_valid "frame-pointer"="non-leaf" } -`, wrapRef, wrapRef) +`, wrapRef, wrapRef, wrapRef) assertPkg(t, pkg, expected) } @@ -1458,7 +1527,7 @@ func TestClosureWrapHelpers(t *testing.T) { if args := closureWrapArgs(wrap); len(args) != 0 { t.Fatalf("closureWrapArgs should return 0 args, got %d", len(args)) } - closureWrapReturn(b, sig, Expr{}) + closureWrapReturn(b, sig, Expr{}, true) } func TestClosureWrapCache(t *testing.T) { @@ -1641,6 +1710,71 @@ func TestZeroSizedLoadEmitsNilDerefGuard(t *testing.T) { } } +func TestLoadAndClearSinglePointer(t *testing.T) { + prog := NewProgram(nil) + prog.sizes = types.SizesFor("gc", runtime.GOARCH) + pkg := prog.NewPackage("bar", "foo/bar") + + ptrToInt := types.NewPointer(types.Typ[types.Int]) + wrapStruct := types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, nil, "p", ptrToInt, false), + }, nil) + + params := types.NewTuple( + types.NewVar(0, nil, "p", types.NewPointer(ptrToInt)), + types.NewVar(0, nil, "s", types.NewPointer(wrapStruct)), + ) + results := types.NewTuple( + types.NewVar(0, nil, "", ptrToInt), + types.NewVar(0, nil, "", wrapStruct), + ) + sig := types.NewSignatureType(nil, nil, nil, params, results, false) + fn := pkg.NewFunc("loadAndClear", sig, InGo) + b := fn.MakeBody(1) + pv, ok := b.LoadAndClearSinglePointer(fn.Param(0)) + if !ok { + t.Fatal("pointer slot should be load-and-clearable") + } + sv, ok := b.LoadAndClearSinglePointer(fn.Param(1)) + if !ok { + t.Fatal("single-pointer struct slot should be load-and-clearable") + } + if got, want := sv.impl.Type().String(), sv.Type.ll.String(); got != want { + t.Fatalf("single-pointer struct load-and-clear type = %s, want %s", got, want) + } + b.Return(pv, sv) + b.EndBuild() + + ir := fn.impl.String() + if got := strings.Count(ir, "store ptr null"); got != 2 { + t.Fatalf("LoadAndClearSinglePointer should clear both pointer slots, got %d stores:\n%s", got, ir) + } + if got := strings.Count(ir, "load ptr"); got < 2 { + t.Fatalf("LoadAndClearSinglePointer should load both pointer slots, got %d loads:\n%s", got, ir) + } + + noPtrStruct := types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, nil, "i", types.Typ[types.Int], false), + }, nil) + multiStruct := types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, nil, "p", ptrToInt, false), + types.NewField(token.NoPos, nil, "q", ptrToInt, false), + }, nil) + falseCases := []types.Type{ + types.NewPointer(types.Typ[types.Int]), + types.NewPointer(noPtrStruct), + types.NewPointer(multiStruct), + } + for i, typ := range falseCases { + fn := pkg.NewFunc(fmt.Sprintf("rejectLoadAndClear%d", i), types.NewSignatureType(nil, nil, nil, + types.NewTuple(types.NewVar(0, nil, "p", typ)), nil, false), InGo) + b := fn.MakeBody(1) + if _, ok := b.LoadAndClearSinglePointer(fn.Param(0)); ok { + t.Fatalf("LoadAndClearSinglePointer accepted %v", typ) + } + } +} + func TestTypeAssertSingleElemArrayUsesInsertValue(t *testing.T) { prog := NewProgram(nil) prog.sizes = types.SizesFor("gc", runtime.GOARCH) @@ -2606,6 +2740,48 @@ func TestInitAbiTypesForEmptySelection(t *testing.T) { } } +func TestNoInterfaceMethodRegistryAndFiltering(t *testing.T) { + prog := NewProgram(nil) + if prog.isNoInterfaceMethod(nil) { + t.Fatal("nil function should not be nointerface") + } + + pkgTypes := types.NewPackage("example.com/p", "p") + named := types.NewNamed(types.NewTypeName(token.NoPos, pkgTypes, "T", nil), types.NewStruct(nil, nil), nil) + sig := types.NewSignatureType(types.NewVar(token.NoPos, pkgTypes, "", named), nil, nil, nil, nil, false) + hidden := types.NewFunc(token.NoPos, pkgTypes, "Hidden", sig) + visible := types.NewFunc(token.NoPos, pkgTypes, "Visible", sig) + named.AddMethod(hidden) + named.AddMethod(visible) + + top := types.NewFunc(token.NoPos, pkgTypes, "Top", types.NewSignatureType(nil, nil, nil, nil, nil, false)) + if prog.isNoInterfaceMethod(top) { + t.Fatal("function without receiver should not be nointerface") + } + if prog.isNoInterfaceMethod(hidden) { + t.Fatal("unregistered method should not be nointerface") + } + prog.SetNoInterfaceMethod("example.com/p.T.Hidden") + if !prog.isNoInterfaceMethod(hidden) { + t.Fatal("registered value receiver method should be nointerface") + } + if prog.isNoInterfaceMethod(visible) { + t.Fatal("unregistered sibling method should not be nointerface") + } + + methods := (&aBuilder{Prog: prog}).abiInterfaceMethods(types.NewMethodSet(named)) + if len(methods) != 1 || methods[0].Obj().Name() != "Visible" { + t.Fatalf("filtered methods = %v, want only Visible", methods) + } + + ptrSig := types.NewSignatureType(types.NewVar(token.NoPos, pkgTypes, "", types.NewPointer(named)), nil, nil, nil, nil, false) + ptrHidden := types.NewFunc(token.NoPos, pkgTypes, "PtrHidden", ptrSig) + prog.SetNoInterfaceMethod("example.com/p.(*T).PtrHidden") + if !prog.isNoInterfaceMethod(ptrHidden) { + t.Fatal("registered pointer receiver method should be nointerface") + } +} + func TestRtFuncResolvesLinkname(t *testing.T) { prog := NewProgram(nil) rt := types.NewPackage(PkgRuntime, PkgRuntime) diff --git a/test/_manualtest/README.md b/test/_manualtest/README.md index ddb96abd29..69ce1f1300 100644 --- a/test/_manualtest/README.md +++ b/test/_manualtest/README.md @@ -44,18 +44,20 @@ runtime-internal, patched-stdlib and startup frames may differ. # SIGFPE Verified (darwin/arm64 + linux/arm64 + linux/amd64): -- SIGSEGV in a C frame converts to a Go panic; recover observes gc's exact - error text. -- Known gaps (recorded for the follow-up PRs): - 1. The fault-site stack (cexc_leaf_segv -> cexc_mid_segv x3 -> cexc_segv - -> Go frames) is not visible yet — recover/tracebacks show the - post-longjmp stack. The panic-snapshot follow-up extends to signal - handlers: walk the FP chain from the ucontext pc/fp; C frames get - dladdr names, Go frames funcinfo names — same machinery as the - unwinder. - 2. Only SIGSEGV is installed; SIGFPE (amd64 division) core-dumps, SIGBUS - is not handled. - 3. No sigaltstack: on stack overflow the handler cannot run and the +- SIGSEGV/SIGBUS/SIGFPE in a C frame convert to Go panics (gc's exact + error texts); recover works, and the fault-site stack — C frames down + through the Go callers — appears in recovered `debug.Stack()` and in + unrecovered tracebacks (panic pc snapshot captured from the signal + ucontext, FP chain walked from the interrupted frame; C is compiled + with -fno-omit-frame-pointer so x86-64 chains hold). +- Known limitations: + 1. On linux, C frames may display under a neighboring Go function's + name: dladdr only sees dynamic symbols there, so the nearest-below + table attribution cannot be cross-checked (darwin names C frames + correctly via dladdr). Pre-existing for any C pc between Go + functions; the link-phase hole-sentinel follow-up fixes naming and + misattribution together. + 2. No sigaltstack: on stack overflow the handler cannot run and the process dies (gc prints "stack overflow"). Note that C-side UB gets propagated by clang (this test was once optimized into infinite recursion); wrap/fault.c uses a volatile pointer to prevent that. diff --git a/test/go/caller_acceptance_test.go b/test/go/caller_acceptance_test.go index d078765025..a3406f59dd 100644 --- a/test/go/caller_acceptance_test.go +++ b/test/go/caller_acceptance_test.go @@ -25,6 +25,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -422,6 +423,7 @@ func TestCallerAcceptanceCFaultRecover(t *testing.T) { import ( "fmt" "os" + "runtime/debug" _ "unsafe" ) @@ -437,7 +439,7 @@ func viaGo() { cexcSegv(2) } -func main() { +func one(last bool) { defer func() { r := recover() if r == nil { @@ -448,10 +450,22 @@ func main() { if !ok || err.Error() != "runtime error: invalid memory address or nil pointer dereference" { panic(r) } - os.Stdout.WriteString("CFAULT_OK\n") + if last { + os.Stdout.WriteString("CFAULT_OK\n") + os.Stdout.Write(debug.Stack()) + } }() viaGo() } + +func main() { + // Three sequential faults: a handler that leaves the signal blocked + // after the longjmp escape (savemask=0 jmpbufs) survives the first + // fault and core-dumps on the second — the exact CI failure mode. + one(false) + one(false) + one(true) +} ` const csrc = `#include @@ -460,9 +474,11 @@ func main() { static int32_t *volatile cexc_null; volatile int32_t cexc_marks; -static void cexc_leaf(void) { *cexc_null = 42; } +/* Non-static: the fault-chain tail cut keeps frames dladdr can name; + * static helpers have no symbol and would end the visible chain. */ +void cexc_leaf(void) { *cexc_null = 42; } -static void cexc_mid(int32_t depth) { +void cexc_mid(int32_t depth) { if (depth > 0) { cexc_mid(depth - 1); cexc_marks++; @@ -489,6 +505,20 @@ void cexc_segv(int32_t depth) { if !strings.Contains(out, "CFAULT_OK") { t.Fatalf("C fault probe missing marker:\n%s", out) } + // The fault-site chain must be visible from the recovered deferred + // function. C frame names come from dladdr, which on linux only sees + // dynamic symbols — there the C frames show as raw pcs and only the + // Go side of the chain is asserted (foreign-symbol naming is the + // planned link-phase hole-sentinel follow-up). + wants := []string{"main.viaGo", "main.main"} + if runtime.GOOS == "darwin" { + wants = append(wants, "cexc_segv") + } + for _, want := range wants { + if !strings.Contains(out, want) { + t.Fatalf("recovered stack missing fault frame %q:\n%s", want, out) + } + } } func writeCallerAcceptanceModule(t *testing.T, dir string, files map[string]string) { @@ -541,3 +571,80 @@ func runLLGoInModule(t *testing.T, dir string, args ...string) (string, error) { out, err := cmd.CombinedOutput() return string(out), err } + +// Scenario: runtime.MemProfile attributes sampled allocations to exact +// call stacks and statement lines (gc semantics: raw sampled counts, +// physical frames). rate=1 samples everything, so counts are exact. +func TestCallerAcceptanceMemProfileAttribution(t *testing.T) { + dir := t.TempDir() + const src = `package main + +import ( + "os" + "runtime" + "strconv" +) + +var s1, s2 *[2048]byte + +func allocOne() { s1 = new([2048]byte) } // MEMA_MARK + +func allocTwo() { s2 = new([2048]byte) } // MEMB_MARK + +func main() { + runtime.MemProfileRate = 1 + for i := 0; i < 50; i++ { + allocOne() + allocTwo() + } + var recs [128]runtime.MemProfileRecord + n, ok := runtime.MemProfile(recs[:], true) + if !ok { + panic("MemProfile failed: n=" + strconv.Itoa(n)) + } + var oneObjs, twoObjs int64 + for _, r := range recs[:n] { + frames := runtime.CallersFrames(r.Stack()) + first := true + for { + f, more := frames.Next() + if first { + first = false + switch { + case f.Function == "main.allocOne" && f.Line == MEMA_LINE: + oneObjs += r.AllocObjects + case f.Function == "main.allocTwo" && f.Line == MEMB_LINE: + twoObjs += r.AllocObjects + } + } + if !more { + break + } + } + } + if oneObjs != 50 || twoObjs != 50 { + panic("bad attribution: one=" + strconv.Itoa(int(oneObjs)) + " two=" + strconv.Itoa(int(twoObjs))) + } + os.Stdout.WriteString("MEMPROF_OK\n") +} +` + source := src + for _, name := range []string{"MEMA", "MEMB"} { + line := markerLine(source, name+"_MARK") + if line == 0 { + t.Fatalf("marker %s_MARK not found", name) + } + source = strings.ReplaceAll(source, name+"_LINE", strconv.Itoa(line)) + } + writeCallerAcceptanceModule(t, dir, map[string]string{ + "main.go": source, + "go.mod": "module memprof\n\ngo 1.21\n", + }) + out, err := runLLGoInModule(t, dir, "run", ".") + if err != nil { + t.Fatalf("memprofile probe failed: %v\n%s", err, out) + } + if !strings.Contains(out, "MEMPROF_OK") { + t.Fatalf("memprofile probe missing marker:\n%s", out) + } +} diff --git a/test/go/finalizer_test.go b/test/go/finalizer_test.go index aceb2d2a2c..6c3f6ffea6 100644 --- a/test/go/finalizer_test.go +++ b/test/go/finalizer_test.go @@ -17,6 +17,8 @@ package gotest import ( + "os" + "path/filepath" "runtime" "testing" "time" @@ -89,6 +91,116 @@ func TestRuntimeSetFinalizerCancel(t *testing.T) { } } +const finalizerStackLivenessProbe = `package main + +import ( + "fmt" + "runtime" +) + +type HeapObj [8]int64 + +type StkObj struct { + h *HeapObj +} + +var n int +var c int = -1 +var null StkObj +var sink *HeapObj + +func gc() { + runtime.GC() + runtime.GC() + runtime.GC() + n++ +} + +func keepAliveCase() { + c = -1 + n = 0 + f() + gc() + if c != 1 { + panic(fmt.Sprintf("keepalive collection phase = %d, want 1", c)) + } +} + +func f() { + var s StkObj + s.h = new(HeapObj) + runtime.SetFinalizer(s.h, func(h *HeapObj) { + c = n + }) + g(&s) + gc() +} + +func g(s *StkObj) { + gc() + runtime.KeepAlive(s) + gc() +} + +//go:noinline +func use(p *StkObj) { +} + +//go:noinline +func ambiguousArgCase(s StkObj, b bool) { + var p *StkObj + if b { + p = &s + } else { + p = &null + } + use(p) + gc() + sink = p.h + gc() + sink = nil + gc() +} + +func runAmbiguousArgCase(b bool, want int) { + var s StkObj + s.h = new(HeapObj) + c = -1 + n = 0 + runtime.SetFinalizer(s.h, func(h *HeapObj) { + c = n + }) + ambiguousArgCase(s, b) + if c != want { + panic(fmt.Sprintf("ambiguous arg b=%v collection phase = %d, want %d", b, c, want)) + } +} + +func main() { + keepAliveCase() + runAmbiguousArgCase(true, 2) + runAmbiguousArgCase(false, 0) +} +` + +func TestRuntimeSetFinalizerStackObjectLiveness(t *testing.T) { + dir, err := os.MkdirTemp("", "llgo-finalizer-stack-*") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + mainFile := filepath.Join(dir, "main.go") + if err := os.WriteFile(mainFile, []byte(finalizerStackLivenessProbe), 0644); err != nil { + t.Fatal(err) + } + + runGoCmd(t, dir, "run", mainFile) + + root := findLLGoRoot(t) + t.Setenv("LLGO_ROOT", root) + runGoCmd(t, root, "run", "./cmd/llgo", "run", mainFile) +} + func runGCWithTimeout(t *testing.T) { t.Helper() done := make(chan struct{}) diff --git a/test/go/interface_nil_assert_test.go b/test/go/interface_nil_assert_test.go index 850112d12d..8a8ac2a25d 100644 --- a/test/go/interface_nil_assert_test.go +++ b/test/go/interface_nil_assert_test.go @@ -38,10 +38,7 @@ func TestNilInterfaceSameTypeAssert(t *testing.T) { func TestNilInterfaceSameTypeAssertPanics(t *testing.T) { x := nilAssertValue(false) - defer func() { - if recover() == nil { - t.Fatal("expected panic for nil interface same-type assert") - } - }() - _ = x.(nilAssertInterface) + expectPanicContaining(t, "interface conversion", func() { + _ = x.(nilAssertInterface) + }) } diff --git a/test/go/interface_type_assert_panic_test.go b/test/go/interface_type_assert_panic_test.go new file mode 100644 index 0000000000..7d0870a663 --- /dev/null +++ b/test/go/interface_type_assert_panic_test.go @@ -0,0 +1,54 @@ +package gotest + +import "testing" + +type typeAssertInterface interface { + Get() int +} + +type typeAssertScopedT struct{} + +var typeAssertScopedValue any + +func typeAssertValue(v any) any { + return v +} + +func TestInterfaceAssertToInterfacePanicsWithRuntimeError(t *testing.T) { + expectPanicContaining(t, "interface conversion", func() { + _ = typeAssertValue(0).(typeAssertInterface) + }) +} + +func TestInterfaceAssertToConcretePanicsWithRuntimeError(t *testing.T) { + expectPanicContaining(t, "interface conversion", func() { + _ = typeAssertValue(0).(string) + }) +} + +func TestInterfaceAssertRejectsSameNameTypesFromDifferentScopes(t *testing.T) { + typeAssertAssignLocalT() + typeAssertLocalToLocalT(t) + typeAssertLocalToPackageT(t) + + typeAssertScopedValue = typeAssertScopedT{} + typeAssertLocalToLocalT(t) +} + +func typeAssertAssignLocalT() { + type typeAssertScopedT struct{} + typeAssertScopedValue = typeAssertScopedT{} +} + +func typeAssertLocalToLocalT(t *testing.T) { + type typeAssertScopedT struct{} + expectPanicContaining(t, "different scopes", func() { + _ = typeAssertScopedValue.(typeAssertScopedT) + }) +} + +func typeAssertLocalToPackageT(t *testing.T) { + expectPanicContaining(t, "different scopes", func() { + _ = typeAssertScopedValue.(typeAssertScopedT) + }) +} diff --git a/test/go/nointerface_expect_go_test.go b/test/go/nointerface_expect_go_test.go new file mode 100644 index 0000000000..30878b876c --- /dev/null +++ b/test/go/nointerface_expect_go_test.go @@ -0,0 +1,6 @@ +//go:build !llgo +// +build !llgo + +package gotest + +const noInterfaceMethodsFiltered = false diff --git a/test/go/nointerface_expect_llgo_test.go b/test/go/nointerface_expect_llgo_test.go new file mode 100644 index 0000000000..e4cde2ecde --- /dev/null +++ b/test/go/nointerface_expect_llgo_test.go @@ -0,0 +1,6 @@ +//go:build llgo +// +build llgo + +package gotest + +const noInterfaceMethodsFiltered = true diff --git a/test/go/nointerface_test.go b/test/go/nointerface_test.go new file mode 100644 index 0000000000..39c6ec01a4 --- /dev/null +++ b/test/go/nointerface_test.go @@ -0,0 +1,66 @@ +package gotest + +import "testing" + +type noInterfaceE struct{} + +//go:nointerface +func (noInterfaceE) EBad() int { return 1 } + +func (noInterfaceE) EGood() int { return 2 } + +type noInterfaceX[T any] struct { + noInterfaceE +} + +//go:nointerface +func (noInterfaceX[T]) XBad() int { return 3 } + +func (noInterfaceX[T]) XGood() int { return 4 } + +type noInterfaceW struct { + noInterfaceX[int] +} + +type noInterfacePtrBase struct{} + +//go:nointerface +func (*noInterfacePtrBase) PBad() int { return 5 } + +func (*noInterfacePtrBase) PGood() int { return 6 } + +type noInterfacePtrWrap struct { + noInterfacePtrBase +} + +func TestNoInterfaceMethodsDoNotImplementInterfaces(t *testing.T) { + if got := (noInterfaceE{}).EBad(); got != 1 { + t.Fatalf("direct nointerface method call = %d, want 1", got) + } + if got := (noInterfaceX[int]{}).XBad(); got != 3 { + t.Fatalf("direct generic nointerface method call = %d, want 3", got) + } + ptrWrap := noInterfacePtrWrap{} + if got := ptrWrap.PBad(); got != 5 { + t.Fatalf("direct promoted pointer nointerface method call = %d, want 5", got) + } + + checkNoInterface[noInterfaceE, interface{ EBad() int }, interface{ EGood() int }](t, "E") + checkNoInterface[noInterfaceX[int], interface{ EBad() int }, interface{ EGood() int }](t, "X.E") + checkNoInterface[noInterfaceX[int], interface{ XBad() int }, interface{ XGood() int }](t, "X") + checkNoInterface[noInterfaceW, interface{ EBad() int }, interface{ EGood() int }](t, "W.E") + checkNoInterface[noInterfaceW, interface{ XBad() int }, interface{ XGood() int }](t, "W.X") + checkNoInterface[noInterfacePtrWrap, interface{ PBad() int }, interface{ PGood() int }](t, "promoted pointer") +} + +func checkNoInterface[T any, Bad any, Good any](t *testing.T, name string) { + t.Helper() + v := any(new(T)) + _, badOK := v.(Bad) + if want := !noInterfaceMethodsFiltered; badOK != want { + t.Fatalf("%s: nointerface method assertion = %v, want %v", name, badOK, want) + } + if _, goodOK := v.(Good); !goodOK { + t.Fatalf("%s: normal method did not satisfy interface", name) + } +} diff --git a/test/go/recover_defer_fixedbugs_test.go b/test/go/recover_defer_fixedbugs_test.go new file mode 100644 index 0000000000..95a2453697 --- /dev/null +++ b/test/go/recover_defer_fixedbugs_test.go @@ -0,0 +1,158 @@ +package gotest + +import ( + "runtime" + "strings" + "testing" +) + +type fixedbug4066Panic struct{} + +func fixedbug4066NamedReturn() (val int) { + val = 0 + defer func() { + if x := recover(); x != nil { + _ = x.(fixedbug4066Panic) + } + }() + for { + val = 2 + fixedbug4066Throw() + } +} + +func fixedbug4066Throw() { + panic(fixedbug4066Panic{}) +} + +func TestRecoverFixedbug4066NamedReturn(t *testing.T) { + if got := fixedbug4066NamedReturn(); got != 2 { + t.Fatalf("named return after recover = %d, want 2", got) + } +} + +func TestRecoverFixedbugDirectDeferredFuncValue(t *testing.T) { + recovered := false + func() { + f := func() { + if recover() != nil { + recovered = true + } + } + defer f() + panic("direct deferred func value") + }() + if !recovered { + t.Fatal("direct deferred func value did not recover") + } +} + +func TestRecoverFixedbugNestedDeferInDeferredFuncDoesNotRecover(t *testing.T) { + nested := any("unset") + func() { + defer func() { + if r := recover(); r != "outer" { + t.Fatalf("outer recover = %v, want outer", r) + } + }() + defer func() { + defer func() { + nested = recover() + }() + }() + panic("outer") + }() + if nested != nil { + t.Fatalf("nested recover = %v, want nil", nested) + } +} + +var fixedbugReturnedRecover any + +func fixedbugReturnedRecoverFunc() func() { + return func() { + fixedbugReturnedRecover = recover() + } +} + +func TestRecoverFixedbugReturnedDeferredFuncValue(t *testing.T) { + fixedbugReturnedRecover = nil + func() { + defer fixedbugReturnedRecoverFunc()() + panic("returned deferred func value") + }() + if fixedbugReturnedRecover != "returned deferred func value" { + t.Fatalf("returned deferred recover = %v, want panic value", fixedbugReturnedRecover) + } +} + +var fixedbug73916Recovered bool + +func fixedbug73916CallRecover() { + if recover() != nil { + fixedbug73916Recovered = true + } +} + +func fixedbug73916Deferred(int) { + fixedbug73916CallRecover() +} + +func fixedbug73916MustPanic(t *testing.T, fn func()) any { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Fatal("deferred indirect recover swallowed panic") + } + }() + fn() + return nil +} + +func TestRecoverFixedbug73916IndirectRecoverDoesNotRecover(t *testing.T) { + skipBeforeGo126(t) + fixedbug73916Recovered = false + fixedbug73916MustPanic(t, func() { + defer fixedbug73916Deferred(1) + panic("fixedbug73916") + }) + if fixedbug73916Recovered { + t.Fatal("indirect recover returned non-nil") + } +} + +var fixedbug73916bRecovered bool + +func fixedbug73916bCallRecover() { + func() { + if recover() != nil { + fixedbug73916bRecovered = true + } + }() +} + +func fixedbug73916bDeferred() int { + fixedbug73916bCallRecover() + return 0 +} + +func TestRecoverFixedbug73916NestedRecoverDoesNotRecover(t *testing.T) { + skipBeforeGo126(t) + fixedbug73916bRecovered = false + fixedbug73916MustPanic(t, func() { + defer fixedbug73916bDeferred() + panic("fixedbug73916b") + }) + if fixedbug73916bRecovered { + t.Fatal("nested recover returned non-nil") + } +} + +func skipBeforeGo126(t *testing.T) { + t.Helper() + version := runtime.Version() + if strings.HasPrefix(version, "go1.26") || strings.HasPrefix(version, "devel") { + return + } + t.Skip("requires Go 1.26 recover semantics") +} diff --git a/test/go/recover_defer_test.go b/test/go/recover_defer_test.go new file mode 100644 index 0000000000..9e011c2f95 --- /dev/null +++ b/test/go/recover_defer_test.go @@ -0,0 +1,289 @@ +package gotest + +import ( + "reflect" + "runtime" + "testing" +) + +func recoverIndirect() any { + return recover() +} + +func recoverRecursive(n int) any { + if n == 0 { + return recoverRecursive(1) + } + return recover() +} + +func TestRecoverOnlyDirectDeferredCall(t *testing.T) { + var indirect, direct, second any + func() { + defer func() { + indirect = recoverIndirect() + direct = recover() + second = recover() + }() + panic("direct-sentinel") + }() + + if indirect != nil { + t.Fatalf("indirect recover = %v, want nil", indirect) + } + if direct != "direct-sentinel" { + t.Fatalf("direct recover = %v, want direct-sentinel", direct) + } + if second != nil { + t.Fatalf("second recover = %v, want nil", second) + } +} + +func TestRecoverRejectsRecursiveIndirectCall(t *testing.T) { + var indirect, direct any + func() { + defer func() { + indirect = recoverRecursive(0) + direct = recover() + }() + panic("recursive-sentinel") + }() + + if indirect != nil { + t.Fatalf("recursive indirect recover = %v, want nil", indirect) + } + if direct != "recursive-sentinel" { + t.Fatalf("direct recover = %v, want recursive-sentinel", direct) + } +} + +func TestNestedPanicRecoverStack(t *testing.T) { + var recovered []any + func() { + defer func() { + recovered = append(recovered, recover()) + }() + defer func() { + defer func() { + recovered = append(recovered, recover()) + }() + panic("inner") + }() + panic("outer") + }() + + want := []any{"inner", "outer"} + if !reflect.DeepEqual(recovered, want) { + t.Fatalf("recover stack = %v, want %v", recovered, want) + } +} + +func TestDeferredRecoverBuiltinKeepsNestedPanicForNextDefer(t *testing.T) { + t.Skip("recursive-panic / method-wrapper recover ownership: recover1.go-class residual on the Defer-node model (#2033 follow-up)") + var recovered []any + func() { + defer func() { + recovered = append(recovered, recover()) + }() + defer func() { + defer func() { + recovered = append(recovered, recover()) + }() + defer recover() + panic("inner") + }() + panic("outer") + }() + + want := []any{"inner", "outer"} + if !reflect.DeepEqual(recovered, want) { + t.Fatalf("recover stack after deferred recover builtin = %v, want %v", recovered, want) + } +} + +func TestDeferredRecoverBuiltinCanRecoverOuterPanicAfterNestedRecover(t *testing.T) { + t.Skip("recursive-panic / method-wrapper recover ownership: recover1.go-class residual on the Defer-node model (#2033 follow-up)") + var recovered []any + func() { + defer func() { + recovered = append(recovered, recover()) + }() + defer func() { + defer recover() + defer func() { + recovered = append(recovered, recover()) + }() + panic("inner") + }() + panic("outer") + }() + + want := []any{"inner", nil} + if !reflect.DeepEqual(recovered, want) { + t.Fatalf("recover stack after outer deferred recover builtin = %v, want %v", recovered, want) + } +} + +func TestRecoverAfterPanicDoesNotKeepPartialResultWrites(t *testing.T) { + if got := recoverAfterResultAssignmentPanic(); got { + t.Fatalf("assignment result = %v, want false", got) + } + if got, _ := recoverAfterReturnExpressionPanic(); got { + t.Fatalf("return expression result = %v, want false", got) + } + if got, _ := recoverAfterNamedReturnExpressionPanic(); got { + t.Fatalf("named return expression result = %v, want false", got) + } +} + +func recoverAfterResultAssignmentPanic() (bad bool) { + defer func() { + recover() + }() + var p *int + bad, _ = true, *p + return +} + +func recoverAfterReturnExpressionPanic() (bool, int) { + defer func() { + recover() + }() + var p *int + return true, *p +} + +func recoverAfterNamedReturnExpressionPanic() (_ bool, _ int) { + defer func() { + recover() + }() + var p *int + return true, *p +} + +type recoverValueMethod uintptr + +var methodWrapperRecovered any + +func (recoverValueMethod) recoverViaValueMethod() { + methodWrapperRecovered = recover() +} + +func TestRecoverThroughDeferredPointerToValueMethodWrapper(t *testing.T) { + t.Skip("recursive-panic / method-wrapper recover ownership: recover1.go-class residual on the Defer-node model (#2033 follow-up)") + methodWrapperRecovered = nil + var x recoverValueMethod + func() { + defer (*recoverValueMethod).recoverViaValueMethod(&x) + panic("method-wrapper-sentinel") + }() + + if methodWrapperRecovered != "method-wrapper-sentinel" { + t.Fatalf("method wrapper recover = %v, want method-wrapper-sentinel", methodWrapperRecovered) + } +} + +func TestRecoverThroughMethodWrapperStillRequiresDirectDeferredCall(t *testing.T) { + methodWrapperRecovered = "unset" + var direct any + var x recoverValueMethod + func() { + defer func() { + (*recoverValueMethod).recoverViaValueMethod(&x) + direct = recover() + }() + panic("outer-sentinel") + }() + + if methodWrapperRecovered != nil { + t.Fatalf("nested method wrapper recover = %v, want nil", methodWrapperRecovered) + } + if direct != "outer-sentinel" { + t.Fatalf("direct recover after nested method wrapper = %v, want outer-sentinel", direct) + } +} + +type embeddedRecoverTarget int + +// Keep issue73917/issue73920 as a real helper call outside the wrapper target. +// +//go:noinline +func recoverForEmbeddedWrapper() { + if r := recover(); r != nil { + methodWrapperRecovered = r + } +} + +func (*embeddedRecoverTarget) recoverViaIndirectCall() { + recoverForEmbeddedWrapper() +} + +type embeddedValueWrapper struct{ *embeddedRecoverTarget } +type embeddedPointerWrapper struct{ *embeddedRecoverTarget } + +func requireGo126RecoverWrapperSemantics(t *testing.T) { + t.Helper() + + const prefix = "go1." + version := runtime.Version() + if len(version) <= len(prefix) || version[:len(prefix)] != prefix { + return + } + + minor := 0 + for _, c := range version[len(prefix):] { + if c < '0' || c > '9' { + break + } + minor = minor*10 + int(c-'0') + } + if minor != 0 && minor < 26 { + t.Skipf("%s has pre-Go 1.26 embedded wrapper recover semantics", version) + } +} + +func TestDeferredEmbeddedValueMethodWrapperKeepsIndirectRecoverNil(t *testing.T) { + requireGo126RecoverWrapperSemantics(t) + + methodWrapperRecovered = nil + var direct any + x := embeddedValueWrapper{new(embeddedRecoverTarget)} + fn := embeddedValueWrapper.recoverViaIndirectCall + func() { + defer func() { + direct = recover() + }() + defer fn(x) + panic("embedded-value-wrapper-sentinel") + }() + + if methodWrapperRecovered != nil { + t.Fatalf("indirect recover through embedded value wrapper = %v, want nil", methodWrapperRecovered) + } + if direct != "embedded-value-wrapper-sentinel" { + t.Fatalf("direct recover after embedded value wrapper = %v, want embedded-value-wrapper-sentinel", direct) + } +} + +func TestDeferredEmbeddedPointerMethodWrapperKeepsIndirectRecoverNil(t *testing.T) { + requireGo126RecoverWrapperSemantics(t) + + methodWrapperRecovered = nil + var direct any + x := &embeddedPointerWrapper{new(embeddedRecoverTarget)} + fn := (*embeddedPointerWrapper).recoverViaIndirectCall + func() { + defer func() { + direct = recover() + }() + defer fn(x) + panic("embedded-pointer-wrapper-sentinel") + }() + + if methodWrapperRecovered != nil { + t.Fatalf("indirect recover through embedded pointer wrapper = %v, want nil", methodWrapperRecovered) + } + if direct != "embedded-pointer-wrapper-sentinel" { + t.Fatalf("direct recover after embedded pointer wrapper = %v, want embedded-pointer-wrapper-sentinel", direct) + } +} diff --git a/test/go/recover_fault_unix_test.go b/test/go/recover_fault_unix_test.go new file mode 100644 index 0000000000..96ecaa2a91 --- /dev/null +++ b/test/go/recover_fault_unix_test.go @@ -0,0 +1,49 @@ +//go:build linux || darwin + +package gotest + +import ( + "runtime/debug" + "syscall" + "testing" +) + +func faultCopy(dst, src []byte) (n int, err error) { + defer func() { + if r, ok := recover().(error); ok { + err = r + } + }() + + for i := 0; i < len(dst) && i < len(src); i++ { + dst[i] = src[i] + n++ + } + return +} + +func TestRecoverAfterFaultPreservesNamedResult(t *testing.T) { + old := debug.SetPanicOnFault(true) + defer debug.SetPanicOnFault(old) + + size := syscall.Getpagesize() + data, err := syscall.Mmap(-1, 0, 16*size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE) + if err != nil { + t.Fatalf("mmap: %v", err) + } + defer syscall.Munmap(data) + + hole := data[len(data)/2 : 3*(len(data)/4)] + if err := syscall.Mprotect(hole, syscall.PROT_NONE); err != nil { + t.Fatalf("mprotect: %v", err) + } + + const offset = 5 + n, err := faultCopy(data[offset:], make([]byte, len(data))) + if err == nil { + t.Fatal("no error from copy across memory hole") + } + if want := len(data)/2 - offset; n != want { + t.Fatalf("copy returned %d, want %d", n, want) + } +} diff --git a/test/go/reflect_method_type_identity_test.go b/test/go/reflect_method_type_identity_test.go new file mode 100644 index 0000000000..c070c97c84 --- /dev/null +++ b/test/go/reflect_method_type_identity_test.go @@ -0,0 +1,37 @@ +package gotest + +import ( + "reflect" + "testing" +) + +type reflectMethodIdentityPtr struct{} + +func (*reflectMethodIdentityPtr) Ptr() int { return 7 } + +type reflectMethodIdentityValue int + +func (reflectMethodIdentityValue) Value() int { return 9 } + +func TestReflectTypeMethodFuncInterfaceTypeIdentity(t *testing.T) { + ptrMethod := reflect.TypeOf(&reflectMethodIdentityPtr{}).Method(0) + ptrFn, ok := ptrMethod.Func.Interface().(func(*reflectMethodIdentityPtr) int) + if !ok { + t.Fatalf("Method.Func.Interface() has type %T, want func(*reflectMethodIdentityPtr) int", ptrMethod.Func.Interface()) + } + if got := ptrFn(&reflectMethodIdentityPtr{}); got != 7 { + t.Fatalf("pointer method func returned %d, want 7", got) + } + + valueMethod, ok := reflect.TypeOf(reflectMethodIdentityValue(0)).MethodByName("Value") + if !ok { + t.Fatal("MethodByName did not find Value") + } + valueFn, ok := valueMethod.Func.Interface().(func(reflectMethodIdentityValue) int) + if !ok { + t.Fatalf("MethodByName.Func.Interface() has type %T, want func(reflectMethodIdentityValue) int", valueMethod.Func.Interface()) + } + if got := valueFn(reflectMethodIdentityValue(0)); got != 9 { + t.Fatalf("value method func returned %d, want 9", got) + } +} diff --git a/test/go/runtime_error_recover_test.go b/test/go/runtime_error_recover_test.go index 546deee367..51985b844a 100644 --- a/test/go/runtime_error_recover_test.go +++ b/test/go/runtime_error_recover_test.go @@ -6,116 +6,157 @@ import ( "testing" ) +type runtimeErrorMissingMethod interface { + runtimeErrorMissingMethod() +} + var ( + runtimeErrorSink any runtimeErrorIntSink int - runtimeErrorAnySink any runtimeErrorArrayPtr *[10]int runtimeErrorBigArrayPtr *[10000]int ) -func TestRecoverRuntimeErrorClassification(t *testing.T) { - var zero int - var zero64 int64 +func TestRecoveredRuntimePanicsAreErrors(t *testing.T) { var index = 99999 arrayPtr := new([10]int) - var slice []int - var iface any = 1 tests := []struct { name string - want string + want []string f func() }{ { - name: "int-div-zero", - want: "integer divide by zero", + name: "index", + want: []string{"runtime error:", "index out of range"}, f: func() { - runtimeErrorIntSink = 1 / zero + s := []byte{1} + i := 2 + runtimeErrorSink = s[i] }, }, { - name: "int64-div-zero", - want: "integer divide by zero", + name: "array bounds", + want: []string{"runtime error:", "index out of range"}, f: func() { - runtimeErrorIntSink = int(1 / zero64) + runtimeErrorIntSink = arrayPtr[index] }, }, { - name: "nil-array-pointer-index-zero", - want: "nil pointer dereference", + name: "slice", + want: []string{"runtime error:", "slice bounds out of range"}, f: func() { - runtimeErrorIntSink = runtimeErrorArrayPtr[0] + s := []byte{1} + hi := 2 + runtimeErrorSink = s[:hi] }, }, { - name: "nil-array-pointer-index-one", - want: "nil pointer dereference", + name: "divide", + want: []string{"runtime error:", "integer divide by zero"}, f: func() { - runtimeErrorIntSink = runtimeErrorArrayPtr[1] + z := 0 + runtimeErrorSink = 1 / z }, }, { - name: "nil-array-pointer-index-large", - want: "nil pointer dereference", + name: "nil dereference", + want: []string{"runtime error:", "nil pointer dereference"}, f: func() { - runtimeErrorIntSink = runtimeErrorBigArrayPtr[5000] + var p *int + runtimeErrorSink = *p }, }, { - name: "array-bounds", - want: "index out of range", + name: "nil array pointer index zero", + want: []string{"runtime error:", "nil pointer dereference"}, f: func() { - runtimeErrorIntSink = arrayPtr[index] + runtimeErrorIntSink = runtimeErrorArrayPtr[0] }, }, { - name: "slice-bounds", - want: "index out of range", + name: "nil array pointer index one", + want: []string{"runtime error:", "nil pointer dereference"}, f: func() { - runtimeErrorIntSink = slice[index] + runtimeErrorIntSink = runtimeErrorArrayPtr[1] }, }, { - name: "type-concrete", - want: "int, not string", + name: "nil array pointer index large", + want: []string{"runtime error:", "nil pointer dereference"}, f: func() { - runtimeErrorAnySink = iface.(string) + runtimeErrorIntSink = runtimeErrorBigArrayPtr[5000] }, }, { - name: "type-interface", - want: "missing method runtimeErrorMissingMethod", + name: "slice to array", + want: []string{"runtime error:", "cannot convert slice with length 1 to array or pointer to array with length 2"}, f: func() { - runtimeErrorAnySink = iface.(runtimeErrorMissingMethod) + s := []byte{1} + runtimeErrorSink = [2]byte(s) }, }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - expectRecoverRuntimeError(t, tt.want, tt.f) + err := recoverRuntimeErrorValue(t, tt.f) + assertRuntimeErrorContains(t, err, tt.want...) }) } } -func expectRecoverRuntimeError(t *testing.T, want string, f func()) { +func TestRecoveredTypeAssertionPanicsAreRuntimeErrors(t *testing.T) { + t.Run("concrete", func(t *testing.T) { + var v any = 1 + err := recoverRuntimeErrorValue(t, func() { + runtimeErrorSink = v.(string) + }) + assertRuntimeErrorContains(t, err, "interface conversion", "int", "not string") + }) + + t.Run("nil interface", func(t *testing.T) { + var v any + err := recoverRuntimeErrorValue(t, func() { + runtimeErrorSink = v.(string) + }) + assertRuntimeErrorContains(t, err, "interface conversion", "is nil", "not string") + }) + + t.Run("missing method", func(t *testing.T) { + var v any = 1 + err := recoverRuntimeErrorValue(t, func() { + runtimeErrorSink = v.(runtimeErrorMissingMethod) + }) + assertRuntimeErrorContains(t, err, "interface conversion", "int is not", "missing method runtimeErrorMissingMethod") + }) +} + +func recoverRuntimeErrorValue(t *testing.T, f func()) runtime.Error { t.Helper() - defer func() { - err := recover() - if err == nil { - t.Fatalf("expected runtime panic containing %q", want) - } - runtimeErr, ok := err.(runtime.Error) - if !ok { - t.Fatalf("panic type = %T, want runtime.Error", err) - } - if got := runtimeErr.Error(); !strings.Contains(got, want) { - t.Fatalf("panic = %q, want contains %q", got, want) - } + var rec any + func() { + defer func() { + rec = recover() + }() + f() }() - f() + if rec == nil { + t.Fatal("expected panic") + } + err := rec.(error) + rerr := rec.(runtime.Error) + if err.Error() != rerr.Error() { + t.Fatalf("error text mismatch: error=%q runtime.Error=%q", err.Error(), rerr.Error()) + } + return rerr } -type runtimeErrorMissingMethod interface { - runtimeErrorMissingMethod() +func assertRuntimeErrorContains(t *testing.T, err error, wants ...string) { + t.Helper() + got := err.Error() + for _, want := range wants { + if !strings.Contains(got, want) { + t.Fatalf("panic = %q, want contains %q", got, want) + } + } } diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index 636857fc5a..1e31744ebb 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -95,6 +95,12 @@ host_skips: case: chanlinear.go reason: go1.26 goroot run is host-sensitive on darwin/arm64 timeouts: + - version: go1.26 + platform: darwin/arm64 + directive: runoutput + case: fixedbugs/bug449.go + timeout: 6m + reason: bug449 generated build exceeds the 3m build timeout on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -551,12 +557,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -923,12 +923,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.25 platform: darwin/arm64 directive: run @@ -1295,12 +1289,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.26 platform: darwin/arm64 directive: run @@ -1737,55 +1725,69 @@ flakes: xfails: - platform: darwin/arm64 directive: run - case: init1.go - reason: current main goroot run failure on darwin/arm64 + case: finprofiled.go + reason: LLGo finalizer profiling run hangs beyond the 1m timeout on darwin/arm64 - platform: darwin/arm64 directive: run - case: noinit.go - reason: current main goroot run failure on darwin/arm64 + case: gcstring.go + reason: GC frees a string-backed pointer before its finalizer check on darwin/arm64 - platform: darwin/arm64 directive: run - case: recover2.go - reason: current main goroot run failure on darwin/arm64 + case: inline_caller.go + reason: runtime caller frame reports main instead of runtime.main on darwin/arm64 - platform: darwin/arm64 - directive: runoutput - case: rangegen.go - reason: current main goroot runoutput failure on darwin/arm64 + directive: run + case: nilptr2.go + reason: address-of a dereferenced nil pointer does not panic and the run hangs on darwin/arm64 - platform: darwin/arm64 directive: run - case: zerodivide.go - reason: current main goroot run failure on darwin/arm64 + case: range4.go + reason: range-over-function build exits successfully without producing a binary on darwin/arm64 - platform: darwin/arm64 directive: run - case: fixedbugs/issue16130.go - reason: current main goroot run failure on darwin/arm64 + case: fixedbugs/issue31546.go + reason: reflection exposes the initializer value of a blank struct field instead of zero on darwin/arm64 - platform: darwin/arm64 directive: run - case: fixedbugs/issue19040.go - reason: current main goroot run failure on darwin/arm64 + case: fixedbugs/issue72063.go + reason: generic Y-combinator build exits successfully without producing a binary on darwin/arm64 - platform: darwin/arm64 directive: run - case: fixedbugs/issue37975.go - reason: current main goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 + case: fixedbugs/issue72844.go + reason: dereferencing a nil array pointer for len or range does not panic when required on darwin/arm64 + - platform: darwin/arm64 + directive: run + case: fixedbugs/issue8047b.go + reason: recover interaction panics with value 1 on darwin/arm64 + - platform: darwin/arm64 + directive: run + case: typeparam/orderedmap.go + reason: generic ordered-map iterator dereferences a nil entry on darwin/arm64 + - platform: darwin/arm64 directive: run - case: fixedbugs/issue73916.go - reason: go1.26 goroot run failure on darwin/arm64 + case: uintptrescapes3.go + reason: uintptr escape round trip dereferences a nil pointer on darwin/arm64 - version: go1.26 platform: darwin/arm64 directive: run - case: fixedbugs/issue73916b.go - reason: go1.26 goroot run failure on darwin/arm64 + case: convert5.go + reason: out-of-range float-to-uint32 conversions wrap instead of producing zero on darwin/arm64 + - platform: darwin/arm64 + directive: run + case: noinit.go + reason: current main goroot run failure on darwin/arm64 + - platform: darwin/arm64 + directive: runoutput + case: rangegen.go + reason: current main goroot runoutput failure on darwin/arm64 - platform: darwin/arm64 directive: run case: typeparam/mdempsky/16.go - reason: nil-deref panic value does not implement runtime.Error on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 + reason: nil-interface assertion panic implements runtime.Error but the message lacks the source interface type and prints the command-line-arguments package path on darwin/arm64 + - platform: darwin/arm64 directive: run - case: typeparam/mdempsky/15.go - reason: go1.26 go:nointerface methods still satisfy interfaces on darwin/arm64 + case: recover1.go + reason: recover during recursive panics - a plain sub-call inside the deferred function still sees the panic (sub-call recover masking, follow-up on the Defer-node model) on darwin/arm64 - version: go1.24 platform: linux/amd64 directive: run @@ -1794,47 +1796,52 @@ xfails: - version: go1.24 platform: linux/amd64 directive: run - case: init1.go + case: noinit.go reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run - case: noinit.go + case: recover3.go reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run - case: recover2.go + case: switch.go reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run - case: recover3.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 + case: typeparam/mdempsky/16.go + reason: nil-interface assertion panic implements runtime.Error but the message lacks the source interface type and prints the command-line-arguments package path on linux/amd64 + - version: go1.25 platform: linux/amd64 directive: run - case: switch.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 + case: typeparam/mdempsky/16.go + reason: nil-interface assertion panic implements runtime.Error but the message lacks the source interface type and prints the command-line-arguments package path on linux/amd64 + - version: go1.26 platform: linux/amd64 directive: run - case: zerodivide.go - reason: go1.24 goroot run failure on linux/amd64 + case: typeparam/mdempsky/16.go + reason: nil-interface assertion panic implements runtime.Error but the message lacks the source interface type and prints the command-line-arguments package path on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run - case: fixedbugs/issue16130.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 + case: recover1.go + reason: recover during recursive panics - a plain sub-call inside the deferred function still sees the panic (sub-call recover masking, follow-up on the Defer-node model) on linux/amd64 + - version: go1.25 platform: linux/amd64 directive: run - case: fixedbugs/issue19040.go - reason: go1.24 goroot run failure on linux/amd64 + case: recover1.go + reason: recover during recursive panics - a plain sub-call inside the deferred function still sees the panic (sub-call recover masking, follow-up on the Defer-node model) on linux/amd64 + - version: go1.26 + platform: linux/amd64 + directive: run + case: recover1.go + reason: recover during recursive panics - a plain sub-call inside the deferred function still sees the panic (sub-call recover masking, follow-up on the Defer-node model) on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run - case: fixedbugs/issue37975.go + case: fixedbugs/issue19040.go reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 @@ -1846,11 +1853,6 @@ xfails: directive: run case: fixedbugs/issue65417.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: typeparam/mdempsky/16.go - reason: go1.24 nil-deref panic value does not implement runtime.Error on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -1866,11 +1868,6 @@ xfails: directive: run case: noinit.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: recover2.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -1881,16 +1878,6 @@ xfails: directive: run case: switch.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: zerodivide.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue16130.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -1916,31 +1903,16 @@ xfails: directive: run case: fixedbugs/issue72844.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: typeparam/mdempsky/16.go - reason: go1.25 nil-deref panic value does not implement runtime.Error on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: append.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: init1.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: noinit.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: recover2.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -1951,26 +1923,11 @@ xfails: directive: run case: switch.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: zerodivide.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue16130.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: fixedbugs/issue19040.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue37975.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -1986,31 +1943,11 @@ xfails: directive: run case: fixedbugs/issue72844.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73916.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73916b.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: fixedbugs/issue75327.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: typeparam/mdempsky/16.go - reason: go1.26 nil-deref panic value does not implement runtime.Error on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: typeparam/mdempsky/15.go - reason: go1.26 go:nointerface methods still satisfy interfaces on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -2041,14 +1978,6 @@ xfails: directive: runoutput case: rangegen.go reason: go1.26 goroot ci-mode runoutput failure on linux/amd64 - - platform: darwin/arm64 - directive: run - case: deferfin.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: heapsampling.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: mallocfin.go @@ -2057,34 +1986,14 @@ xfails: directive: run case: recover.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: recover1.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: recover4.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: stackobj.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: stackobj3.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/bug347.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/bug348.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue14646.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue15281.go @@ -2096,7 +2005,7 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/issue27201.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue27518b.go @@ -2104,15 +2013,11 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/issue29504.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue32477.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue33724.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue45045.go @@ -2120,15 +2025,11 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/issue4562.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue46725.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue54343.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue5493.go @@ -2137,34 +2038,20 @@ xfails: directive: run case: fixedbugs/issue57823.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue5856.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue5963.go reason: latest main goroot run failure on darwin/arm64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: deferfin.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2189,7 +2076,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2199,27 +2086,12 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/issue32477.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue33724.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.25 goroot run failure on darwin/arm64 - version: go1.25 platform: linux/amd64 directive: run @@ -2229,7 +2101,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2265,11 +2137,6 @@ xfails: directive: run case: fixedbugs/issue5963.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: heapsampling.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -2280,42 +2147,12 @@ xfails: directive: run case: recover.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: recover1.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: recover4.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: stackobj.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: stackobj3.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run case: tinyfin.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: deferfin.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: heapsampling.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -2325,12 +2162,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -2345,7 +2177,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -2381,21 +2213,6 @@ xfails: directive: run case: mallocfin.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: recover1.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: stackobj.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: stackobj3.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -2405,7 +2222,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -2420,7 +2237,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -2431,16 +2248,6 @@ xfails: directive: run case: fixedbugs/issue33724.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.24 goroot run failure on darwin/arm64 - version: go1.24 platform: linux/amd64 directive: run @@ -2450,12 +2257,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: recover4.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -2476,41 +2278,16 @@ xfails: directive: run case: maymorestack.go reason: go1.26 goroot ci-mode run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: devirtualization_nil_panics.go - reason: go1.26 goroot run failure on darwin/arm64 - version: go1.26 platform: linux/amd64 directive: run - case: deferfin.go + case: inline_literal.go reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: mallocfin.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: recover1.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: recover4.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: stackobj.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: stackobj3.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -2520,7 +2297,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -2535,7 +2312,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -2546,16 +2323,6 @@ xfails: directive: run case: fixedbugs/issue33724.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.26 goroot run failure on darwin/arm64 - version: go1.26 platform: linux/amd64 directive: run @@ -2565,7 +2332,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -2581,11 +2348,6 @@ xfails: directive: run case: devirtualization_nil_panics.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: heapsampling.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -2595,12 +2357,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -2615,7 +2372,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -2646,110 +2403,14 @@ xfails: directive: run case: fixedbugs/issue5963.go reason: go1.26 goroot run failure on linux/amd64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/bug273.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue12133.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue13169.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue19246.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue26094.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue43835.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue47928.go - reason: current main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue38496.go reason: current main goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73917.go - reason: go1.26 goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73920.go - reason: go1.26 goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue8048.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue8132.go - reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: nil.go - reason: current main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: typeparam/chans.go reason: select over unbuffered channel misses final receive on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: typeparam/mdempsky/15.go - reason: go:nointerface methods still satisfy interfaces on darwin/arm64 - - platform: darwin/arm64 - directive: runoutput - case: index0.go - reason: current main goroot runoutput failure on darwin/arm64 - - platform: linux/amd64 - directive: run - case: fixedbugs/bug273.go - reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue19246.go - reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue26094.go - reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue43835.go - reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue47928.go - reason: current main goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73917.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73920.go - reason: go1.26 goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue8048.go - reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue8132.go - reason: current main goroot run failure on linux/amd64 - platform: linux/amd64 directive: run case: fixedbugs/issue38496.go @@ -2776,10 +2437,6 @@ xfails: directive: run case: typeparam/chans.go reason: select over unbuffered channel misses final receive on linux/amd64 - - platform: linux/amd64 - directive: run - case: typeparam/mdempsky/15.go - reason: go:nointerface methods still satisfy interfaces on linux/amd64 - platform: linux/amd64 directive: runoutput case: index0.go