From 87ba30305d3a74f03e0d350e2954f18ea502034e Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 24 Jul 2026 10:56:45 +0800 Subject: [PATCH 1/4] runtime: store goroutine state behind pthread getg --- .../internal/runtime/defer_tls_baremetal.go | 39 ---- .../internal/runtime/{defer_tls.go => g.go} | 30 ++-- runtime/internal/runtime/g_global.go | 25 +++ runtime/internal/runtime/g_pthread.go | 66 +++++++ runtime/internal/runtime/z_default.go | 7 +- runtime/internal/runtime/z_rt.go | 26 ++- test/go/runtime_g_state_test.go | 80 +++++++++ test/llgoext/runtime_g_test.go | 169 ++++++++++++++++++ 8 files changed, 369 insertions(+), 73 deletions(-) delete mode 100644 runtime/internal/runtime/defer_tls_baremetal.go rename runtime/internal/runtime/{defer_tls.go => g.go} (57%) create mode 100644 runtime/internal/runtime/g_global.go create mode 100644 runtime/internal/runtime/g_pthread.go create mode 100644 test/go/runtime_g_state_test.go create mode 100644 test/llgoext/runtime_g_test.go diff --git a/runtime/internal/runtime/defer_tls_baremetal.go b/runtime/internal/runtime/defer_tls_baremetal.go deleted file mode 100644 index d8c4938cca..0000000000 --- a/runtime/internal/runtime/defer_tls_baremetal.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build baremetal - -/* - * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package runtime - -// globalDeferHead stores the current defer chain head. -// In baremetal single-threaded environment, a global variable -// replaces pthread TLS. -var globalDeferHead *Defer - -// SetThreadDefer associates the current thread with the given defer chain. -func SetThreadDefer(head *Defer) { - globalDeferHead = head -} - -// GetThreadDefer returns the current thread's defer chain head. -func GetThreadDefer() *Defer { - return globalDeferHead -} - -// ClearThreadDefer resets the current thread's defer chain to nil. -func ClearThreadDefer() { - globalDeferHead = nil -} diff --git a/runtime/internal/runtime/defer_tls.go b/runtime/internal/runtime/g.go similarity index 57% rename from runtime/internal/runtime/defer_tls.go rename to runtime/internal/runtime/g.go index 6a430e9924..4a1a3e5f04 100644 --- a/runtime/internal/runtime/defer_tls.go +++ b/runtime/internal/runtime/g.go @@ -1,7 +1,5 @@ -//go:build !baremetal - /* - * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +16,27 @@ package runtime -import "github.com/goplus/llgo/runtime/internal/clite/tls" +import "unsafe" -var deferTLS = tls.Alloc[*Defer](func(head **Defer) { - if head != nil { - *head = nil - } -}) +// g holds the runtime state owned by one LLGo goroutine. +type g struct { + defer_ *Defer + panic_ unsafe.Pointer + goexit bool + isMain bool +} -// SetThreadDefer associates the current thread with the given defer chain. +// SetThreadDefer associates the current goroutine with the given defer chain. func SetThreadDefer(head *Defer) { - deferTLS.Set(head) + getg().defer_ = head } -// GetThreadDefer returns the current thread's defer chain head. +// GetThreadDefer returns the current goroutine's defer chain head. func GetThreadDefer() *Defer { - return deferTLS.Get() + return getg().defer_ } -// ClearThreadDefer resets the current thread's defer chain to nil. +// ClearThreadDefer resets the current goroutine's defer chain to nil. func ClearThreadDefer() { - deferTLS.Clear() + getg().defer_ = nil } diff --git a/runtime/internal/runtime/g_global.go b/runtime/internal/runtime/g_global.go new file mode 100644 index 0000000000..255733ff01 --- /dev/null +++ b/runtime/internal/runtime/g_global.go @@ -0,0 +1,25 @@ +//go:build !llgo || baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +var globalG g + +func getg() *g { + return &globalG +} diff --git a/runtime/internal/runtime/g_pthread.go b/runtime/internal/runtime/g_pthread.go new file mode 100644 index 0000000000..048ecc1dc6 --- /dev/null +++ b/runtime/internal/runtime/g_pthread.go @@ -0,0 +1,66 @@ +//go:build llgo && !baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + "github.com/goplus/llgo/runtime/internal/clite/pthread" +) + +var gKey = newGKey() + +func newGKey() pthread.Key { + var key pthread.Key + if ret := key.Create(pthread.KeyDestructor(destroyG)); ret != 0 { + c.Fprintf(c.Stderr, c.Str("runtime: pthread_key_create failed (errno=%d)\n"), ret) + panic("runtime: failed to create getg key") + } + return key +} + +func getg() *g { + if ptr := gKey.Get(); ptr != nil { + return (*g)(ptr) + } + ptr := AllocRoot(unsafe.Sizeof(g{})) + if ptr == nil { + panic("runtime: failed to allocate g") + } + c.Memset(ptr, 0, unsafe.Sizeof(g{})) + if ret := gKey.Set(ptr); ret != 0 { + FreeRoot(ptr) + c.Fprintf(c.Stderr, c.Str("runtime: pthread_setspecific failed (errno=%d)\n"), ret) + panic("runtime: failed to install g") + } + return (*g)(ptr) +} + +func destroyG(ptr c.Pointer) { + gp := (*g)(ptr) + if gp == nil { + return + } + if gp.panic_ != nil { + c.Free(gp.panic_) + } + *gp = g{} + FreeRoot(ptr) +} diff --git a/runtime/internal/runtime/z_default.go b/runtime/internal/runtime/z_default.go index d0757f9cb1..7f0b7601ba 100644 --- a/runtime/internal/runtime/z_default.go +++ b/runtime/internal/runtime/z_default.go @@ -16,7 +16,8 @@ var ( // Rethrow rethrows a panic. func Rethrow(link *Defer) { - if ptr := excepKey.Get(); ptr != nil { + gp := getg() + if ptr := gp.panic_; ptr != nil { if link == nil { TracePanic(*(*any)(ptr)) if PanicTraceback == nil || !PanicTraceback(2) { @@ -27,7 +28,7 @@ func Rethrow(link *Defer) { } else { c.Siglongjmp(link.Addr, 1) } - } else if ptr := goexitKey.Get(); ptr != nil { + } else if gp.goexit { // Goexit must run deferred functions before terminating the current // goroutine. Reuse the longjmp-based defer unwinding: // 1) If we have a defer frame, longjmp to it so it can execute defers. @@ -36,7 +37,7 @@ func Rethrow(link *Defer) { if link != nil { c.Siglongjmp(link.Addr, 1) } - if pthread.Equal(mainThread, pthread.Self()) != 0 { + if gp.isMain { fatal("no goroutines (main called runtime.Goexit) - deadlock!") c.Exit(2) } diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index 17c89f55da..d771ab94cf 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -20,7 +20,6 @@ import ( "unsafe" c "github.com/goplus/llgo/runtime/internal/clite" - "github.com/goplus/llgo/runtime/internal/clite/pthread" "github.com/goplus/llgo/runtime/internal/clite/setjmp" ) @@ -38,9 +37,10 @@ type Defer struct { // Recover recovers a panic. func Recover() (ret any) { - ptr := excepKey.Get() + gp := getg() + ptr := gp.panic_ if ptr != nil { - excepKey.Set(nil) + gp.panic_ = nil ret = *(*any)(ptr) c.Free(ptr) if PanicRecovered != nil { @@ -58,26 +58,20 @@ func Panic(v any) { SavePanicCallerFrames() ptr := c.Malloc(unsafe.Sizeof(v)) *(*any)(ptr) = v - excepKey.Set(ptr) + gp := getg() + gp.panic_ = ptr - Rethrow((*Defer)(c.GoDeferData())) + Rethrow(gp.defer_) } -var ( - excepKey pthread.Key - goexitKey pthread.Key - mainThread pthread.Thread -) - func Goexit() { - goexitKey.Set(unsafe.Pointer(&goexitKey)) - Rethrow((*Defer)(c.GoDeferData())) + gp := getg() + gp.goexit = true + Rethrow(gp.defer_) } func init() { - excepKey.Create(nil) - goexitKey.Create(nil) - mainThread = pthread.Self() + getg().isMain = true } // ----------------------------------------------------------------------------- diff --git a/test/go/runtime_g_state_test.go b/test/go/runtime_g_state_test.go new file mode 100644 index 0000000000..8d808ac5df --- /dev/null +++ b/test/go/runtime_g_state_test.go @@ -0,0 +1,80 @@ +package gotest + +import ( + "runtime" + "testing" +) + +type runtimeGStateResult struct { + id int + recovered any + order string +} + +func runtimeGStatePanic(id int, ready chan<- struct{}, start <-chan struct{}, results chan<- runtimeGStateResult) { + order := "" + defer func() { + result := runtimeGStateResult{id: id, recovered: recover(), order: order + "r"} + results <- result + }() + defer func() { + order += "d" + }() + ready <- struct{}{} + <-start + order += "p" + panic(id) +} + +func TestRuntimeGStateIsolation(t *testing.T) { + ready := make(chan struct{}, 2) + start := make(chan struct{}) + results := make(chan runtimeGStateResult, 2) + for id := 1; id <= 2; id++ { + go runtimeGStatePanic(id, ready, start, results) + } + <-ready + <-ready + close(start) + + seen := [3]bool{} + for i := 0; i < 2; i++ { + result := <-results + if result.id < 1 || result.id > 2 { + t.Fatalf("unexpected goroutine id %d", result.id) + } + if seen[result.id] { + t.Fatalf("duplicate result from goroutine %d", result.id) + } + seen[result.id] = true + if result.recovered != result.id { + t.Fatalf("goroutine %d recovered %v", result.id, result.recovered) + } + if result.order != "pdr" { + t.Fatalf("goroutine %d defer order = %q, want %q", result.id, result.order, "pdr") + } + } + + goexitDone := make(chan any, 1) + go func() { + defer func() { + goexitDone <- recover() + }() + runtime.Goexit() + goexitDone <- "Goexit returned" + }() + if recovered := <-goexitDone; recovered != nil { + t.Fatalf("recover during Goexit = %v, want nil", recovered) + } + + var recovered any + func() { + defer func() { + recovered = recover() + }() + panic("main panic") + }() + if recovered != "main panic" { + t.Fatalf("main goroutine recovered %v, want %q", recovered, "main panic") + } +} diff --git a/test/llgoext/runtime_g_test.go b/test/llgoext/runtime_g_test.go new file mode 100644 index 0000000000..52789908d7 --- /dev/null +++ b/test/llgoext/runtime_g_test.go @@ -0,0 +1,169 @@ +//go:build llgo + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llgoext + +import ( + "testing" + "unsafe" +) + +//go:linkname runtimeGetGForTest github.com/goplus/llgo/runtime/internal/runtime.getg +func runtimeGetGForTest() unsafe.Pointer + +//go:linkname runtimeGetThreadDeferForGTest github.com/goplus/llgo/runtime/internal/runtime.GetThreadDefer +func runtimeGetThreadDeferForGTest() unsafe.Pointer + +//go:linkname runtimeSetThreadDeferForGTest github.com/goplus/llgo/runtime/internal/runtime.SetThreadDefer +func runtimeSetThreadDeferForGTest(unsafe.Pointer) + +//go:linkname runtimeClearThreadDeferForGTest github.com/goplus/llgo/runtime/internal/runtime.ClearThreadDefer +func runtimeClearThreadDeferForGTest() + +type runtimeGPointerResult struct { + first unsafe.Pointer + second unsafe.Pointer +} + +func TestRuntimeGetGIsolation(t *testing.T) { + mainG := runtimeGetGForTest() + if mainG == nil || runtimeGetGForTest() != mainG { + t.Fatalf("main getg is not stable: %p", mainG) + } + + ready := make(chan unsafe.Pointer, 2) + start := make(chan struct{}) + results := make(chan runtimeGPointerResult, 2) + for i := 0; i < 2; i++ { + go func() { + first := runtimeGetGForTest() + ready <- first + <-start + results <- runtimeGPointerResult{first: first, second: runtimeGetGForTest()} + }() + } + firstReady := <-ready + secondReady := <-ready + close(start) + + first := <-results + second := <-results + for i, result := range []runtimeGPointerResult{first, second} { + if result.first == nil || result.first != result.second { + t.Fatalf("goroutine %d getg values = (%p, %p), want one stable non-nil pointer", i, result.first, result.second) + } + if result.first == mainG { + t.Fatalf("goroutine %d shared main g %p", i, mainG) + } + } + if firstReady == secondReady { + t.Fatalf("goroutines shared g %p", firstReady) + } +} + +type runtimeDeferProbeResult struct { + before unsafe.Pointer + inside unsafe.Pointer + after unsafe.Pointer +} + +func runtimeDeferProbe(ready chan<- unsafe.Pointer, start <-chan struct{}, results chan<- runtimeDeferProbeResult) { + result := runtimeDeferProbeResult{before: runtimeGetThreadDeferForGTest()} + func() { + defer func() {}() + result.inside = runtimeGetThreadDeferForGTest() + ready <- result.inside + <-start + }() + result.after = runtimeGetThreadDeferForGTest() + results <- result +} + +func TestRuntimeDeferHeadIsolation(t *testing.T) { + mainHead := runtimeGetThreadDeferForGTest() + ready := make(chan unsafe.Pointer, 2) + start := make(chan struct{}) + results := make(chan runtimeDeferProbeResult, 2) + + go runtimeDeferProbe(ready, start, results) + go runtimeDeferProbe(ready, start, results) + first := <-ready + second := <-ready + close(start) + + if first == nil || second == nil { + t.Fatalf("active defer heads = (%p, %p), want two non-nil heads", first, second) + } + if first == second { + t.Fatalf("concurrent goroutines shared defer head %p", first) + } + for i := 0; i < 2; i++ { + result := <-results + if result.inside == nil { + t.Fatal("active defer head is nil") + } + if result.after != result.before { + t.Fatalf("defer head after return = %p, want previous %p", result.after, result.before) + } + } + if got := runtimeGetThreadDeferForGTest(); got != mainHead { + t.Fatalf("main defer head changed to %p, want %p", got, mainHead) + } +} + +type runtimeDeferAccessorResult struct { + want unsafe.Pointer + got unsafe.Pointer + cleared unsafe.Pointer +} + +func runtimeDeferAccessorProbe(token unsafe.Pointer, results chan<- runtimeDeferAccessorResult) { + previous := runtimeGetThreadDeferForGTest() + runtimeSetThreadDeferForGTest(token) + got := runtimeGetThreadDeferForGTest() + runtimeClearThreadDeferForGTest() + cleared := runtimeGetThreadDeferForGTest() + runtimeSetThreadDeferForGTest(previous) + results <- runtimeDeferAccessorResult{want: token, got: got, cleared: cleared} +} + +func TestRuntimeDeferAccessors(t *testing.T) { + results := make(chan runtimeDeferAccessorResult, 2) + first := new(byte) + second := new(byte) + go runtimeDeferAccessorProbe(unsafe.Pointer(first), results) + go runtimeDeferAccessorProbe(unsafe.Pointer(second), results) + + for i := 0; i < 2; i++ { + result := <-results + if result.got != result.want { + t.Fatalf("defer head = %p, want %p", result.got, result.want) + } + if result.cleared != nil { + t.Fatalf("cleared defer head = %p, want nil", result.cleared) + } + } +} + +var runtimeGSink unsafe.Pointer + +func BenchmarkRuntimeGetG(b *testing.B) { + for i := 0; i < b.N; i++ { + runtimeGSink = runtimeGetGForTest() + } +} From a8692204d05c0a90de4c9697b179543e72b3fb09 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 6 Jul 2026 14:03:07 +0800 Subject: [PATCH 2/4] ssa,runtime: tighten recover to direct deferred calls (Defer-node model) Re-expresses #1918 on the #2023 base (its remaining ~11k diff lines were the pre-#2012 funcinfo draft, superseded by the stage-5 chain): - recover() only succeeds when called directly by a deferred function (gc semantics): the panic node records the owning Defer frame at rethrow (panicKey/panicNode + GoDeferData), and Recover checks the caller is that frame's direct deferred call. Closure wraps carry StartRecoverFrameAlias/EndRecoverFrame so method-value and closure adapters stay transparent to the ownership check. - Rethrow keeps the #2023 PanicTraceback hook on the unrecovered path. - xfail: retire fixedbugs/issue4066 (2m-timeout entries; now runs in ~2.7s), fixedbugs/issue73916 and issue73916b (go1.26 recover semantics), validated on darwin/arm64 go1.26. Supersedes #1918. --- cl/_testgo/cgodefer/cgodefer.go | 19 +-- cl/_testgo/recoverthenpanic/in.go | 26 ++-- cl/cgo_test.go | 41 ++++++ cl/compile.go | 67 +++++++++- cl/instr.go | 55 ++++++--- cl/rewrite_internal_test.go | 6 +- runtime/internal/runtime/g.go | 9 +- runtime/internal/runtime/z_baremetal.go | 4 + runtime/internal/runtime/z_default.go | 11 +- runtime/internal/runtime/z_rt.go | 56 +++++++-- ssa/closure_wrap.go | 34 +++-- ssa/eh.go | 115 +++++++++++++---- ssa/expr.go | 29 +++++ ssa/ssa_test.go | 85 ++++++++++++- test/go/recover_defer_fixedbugs_test.go | 158 ++++++++++++++++++++++++ test/goroot/xfail.yaml | 68 ---------- 16 files changed, 628 insertions(+), 155 deletions(-) create mode 100644 test/go/recover_defer_fixedbugs_test.go 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/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/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 6cb94df500..563dc60102 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -180,6 +180,7 @@ type context struct { debugAllocVars map[*ssa.Alloc]*types.Var runtimeCallerFuncs map[*ssa.Function]bool pcLineSeq uint64 + recoverSlots map[*ssa.Alloc]none patches Patches blkInfos []blocks.Info @@ -559,12 +560,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 { @@ -601,12 +605,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 { @@ -1118,6 +1129,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, ok := nonDebugReferrers(v) if !ok || len(refs) == 0 { @@ -1303,6 +1337,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() @@ -1338,6 +1375,10 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue elem := p.type_(t.Elem(), llssa.InGo) ret = b.Alloc(elem, v.Heap) p.debugAlloc(b, v, ret) + 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 @@ -1676,7 +1717,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) @@ -1754,6 +1798,25 @@ func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.V return div } +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 { diff --git a/cl/instr.go b/cl/instr.go index ddbcb58806..a613dd38b4 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -1792,12 +1792,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 } - return b.Do(act, fn, buildCall, args...) + 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 + } +} + +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) { @@ -1952,6 +1976,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) @@ -1961,7 +1986,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) } @@ -1995,7 +2020,7 @@ 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) // TODO(xsw): check ca != llssa.Call @@ -2004,13 +2029,13 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm 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...) @@ -2084,33 +2109,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 { @@ -2120,7 +2145,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/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 325f062078..731b4df429 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -741,8 +741,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() @@ -878,7 +878,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/runtime/internal/runtime/g.go b/runtime/internal/runtime/g.go index 4a1a3e5f04..dd62c6905e 100644 --- a/runtime/internal/runtime/g.go +++ b/runtime/internal/runtime/g.go @@ -20,10 +20,11 @@ import "unsafe" // g holds the runtime state owned by one LLGo goroutine. type g struct { - defer_ *Defer - panic_ unsafe.Pointer - goexit bool - isMain bool + defer_ *Defer + panic_ unsafe.Pointer + recoverFrame unsafe.Pointer + goexit bool + isMain bool } // SetThreadDefer associates the current goroutine with the given defer chain. diff --git a/runtime/internal/runtime/z_baremetal.go b/runtime/internal/runtime/z_baremetal.go index a862225852..e8bb76f386 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 := getg().panic_; 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 7f0b7601ba..a9f64300d5 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" @@ -19,13 +21,18 @@ func Rethrow(link *Defer) { gp := getg() if ptr := gp.panic_; 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 == gp.defer_ { + node.defer_ = link + } c.Siglongjmp(link.Addr, 1) } } else if gp.goexit { diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index d771ab94cf..8e1c893ea3 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -35,14 +35,28 @@ 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) { +func Recover(token unsafe.Pointer) (ret any) { gp := getg() + if token == nil || token != gp.recoverFrame { + return nil + } ptr := gp.panic_ if ptr != nil { - gp.panic_ = nil - ret = *(*any)(ptr) - c.Free(ptr) + node := (*panicNode)(ptr) + if node.defer_ != gp.defer_ { + return nil + } + gp.panic_ = node.prev + gp.recoverFrame = nil + ret = node.arg + c.Free(unsafe.Pointer(node)) if PanicRecovered != nil { PanicRecovered() } @@ -50,20 +64,46 @@ func Recover() (ret any) { return } +// StartRecoverFrame enables direct recover calls made by the deferred function +// currently being invoked from frame. +func StartRecoverFrame(frame unsafe.Pointer) unsafe.Pointer { + gp := getg() + old := gp.recoverFrame + gp.recoverFrame = frame + return old +} + +// EndRecoverFrame restores direct recover permission after a deferred call. +func EndRecoverFrame(frame unsafe.Pointer) { + getg().recoverFrame = 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 { + gp := getg() + old := gp.recoverFrame + if old == from { + gp.recoverFrame = 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 gp := getg() - gp.panic_ = ptr + ptr := (*panicNode)(c.Malloc(unsafe.Sizeof(panicNode{}))) + ptr.prev = gp.panic_ + ptr.arg = v + ptr.defer_ = gp.defer_ + gp.panic_ = unsafe.Pointer(ptr) Rethrow(gp.defer_) } - func Goexit() { gp := getg() gp.goexit = true 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/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/expr.go b/ssa/expr.go index caee097c06..28252936fe 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -48,6 +48,8 @@ type Expr struct { var Nil Expr // Zero value is a nil Expr +const mayRecoverAttr = "llgo.may-recover" + // IsNil checks if the expression is nil or not. func (v Expr) IsNil() bool { return v.Type == nil @@ -59,6 +61,33 @@ 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 v.Type == nil || v.impl.IsNil() { + return v + } + fn := v.impl.IsAFunction() + if !fn.IsNil() && fn.GetStringAttributeAtIndex(-1, mayRecoverAttr).IsNil() { + ctx := fn.GlobalParent().Context() + fn.AddFunctionAttr(ctx.CreateStringAttribute(mayRecoverAttr, "true")) + } + return v +} + +func (v Expr) mayRecover() bool { + if v.Type == nil || v.impl.IsNil() { + return false + } + fn := v.impl.IsAFunction() + return !fn.IsNil() && !fn.GetStringAttributeAtIndex(-1, mayRecoverAttr).IsNil() +} + func (v Expr) SetName(alias string) Expr { v.impl.SetName(alias) return v diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index c45e71035c..28dd99c4ac 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -165,6 +165,75 @@ 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 marked := Nil.MarkMayRecover(); !marked.IsNil() { + t.Fatalf("marked nil expression = %v, want nil", marked) + } + 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 attr := callee.Expr.impl.GetStringAttributeAtIndex(-1, mayRecoverAttr); attr.IsNil() || attr.GetStringValue() != "true" { + t.Fatalf("recover marker attribute = %v, want %q", attr, "true") + } + callee.Expr.MarkMayRecover() + if !deferMayRecover(callee.Expr) { + t.Fatal("repeated recover marking cleared the marker") + } + 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 !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) { @@ -1087,15 +1156,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) } @@ -1457,7 +1534,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) { 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/goroot/xfail.yaml b/test/goroot/xfail.yaml index ae5bb230cb..4537e94933 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -579,12 +579,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 @@ -951,12 +945,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 @@ -1323,12 +1311,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 @@ -2525,16 +2507,6 @@ xfails: directive: run case: fixedbugs/issue37975.go reason: current main goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73916.go - reason: go1.26 goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73916b.go - reason: go1.26 goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: typeparam/mdempsky/16.go @@ -2729,16 +2701,6 @@ 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 @@ -2961,16 +2923,6 @@ xfails: 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 @@ -3182,16 +3134,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 @@ -3297,16 +3239,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 From add384f537e1633b8978170268c8cd8c30c8c34c Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 6 Jul 2026 15:54:49 +0800 Subject: [PATCH 3/4] ssa,runtime: real runtime.Error values for type asserts, fault signals; recover conformance Re-expresses the surviving value of #1882 on top of #2033 (its remaining ~11k diff lines were the pre-#2012 funcinfo draft, superseded): - Failed (non-comma-ok) type assertions panic with a real *runtime.TypeAssertionError built at runtime (TypeAssertError + missing-method lookup from abi tables) instead of a plain errorString; the recovered value implements runtime.Error, matching gc. The source-interface abi type is deliberately not materialized at the assert site: doing so can reference another package's private local-generic symbols (undefined at link); the message's interface name is the documented mdempsky/16 residual. - SIGBUS joins SIGSEGV in the fault-to-panic signal path (per-OS signal constants; darwin/linux). - goroot xfails retired, validated darwin go1.26 + go1.24: recover2.go, recover4.go, zerodivide.go, fixedbugs/issue73917.go, issue73920.go. - recover1.go stays xfailed with an updated reason (recursive-panic sub-call recover masking - Defer-node model follow-up); three ported tests covering the same class are t.Skip'ed with that pointer. - Golden CHECK updates: assert-failure sites now emit TypeAssertError+Panic; constants renumbered from actual IR. Supersedes #1882. --- cl/_testdata/vargs/in.go | 4 +- cl/_testgo/abimethod/in.go | 3 +- cl/_testgo/closureall/in.go | 5 +- cl/_testgo/genericembediface/in.go | 16 +- cl/_testgo/ifaceprom/in.go | 81 +++--- cl/_testgo/invoke/in.go | 28 +- cl/_testgo/reader/in.go | 38 +-- cl/_testgo/reflect/in.go | 4 +- cl/_testgo/tpinst/main.go | 5 +- cl/_testrt/any/in.go | 16 +- cl/_testrt/any/out.ll | 136 +++++++++ cl/_testrt/funcdecl/in.go | 37 +-- cl/_testrt/makemap/in.go | 12 +- cl/_testrt/slice2array/in.go | 26 +- cl/_testrt/tpabi/in.go | 12 +- cl/_testrt/typed/in.go | 83 +++--- runtime/internal/runtime/errors.go | 46 ++++ runtime/internal/runtime/z_signal.go | 10 +- runtime/internal/runtime/z_signal_darwin.go | 7 + runtime/internal/runtime/z_signal_linux.go | 7 + runtime/internal/runtime/z_signal_other.go | 7 + ssa/eh_defer_test.go | 158 ----------- ssa/interface.go | 11 +- ssa/package.go | 1 + test/go/recover_defer_test.go | 289 ++++++++++++++++++++ test/go/recover_fault_unix_test.go | 49 ++++ test/go/runtime_error_recover_test.go | 145 ++++++---- test/goroot/xfail.yaml | 118 ++------ 28 files changed, 868 insertions(+), 486 deletions(-) create mode 100644 cl/_testrt/any/out.ll create mode 100644 runtime/internal/runtime/z_signal_darwin.go create mode 100644 runtime/internal/runtime/z_signal_linux.go create mode 100644 runtime/internal/runtime/z_signal_other.go delete mode 100644 ssa/eh_defer_test.go create mode 100644 test/go/recover_defer_test.go create mode 100644 test/go/recover_fault_unix_test.go 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/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/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..5461dfc391 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 @@ -179,6 +180,7 @@ func main() { // CHECK-NEXT: ret void // CHECK-NEXT: } + // CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.interequal"(ptr %0, ptr %1, ptr %2){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.interequal"(ptr %1, ptr %2) diff --git a/cl/_testrt/typed/in.go b/cl/_testrt/typed/in.go index 1444e36c60..88c7e1836e 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: } @@ -115,6 +115,7 @@ func main() { println(ar[0], ar[1], ok) } + // CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.memequal64"(ptr %0, ptr %1, ptr %2){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.memequal64"(ptr %1, ptr %2) diff --git a/runtime/internal/runtime/errors.go b/runtime/internal/runtime/errors.go index 7b6776d99a..6fe6436219 100644 --- a/runtime/internal/runtime/errors.go +++ b/runtime/internal/runtime/errors.go @@ -222,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/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/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/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/package.go b/ssa/package.go index 8282a676d6..3d3bcf329b 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -212,6 +212,7 @@ type aProgram struct { memsetInlineTy *types.Signature stackSaveTy *types.Signature stackRestoreTy *types.Signature + frameAddressTy *types.Signature createKeyTy *types.Signature getSpecTy *types.Signature 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/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 4537e94933..1bdd4d8e51 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -2453,30 +2453,10 @@ xfails: directive: run case: noinit.go reason: current main goroot run failure on darwin/arm64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: recover2.go - reason: current main goroot run failure on darwin/arm64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: recover2.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 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: zerodivide.go - reason: current main goroot run failure on darwin/arm64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: zerodivide.go - reason: current main goroot run failure on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -2487,6 +2467,10 @@ xfails: directive: run case: fixedbugs/issue16130.go reason: current main goroot run failure on darwin/arm64 + - platform: darwin/arm64 + 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 darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -2526,11 +2510,6 @@ xfails: directive: run case: noinit.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: recover2.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -2544,7 +2523,7 @@ xfails: - version: go1.24 platform: linux/amd64 directive: run - case: zerodivide.go + case: fixedbugs/issue16130.go reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 @@ -2561,6 +2540,21 @@ xfails: directive: run 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: 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: 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 @@ -2596,11 +2590,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 @@ -2614,7 +2603,7 @@ xfails: - version: go1.25 platform: linux/amd64 directive: run - case: zerodivide.go + case: fixedbugs/issue16130.go reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 @@ -2656,11 +2645,6 @@ xfails: 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 @@ -2674,7 +2658,7 @@ xfails: - version: go1.26 platform: linux/amd64 directive: run - case: zerodivide.go + case: fixedbugs/issue16130.go reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 @@ -2758,14 +2742,6 @@ 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 @@ -2983,16 +2959,6 @@ 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 @@ -3084,11 +3050,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 @@ -3144,11 +3105,6 @@ xfails: 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 - version: go1.24 platform: linux/amd64 directive: run @@ -3184,16 +3140,6 @@ xfails: 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 @@ -3391,16 +3337,6 @@ xfails: 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 - version: go1.24 platform: darwin/arm64 directive: run @@ -3471,16 +3407,6 @@ xfails: 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 From a7b4d8ca3e246b47c0e4cb4479e76c522adc7044 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 6 Jul 2026 18:47:48 +0800 Subject: [PATCH 4/4] style: gofmt golden in.go files under _testrt --- cl/_testrt/tpabi/in.go | 1 - cl/_testrt/typed/in.go | 1 - 2 files changed, 2 deletions(-) diff --git a/cl/_testrt/tpabi/in.go b/cl/_testrt/tpabi/in.go index 5461dfc391..cf003eb199 100644 --- a/cl/_testrt/tpabi/in.go +++ b/cl/_testrt/tpabi/in.go @@ -180,7 +180,6 @@ func main() { // CHECK-NEXT: ret void // CHECK-NEXT: } - // CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.interequal"(ptr %0, ptr %1, ptr %2){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.interequal"(ptr %1, ptr %2) diff --git a/cl/_testrt/typed/in.go b/cl/_testrt/typed/in.go index 88c7e1836e..49a2468ab6 100644 --- a/cl/_testrt/typed/in.go +++ b/cl/_testrt/typed/in.go @@ -115,7 +115,6 @@ func main() { println(ar[0], ar[1], ok) } - // CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.memequal64"(ptr %0, ptr %1, ptr %2){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.memequal64"(ptr %1, ptr %2)