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 new file mode 100644 index 0000000000..5b6acc668a --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm-target/flags.txt @@ -0,0 +1 @@ +-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..fa31f1a8d5 --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm-target/in.go @@ -0,0 +1,156 @@ +// 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") +} + +func main() { + run() +} + +// 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/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..fa31f1a8d5 --- /dev/null +++ b/cl/_testgo/deferdispatch-wasm/in.go @@ -0,0 +1,156 @@ +// 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") +} + +func main() { + run() +} + +// 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 new file mode 100644 index 0000000000..985b00eb16 --- /dev/null +++ b/cl/_testgo/deferdispatch/in.go @@ -0,0 +1,140 @@ +// 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") +} + +func main() { + run() +} + +// 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: } 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) 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/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 2629328a05..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 { @@ -44,7 +45,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 +61,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 +76,46 @@ 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 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) } + *conf = next return nil } diff --git a/internal/llgen/llgenf_test.go b/internal/llgen/llgenf_test.go index 006e0c5e1e..8a80433d1e 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 := "-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 != "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 { t.Fatalf("OptLevel = %v, want O0", conf.OptLevel) } @@ -50,15 +53,45 @@ 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", + "-target=does-not-exist\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 090e3e4cbd..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 unsafe.Pointer // block address after Rethrow - Rund unsafe.Pointer // block address 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 104b8e04af..59d2723ce5 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 // 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) + 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 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 } // 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 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 @@ -267,13 +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() - ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().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) + 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())) @@ -282,13 +296,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 +395,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 +413,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 } @@ -419,9 +433,6 @@ func (b Builder) loopDeferDrainer(self *aDefer) { return } - drainEntry := b.blk - drainEntryAddr := drainEntry.Addr() - prog := b.Prog condBlk := b.Func.MakeBlock() exitBlk := 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,61 @@ 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.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) { + 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(loaded, blocks) + return + } + + 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 { + 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 + // A partially constructed defer state has no dispatch target yet. + // initDeferState seeds selector 0 with the terminal rethrow target. + 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 +631,40 @@ func (p Function) endDefer(b Builder) { stmts := self.stmts n := len(stmts) - rethsNext := make([]BasicBlock, n+1) - blks := p.MakeBlocks(n - 1) - copy(rethsNext[1:], blks) - rethsNext[0] = rethrowBlk - rethsNext[n] = procBlk + 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. + // Keep the slice in native continuation order; wasm dispatch uses index. + rethTargets := make([]deferTarget, n+1) + if n > 0 { + rethTargets[0] = deferTarget{index: 1, block: rethrowBlk} + for i, blk := range blks { + rethTargets[i+1] = deferTarget{index: i + 2, block: blk} + } + } + rethTargets[n] = deferTarget{index: 0, block: procBlk} 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 := rethTargets[i] + resume := rethTargets[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..88c8364439 100644 --- a/ssa/eh_defer_test.go +++ b/ssa/eh_defer_test.go @@ -156,3 +156,76 @@ func TestConditionalDeferIR(t *testing.T) { t.Fatalf("expected conditional defer bitmask operations in IR, got:\n%s", ir) } } + +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", 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.VoidPtr() { + t.Fatalf("runtime.Defer field %d type = %v, want unsafe.Pointer", 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 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 + // 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() + }) + } +} 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)