From bf416bc761cb9ebab3cd6266418118bbfe5434a0 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 14:17:05 +0800 Subject: [PATCH 1/9] ssa: use static defer continuation selectors --- cl/_testgo/cgodefer/cgodefer.go | 28 +++++-- cl/_testgo/cgomacro/cgomacro.go | 28 +++++-- cl/_testgo/cgopython/cgopython.go | 28 +++++-- cl/_testgo/defer5/in.go | 4 +- cl/_testgo/deferloop/in.go | 5 +- cl/_testgo/recoverthenpanic/in.go | 53 +++++++++---- runtime/internal/runtime/z_rt.go | 4 +- ssa/eh.go | 128 ++++++++++++++++++++---------- ssa/eh_defer_test.go | 60 ++++++++++++++ 9 files changed, 249 insertions(+), 89 deletions(-) diff --git a/cl/_testgo/cgodefer/cgodefer.go b/cl/_testgo/cgodefer/cgodefer.go index 5ce8dd6bfe..f9c1d5a7bd 100644 --- a/cl/_testgo/cgodefer/cgodefer.go +++ b/cl/_testgo/cgodefer/cgodefer.go @@ -38,7 +38,7 @@ import "C" // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 2 // CHECK-NEXT: store ptr %8, ptr %13, align 8 // CHECK-NEXT: %14 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %14, align 8 +// CHECK-NEXT: store i64 0, ptr %14, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %10) // CHECK-NEXT: %15 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 1 // CHECK-NEXT: %16 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 3 @@ -53,7 +53,7 @@ import "C" // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %16, align 8 +// CHECK-NEXT: store i64 1, ptr %16, align 8 // CHECK-NEXT: %21 = load i64, ptr %15, align 8 // CHECK-NEXT: %22 = load ptr, ptr %18, align 8 // CHECK-NEXT: %23 = icmp ne ptr %22, null @@ -73,13 +73,16 @@ import "C" // CHECK-NEXT: %28 = getelementptr inbounds { ptr, i64, { ptr, ptr } }, ptr %25, i32 0, i32 2 // CHECK-NEXT: store { ptr, ptr } %7, ptr %28, align 8 // CHECK-NEXT: store ptr %25, ptr %18, align 8 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %17, align 8 +// CHECK-NEXT: store i64 1, ptr %17, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %17, align 8 -// CHECK-NEXT: %29 = load ptr, ptr %16, align 8 -// CHECK-NEXT: indirectbr ptr %29, [label %_llgo_3, label %_llgo_2] +// CHECK-NEXT: store i64 0, ptr %17, align 8 +// CHECK-NEXT: %29 = load i64, ptr %16, align 8 +// CHECK-NEXT: switch i64 %29, label %_llgo_10 [ +// CHECK-NEXT: i64 0, label %_llgo_2 +// CHECK-NEXT: i64 1, label %_llgo_3 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_8 // CHECK-NEXT: ret void @@ -101,8 +104,17 @@ import "C" // 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 i64, ptr %17, align 8 +// CHECK-NEXT: switch i64 %38, label %_llgo_9 [ +// CHECK-NEXT: i64 0, label %_llgo_3 +// CHECK-NEXT: i64 1, label %_llgo_6 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_10: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable // CHECK-NEXT: } func main() { // CHECK-LABEL: define { ptr, ptr } @"main.main$1"(ptr {{(nest|swiftself)}} %0){{.*}} { diff --git a/cl/_testgo/cgomacro/cgomacro.go b/cl/_testgo/cgomacro/cgomacro.go index c2c152d37d..6a95c03111 100644 --- a/cl/_testgo/cgomacro/cgomacro.go +++ b/cl/_testgo/cgomacro/cgomacro.go @@ -130,7 +130,7 @@ import ( // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 2 // CHECK-NEXT: store ptr %3, ptr %8, align 8 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %9, align 8 +// CHECK-NEXT: store i64 0, ptr %9, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %5) // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 1 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 @@ -145,14 +145,17 @@ import ( // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %11, align 8 +// CHECK-NEXT: store i64 1, ptr %11, align 8 // CHECK-NEXT: %16 = load i64, ptr %10, align 8 // CHECK-NEXT: %17 = call [0 x i8] @main._Cfunc_Py_Finalize() // CHECK-NEXT: %18 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, align 8 // CHECK-NEXT: %19 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %18, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %19) -// CHECK-NEXT: %20 = load ptr, ptr %12, align 8 -// CHECK-NEXT: indirectbr ptr %20, [label %_llgo_3, label %_llgo_6] +// CHECK-NEXT: %20 = load i64, ptr %12, align 8 +// CHECK-NEXT: switch i64 %20, label %_llgo_7 [ +// CHECK-NEXT: i64 0, label %_llgo_3 +// CHECK-NEXT: i64 1, label %_llgo_6 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %3) @@ -165,16 +168,25 @@ import ( // CHECK-NEXT: %24 = call i32 @"main.main$5"() // CHECK-NEXT: %25 = call i32 @"main.main$6"() // CHECK-NEXT: %26 = call i32 @"main.main$7"() -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %12, align 8 +// CHECK-NEXT: store i64 1, ptr %12, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %12, align 8 -// CHECK-NEXT: %27 = load ptr, ptr %11, align 8 -// CHECK-NEXT: indirectbr ptr %27, [label %_llgo_3, label %_llgo_2] +// CHECK-NEXT: store i64 0, ptr %12, align 8 +// CHECK-NEXT: %27 = load i64, ptr %11, align 8 +// CHECK-NEXT: switch i64 %27, label %_llgo_8 [ +// CHECK-NEXT: i64 0, label %_llgo_2 +// CHECK-NEXT: i64 1, label %_llgo_3 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 // CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_2 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_8: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable // CHECK-NEXT: } func main() { C.test_stdout() diff --git a/cl/_testgo/cgopython/cgopython.go b/cl/_testgo/cgopython/cgopython.go index aea2a0f615..977ef31040 100644 --- a/cl/_testgo/cgopython/cgopython.go +++ b/cl/_testgo/cgopython/cgopython.go @@ -63,7 +63,7 @@ import "C" // CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 2 // CHECK-NEXT: store ptr %1, ptr %6, align 8 // CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %7, align 8 +// CHECK-NEXT: store i64 0, ptr %7, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %3) // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 1 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 3 @@ -78,14 +78,17 @@ import "C" // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %9, align 8 +// CHECK-NEXT: store i64 1, ptr %9, align 8 // CHECK-NEXT: %14 = load i64, ptr %8, align 8 // CHECK-NEXT: %15 = call [0 x i8] @main._Cfunc_Py_Finalize() // CHECK-NEXT: %16 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, align 8 // CHECK-NEXT: %17 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %16, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %17) -// CHECK-NEXT: %18 = load ptr, ptr %10, align 8 -// CHECK-NEXT: indirectbr ptr %18, [label %_llgo_3, label %_llgo_6] +// CHECK-NEXT: %18 = load i64, ptr %10, align 8 +// CHECK-NEXT: switch i64 %18, label %_llgo_7 [ +// CHECK-NEXT: i64 0, label %_llgo_3 +// CHECK-NEXT: i64 1, label %_llgo_6 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %1) @@ -94,16 +97,25 @@ import "C" // CHECK-NEXT: _llgo_4: ; preds = %_llgo_0 // CHECK-NEXT: %19 = call ptr @"{{.*}}/runtime/internal/runtime.CString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 23 }) // CHECK-NEXT: %20 = call i32 @main._Cfunc_PyRun_SimpleString(ptr %19) -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %10, align 8 +// CHECK-NEXT: store i64 1, ptr %10, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %10, align 8 -// CHECK-NEXT: %21 = load ptr, ptr %9, align 8 -// CHECK-NEXT: indirectbr ptr %21, [label %_llgo_3, label %_llgo_2] +// CHECK-NEXT: store i64 0, ptr %10, align 8 +// CHECK-NEXT: %21 = load i64, ptr %9, align 8 +// CHECK-NEXT: switch i64 %21, label %_llgo_8 [ +// CHECK-NEXT: i64 0, label %_llgo_2 +// CHECK-NEXT: i64 1, label %_llgo_3 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 // CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_2 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_8: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable // CHECK-NEXT: } func main() { C.Py_Initialize() diff --git a/cl/_testgo/defer5/in.go b/cl/_testgo/defer5/in.go index 4bcca285f0..ec59812005 100644 --- a/cl/_testgo/defer5/in.go +++ b/cl/_testgo/defer5/in.go @@ -4,12 +4,12 @@ package main // CHECK-LABEL: define void @main.main(){{.*}} { func main() { // CHECK: GetThreadDefer - // CHECK: blockaddress(@main.main, %_llgo_2) - // CHECK: blockaddress(@main.main, %_llgo_8) + // CHECK: switch i{{(32|64)}} // CHECK: call void @"main.main$1"() // CHECK: call void @"main.main$2"() // CHECK: FreeDeferNode // CHECK: FreeDeferNode + // CHECK: switch i{{(32|64)}} defer println("A") defer func() { if e := recover(); e != nil { diff --git a/cl/_testgo/deferloop/in.go b/cl/_testgo/deferloop/in.go index 7fec91526a..c4c0568599 100644 --- a/cl/_testgo/deferloop/in.go +++ b/cl/_testgo/deferloop/in.go @@ -5,10 +5,11 @@ package main func main() { // CHECK: GetThreadDefer for i := 0; i < 3; i++ { - // CHECK: blockaddress(@main.main, %_llgo_6) + // CHECK: switch i{{(32|64)}} defer println("loop", i) } + // CHECK: switch i{{(32|64)}} + // CHECK: FreeDeferNode // CHECK: PrintString // CHECK: PrintInt - // CHECK: FreeDeferNode } diff --git a/cl/_testgo/recoverthenpanic/in.go b/cl/_testgo/recoverthenpanic/in.go index 6d1364b14f..c6ef4d3eaa 100644 --- a/cl/_testgo/recoverthenpanic/in.go +++ b/cl/_testgo/recoverthenpanic/in.go @@ -20,7 +20,7 @@ package main // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 2 // CHECK-NEXT: store ptr %3, ptr %8, align 8 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_5), ptr %9, align 8 +// CHECK-NEXT: store i64 0, ptr %9, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %5) // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 1 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 @@ -51,7 +51,7 @@ package main // CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_4 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 3 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_8), ptr %12, align 8 +// CHECK-NEXT: store i64 1, ptr %12, align 8 // CHECK-NEXT: br label %_llgo_5 // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_6 @@ -61,7 +61,7 @@ package main // CHECK-NEXT: br i1 %2, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_7, %_llgo_2 -// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_6), ptr %11, align 8 +// CHECK-NEXT: store i64 1, ptr %11, align 8 // CHECK-NEXT: %23 = load i64, ptr %10, align 8 // CHECK-NEXT: %24 = and i64 %23, 1 // CHECK-NEXT: %25 = icmp ne i64 %24, 0 @@ -72,9 +72,12 @@ package main // CHECK-NEXT: br label %_llgo_3 // CHECK-EMPTY: // CHECK-NEXT: _llgo_7: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_6), ptr %12, align 8 -// CHECK-NEXT: %26 = load ptr, ptr %11, align 8 -// CHECK-NEXT: indirectbr ptr %26, [label %_llgo_6, label %_llgo_5] +// CHECK-NEXT: store i64 0, ptr %12, align 8 +// CHECK-NEXT: %26 = load i64, ptr %11, align 8 +// CHECK-NEXT: switch i64 %26, label %_llgo_14 [ +// CHECK-NEXT: i64 0, label %_llgo_5 +// CHECK-NEXT: i64 1, label %_llgo_6 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_10 // CHECK-NEXT: ret void @@ -88,8 +91,11 @@ package main // CHECK-NEXT: %29 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, align 8 // CHECK-NEXT: %30 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %29, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %30) -// CHECK-NEXT: %31 = load ptr, ptr %12, align 8 -// CHECK-NEXT: indirectbr ptr %31, [label %_llgo_6, label %_llgo_8] +// CHECK-NEXT: %31 = load i64, ptr %12, align 8 +// CHECK-NEXT: switch i64 %31, label %_llgo_13 [ +// CHECK-NEXT: i64 0, label %_llgo_6 +// CHECK-NEXT: i64 1, label %_llgo_8 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_11: ; preds = %_llgo_9 // CHECK-NEXT: %32 = load ptr, ptr %13, align 8 @@ -103,6 +109,12 @@ package main // CHECK-EMPTY: // CHECK-NEXT: _llgo_12: ; preds = %_llgo_9 // CHECK-NEXT: br label %_llgo_10 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_13: ; preds = %_llgo_10 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_14: ; preds = %_llgo_7 +// CHECK-NEXT: unreachable // CHECK-NEXT: } func End() { @@ -144,7 +156,7 @@ func main() { // CHECK-NEXT: %5 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 2 // CHECK-NEXT: store ptr %0, ptr %5, align 8 // CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %6, align 8 +// CHECK-NEXT: store i64 0, ptr %6, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %2) // CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 @@ -159,14 +171,16 @@ func main() { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5 -// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %8, align 8 +// CHECK-NEXT: store i64 1, ptr %8, align 8 // CHECK-NEXT: %13 = load i64, ptr %7, align 8 // CHECK-NEXT: call void @main.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: %16 = load i64, ptr %9, align 8 +// CHECK-NEXT: switch i64 %16, label %_llgo_6 [ +// CHECK-NEXT: i64 0, label %_llgo_3 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %0) @@ -180,7 +194,16 @@ func main() { // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.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: store i64 0, ptr %9, align 8 +// CHECK-NEXT: %19 = load i64, ptr %8, align 8 +// CHECK-NEXT: switch i64 %19, label %_llgo_7 [ +// CHECK-NEXT: i64 0, label %_llgo_2 +// CHECK-NEXT: i64 1, label %_llgo_3 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index 090e3e4cbd..fc707c3abe 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -30,8 +30,8 @@ type Defer struct { Addr unsafe.Pointer // sigjmpbuf Bits uintptr Link *Defer - Reth unsafe.Pointer // block address after Rethrow - Rund unsafe.Pointer // block address after RunDefers + Reth uintptr // continuation selector after Rethrow + Rund uintptr // continuation selector after RunDefers Args unsafe.Pointer // defer func and args links } diff --git a/ssa/eh.go b/ssa/eh.go index 104b8e04af..6d88384444 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -144,21 +144,29 @@ func (p Function) deferInitBuilder() (b Builder, next BasicBlock) { } type aDefer struct { - nextBit int // next defer bit - data Expr // pointer to runtime.Defer - bitsPtr Expr // pointer to defer bits - rethPtr Expr // next block of Rethrow - rundPtr Expr // next block of RunDefers - argsPtr Expr // func and args links - procBlk BasicBlock // deferProc block - panicBlk BasicBlock // panic block (runDefers and rethrow) - rundsNext []BasicBlock // next blocks of RunDefers + nextBit int // next defer bit + data Expr // pointer to runtime.Defer + bitsPtr Expr // pointer to defer bits + rethPtr Expr // Rethrow continuation selector + rundPtr Expr // RunDefers continuation selector + argsPtr Expr // func and args links + procBlk BasicBlock // deferProc block + panicBlk BasicBlock // panic block (runDefers and rethrow) + rundTargets []deferTarget // loopDrainerGenerated marks whether we've already generated the loop-defer // drain loop for the current contiguous run of DeferInLoop statements (when // walking defers in reverse order in endDefer). loopDrainerGenerated bool loopCases []loopDeferCase - stmts []func(bits Expr) + stmts []func(bits Expr, resume deferTarget) +} + +// deferTarget pairs the integer stored in runtime.Defer with its local block. +// Selectors are dense within each dispatch so LLVM can choose the target's +// preferred switch lowering without exposing block addresses. +type deferTarget struct { + index int + block BasicBlock } // loopDeferCase represents a defer statement inside a loop. @@ -176,8 +184,8 @@ const ( // 0: addr sigjmpbuf // 1: bits uintptr // 2: link *Defer - // 3: reth voidptr: block address after Rethrow - // 4: rund voidptr: block address after RunDefers + // 3: reth uintptr: continuation selector after Rethrow + // 4: rund uintptr: continuation selector after RunDefers // 5: func and args links deferSigjmpbuf = iota deferBits @@ -267,7 +275,8 @@ func (b Builder) initDeferState(procBlk, rethrowBlk BasicBlock) (*aDefer, Expr, zero := prog.Val(uintptr(0)) link := b.Call(b.Pkg.rtFunc("GetThreadDefer")) jb := b.AllocaSigjmpBuf() - ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl) + // Reth selector 0 is reserved for procBlk; Rund is zero-initialized. + ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, zero.impl) deferData := Expr{ptr, prog.DeferPtr()} b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData) bitsPtr := b.FieldAddr(deferData, deferBits) @@ -282,13 +291,13 @@ func (b Builder) initDeferState(procBlk, rethrowBlk BasicBlock) (*aDefer, Expr, retval := b.Sigsetjmp(jb, czero) self.defer_ = &aDefer{ - data: deferData, - bitsPtr: bitsPtr, - rethPtr: rethPtr, - rundPtr: rundPtr, - argsPtr: argsPtr, - procBlk: procBlk, - rundsNext: []BasicBlock{rethrowBlk}, + data: deferData, + bitsPtr: bitsPtr, + rethPtr: rethPtr, + rundPtr: rundPtr, + argsPtr: argsPtr, + procBlk: procBlk, + rundTargets: []deferTarget{{index: 0, block: rethrowBlk}}, } if len(self.pendingLoopCases) > 0 { self.defer_.loopCases = append(self.defer_.loopCases, self.pendingLoopCases...) @@ -381,7 +390,7 @@ func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Bui } func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr, nextbit Expr) { - self.stmts = append(self.stmts, func(bits Expr) { + self.stmts = append(self.stmts, func(bits Expr, resume deferTarget) { switch kind { case DeferInCond: // Leaving a run of loop defers; allow the next loop-defer statement @@ -399,18 +408,18 @@ func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCal self.loopDrainerGenerated = false b.callDefer(self, typ, buildCall, fn, args) case DeferInLoop: - b.loopDeferDrainer(self) + b.loopDeferDrainer(self, resume) } }) } func (b Builder) appendLoopDeferDrainer(self *aDefer) { - self.stmts = append(self.stmts, func(Expr) { - b.loopDeferDrainer(self) + self.stmts = append(self.stmts, func(_ Expr, resume deferTarget) { + b.loopDeferDrainer(self, resume) }) } -func (b Builder) loopDeferDrainer(self *aDefer) { +func (b Builder) loopDeferDrainer(self *aDefer, resume deferTarget) { if self.loopDrainerGenerated { return } @@ -420,7 +429,9 @@ func (b Builder) loopDeferDrainer(self *aDefer) { } drainEntry := b.blk - drainEntryAddr := drainEntry.Addr() + if drainEntry != resume.block { + panic("loop defer resume target does not match the drainer entry") + } prog := b.Prog condBlk := b.Func.MakeBlock() @@ -456,7 +467,7 @@ func (b Builder) loopDeferDrainer(self *aDefer) { b.If(match, caseBlks[i], nextBlk) b.SetBlockEx(caseBlks[i], AtEnd, true) - b.Store(self.rethPtr, drainEntryAddr) + b.storeDeferTarget(self.rethPtr, resume) b.callDefer(self, c.typ, c.buildCall, c.fn, c.args) b.Jump(condBlk) } @@ -557,26 +568,43 @@ func (b Builder) RunDefers() { return } blk := b.Func.MakeBlock() - self.rundsNext = append(self.rundsNext, blk) + target := deferTarget{index: len(self.rundTargets), block: blk} + self.rundTargets = append(self.rundTargets, target) - b.Store(self.rundPtr, blk.Addr()) + b.storeDeferTarget(self.rundPtr, target) b.Jump(self.procBlk) b.SetBlockEx(blk, AtEnd, false) b.blk.last = blk.last } +func (b Builder) storeDeferTarget(ptr Expr, target deferTarget) { + b.Store(ptr, b.Prog.Val(uintptr(target.index))) +} + +func (b Builder) jumpDeferTarget(ptr Expr, targets []deferTarget) { + selector := b.Load(ptr) + invalid := b.Func.MakeBlock() + sw := b.impl.CreateSwitch(selector.impl, invalid.first, len(targets)) + for _, target := range targets { + sw.AddCase(b.Prog.Val(uintptr(target.index)).impl, target.block.first) + } + b.SetBlockEx(invalid, AtEnd, false) + b.Unreachable() +} + func (p Function) endDefer(b Builder) { self := p.defer_ if self == nil { return } - nexts := self.rundsNext - if len(nexts) == 0 { + rundTargets := self.rundTargets + if len(rundTargets) == 0 { return } - rethrowBlk := nexts[0] + rethrowTarget := rundTargets[0] + rethrowBlk := rethrowTarget.block procBlk := self.procBlk panicBlk := self.panicBlk rethPtr := self.rethPtr @@ -585,28 +613,40 @@ func (p Function) endDefer(b Builder) { stmts := self.stmts n := len(stmts) - rethsNext := make([]BasicBlock, n+1) + rethChain := make([]deferTarget, n+1) blks := p.MakeBlocks(n - 1) - copy(rethsNext[1:], blks) - rethsNext[0] = rethrowBlk - rethsNext[n] = procBlk + // Reth selector 0 must remain procBlk because initDeferState installs it + // before the final number of deferred statements is known. Selector 1 is + // the terminal rethrow, followed by the intermediate continuation blocks. + rethTargets := make([]deferTarget, n+1) + rethTargets[0] = deferTarget{index: 0, block: procBlk} + if n > 0 { + rethTargets[1] = deferTarget{index: 1, block: rethrowBlk} + for i, blk := range blks { + rethTargets[i+2] = deferTarget{index: i + 2, block: blk} + } + rethChain[0] = rethTargets[1] + copy(rethChain[1:], rethTargets[2:]) + } + rethChain[n] = rethTargets[0] for i := n - 1; i >= 0; i-- { - rethNext := rethsNext[i] - b.SetBlockEx(rethsNext[i+1], AtEnd, true) - b.Store(rethPtr, rethNext.Addr()) - stmts[i](b.Load(bitsPtr)) + rethNext := rethChain[i] + resume := rethChain[i+1] + b.SetBlockEx(resume.block, AtEnd, true) + b.storeDeferTarget(rethPtr, rethNext) + stmts[i](b.Load(bitsPtr), resume) if i != 0 { - b.Jump(rethNext) + b.Jump(rethNext.block) } } link := b.getField(b.Load(self.data), deferLink) b.Call(b.Pkg.rtFunc("SetThreadDefer"), link) - b.IndirectJump(b.Load(rundPtr), nexts) + b.jumpDeferTarget(rundPtr, rundTargets) b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow - b.Store(rundPtr, rethrowBlk.Addr()) - b.IndirectJump(b.Load(rethPtr), rethsNext) + b.storeDeferTarget(rundPtr, rethrowTarget) + b.jumpDeferTarget(rethPtr, rethTargets) } // ----------------------------------------------------------------------------- diff --git a/ssa/eh_defer_test.go b/ssa/eh_defer_test.go index 5f99729b1e..04abbed810 100644 --- a/ssa/eh_defer_test.go +++ b/ssa/eh_defer_test.go @@ -156,3 +156,63 @@ func TestConditionalDeferIR(t *testing.T) { t.Fatalf("expected conditional defer bitmask operations in IR, got:\n%s", ir) } } + +func TestDeferContinuationSelectors(t *testing.T) { + tests := []struct { + name string + target *ssa.Target + switchWidth string + }{ + {name: "linux-amd64", target: &ssa.Target{GOOS: "linux", GOARCH: "amd64"}, switchWidth: "i64"}, + {name: "darwin-arm64", target: &ssa.Target{GOOS: "darwin", GOARCH: "arm64"}, switchWidth: "i64"}, + {name: "wasip1-wasm", target: &ssa.Target{GOOS: "wasip1", GOARCH: "wasm"}, switchWidth: "i32"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + prog := ssatest.NewProgram(t, tt.target) + for _, field := range []int{3, 4} { + if got := prog.Field(prog.Defer(), field); got != prog.Uintptr() { + t.Fatalf("runtime.Defer field %d type = %v, want uintptr", field, got) + } + } + 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.Defer(ssa.DeferAlways, callee.Expr, ssa.Builder.Call) + b.RunDefers() + b.RunDefers() + b.Return() + b.EndBuild() + + ir := pkg.Module().String() + if strings.Contains(ir, "blockaddress") || strings.Contains(ir, "indirectbr") { + t.Fatalf("defer continuations must not expose block addresses:\n%s", ir) + } + if got := strings.Count(ir, "switch "+tt.switchWidth); got != 2 { + t.Fatalf("got %d %s switches, want RunDefers and Rethrow dispatch:\n%s", got, tt.switchWidth, ir) + } + if got := strings.Count(ir, "unreachable"); got < 2 { + t.Fatalf("got %d unreachable defaults, want one per defer dispatch:\n%s", got, ir) + } + + // RunDefers can establish defer state before any defer statement has + // been registered. Its Reth selector still has to dispatch to procBlk. + empty := pkg.NewFunc("empty", ssa.NoArgsNoRet, ssa.InGo) + eb := empty.MakeBody(1) + empty.SetRecover(empty.MakeBlock()) + eb.Return() + eb.SetBlockEx(empty.Block(0), ssa.BeforeLast, true) + eb.RunDefers() + eb.Return() + eb.EndBuild() + }) + } +} From 47d94be50a1da2ae5333cadedd9abcca85bcdaa2 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 18:22:28 +0800 Subject: [PATCH 2/9] ssa: initialize defer selectors explicitly --- cl/_testgo/cgodefer/cgodefer.go | 1 + cl/_testgo/cgomacro/cgomacro.go | 1 + cl/_testgo/cgopython/cgopython.go | 1 + cl/_testgo/recoverthenpanic/in.go | 2 ++ ssa/eh.go | 8 ++++++-- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cl/_testgo/cgodefer/cgodefer.go b/cl/_testgo/cgodefer/cgodefer.go index f9c1d5a7bd..b9739a724a 100644 --- a/cl/_testgo/cgodefer/cgodefer.go +++ b/cl/_testgo/cgodefer/cgodefer.go @@ -44,6 +44,7 @@ import "C" // CHECK-NEXT: %16 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 3 // CHECK-NEXT: %17 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 4 // CHECK-NEXT: %18 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %17, align 8 // CHECK-NEXT: store ptr null, ptr %18, align 8 // CHECK-NEXT: %19 = call i32 @{{.*}}sigsetjmp(ptr %9, i32 0) // CHECK-NEXT: %20 = icmp eq i32 %19, 0 diff --git a/cl/_testgo/cgomacro/cgomacro.go b/cl/_testgo/cgomacro/cgomacro.go index 6a95c03111..0e8533a480 100644 --- a/cl/_testgo/cgomacro/cgomacro.go +++ b/cl/_testgo/cgomacro/cgomacro.go @@ -136,6 +136,7 @@ import ( // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 4 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %12, align 8 // CHECK-NEXT: store ptr null, ptr %13, align 8 // CHECK-NEXT: %14 = call i32 @{{.*}}sigsetjmp(ptr %4, i32 0) // CHECK-NEXT: %15 = icmp eq i32 %14, 0 diff --git a/cl/_testgo/cgopython/cgopython.go b/cl/_testgo/cgopython/cgopython.go index 977ef31040..a0152704af 100644 --- a/cl/_testgo/cgopython/cgopython.go +++ b/cl/_testgo/cgopython/cgopython.go @@ -69,6 +69,7 @@ import "C" // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 3 // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 4 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %10, align 8 // CHECK-NEXT: store ptr null, ptr %11, align 8 // CHECK-NEXT: %12 = call i32 @{{.*}}sigsetjmp(ptr %2, i32 0) // CHECK-NEXT: %13 = icmp eq i32 %12, 0 diff --git a/cl/_testgo/recoverthenpanic/in.go b/cl/_testgo/recoverthenpanic/in.go index c6ef4d3eaa..e35f0bcdbf 100644 --- a/cl/_testgo/recoverthenpanic/in.go +++ b/cl/_testgo/recoverthenpanic/in.go @@ -26,6 +26,7 @@ package main // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 4 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %12, align 8 // CHECK-NEXT: store ptr null, ptr %13, align 8 // CHECK-NEXT: %14 = call i32 @{{.*}}sigsetjmp(ptr %4, i32 0) // CHECK-NEXT: %15 = icmp eq i32 %14, 0 @@ -162,6 +163,7 @@ func main() { // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 4 // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %9, align 8 // CHECK-NEXT: store ptr null, ptr %10, align 8 // CHECK-NEXT: %11 = call i32 @{{.*}}sigsetjmp(ptr %1, i32 0) // CHECK-NEXT: %12 = icmp eq i32 %11, 0 diff --git a/ssa/eh.go b/ssa/eh.go index 6d88384444..6931732698 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -275,7 +275,7 @@ func (b Builder) initDeferState(procBlk, rethrowBlk BasicBlock) (*aDefer, Expr, zero := prog.Val(uintptr(0)) link := b.Call(b.Pkg.rtFunc("GetThreadDefer")) jb := b.AllocaSigjmpBuf() - // Reth selector 0 is reserved for procBlk; Rund is zero-initialized. + // Reth selector 0 is reserved for procBlk; Rund selector 0 is rethrowBlk. ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, zero.impl) deferData := Expr{ptr, prog.DeferPtr()} b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData) @@ -283,6 +283,7 @@ func (b Builder) initDeferState(procBlk, rethrowBlk BasicBlock) (*aDefer, Expr, rethPtr := b.FieldAddr(deferData, deferRethrow) rundPtr := b.FieldAddr(deferData, deferRunDefers) argsPtr := b.FieldAddr(deferData, deferArgs) + b.Store(rundPtr, zero) // Initialize the args list so later guards (e.g. DeferAlways/DeferInLoop) // can safely detect an empty chain without a prior push. b.Store(argsPtr, prog.Nil(prog.VoidPtr())) @@ -614,7 +615,10 @@ func (p Function) endDefer(b Builder) { stmts := self.stmts n := len(stmts) rethChain := make([]deferTarget, n+1) - blks := p.MakeBlocks(n - 1) + var blks []BasicBlock + if n > 1 { + blks = p.MakeBlocks(n - 1) + } // Reth selector 0 must remain procBlk because initDeferState installs it // before the final number of deferred statements is known. Selector 1 is // the terminal rethrow, followed by the intermediate continuation blocks. From 2353dec74d1f59536f29e3e1f0dc17d1d15ba197 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 19:31:11 +0800 Subject: [PATCH 3/9] test: cover defer selector dispatch end to end --- cl/_testgo/deferiface/in.go | 29 +++++++++++----- internal/build/testdata/wasm-runtime/main.go | 35 ++++++++++++++++++++ test/defer_test.go | 27 +++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/cl/_testgo/deferiface/in.go b/cl/_testgo/deferiface/in.go index bcee22b9ae..996ad051f0 100644 --- a/cl/_testgo/deferiface/in.go +++ b/cl/_testgo/deferiface/in.go @@ -79,12 +79,13 @@ func main() { // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 2 // CHECK-NEXT: store ptr %7, ptr %12, align 8 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 3 -// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_2), ptr %13, align 8 +// CHECK-NEXT: store i64 0, ptr %13, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %9) // CHECK-NEXT: %14 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 1 // CHECK-NEXT: %15 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 3 // CHECK-NEXT: %16 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 4 // CHECK-NEXT: %17 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 5 +// CHECK-NEXT: store i64 0, ptr %16, align 8 // CHECK-NEXT: store ptr null, ptr %17, align 8 // CHECK-NEXT: %18 = call i32 @{{.*}}sigsetjmp(ptr %8, i32 0) // CHECK-NEXT: %19 = icmp eq i32 %18, 0 @@ -94,7 +95,7 @@ func main() { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %15, align 8 +// CHECK-NEXT: store i64 1, ptr %15, align 8 // CHECK-NEXT: %20 = load i64, ptr %14, align 8 // CHECK-NEXT: %21 = load ptr, ptr %17, align 8 // CHECK-NEXT: %22 = icmp ne ptr %21, null @@ -116,13 +117,16 @@ func main() { // CHECK-NEXT: store ptr %24, ptr %17, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @8, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_6), ptr %16, align 8 +// CHECK-NEXT: store i64 1, ptr %16, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %16, align 8 -// CHECK-NEXT: %28 = load ptr, ptr %15, align 8 -// CHECK-NEXT: indirectbr ptr %28, [label %_llgo_3, label %_llgo_2] +// CHECK-NEXT: store i64 0, ptr %16, align 8 +// CHECK-NEXT: %28 = load i64, ptr %15, align 8 +// CHECK-NEXT: switch i64 %28, label %_llgo_10 [ +// CHECK-NEXT: i64 0, label %_llgo_2 +// CHECK-NEXT: i64 1, label %_llgo_3 +// CHECK-NEXT: ] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_8 // CHECK-NEXT: ret void @@ -143,6 +147,15 @@ func main() { // CHECK-NEXT: %35 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, align 8 // CHECK-NEXT: %36 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %35, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %36) -// CHECK-NEXT: %37 = load ptr, ptr %16, align 8 -// CHECK-NEXT: indirectbr ptr %37, [label %_llgo_3, label %_llgo_6] +// CHECK-NEXT: %37 = load i64, ptr %16, align 8 +// CHECK-NEXT: switch i64 %37, label %_llgo_9 [ +// CHECK-NEXT: i64 0, label %_llgo_3 +// CHECK-NEXT: i64 1, label %_llgo_6 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_10: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable // CHECK-NEXT: } diff --git a/internal/build/testdata/wasm-runtime/main.go b/internal/build/testdata/wasm-runtime/main.go index af4a422b48..8863f26487 100644 --- a/internal/build/testdata/wasm-runtime/main.go +++ b/internal/build/testdata/wasm-runtime/main.go @@ -3,5 +3,40 @@ package main import "runtime" func main() { + exerciseDeferContinuations() println(runtime.GOOS) } + +func exerciseDeferContinuations() { + if got := normalLoopDefers(); got != 321 { + panic("normal loop defer order") + } + order, recovered := panicWhileDrainingLoopDefers() + if order != 321 || recovered != "wasm-loop-defer-boom" { + panic("panic loop defer order") + } +} + +func normalLoopDefers() (order int) { + for i := 1; i <= 3; i++ { + value := i + defer func() { order = order*10 + value }() + } + return +} + +func panicWhileDrainingLoopDefers() (order int, recovered any) { + defer func() { recovered = recover() }() + func() { + for i := 1; i <= 3; i++ { + value := i + defer func() { + order = order*10 + value + if value == 2 { + panic("wasm-loop-defer-boom") + } + }() + } + }() + return +} diff --git a/test/defer_test.go b/test/defer_test.go index 3a63c7248f..c1dc73d3f3 100644 --- a/test/defer_test.go +++ b/test/defer_test.go @@ -329,6 +329,33 @@ func TestPanicCrossTwoFunctionsRecover(t *testing.T) { } } +func runLoopDeferPanicDuringDrain() (order []int, recovered any) { + defer func() { recovered = recover() }() + func() { + for i := 0; i < 3; i++ { + value := i + defer func() { + order = append(order, value) + if value == 1 { + panic("loop-defer-boom") + } + }() + } + }() + return +} + +func TestPanicWhileDrainingLoopDefers(t *testing.T) { + order, recovered := runLoopDeferPanicDuringDrain() + if recovered != "loop-defer-boom" { + t.Fatalf("unexpected recovered value: got %v, want %q", recovered, "loop-defer-boom") + } + wantOrder := []int{2, 1, 0} + if !reflect.DeepEqual(order, wantOrder) { + t.Fatalf("unexpected loop defer order: got %v, want %v", order, wantOrder) + } +} + // Test for issue #1488: Deferred method literal stub uses undefined value type emitRecorder struct { last func(int) From 96b82b9f715f98f7604de0aa1d10afcfb0256ed9 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 20:55:30 +0800 Subject: [PATCH 4/9] ssa/wasm: preserve native defer dispatch --- cl/_testgo/cgodefer/cgodefer.go | 29 +++------ cl/_testgo/cgomacro/cgomacro.go | 29 +++------ cl/_testgo/cgopython/cgopython.go | 29 +++------ cl/_testgo/defer5/in.go | 4 +- .../deferdispatch-wasm-target/flags.txt | 3 + cl/_testgo/deferdispatch-wasm-target/in.go | 20 ++++++ cl/_testgo/deferdispatch-wasm/flags.txt | 1 + cl/_testgo/deferdispatch-wasm/in.go | 20 ++++++ cl/_testgo/deferdispatch/in.go | 17 +++++ cl/_testgo/deferiface/in.go | 29 +++------ cl/_testgo/deferloop/in.go | 5 +- cl/_testgo/recoverthenpanic/in.go | 55 +++++----------- internal/llgen/llgenf.go | 34 ++++++++-- internal/llgen/llgenf_test.go | 58 +++++++++++++---- runtime/internal/runtime/z_rt.go | 4 +- ssa/eh.go | 65 +++++++++++-------- ssa/eh_defer_test.go | 37 +++++++---- 17 files changed, 252 insertions(+), 187 deletions(-) create mode 100644 cl/_testgo/deferdispatch-wasm-target/flags.txt create mode 100644 cl/_testgo/deferdispatch-wasm-target/in.go create mode 100644 cl/_testgo/deferdispatch-wasm/flags.txt create mode 100644 cl/_testgo/deferdispatch-wasm/in.go create mode 100644 cl/_testgo/deferdispatch/in.go diff --git a/cl/_testgo/cgodefer/cgodefer.go b/cl/_testgo/cgodefer/cgodefer.go index b9739a724a..5ce8dd6bfe 100644 --- a/cl/_testgo/cgodefer/cgodefer.go +++ b/cl/_testgo/cgodefer/cgodefer.go @@ -38,13 +38,12 @@ import "C" // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 2 // CHECK-NEXT: store ptr %8, ptr %13, align 8 // CHECK-NEXT: %14 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %14, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %14, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %10) // CHECK-NEXT: %15 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 1 // CHECK-NEXT: %16 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 3 // CHECK-NEXT: %17 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 4 // CHECK-NEXT: %18 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %10, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %17, align 8 // CHECK-NEXT: store ptr null, ptr %18, align 8 // CHECK-NEXT: %19 = call i32 @{{.*}}sigsetjmp(ptr %9, i32 0) // CHECK-NEXT: %20 = icmp eq i32 %19, 0 @@ -54,7 +53,7 @@ import "C" // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store i64 1, ptr %16, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %16, align 8 // CHECK-NEXT: %21 = load i64, ptr %15, align 8 // CHECK-NEXT: %22 = load ptr, ptr %18, align 8 // CHECK-NEXT: %23 = icmp ne ptr %22, null @@ -74,16 +73,13 @@ import "C" // CHECK-NEXT: %28 = getelementptr inbounds { ptr, i64, { ptr, ptr } }, ptr %25, i32 0, i32 2 // CHECK-NEXT: store { ptr, ptr } %7, ptr %28, align 8 // CHECK-NEXT: store ptr %25, ptr %18, align 8 -// CHECK-NEXT: store i64 1, ptr %17, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %17, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %17, align 8 -// CHECK-NEXT: %29 = load i64, ptr %16, align 8 -// CHECK-NEXT: switch i64 %29, label %_llgo_10 [ -// CHECK-NEXT: i64 0, label %_llgo_2 -// CHECK-NEXT: i64 1, label %_llgo_3 -// CHECK-NEXT: ] +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %17, align 8 +// CHECK-NEXT: %29 = load ptr, ptr %16, align 8 +// CHECK-NEXT: indirectbr ptr %29, [label %_llgo_3, label %_llgo_2] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_8 // CHECK-NEXT: ret void @@ -105,17 +101,8 @@ import "C" // 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 i64, ptr %17, align 8 -// CHECK-NEXT: switch i64 %38, label %_llgo_9 [ -// CHECK-NEXT: i64 0, label %_llgo_3 -// CHECK-NEXT: i64 1, label %_llgo_6 -// CHECK-NEXT: ] -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_10: ; preds = %_llgo_5 -// CHECK-NEXT: unreachable +// CHECK-NEXT: %38 = load ptr, ptr %17, align 8 +// CHECK-NEXT: indirectbr ptr %38, [label %_llgo_3, label %_llgo_6] // CHECK-NEXT: } func main() { // CHECK-LABEL: define { ptr, ptr } @"main.main$1"(ptr {{(nest|swiftself)}} %0){{.*}} { diff --git a/cl/_testgo/cgomacro/cgomacro.go b/cl/_testgo/cgomacro/cgomacro.go index 0e8533a480..c2c152d37d 100644 --- a/cl/_testgo/cgomacro/cgomacro.go +++ b/cl/_testgo/cgomacro/cgomacro.go @@ -130,13 +130,12 @@ import ( // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 2 // CHECK-NEXT: store ptr %3, ptr %8, align 8 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %9, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %9, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %5) // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 1 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 4 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %12, align 8 // CHECK-NEXT: store ptr null, ptr %13, align 8 // CHECK-NEXT: %14 = call i32 @{{.*}}sigsetjmp(ptr %4, i32 0) // CHECK-NEXT: %15 = icmp eq i32 %14, 0 @@ -146,17 +145,14 @@ import ( // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store i64 1, ptr %11, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %11, align 8 // CHECK-NEXT: %16 = load i64, ptr %10, align 8 // CHECK-NEXT: %17 = call [0 x i8] @main._Cfunc_Py_Finalize() // CHECK-NEXT: %18 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, align 8 // CHECK-NEXT: %19 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %18, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %19) -// CHECK-NEXT: %20 = load i64, ptr %12, align 8 -// CHECK-NEXT: switch i64 %20, label %_llgo_7 [ -// CHECK-NEXT: i64 0, label %_llgo_3 -// CHECK-NEXT: i64 1, label %_llgo_6 -// CHECK-NEXT: ] +// CHECK-NEXT: %20 = load ptr, ptr %12, align 8 +// CHECK-NEXT: indirectbr ptr %20, [label %_llgo_3, label %_llgo_6] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %3) @@ -169,25 +165,16 @@ import ( // CHECK-NEXT: %24 = call i32 @"main.main$5"() // CHECK-NEXT: %25 = call i32 @"main.main$6"() // CHECK-NEXT: %26 = call i32 @"main.main$7"() -// CHECK-NEXT: store i64 1, ptr %12, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %12, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %12, align 8 -// CHECK-NEXT: %27 = load i64, ptr %11, align 8 -// CHECK-NEXT: switch i64 %27, label %_llgo_8 [ -// CHECK-NEXT: i64 0, label %_llgo_2 -// CHECK-NEXT: i64 1, label %_llgo_3 -// CHECK-NEXT: ] +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %12, align 8 +// CHECK-NEXT: %27 = load ptr, ptr %11, align 8 +// CHECK-NEXT: indirectbr ptr %27, [label %_llgo_3, label %_llgo_2] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 // CHECK-NEXT: ret void -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_7: ; preds = %_llgo_2 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_8: ; preds = %_llgo_5 -// CHECK-NEXT: unreachable // CHECK-NEXT: } func main() { C.test_stdout() diff --git a/cl/_testgo/cgopython/cgopython.go b/cl/_testgo/cgopython/cgopython.go index a0152704af..aea2a0f615 100644 --- a/cl/_testgo/cgopython/cgopython.go +++ b/cl/_testgo/cgopython/cgopython.go @@ -63,13 +63,12 @@ import "C" // CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 2 // CHECK-NEXT: store ptr %1, ptr %6, align 8 // CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %7, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %7, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %3) // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 1 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 3 // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 4 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %10, align 8 // CHECK-NEXT: store ptr null, ptr %11, align 8 // CHECK-NEXT: %12 = call i32 @{{.*}}sigsetjmp(ptr %2, i32 0) // CHECK-NEXT: %13 = icmp eq i32 %12, 0 @@ -79,17 +78,14 @@ import "C" // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store i64 1, ptr %9, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %9, align 8 // CHECK-NEXT: %14 = load i64, ptr %8, align 8 // CHECK-NEXT: %15 = call [0 x i8] @main._Cfunc_Py_Finalize() // CHECK-NEXT: %16 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %3, align 8 // CHECK-NEXT: %17 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %16, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %17) -// CHECK-NEXT: %18 = load i64, ptr %10, align 8 -// CHECK-NEXT: switch i64 %18, label %_llgo_7 [ -// CHECK-NEXT: i64 0, label %_llgo_3 -// CHECK-NEXT: i64 1, label %_llgo_6 -// CHECK-NEXT: ] +// CHECK-NEXT: %18 = load ptr, ptr %10, align 8 +// CHECK-NEXT: indirectbr ptr %18, [label %_llgo_3, label %_llgo_6] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %1) @@ -98,25 +94,16 @@ import "C" // CHECK-NEXT: _llgo_4: ; preds = %_llgo_0 // CHECK-NEXT: %19 = call ptr @"{{.*}}/runtime/internal/runtime.CString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 23 }) // CHECK-NEXT: %20 = call i32 @main._Cfunc_PyRun_SimpleString(ptr %19) -// CHECK-NEXT: store i64 1, ptr %10, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_6), ptr %10, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %10, align 8 -// CHECK-NEXT: %21 = load i64, ptr %9, align 8 -// CHECK-NEXT: switch i64 %21, label %_llgo_8 [ -// CHECK-NEXT: i64 0, label %_llgo_2 -// CHECK-NEXT: i64 1, label %_llgo_3 -// CHECK-NEXT: ] +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %10, align 8 +// CHECK-NEXT: %21 = load ptr, ptr %9, align 8 +// CHECK-NEXT: indirectbr ptr %21, [label %_llgo_3, label %_llgo_2] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 // CHECK-NEXT: ret void -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_7: ; preds = %_llgo_2 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_8: ; preds = %_llgo_5 -// CHECK-NEXT: unreachable // CHECK-NEXT: } func main() { C.Py_Initialize() diff --git a/cl/_testgo/defer5/in.go b/cl/_testgo/defer5/in.go index ec59812005..4bcca285f0 100644 --- a/cl/_testgo/defer5/in.go +++ b/cl/_testgo/defer5/in.go @@ -4,12 +4,12 @@ package main // CHECK-LABEL: define void @main.main(){{.*}} { func main() { // CHECK: GetThreadDefer - // CHECK: switch i{{(32|64)}} + // CHECK: blockaddress(@main.main, %_llgo_2) + // CHECK: blockaddress(@main.main, %_llgo_8) // CHECK: call void @"main.main$1"() // CHECK: call void @"main.main$2"() // CHECK: FreeDeferNode // CHECK: FreeDeferNode - // CHECK: switch i{{(32|64)}} defer println("A") defer func() { if e := recover(); e != nil { diff --git a/cl/_testgo/deferdispatch-wasm-target/flags.txt b/cl/_testgo/deferdispatch-wasm-target/flags.txt new file mode 100644 index 0000000000..c140566ce8 --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm-target/flags.txt @@ -0,0 +1,3 @@ +# Wasm named targets currently use GOOS/GOARCH for toolchain selection. +GOOS=js GOARCH=wasm +-target=wasm diff --git a/cl/_testgo/deferdispatch-wasm-target/in.go b/cl/_testgo/deferdispatch-wasm-target/in.go new file mode 100644 index 0000000000..ea22c1f291 --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm-target/in.go @@ -0,0 +1,20 @@ +// LITTEST +package main + +func run() { + defer println("first") + defer println("second") +} + +func main() { + run() +} + +// CHECK-LABEL: define void @main.run() +// CHECK-NOT: blockaddress +// CHECK: switch i32 +// CHECK-NOT: blockaddress +// CHECK: switch i32 +// CHECK-NOT: blockaddress +// CHECK-NOT: indirectbr +// CHECK: } diff --git a/cl/_testgo/deferdispatch-wasm/flags.txt b/cl/_testgo/deferdispatch-wasm/flags.txt new file mode 100644 index 0000000000..be36b8150d --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm/flags.txt @@ -0,0 +1 @@ +GOOS=wasip1 GOARCH=wasm diff --git a/cl/_testgo/deferdispatch-wasm/in.go b/cl/_testgo/deferdispatch-wasm/in.go new file mode 100644 index 0000000000..ea22c1f291 --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm/in.go @@ -0,0 +1,20 @@ +// LITTEST +package main + +func run() { + defer println("first") + defer println("second") +} + +func main() { + run() +} + +// CHECK-LABEL: define void @main.run() +// CHECK-NOT: blockaddress +// CHECK: switch i32 +// CHECK-NOT: blockaddress +// CHECK: switch i32 +// CHECK-NOT: blockaddress +// CHECK-NOT: indirectbr +// CHECK: } diff --git a/cl/_testgo/deferdispatch/in.go b/cl/_testgo/deferdispatch/in.go new file mode 100644 index 0000000000..a3a4a2050c --- /dev/null +++ b/cl/_testgo/deferdispatch/in.go @@ -0,0 +1,17 @@ +// LITTEST +package main + +func run() { + defer println("first") + defer println("second") +} + +func main() { + run() +} + +// CHECK-LABEL: define void @main.run() +// CHECK: blockaddress +// CHECK: indirectbr ptr +// CHECK: indirectbr ptr +// CHECK: } diff --git a/cl/_testgo/deferiface/in.go b/cl/_testgo/deferiface/in.go index 996ad051f0..bcee22b9ae 100644 --- a/cl/_testgo/deferiface/in.go +++ b/cl/_testgo/deferiface/in.go @@ -79,13 +79,12 @@ func main() { // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 2 // CHECK-NEXT: store ptr %7, ptr %12, align 8 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %13, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_2), ptr %13, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %9) // CHECK-NEXT: %14 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 1 // CHECK-NEXT: %15 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 3 // CHECK-NEXT: %16 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 4 // CHECK-NEXT: %17 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %16, align 8 // CHECK-NEXT: store ptr null, ptr %17, align 8 // CHECK-NEXT: %18 = call i32 @{{.*}}sigsetjmp(ptr %8, i32 0) // CHECK-NEXT: %19 = icmp eq i32 %18, 0 @@ -95,7 +94,7 @@ func main() { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 -// CHECK-NEXT: store i64 1, ptr %15, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %15, align 8 // CHECK-NEXT: %20 = load i64, ptr %14, align 8 // CHECK-NEXT: %21 = load ptr, ptr %17, align 8 // CHECK-NEXT: %22 = icmp ne ptr %21, null @@ -117,16 +116,13 @@ func main() { // CHECK-NEXT: store ptr %24, ptr %17, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @8, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: store i64 1, ptr %16, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_6), ptr %16, align 8 // CHECK-NEXT: br label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %16, align 8 -// CHECK-NEXT: %28 = load i64, ptr %15, align 8 -// CHECK-NEXT: switch i64 %28, label %_llgo_10 [ -// CHECK-NEXT: i64 0, label %_llgo_2 -// CHECK-NEXT: i64 1, label %_llgo_3 -// CHECK-NEXT: ] +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %16, align 8 +// CHECK-NEXT: %28 = load ptr, ptr %15, align 8 +// CHECK-NEXT: indirectbr ptr %28, [label %_llgo_3, label %_llgo_2] // CHECK-EMPTY: // CHECK-NEXT: _llgo_6: ; preds = %_llgo_8 // CHECK-NEXT: ret void @@ -147,15 +143,6 @@ func main() { // CHECK-NEXT: %35 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %9, align 8 // CHECK-NEXT: %36 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %35, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %36) -// CHECK-NEXT: %37 = load i64, ptr %16, align 8 -// CHECK-NEXT: switch i64 %37, label %_llgo_9 [ -// CHECK-NEXT: i64 0, label %_llgo_3 -// CHECK-NEXT: i64 1, label %_llgo_6 -// CHECK-NEXT: ] -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_10: ; preds = %_llgo_5 -// CHECK-NEXT: unreachable +// CHECK-NEXT: %37 = load ptr, ptr %16, align 8 +// CHECK-NEXT: indirectbr ptr %37, [label %_llgo_3, label %_llgo_6] // CHECK-NEXT: } diff --git a/cl/_testgo/deferloop/in.go b/cl/_testgo/deferloop/in.go index c4c0568599..7fec91526a 100644 --- a/cl/_testgo/deferloop/in.go +++ b/cl/_testgo/deferloop/in.go @@ -5,11 +5,10 @@ package main func main() { // CHECK: GetThreadDefer for i := 0; i < 3; i++ { - // CHECK: switch i{{(32|64)}} + // CHECK: blockaddress(@main.main, %_llgo_6) defer println("loop", i) } - // CHECK: switch i{{(32|64)}} - // CHECK: FreeDeferNode // CHECK: PrintString // CHECK: PrintInt + // CHECK: FreeDeferNode } diff --git a/cl/_testgo/recoverthenpanic/in.go b/cl/_testgo/recoverthenpanic/in.go index e35f0bcdbf..6d1364b14f 100644 --- a/cl/_testgo/recoverthenpanic/in.go +++ b/cl/_testgo/recoverthenpanic/in.go @@ -20,13 +20,12 @@ package main // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 2 // CHECK-NEXT: store ptr %3, ptr %8, align 8 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %9, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_5), ptr %9, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %5) // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 1 // CHECK-NEXT: %11 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 3 // CHECK-NEXT: %12 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 4 // CHECK-NEXT: %13 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %12, align 8 // CHECK-NEXT: store ptr null, ptr %13, align 8 // CHECK-NEXT: %14 = call i32 @{{.*}}sigsetjmp(ptr %4, i32 0) // CHECK-NEXT: %15 = icmp eq i32 %14, 0 @@ -52,7 +51,7 @@ package main // CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_4 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 3 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) -// CHECK-NEXT: store i64 1, ptr %12, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_8), ptr %12, align 8 // CHECK-NEXT: br label %_llgo_5 // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_6 @@ -62,7 +61,7 @@ package main // CHECK-NEXT: br i1 %2, label %_llgo_1, label %_llgo_2 // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_7, %_llgo_2 -// CHECK-NEXT: store i64 1, ptr %11, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_6), ptr %11, align 8 // CHECK-NEXT: %23 = load i64, ptr %10, align 8 // CHECK-NEXT: %24 = and i64 %23, 1 // CHECK-NEXT: %25 = icmp ne i64 %24, 0 @@ -73,12 +72,9 @@ package main // CHECK-NEXT: br label %_llgo_3 // CHECK-EMPTY: // CHECK-NEXT: _llgo_7: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %12, align 8 -// CHECK-NEXT: %26 = load i64, ptr %11, align 8 -// CHECK-NEXT: switch i64 %26, label %_llgo_14 [ -// CHECK-NEXT: i64 0, label %_llgo_5 -// CHECK-NEXT: i64 1, label %_llgo_6 -// CHECK-NEXT: ] +// CHECK-NEXT: store ptr blockaddress(@main.End, %_llgo_6), ptr %12, align 8 +// CHECK-NEXT: %26 = load ptr, ptr %11, align 8 +// CHECK-NEXT: indirectbr ptr %26, [label %_llgo_6, label %_llgo_5] // CHECK-EMPTY: // CHECK-NEXT: _llgo_8: ; preds = %_llgo_10 // CHECK-NEXT: ret void @@ -92,11 +88,8 @@ package main // CHECK-NEXT: %29 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %5, align 8 // CHECK-NEXT: %30 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %29, 2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %30) -// CHECK-NEXT: %31 = load i64, ptr %12, align 8 -// CHECK-NEXT: switch i64 %31, label %_llgo_13 [ -// CHECK-NEXT: i64 0, label %_llgo_6 -// CHECK-NEXT: i64 1, label %_llgo_8 -// CHECK-NEXT: ] +// CHECK-NEXT: %31 = load ptr, ptr %12, align 8 +// CHECK-NEXT: indirectbr ptr %31, [label %_llgo_6, label %_llgo_8] // CHECK-EMPTY: // CHECK-NEXT: _llgo_11: ; preds = %_llgo_9 // CHECK-NEXT: %32 = load ptr, ptr %13, align 8 @@ -110,12 +103,6 @@ package main // CHECK-EMPTY: // CHECK-NEXT: _llgo_12: ; preds = %_llgo_9 // CHECK-NEXT: br label %_llgo_10 -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_13: ; preds = %_llgo_10 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_14: ; preds = %_llgo_7 -// CHECK-NEXT: unreachable // CHECK-NEXT: } func End() { @@ -157,13 +144,12 @@ func main() { // CHECK-NEXT: %5 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 2 // CHECK-NEXT: store ptr %0, ptr %5, align 8 // CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 -// CHECK-NEXT: store i64 0, ptr %6, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_2), ptr %6, align 8 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %2) // CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 // CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 // CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 4 // CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 5 -// CHECK-NEXT: store i64 0, ptr %9, align 8 // CHECK-NEXT: store ptr null, ptr %10, align 8 // CHECK-NEXT: %11 = call i32 @{{.*}}sigsetjmp(ptr %1, i32 0) // CHECK-NEXT: %12 = icmp eq i32 %11, 0 @@ -173,16 +159,14 @@ func main() { // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_2: ; preds = %_llgo_5 -// CHECK-NEXT: store i64 1, ptr %8, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.main, %_llgo_3), ptr %8, align 8 // CHECK-NEXT: %13 = load i64, ptr %7, align 8 // CHECK-NEXT: call void @main.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 i64, ptr %9, align 8 -// CHECK-NEXT: switch i64 %16, label %_llgo_6 [ -// CHECK-NEXT: i64 0, label %_llgo_3 -// CHECK-NEXT: ] +// CHECK-NEXT: %16 = load ptr, ptr %9, align 8 +// CHECK-NEXT: indirectbr ptr %16, [label %_llgo_3] // CHECK-EMPTY: // CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_2 // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Rethrow"(ptr %0) @@ -196,16 +180,7 @@ func main() { // CHECK-NEXT: unreachable // CHECK-EMPTY: // CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 -// CHECK-NEXT: store i64 0, ptr %9, align 8 -// CHECK-NEXT: %19 = load i64, ptr %8, align 8 -// CHECK-NEXT: switch i64 %19, label %_llgo_7 [ -// CHECK-NEXT: i64 0, label %_llgo_2 -// CHECK-NEXT: i64 1, label %_llgo_3 -// CHECK-NEXT: ] -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_6: ; preds = %_llgo_2 -// CHECK-NEXT: unreachable -// CHECK-EMPTY: -// CHECK-NEXT: _llgo_7: ; preds = %_llgo_5 -// CHECK-NEXT: unreachable +// CHECK-NEXT: store ptr blockaddress(@main.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: } diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 2629328a05..45baebf0f4 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -44,7 +44,7 @@ func genFrom(pkgPath string, abiMode build.AbiMode) (build.Package, error) { AbiMode: abiMode, GenLL: true, } - if err := applyGoBuildFlagsFile(conf, filepath.Join(pkgPath, "flags.txt")); err != nil { + if err := applyFlagsFile(conf, filepath.Join(pkgPath, "flags.txt")); err != nil { return nil, err } pkgs, err := build.Do([]string{pkgPath}, conf) @@ -60,7 +60,7 @@ func DoFile(fileOrPkg, outFile string) { check(err) } -func readGoBuildFlags(flagsFile string) ([]string, error) { +func readFlags(flagsFile string) ([]string, error) { data, err := os.ReadFile(flagsFile) if os.IsNotExist(err) { return nil, nil @@ -75,14 +75,38 @@ func readGoBuildFlags(flagsFile string) ([]string, error) { return flags, nil } -func applyGoBuildFlagsFile(conf *build.Config, flagsFile string) error { - flags, err := readGoBuildFlags(flagsFile) +func applyFlagsFile(conf *build.Config, flagsFile string) error { + flags, err := readFlags(flagsFile) if err != nil { return err } - if err := goflags.ApplyBuildFlags(conf, flags); err != nil { + next := *conf + goFlags := make([]string, 0, len(flags)) + for i := 0; i < len(flags); i++ { + flag := flags[i] + switch { + case strings.HasPrefix(flag, "GOOS="): + next.Goos = strings.TrimPrefix(flag, "GOOS=") + case strings.HasPrefix(flag, "GOARCH="): + next.Goarch = strings.TrimPrefix(flag, "GOARCH=") + case flag == "-target" || flag == "--target": + if i+1 == len(flags) { + return fmt.Errorf("apply %s: %s requires a value", flagsFile, flag) + } + i++ + next.Target = flags[i] + case strings.HasPrefix(flag, "-target="): + next.Target = strings.TrimPrefix(flag, "-target=") + case strings.HasPrefix(flag, "--target="): + next.Target = strings.TrimPrefix(flag, "--target=") + default: + goFlags = append(goFlags, flag) + } + } + if err := goflags.ApplyBuildFlags(&next, goFlags); err != nil { return fmt.Errorf("apply %s: %w", flagsFile, err) } + *conf = next return nil } diff --git a/internal/llgen/llgenf_test.go b/internal/llgen/llgenf_test.go index 006e0c5e1e..3dacfd3fb1 100644 --- a/internal/llgen/llgenf_test.go +++ b/internal/llgen/llgenf_test.go @@ -26,22 +26,25 @@ import ( "github.com/goplus/llgo/internal/optlevel" ) -func TestApplyGoBuildFlagsFile(t *testing.T) { +func TestApplyFlagsFile(t *testing.T) { dir := t.TempDir() missing := filepath.Join(dir, "missing.txt") - if err := applyGoBuildFlagsFile(new(build.Config), missing); err != nil { + if err := applyFlagsFile(new(build.Config), missing); err != nil { t.Fatalf("missing flags file: %v", err) } path := filepath.Join(dir, "flags.txt") - data := "--gcflags 'all=-N -l'\n-ldflags=--s --w=false # keep DWARF\n" + data := "GOOS=wasip1 GOARCH=wasm\n-target=wasm\n--gcflags 'all=-N -l'\n-ldflags=--s --w=false # keep DWARF\n" if err := os.WriteFile(path, []byte(data), 0o644); err != nil { t.Fatal(err) } conf := new(build.Config) - if err := applyGoBuildFlagsFile(conf, path); err != nil { + if err := applyFlagsFile(conf, path); err != nil { t.Fatal(err) } + if conf.Goos != "wasip1" || conf.Goarch != "wasm" || conf.Target != "wasm" { + t.Fatalf("target config = %s/%s, target %q", conf.Goos, conf.Goarch, conf.Target) + } if conf.OptLevel != optlevel.O0 { t.Fatalf("OptLevel = %v, want O0", conf.OptLevel) } @@ -50,15 +53,44 @@ func TestApplyGoBuildFlagsFile(t *testing.T) { } } -func TestApplyGoBuildFlagsFileErrorIncludesPath(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "flags.txt") - data := "-ldflags='unterminated\n" - if err := os.WriteFile(path, []byte(data), 0o644); err != nil { - t.Fatal(err) +func TestApplyFlagsFileErrorIncludesPath(t *testing.T) { + tests := []string{ + "-ldflags='unterminated\n", + "-ldflags=-w=invalid\n", + "-target\n", + } + for _, data := range tests { + t.Run(strings.TrimSpace(data), func(t *testing.T) { + path := filepath.Join(t.TempDir(), "flags.txt") + if err := os.WriteFile(path, []byte(data), 0o644); err != nil { + t.Fatal(err) + } + conf := &build.Config{Target: "original"} + err := applyFlagsFile(conf, path) + if err == nil || !strings.Contains(err.Error(), path) { + t.Fatalf("applyFlagsFile(%q) error = %v, want path", data, err) + } + if conf.Target != "original" { + t.Fatalf("failed application changed target to %q", conf.Target) + } + }) } - err := applyGoBuildFlagsFile(new(build.Config), path) - if err == nil || !strings.Contains(err.Error(), path) { - t.Fatalf("applyGoBuildFlagsFile(%q) error = %v, want path", data, err) +} + +func TestApplyFlagsFileTargetForms(t *testing.T) { + for _, data := range []string{"-target wasi", "--target wasi", "--target=wasi"} { + t.Run(data, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "flags.txt") + if err := os.WriteFile(path, []byte(data), 0o644); err != nil { + t.Fatal(err) + } + conf := new(build.Config) + if err := applyFlagsFile(conf, path); err != nil { + t.Fatal(err) + } + if conf.Target != "wasi" { + t.Fatalf("Target = %q, want wasi", conf.Target) + } + }) } } diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index fc707c3abe..915905e77a 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -30,8 +30,8 @@ type Defer struct { Addr unsafe.Pointer // sigjmpbuf Bits uintptr Link *Defer - Reth uintptr // continuation selector after Rethrow - Rund uintptr // continuation selector after RunDefers + Reth unsafe.Pointer // native block address or wasm continuation selector + Rund unsafe.Pointer // native block address or wasm continuation selector Args unsafe.Pointer // defer func and args links } diff --git a/ssa/eh.go b/ssa/eh.go index 6931732698..183331ceff 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -147,8 +147,8 @@ type aDefer struct { nextBit int // next defer bit data Expr // pointer to runtime.Defer bitsPtr Expr // pointer to defer bits - rethPtr Expr // Rethrow continuation selector - rundPtr Expr // RunDefers continuation selector + rethPtr Expr // native block address or wasm Rethrow selector + rundPtr Expr // native block address or wasm RunDefers selector argsPtr Expr // func and args links procBlk BasicBlock // deferProc block panicBlk BasicBlock // panic block (runDefers and rethrow) @@ -161,9 +161,9 @@ type aDefer struct { stmts []func(bits Expr, resume deferTarget) } -// deferTarget pairs the integer stored in runtime.Defer with its local block. -// Selectors are dense within each dispatch so LLVM can choose the target's -// preferred switch lowering without exposing block addresses. +// deferTarget pairs a wasm selector with the corresponding local block. +// Native targets store the block address directly; wasm selectors are dense +// within each dispatch so LLVM can lower them efficiently. type deferTarget struct { index int block BasicBlock @@ -184,8 +184,8 @@ const ( // 0: addr sigjmpbuf // 1: bits uintptr // 2: link *Defer - // 3: reth uintptr: continuation selector after Rethrow - // 4: rund uintptr: continuation selector after RunDefers + // 3: reth voidptr: native block address or wasm Rethrow selector + // 4: rund voidptr: native block address or wasm RunDefers selector // 5: func and args links deferSigjmpbuf = iota deferBits @@ -275,15 +275,19 @@ func (b Builder) initDeferState(procBlk, rethrowBlk BasicBlock) (*aDefer, Expr, zero := prog.Val(uintptr(0)) link := b.Call(b.Pkg.rtFunc("GetThreadDefer")) jb := b.AllocaSigjmpBuf() - // Reth selector 0 is reserved for procBlk; Rund selector 0 is rethrowBlk. - ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, zero.impl) + // Wasm Reth selector 0 is reserved for procBlk; Rund selector 0 is + // rethrowBlk. Native targets store the corresponding block addresses. + initialReth := deferTarget{index: 0, block: procBlk} + ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, b.deferTargetValue(initialReth).impl) deferData := Expr{ptr, prog.DeferPtr()} b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData) bitsPtr := b.FieldAddr(deferData, deferBits) rethPtr := b.FieldAddr(deferData, deferRethrow) rundPtr := b.FieldAddr(deferData, deferRunDefers) argsPtr := b.FieldAddr(deferData, deferArgs) - b.Store(rundPtr, zero) + if prog.target.GOARCH == "wasm" { + b.storeDeferTarget(rundPtr, deferTarget{index: 0, block: rethrowBlk}) + } // Initialize the args list so later guards (e.g. DeferAlways/DeferInLoop) // can safely detect an empty chain without a prior push. b.Store(argsPtr, prog.Nil(prog.VoidPtr())) @@ -429,11 +433,6 @@ func (b Builder) loopDeferDrainer(self *aDefer, resume deferTarget) { return } - drainEntry := b.blk - if drainEntry != resume.block { - panic("loop defer resume target does not match the drainer entry") - } - prog := b.Prog condBlk := b.Func.MakeBlock() exitBlk := b.Func.MakeBlock() @@ -580,11 +579,28 @@ func (b Builder) RunDefers() { } func (b Builder) storeDeferTarget(ptr Expr, target deferTarget) { - b.Store(ptr, b.Prog.Val(uintptr(target.index))) + b.Store(ptr, b.deferTargetValue(target)) +} + +func (b Builder) deferTargetValue(target deferTarget) Expr { + if b.Prog.target.GOARCH == "wasm" { + return b.PtrCast(b.Prog.VoidPtr(), b.Prog.Val(uintptr(target.index))) + } + return target.block.Addr() } func (b Builder) jumpDeferTarget(ptr Expr, targets []deferTarget) { - selector := b.Load(ptr) + target := b.Load(ptr) + if b.Prog.target.GOARCH != "wasm" { + blocks := make([]BasicBlock, len(targets)) + for i, target := range targets { + blocks[i] = target.block + } + b.IndirectJump(target, blocks) + return + } + + selector := b.Convert(b.Prog.Uintptr(), target) invalid := b.Func.MakeBlock() sw := b.impl.CreateSwitch(selector.impl, invalid.first, len(targets)) for _, target := range targets { @@ -614,7 +630,6 @@ func (p Function) endDefer(b Builder) { stmts := self.stmts n := len(stmts) - rethChain := make([]deferTarget, n+1) var blks []BasicBlock if n > 1 { blks = p.MakeBlocks(n - 1) @@ -622,21 +637,19 @@ func (p Function) endDefer(b Builder) { // Reth selector 0 must remain procBlk because initDeferState installs it // before the final number of deferred statements is known. Selector 1 is // the terminal rethrow, followed by the intermediate continuation blocks. + // Keep the slice in native continuation order; wasm dispatch uses index. rethTargets := make([]deferTarget, n+1) - rethTargets[0] = deferTarget{index: 0, block: procBlk} if n > 0 { - rethTargets[1] = deferTarget{index: 1, block: rethrowBlk} + rethTargets[0] = deferTarget{index: 1, block: rethrowBlk} for i, blk := range blks { - rethTargets[i+2] = deferTarget{index: i + 2, block: blk} + rethTargets[i+1] = deferTarget{index: i + 2, block: blk} } - rethChain[0] = rethTargets[1] - copy(rethChain[1:], rethTargets[2:]) } - rethChain[n] = rethTargets[0] + rethTargets[n] = deferTarget{index: 0, block: procBlk} for i := n - 1; i >= 0; i-- { - rethNext := rethChain[i] - resume := rethChain[i+1] + rethNext := rethTargets[i] + resume := rethTargets[i+1] b.SetBlockEx(resume.block, AtEnd, true) b.storeDeferTarget(rethPtr, rethNext) stmts[i](b.Load(bitsPtr), resume) diff --git a/ssa/eh_defer_test.go b/ssa/eh_defer_test.go index 04abbed810..88c8364439 100644 --- a/ssa/eh_defer_test.go +++ b/ssa/eh_defer_test.go @@ -157,22 +157,23 @@ func TestConditionalDeferIR(t *testing.T) { } } -func TestDeferContinuationSelectors(t *testing.T) { +func TestDeferContinuationDispatch(t *testing.T) { tests := []struct { name string target *ssa.Target switchWidth string + wasm bool }{ {name: "linux-amd64", target: &ssa.Target{GOOS: "linux", GOARCH: "amd64"}, switchWidth: "i64"}, {name: "darwin-arm64", target: &ssa.Target{GOOS: "darwin", GOARCH: "arm64"}, switchWidth: "i64"}, - {name: "wasip1-wasm", target: &ssa.Target{GOOS: "wasip1", GOARCH: "wasm"}, switchWidth: "i32"}, + {name: "wasip1-wasm", target: &ssa.Target{GOOS: "wasip1", GOARCH: "wasm"}, switchWidth: "i32", wasm: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { prog := ssatest.NewProgram(t, tt.target) for _, field := range []int{3, 4} { - if got := prog.Field(prog.Defer(), field); got != prog.Uintptr() { - t.Fatalf("runtime.Defer field %d type = %v, want uintptr", field, got) + if got := prog.Field(prog.Defer(), field); got != prog.VoidPtr() { + t.Fatalf("runtime.Defer field %d type = %v, want unsafe.Pointer", field, got) } } pkg := prog.NewPackage("foo", "foo") @@ -193,14 +194,26 @@ func TestDeferContinuationSelectors(t *testing.T) { b.EndBuild() ir := pkg.Module().String() - if strings.Contains(ir, "blockaddress") || strings.Contains(ir, "indirectbr") { - t.Fatalf("defer continuations must not expose block addresses:\n%s", ir) - } - if got := strings.Count(ir, "switch "+tt.switchWidth); got != 2 { - t.Fatalf("got %d %s switches, want RunDefers and Rethrow dispatch:\n%s", got, tt.switchWidth, ir) - } - if got := strings.Count(ir, "unreachable"); got < 2 { - t.Fatalf("got %d unreachable defaults, want one per defer dispatch:\n%s", got, ir) + if tt.wasm { + if strings.Contains(ir, "blockaddress") || strings.Contains(ir, "indirectbr") { + t.Fatalf("wasm defer continuations must not expose block addresses:\n%s", ir) + } + if got := strings.Count(ir, "switch "+tt.switchWidth); got != 2 { + t.Fatalf("got %d %s switches, want wasm RunDefers and Rethrow dispatch:\n%s", got, tt.switchWidth, ir) + } + if got := strings.Count(ir, "unreachable"); got < 2 { + t.Fatalf("got %d unreachable defaults, want one per wasm defer dispatch:\n%s", got, ir) + } + } else { + if !strings.Contains(ir, "blockaddress") { + t.Fatalf("native defer continuations must retain block addresses:\n%s", ir) + } + if got := strings.Count(ir, "indirectbr"); got != 2 { + t.Fatalf("got %d indirect branches, want native RunDefers and Rethrow dispatch:\n%s", got, ir) + } + if strings.Contains(ir, "switch "+tt.switchWidth) { + t.Fatalf("native defer continuations must not use selector switches:\n%s", ir) + } } // RunDefers can establish defer state before any defer statement has From bca3c5ce45e36728726e124a729ceb4323c2b2ee Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 21:27:15 +0800 Subject: [PATCH 5/9] build: derive wasm platform from named target --- internal/crosscompile/crosscompile.go | 17 +++++++++++++++-- internal/crosscompile/crosscompile_test.go | 13 +++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index c017c7e5f4..0798d40e28 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -718,8 +718,21 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor // Use extends the original Use function to support target-based configuration // If targetName is provided, it takes precedence over goos/goarch func Use(goos, goarch, targetName string, wasiThreads, forceEspClang bool, level optlevel.Level, ltoMode lto.Mode, goGlobalDCE bool) (export Export, err error) { - if targetName != "" && !strings.HasPrefix(targetName, "wasm") && !strings.HasPrefix(targetName, "wasi") { + if targetName == "" { + return use(goos, goarch, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) + } + + config, err := targets.NewDefaultResolver().Resolve(targetName) + if err != nil { + return export, fmt.Errorf("failed to resolve target %s: %w", targetName, err) + } + if config.GOARCH != "wasm" { return UseTarget(targetName, level, ltoMode) } - return use(goos, goarch, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) + export, err = use(config.GOOS, config.GOARCH, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) + if err == nil { + export.GOOS = config.GOOS + export.GOARCH = config.GOARCH + } + return } diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index ea89a9596a..801533ef4c 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -333,6 +333,19 @@ func TestUseWithTarget(t *testing.T) { t.Errorf("Expected CPU generic in CCFLAGS, got %v", export.CCFLAGS) } + // Named WebAssembly targets use the GOOS/GOARCH from their target configuration + // with the standard WebAssembly toolchain setup. + export, err = Use("linux", "amd64", "wasm", false, false, optlevel.O2, lto.Off, false) + if err != nil { + t.Fatalf("Use(wasm) failed: %v", err) + } + if export.GOOS != "js" || export.GOARCH != "wasm" { + t.Fatalf("wasm target platform = %s/%s, want js/wasm", export.GOOS, export.GOARCH) + } + if !hasFlagValue(export.CCFLAGS, "-target", "wasm32-unknown-emscripten") { + t.Fatalf("wasm target CCFLAGS = %v, want emscripten target", export.CCFLAGS) + } + // Test fallback to goos/goarch when no target specified export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2, lto.Thin, false) if err != nil { From 09b8b9521d5421c737a98b2e67d419f335a56c2b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 21:27:30 +0800 Subject: [PATCH 6/9] test: generate complete defer dispatch checks --- chore/litgen/litgen_test.go | 37 +++++ .../deferdispatch-wasm-target/flags.txt | 2 - cl/_testgo/deferdispatch-wasm-target/in.go | 152 +++++++++++++++++- cl/_testgo/deferdispatch-wasm/in.go | 152 +++++++++++++++++- cl/_testgo/deferdispatch/flags.txt | 1 + cl/_testgo/deferdispatch/in.go | 133 ++++++++++++++- 6 files changed, 454 insertions(+), 23 deletions(-) create mode 100644 cl/_testgo/deferdispatch/flags.txt diff --git a/chore/litgen/litgen_test.go b/chore/litgen/litgen_test.go index 86f5a1a6a6..75d9cd41e7 100644 --- a/chore/litgen/litgen_test.go +++ b/chore/litgen/litgen_test.go @@ -47,3 +47,40 @@ func TestProcessPath_SingleFileUsesContainingDir(t *testing.T) { t.Fatalf("single-file mode did not include the containing package:\n%s", text) } } + +func TestProcessPath_UsesFlagsFileTarget(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + pkgDir, err := os.MkdirTemp(wd, "flags-target-") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = os.RemoveAll(pkgDir) + }) + sourceFile := filepath.Join(pkgDir, "in.go") + if err := os.WriteFile(sourceFile, []byte("// LITTEST\npackage main\n\nfunc main() { defer func() {}() }\n"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(pkgDir, "flags.txt"), []byte("-target=wasm\n"), 0644); err != nil { + t.Fatal(err) + } + + if err := processPath(sourceFile); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(sourceFile) + if err != nil { + t.Fatal(err) + } + text := string(data) + if !strings.Contains(text, "// CHECK-NEXT: switch i32") { + t.Fatalf("litgen did not use the wasm target from flags.txt:\n%s", text) + } + if strings.Contains(text, "blockaddress") || strings.Contains(text, "indirectbr") { + t.Fatalf("litgen generated native defer dispatch for a wasm target:\n%s", text) + } +} diff --git a/cl/_testgo/deferdispatch-wasm-target/flags.txt b/cl/_testgo/deferdispatch-wasm-target/flags.txt index c140566ce8..5b6acc668a 100644 --- a/cl/_testgo/deferdispatch-wasm-target/flags.txt +++ b/cl/_testgo/deferdispatch-wasm-target/flags.txt @@ -1,3 +1 @@ -# Wasm named targets currently use GOOS/GOARCH for toolchain selection. -GOOS=js GOARCH=wasm -target=wasm diff --git a/cl/_testgo/deferdispatch-wasm-target/in.go b/cl/_testgo/deferdispatch-wasm-target/in.go index ea22c1f291..fa31f1a8d5 100644 --- a/cl/_testgo/deferdispatch-wasm-target/in.go +++ b/cl/_testgo/deferdispatch-wasm-target/in.go @@ -1,6 +1,9 @@ // LITTEST package main +// CHECK: {{^}}@0 = private unnamed_addr constant [5 x i8] c"first", align 1{{$}} +// CHECK: {{^}}@1 = private unnamed_addr constant [6 x i8] c"second", align 1{{$}} + func run() { defer println("first") defer println("second") @@ -10,11 +13,144 @@ func main() { run() } -// CHECK-LABEL: define void @main.run() -// CHECK-NOT: blockaddress -// CHECK: switch i32 -// CHECK-NOT: blockaddress -// CHECK: switch i32 -// CHECK-NOT: blockaddress -// CHECK-NOT: indirectbr -// CHECK: } +// CHECK-LABEL: define void @main.init(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"main.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 @"main.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 @main.main(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: call void @main.run() +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK-LABEL: define void @main.run(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.GetThreadDefer"() +// CHECK-NEXT: %1 = alloca i8, i32 200, align 1 +// CHECK-NEXT: %2 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 24) +// CHECK-NEXT: %3 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 0 +// CHECK-NEXT: store ptr %1, ptr %3, align 4 +// CHECK-NEXT: %4 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: store i32 0, ptr %4, align 4 +// CHECK-NEXT: %5 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 2 +// CHECK-NEXT: store ptr %0, ptr %5, align 4 +// CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: store ptr null, ptr %6, align 4 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %2) +// CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 4 +// CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 5 +// CHECK-NEXT: store ptr null, ptr %9, align 4 +// CHECK-NEXT: store ptr null, ptr %10, align 4 +// CHECK-NEXT: %11 = call i32 @setjmp(ptr %1) +// CHECK-NEXT: %12 = icmp eq i32 %11, 0 +// CHECK-NEXT: br i1 %12, label %_llgo_4, label %_llgo_5 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_3 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 +// CHECK-NEXT: store ptr inttoptr (i32 2 to ptr), ptr %8, align 4 +// CHECK-NEXT: %13 = load i32, ptr %7, align 4 +// CHECK-NEXT: %14 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %15 = icmp ne ptr %14, null +// CHECK-NEXT: br i1 %15, label %_llgo_8, label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_11 +// 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: %16 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %17 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 16) +// CHECK-NEXT: %18 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 0 +// CHECK-NEXT: store ptr %16, ptr %18, align 4 +// CHECK-NEXT: %19 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 1 +// CHECK-NEXT: store i32 0, ptr %19, align 4 +// CHECK-NEXT: %20 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i32 5 }, ptr %20, align 4 +// CHECK-NEXT: store ptr %17, ptr %10, align 4 +// CHECK-NEXT: %21 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %22 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 16) +// CHECK-NEXT: %23 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 0 +// CHECK-NEXT: store ptr %21, ptr %23, align 4 +// CHECK-NEXT: %24 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 1 +// CHECK-NEXT: store i32 1, ptr %24, align 4 +// CHECK-NEXT: %25 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i32 6 }, ptr %25, align 4 +// CHECK-NEXT: store ptr %22, ptr %10, align 4 +// CHECK-NEXT: store ptr inttoptr (i32 1 to ptr), ptr %9, align 4 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 +// CHECK-NEXT: store ptr null, ptr %9, align 4 +// CHECK-NEXT: %26 = load ptr, ptr %8, align 4 +// CHECK-NEXT: %27 = ptrtoint ptr %26 to i32 +// CHECK-NEXT: switch i32 %27, label %_llgo_13 [ +// CHECK-NEXT: i32 1, label %_llgo_3 +// CHECK-NEXT: i32 2, label %_llgo_7 +// CHECK-NEXT: i32 0, label %_llgo_2 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_6: ; preds = %_llgo_11 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_5, %_llgo_9 +// CHECK-NEXT: store ptr inttoptr (i32 1 to ptr), ptr %8, align 4 +// CHECK-NEXT: %28 = load i32, ptr %7, align 4 +// CHECK-NEXT: %29 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %30 = icmp ne ptr %29, null +// CHECK-NEXT: br i1 %30, label %_llgo_10, label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 +// CHECK-NEXT: %31 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %32 = load { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %31, align 4 +// CHECK-NEXT: %33 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %32, 0 +// CHECK-NEXT: store ptr %33, ptr %10, align 4 +// CHECK-NEXT: %34 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %32, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %31) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %34) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8, %_llgo_2 +// CHECK-NEXT: br label %_llgo_7 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_10: ; preds = %_llgo_7 +// CHECK-NEXT: %35 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %36 = load { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %35, align 4 +// CHECK-NEXT: %37 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %36, 0 +// CHECK-NEXT: store ptr %37, ptr %10, align 4 +// CHECK-NEXT: %38 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %36, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %35) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %38) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_11: ; preds = %_llgo_10, %_llgo_7 +// CHECK-NEXT: %39 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, align 4 +// CHECK-NEXT: %40 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %39, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %40) +// CHECK-NEXT: %41 = load ptr, ptr %9, align 4 +// CHECK-NEXT: %42 = ptrtoint ptr %41 to i32 +// CHECK-NEXT: switch i32 %42, label %_llgo_12 [ +// CHECK-NEXT: i32 0, label %_llgo_3 +// CHECK-NEXT: i32 1, label %_llgo_6 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_12: ; preds = %_llgo_11 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_13: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable +// CHECK-NEXT: } diff --git a/cl/_testgo/deferdispatch-wasm/in.go b/cl/_testgo/deferdispatch-wasm/in.go index ea22c1f291..fa31f1a8d5 100644 --- a/cl/_testgo/deferdispatch-wasm/in.go +++ b/cl/_testgo/deferdispatch-wasm/in.go @@ -1,6 +1,9 @@ // LITTEST package main +// CHECK: {{^}}@0 = private unnamed_addr constant [5 x i8] c"first", align 1{{$}} +// CHECK: {{^}}@1 = private unnamed_addr constant [6 x i8] c"second", align 1{{$}} + func run() { defer println("first") defer println("second") @@ -10,11 +13,144 @@ func main() { run() } -// CHECK-LABEL: define void @main.run() -// CHECK-NOT: blockaddress -// CHECK: switch i32 -// CHECK-NOT: blockaddress -// CHECK: switch i32 -// CHECK-NOT: blockaddress -// CHECK-NOT: indirectbr -// CHECK: } +// CHECK-LABEL: define void @main.init(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"main.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 @"main.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 @main.main(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: call void @main.run() +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK-LABEL: define void @main.run(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.GetThreadDefer"() +// CHECK-NEXT: %1 = alloca i8, i32 200, align 1 +// CHECK-NEXT: %2 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 24) +// CHECK-NEXT: %3 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 0 +// CHECK-NEXT: store ptr %1, ptr %3, align 4 +// CHECK-NEXT: %4 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: store i32 0, ptr %4, align 4 +// CHECK-NEXT: %5 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 2 +// CHECK-NEXT: store ptr %0, ptr %5, align 4 +// CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: store ptr null, ptr %6, align 4 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %2) +// CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 4 +// CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 5 +// CHECK-NEXT: store ptr null, ptr %9, align 4 +// CHECK-NEXT: store ptr null, ptr %10, align 4 +// CHECK-NEXT: %11 = call i32 @setjmp(ptr %1) +// CHECK-NEXT: %12 = icmp eq i32 %11, 0 +// CHECK-NEXT: br i1 %12, label %_llgo_4, label %_llgo_5 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_3 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 +// CHECK-NEXT: store ptr inttoptr (i32 2 to ptr), ptr %8, align 4 +// CHECK-NEXT: %13 = load i32, ptr %7, align 4 +// CHECK-NEXT: %14 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %15 = icmp ne ptr %14, null +// CHECK-NEXT: br i1 %15, label %_llgo_8, label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_11 +// 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: %16 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %17 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 16) +// CHECK-NEXT: %18 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 0 +// CHECK-NEXT: store ptr %16, ptr %18, align 4 +// CHECK-NEXT: %19 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 1 +// CHECK-NEXT: store i32 0, ptr %19, align 4 +// CHECK-NEXT: %20 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i32 5 }, ptr %20, align 4 +// CHECK-NEXT: store ptr %17, ptr %10, align 4 +// CHECK-NEXT: %21 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %22 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i32 16) +// CHECK-NEXT: %23 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 0 +// CHECK-NEXT: store ptr %21, ptr %23, align 4 +// CHECK-NEXT: %24 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 1 +// CHECK-NEXT: store i32 1, ptr %24, align 4 +// CHECK-NEXT: %25 = getelementptr inbounds { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i32 6 }, ptr %25, align 4 +// CHECK-NEXT: store ptr %22, ptr %10, align 4 +// CHECK-NEXT: store ptr inttoptr (i32 1 to ptr), ptr %9, align 4 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 +// CHECK-NEXT: store ptr null, ptr %9, align 4 +// CHECK-NEXT: %26 = load ptr, ptr %8, align 4 +// CHECK-NEXT: %27 = ptrtoint ptr %26 to i32 +// CHECK-NEXT: switch i32 %27, label %_llgo_13 [ +// CHECK-NEXT: i32 1, label %_llgo_3 +// CHECK-NEXT: i32 2, label %_llgo_7 +// CHECK-NEXT: i32 0, label %_llgo_2 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_6: ; preds = %_llgo_11 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_5, %_llgo_9 +// CHECK-NEXT: store ptr inttoptr (i32 1 to ptr), ptr %8, align 4 +// CHECK-NEXT: %28 = load i32, ptr %7, align 4 +// CHECK-NEXT: %29 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %30 = icmp ne ptr %29, null +// CHECK-NEXT: br i1 %30, label %_llgo_10, label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 +// CHECK-NEXT: %31 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %32 = load { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %31, align 4 +// CHECK-NEXT: %33 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %32, 0 +// CHECK-NEXT: store ptr %33, ptr %10, align 4 +// CHECK-NEXT: %34 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %32, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %31) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %34) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8, %_llgo_2 +// CHECK-NEXT: br label %_llgo_7 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_10: ; preds = %_llgo_7 +// CHECK-NEXT: %35 = load ptr, ptr %10, align 4 +// CHECK-NEXT: %36 = load { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" }, ptr %35, align 4 +// CHECK-NEXT: %37 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %36, 0 +// CHECK-NEXT: store ptr %37, ptr %10, align 4 +// CHECK-NEXT: %38 = extractvalue { ptr, i32, %"{{.*}}/runtime/internal/runtime.String" } %36, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %35) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %38) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_11: ; preds = %_llgo_10, %_llgo_7 +// CHECK-NEXT: %39 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, align 4 +// CHECK-NEXT: %40 = extractvalue %"{{.*}}/runtime/internal/runtime.Defer" %39, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %40) +// CHECK-NEXT: %41 = load ptr, ptr %9, align 4 +// CHECK-NEXT: %42 = ptrtoint ptr %41 to i32 +// CHECK-NEXT: switch i32 %42, label %_llgo_12 [ +// CHECK-NEXT: i32 0, label %_llgo_3 +// CHECK-NEXT: i32 1, label %_llgo_6 +// CHECK-NEXT: ] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_12: ; preds = %_llgo_11 +// CHECK-NEXT: unreachable +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_13: ; preds = %_llgo_5 +// CHECK-NEXT: unreachable +// CHECK-NEXT: } diff --git a/cl/_testgo/deferdispatch/flags.txt b/cl/_testgo/deferdispatch/flags.txt new file mode 100644 index 0000000000..da594133f1 --- /dev/null +++ b/cl/_testgo/deferdispatch/flags.txt @@ -0,0 +1 @@ +GOOS=linux GOARCH=amd64 diff --git a/cl/_testgo/deferdispatch/in.go b/cl/_testgo/deferdispatch/in.go index a3a4a2050c..985b00eb16 100644 --- a/cl/_testgo/deferdispatch/in.go +++ b/cl/_testgo/deferdispatch/in.go @@ -1,6 +1,9 @@ // LITTEST package main +// CHECK: {{^}}@0 = private unnamed_addr constant [5 x i8] c"first", align 1{{$}} +// CHECK: {{^}}@1 = private unnamed_addr constant [6 x i8] c"second", align 1{{$}} + func run() { defer println("first") defer println("second") @@ -10,8 +13,128 @@ func main() { run() } -// CHECK-LABEL: define void @main.run() -// CHECK: blockaddress -// CHECK: indirectbr ptr -// CHECK: indirectbr ptr -// CHECK: } +// CHECK-LABEL: define void @main.init(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"main.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 @"main.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 @main.main(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: call void @main.run() +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK-LABEL: define void @main.run(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.GetThreadDefer"() +// CHECK-NEXT: %1 = alloca i8, i64 200, align 1 +// CHECK-NEXT: %2 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 48) +// CHECK-NEXT: %3 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 0 +// CHECK-NEXT: store ptr %1, ptr %3, align 8 +// CHECK-NEXT: %4 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: store i64 0, ptr %4, align 8 +// CHECK-NEXT: %5 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 2 +// CHECK-NEXT: store ptr %0, ptr %5, align 8 +// CHECK-NEXT: %6 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_2), ptr %6, align 8 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.SetThreadDefer"(ptr %2) +// CHECK-NEXT: %7 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 1 +// CHECK-NEXT: %8 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 3 +// CHECK-NEXT: %9 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 4 +// CHECK-NEXT: %10 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, i32 0, i32 5 +// CHECK-NEXT: store ptr null, ptr %10, align 8 +// CHECK-NEXT: %11 = call i32 @__sigsetjmp(ptr %1, i32 0) +// CHECK-NEXT: %12 = icmp eq i32 %11, 0 +// CHECK-NEXT: br i1 %12, label %_llgo_4, label %_llgo_5 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_3 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_5, %_llgo_4 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_7), ptr %8, align 8 +// CHECK-NEXT: %13 = load i64, ptr %7, align 8 +// CHECK-NEXT: %14 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %15 = icmp ne ptr %14, null +// CHECK-NEXT: br i1 %15, label %_llgo_8, label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_11 +// 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: %16 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %17 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 32) +// CHECK-NEXT: %18 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 0 +// CHECK-NEXT: store ptr %16, ptr %18, align 8 +// CHECK-NEXT: %19 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 1 +// CHECK-NEXT: store i64 0, ptr %19, align 8 +// CHECK-NEXT: %20 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %17, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 5 }, ptr %20, align 8 +// CHECK-NEXT: store ptr %17, ptr %10, align 8 +// CHECK-NEXT: %21 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %22 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 32) +// CHECK-NEXT: %23 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 0 +// CHECK-NEXT: store ptr %21, ptr %23, align 8 +// CHECK-NEXT: %24 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 1 +// CHECK-NEXT: store i64 1, ptr %24, align 8 +// CHECK-NEXT: %25 = getelementptr inbounds { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %22, i32 0, i32 2 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 6 }, ptr %25, align 8 +// CHECK-NEXT: store ptr %22, ptr %10, align 8 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_6), ptr %9, align 8 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_5: ; preds = %_llgo_0 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %9, align 8 +// CHECK-NEXT: %26 = load ptr, ptr %8, align 8 +// CHECK-NEXT: indirectbr ptr %26, [label %_llgo_3, label %_llgo_7, label %_llgo_2] +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_6: ; preds = %_llgo_11 +// CHECK-NEXT: ret void +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_7: ; preds = %_llgo_5, %_llgo_9 +// CHECK-NEXT: store ptr blockaddress(@main.run, %_llgo_3), ptr %8, align 8 +// CHECK-NEXT: %27 = load i64, ptr %7, align 8 +// CHECK-NEXT: %28 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %29 = icmp ne ptr %28, null +// CHECK-NEXT: br i1 %29, label %_llgo_10, label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_8: ; preds = %_llgo_2 +// CHECK-NEXT: %30 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %31 = load { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %30, align 8 +// CHECK-NEXT: %32 = extractvalue { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" } %31, 0 +// CHECK-NEXT: store ptr %32, ptr %10, align 8 +// CHECK-NEXT: %33 = extractvalue { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" } %31, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %30) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %33) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_9 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_9: ; preds = %_llgo_8, %_llgo_2 +// CHECK-NEXT: br label %_llgo_7 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_10: ; preds = %_llgo_7 +// CHECK-NEXT: %34 = load ptr, ptr %10, align 8 +// CHECK-NEXT: %35 = load { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" }, ptr %34, align 8 +// CHECK-NEXT: %36 = extractvalue { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" } %35, 0 +// CHECK-NEXT: store ptr %36, ptr %10, align 8 +// CHECK-NEXT: %37 = extractvalue { ptr, i64, %"{{.*}}/runtime/internal/runtime.String" } %35, 2 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.FreeDeferNode"(ptr %34) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %37) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// CHECK-NEXT: br label %_llgo_11 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_11: ; preds = %_llgo_10, %_llgo_7 +// CHECK-NEXT: %38 = load %"{{.*}}/runtime/internal/runtime.Defer", ptr %2, 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 %9, align 8 +// CHECK-NEXT: indirectbr ptr %40, [label %_llgo_3, label %_llgo_6] +// CHECK-NEXT: } From 756a51cd7253041f785df8d1dd10de9ca869b902 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 21:32:23 +0800 Subject: [PATCH 7/9] ssa: clarify defer dispatch invariants --- ssa/eh.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ssa/eh.go b/ssa/eh.go index 183331ceff..59d2723ce5 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -590,17 +590,17 @@ func (b Builder) deferTargetValue(target deferTarget) Expr { } func (b Builder) jumpDeferTarget(ptr Expr, targets []deferTarget) { - target := b.Load(ptr) + loaded := b.Load(ptr) if b.Prog.target.GOARCH != "wasm" { blocks := make([]BasicBlock, len(targets)) for i, target := range targets { blocks[i] = target.block } - b.IndirectJump(target, blocks) + b.IndirectJump(loaded, blocks) return } - selector := b.Convert(b.Prog.Uintptr(), target) + selector := b.Convert(b.Prog.Uintptr(), loaded) invalid := b.Func.MakeBlock() sw := b.impl.CreateSwitch(selector.impl, invalid.first, len(targets)) for _, target := range targets { @@ -616,10 +616,11 @@ func (p Function) endDefer(b Builder) { return } rundTargets := self.rundTargets + // A partially constructed defer state has no dispatch target yet. + // initDeferState seeds selector 0 with the terminal rethrow target. if len(rundTargets) == 0 { return } - rethrowTarget := rundTargets[0] rethrowBlk := rethrowTarget.block procBlk := self.procBlk From 7523cf4b3fe6cea8ede650dc54c15ae81680e2be Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 21:43:45 +0800 Subject: [PATCH 8/9] llgen: derive platform from flags target --- internal/crosscompile/crosscompile.go | 17 ++--------------- internal/crosscompile/crosscompile_test.go | 13 ------------- internal/llgen/llgenf.go | 9 +++++++++ internal/llgen/llgenf_test.go | 5 +++-- 4 files changed, 14 insertions(+), 30 deletions(-) diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 0798d40e28..c017c7e5f4 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -718,21 +718,8 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor // Use extends the original Use function to support target-based configuration // If targetName is provided, it takes precedence over goos/goarch func Use(goos, goarch, targetName string, wasiThreads, forceEspClang bool, level optlevel.Level, ltoMode lto.Mode, goGlobalDCE bool) (export Export, err error) { - if targetName == "" { - return use(goos, goarch, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) - } - - config, err := targets.NewDefaultResolver().Resolve(targetName) - if err != nil { - return export, fmt.Errorf("failed to resolve target %s: %w", targetName, err) - } - if config.GOARCH != "wasm" { + if targetName != "" && !strings.HasPrefix(targetName, "wasm") && !strings.HasPrefix(targetName, "wasi") { return UseTarget(targetName, level, ltoMode) } - export, err = use(config.GOOS, config.GOARCH, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) - if err == nil { - export.GOOS = config.GOOS - export.GOARCH = config.GOARCH - } - return + return use(goos, goarch, wasiThreads, forceEspClang, level, ltoMode, goGlobalDCE) } diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index 801533ef4c..ea89a9596a 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -333,19 +333,6 @@ func TestUseWithTarget(t *testing.T) { t.Errorf("Expected CPU generic in CCFLAGS, got %v", export.CCFLAGS) } - // Named WebAssembly targets use the GOOS/GOARCH from their target configuration - // with the standard WebAssembly toolchain setup. - export, err = Use("linux", "amd64", "wasm", false, false, optlevel.O2, lto.Off, false) - if err != nil { - t.Fatalf("Use(wasm) failed: %v", err) - } - if export.GOOS != "js" || export.GOARCH != "wasm" { - t.Fatalf("wasm target platform = %s/%s, want js/wasm", export.GOOS, export.GOARCH) - } - if !hasFlagValue(export.CCFLAGS, "-target", "wasm32-unknown-emscripten") { - t.Fatalf("wasm target CCFLAGS = %v, want emscripten target", export.CCFLAGS) - } - // Test fallback to goos/goarch when no target specified export, err = Use(runtime.GOOS, runtime.GOARCH, "", false, false, optlevel.O2, lto.Thin, false) if err != nil { diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 45baebf0f4..1e45eaf26d 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -25,6 +25,7 @@ import ( "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/goflags" + "github.com/goplus/llgo/internal/targets" ) func GenFrom(fileOrPkg string) string { @@ -103,6 +104,14 @@ func applyFlagsFile(conf *build.Config, flagsFile string) error { goFlags = append(goFlags, flag) } } + if next.Target != "" { + target, err := targets.NewDefaultResolver().Resolve(next.Target) + if err != nil { + return fmt.Errorf("apply %s: %w", flagsFile, err) + } + next.Goos = target.GOOS + next.Goarch = target.GOARCH + } if err := goflags.ApplyBuildFlags(&next, goFlags); err != nil { return fmt.Errorf("apply %s: %w", flagsFile, err) } diff --git a/internal/llgen/llgenf_test.go b/internal/llgen/llgenf_test.go index 3dacfd3fb1..8a80433d1e 100644 --- a/internal/llgen/llgenf_test.go +++ b/internal/llgen/llgenf_test.go @@ -34,7 +34,7 @@ func TestApplyFlagsFile(t *testing.T) { } path := filepath.Join(dir, "flags.txt") - data := "GOOS=wasip1 GOARCH=wasm\n-target=wasm\n--gcflags 'all=-N -l'\n-ldflags=--s --w=false # keep DWARF\n" + data := "-target=wasm\n--gcflags 'all=-N -l'\n-ldflags=--s --w=false # keep DWARF\n" if err := os.WriteFile(path, []byte(data), 0o644); err != nil { t.Fatal(err) } @@ -42,7 +42,7 @@ func TestApplyFlagsFile(t *testing.T) { if err := applyFlagsFile(conf, path); err != nil { t.Fatal(err) } - if conf.Goos != "wasip1" || conf.Goarch != "wasm" || conf.Target != "wasm" { + if conf.Goos != "js" || conf.Goarch != "wasm" || conf.Target != "wasm" { t.Fatalf("target config = %s/%s, target %q", conf.Goos, conf.Goarch, conf.Target) } if conf.OptLevel != optlevel.O0 { @@ -58,6 +58,7 @@ func TestApplyFlagsFileErrorIncludesPath(t *testing.T) { "-ldflags='unterminated\n", "-ldflags=-w=invalid\n", "-target\n", + "-target=does-not-exist\n", } for _, data := range tests { t.Run(strings.TrimSpace(data), func(t *testing.T) { From dcff207d2e16353ed1f55841dad54b9788f6acbc Mon Sep 17 00:00:00 2001 From: Li Jie Date: Mon, 3 Aug 2026 23:16:14 +0800 Subject: [PATCH 9/9] build: skip object emission in generation mode --- internal/build/build.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/build/build.go b/internal/build/build.go index 411d733568..4e3ae2a499 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -1886,6 +1886,11 @@ func compilePackageModule(ctx *context, aPkg *aPackage, externs []string, verbos } } emitFuncInfoEntrySites(ctx, ret) + // ModeGen callers consume the in-memory LLVM module directly. They do not + // need cgo/link objects or a package archive for a later link step. + if ctx.mode == ModeGen { + return nil + } printCmds := ctx.shouldPrintCommands(verbose) cgoLLFiles, cgoLdflags, err := buildCgo(ctx, aPkg, aPkg.Package.Syntax, externs, printCmds)