diff --git a/c/sqlite/llgo_autogen.lla b/c/sqlite/llgo_autogen.lla index 4672bbad..93b7c3ad 100644 Binary files a/c/sqlite/llgo_autogen.lla and b/c/sqlite/llgo_autogen.lla differ diff --git a/cl/_testdata/print/out.ll b/cl/_testdata/print/out.ll index 9fadd77e..1744b3f4 100644 --- a/cl/_testdata/print/out.ll +++ b/cl/_testdata/print/out.ll @@ -191,7 +191,7 @@ _llgo_1: ; preds = %_llgo_3 call void @main.println(%"github.com/goplus/llgo/internal/runtime.Slice" %47) br label %_llgo_2 -_llgo_2: ; preds = %_llgo_3, %_llgo_1, %_llgo_0 +_llgo_2: ; preds = %_llgo_1, %_llgo_3, %_llgo_0 %48 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 48) %49 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %48, i64 0 %50 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 @@ -1451,7 +1451,7 @@ _llgo_1: ; preds = %_llgo_3 %7 = icmp ult i64 %9, 10 br i1 %7, label %_llgo_2, label %_llgo_4 -_llgo_2: ; preds = %_llgo_3, %_llgo_1 +_llgo_2: ; preds = %_llgo_1, %_llgo_3 %8 = call %"github.com/goplus/llgo/internal/runtime.Slice" @"github.com/goplus/llgo/internal/runtime.NewSlice3"(ptr %1, i64 1, i64 100, i64 %10, i64 100, i64 100) call void @main.gwrite(%"github.com/goplus/llgo/internal/runtime.Slice" %8) ret void diff --git a/cl/_testdata/utf8/out.ll b/cl/_testdata/utf8/out.ll index 964a3e9c..e24d991d 100644 --- a/cl/_testdata/utf8/out.ll +++ b/cl/_testdata/utf8/out.ll @@ -53,7 +53,7 @@ _llgo_0: _llgo_1: ; preds = %_llgo_3 %2 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 0 - store ptr @0, ptr %3, align 8 + store ptr @1, ptr %3, align 8 %4 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 1 store i64 7, ptr %4, align 4 %5 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %2, align 8 @@ -79,7 +79,7 @@ _llgo_3: ; preds = %_llgo_1, %_llgo_0 %15 = phi i64 [ 0, %_llgo_0 ], [ %11, %_llgo_1 ] %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 - store ptr @1, ptr %17, align 8 + store ptr @0, ptr %17, align 8 %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 store i64 7, ptr %18, align 4 %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 diff --git a/cl/_testdefer/loop/in.go b/cl/_testdefer/loop/in.go new file mode 100644 index 00000000..ebd8938e --- /dev/null +++ b/cl/_testdefer/loop/in.go @@ -0,0 +1,20 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + for i := 0; i < 3; i++ { + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + return + } + } + defer println("bye") +} diff --git a/cl/_testdefer/loop/out.txt b/cl/_testdefer/loop/out.txt new file mode 100644 index 00000000..ad86720d --- /dev/null +++ b/cl/_testdefer/loop/out.txt @@ -0,0 +1,7 @@ + 0: always + 1: cond + 4: loop + 2: loop + 5: loop + 3: cond + 6: cond diff --git a/cl/_testdefer/multiret/in.go b/cl/_testdefer/multiret/in.go new file mode 100644 index 00000000..82d34e31 --- /dev/null +++ b/cl/_testdefer/multiret/in.go @@ -0,0 +1,18 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + return + } + defer println("bye") +} diff --git a/cl/_testdefer/multiret/out.txt b/cl/_testdefer/multiret/out.txt new file mode 100644 index 00000000..c47bbe4c --- /dev/null +++ b/cl/_testdefer/multiret/out.txt @@ -0,0 +1,4 @@ + 0: always + 1: cond + 2: cond + 3: cond diff --git a/cl/_testdefer/print/in.go b/cl/_testdefer/print/in.go new file mode 100644 index 00000000..c9726adc --- /dev/null +++ b/cl/_testdefer/print/in.go @@ -0,0 +1,52 @@ +package main + +func f() float64 { + return 1.0 +} + +func main() { + var v = f() + const n = 7 // digits printed + var buf [n + 7]byte + buf[0] = '+' + e := 0 // exp + if v == 0 { + if 1/v < 0 { + buf[0] = '-' + } + } else { + if v < 0 { + v = -v + buf[0] = '-' + } + + // normalize + for v >= 10 { + e++ + v /= 10 + } + for v < 1 { + e-- + v *= 10 + } + + // round + h := 5.0 + for i := 0; i < n; i++ { + h /= 10 + } + v += h + if v >= 10 { + e++ + v /= 10 + } + } + + // format +d.dddd+edd + for i := 0; i < n; i++ { + s := int(v) + buf[i+2] = byte(s + '0') + v -= float64(s) + v *= 10 + } +} diff --git a/cl/_testdefer/print/out.txt b/cl/_testdefer/print/out.txt new file mode 100644 index 00000000..85787635 --- /dev/null +++ b/cl/_testdefer/print/out.txt @@ -0,0 +1,18 @@ + 0: always + 1: cond + 3: cond + 4: cond + 5: cond + 7: loop + 6: loop +10: loop + 8: loop + 9: cond +13: loop +11: loop +12: cond +14: cond + 2: cond +17: loop +15: loop +16: always diff --git a/cl/_testdefer/singleret/in.go b/cl/_testdefer/singleret/in.go new file mode 100644 index 00000000..cd95da67 --- /dev/null +++ b/cl/_testdefer/singleret/in.go @@ -0,0 +1,17 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + } + defer println("bye") +} diff --git a/cl/_testdefer/singleret/out.txt b/cl/_testdefer/singleret/out.txt new file mode 100644 index 00000000..530763ee --- /dev/null +++ b/cl/_testdefer/singleret/out.txt @@ -0,0 +1,5 @@ + 0: always + 1: cond + 2: cond + 4: cond + 3: always diff --git a/cl/_testgo/defer/out.ll b/cl/_testgo/defer/out.ll index ebb35a1c..0a15d5ef 100644 --- a/cl/_testgo/defer/out.ll +++ b/cl/_testgo/defer/out.ll @@ -53,108 +53,97 @@ _llgo_0: call void @main.init() %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %4, i32 0, i32 1 %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Defer", ptr %4, i32 0, i32 3 - %11 = load i64, ptr %9, align 4 - %12 = or i64 %11, 1 - store i64 %12, ptr %9, align 4 - %13 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %14 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %13, i32 0, i32 0 - store ptr @0, ptr %14, align 8 - %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %13, i32 0, i32 1 - store i64 5, ptr %15, align 4 - %16 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %13, align 8 - %17 = call i1 @main.f(%"github.com/goplus/llgo/internal/runtime.String" %16) - br i1 %17, label %_llgo_2, label %_llgo_3 + %11 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %11, i32 0, i32 0 + store ptr @0, ptr %12, align 8 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %11, i32 0, i32 1 + store i64 5, ptr %13, align 4 + %14 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %11, align 8 + %15 = call i1 @main.f(%"github.com/goplus/llgo/internal/runtime.String" %14) + br i1 %15, label %_llgo_2, label %_llgo_3 _llgo_1: ; No predecessors! ret i32 0 _llgo_2: ; preds = %_llgo_0 - %18 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 0 - store ptr @1, ptr %19, align 8 - %20 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 1 - store i64 5, ptr %20, align 4 - %21 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %18, align 8 - %22 = load i64, ptr %9, align 4 - %23 = or i64 %22, 2 - store i64 %23, ptr %9, align 4 - %24 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %25 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %24, i32 0, i32 0 - store ptr @2, ptr %25, align 8 - %26 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %24, i32 0, i32 1 - store i64 3, ptr %26, align 4 - %27 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %24, align 8 - %28 = load i64, ptr %9, align 4 - %29 = or i64 %28, 4 - store i64 %29, ptr %9, align 4 + %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 + store ptr @1, ptr %17, align 8 + %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 + store i64 5, ptr %18, align 4 + %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 + %20 = load i64, ptr %9, align 4 + %21 = or i64 %20, 1 + store i64 %21, ptr %9, align 4 + %22 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %23 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %22, i32 0, i32 0 + store ptr @2, ptr %23, align 8 + %24 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %22, i32 0, i32 1 + store i64 3, ptr %24, align 4 + %25 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %22, align 8 + %26 = load i64, ptr %9, align 4 + %27 = or i64 %26, 2 + store i64 %27, ptr %9, align 4 store i64 0, ptr %10, align 4 br label %_llgo_4 _llgo_3: ; preds = %_llgo_0 - %30 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %31 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %30, i32 0, i32 0 - store ptr @3, ptr %31, align 8 - %32 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %30, i32 0, i32 1 - store i64 5, ptr %32, align 4 - %33 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %30, align 8 - %34 = load i64, ptr %9, align 4 - %35 = or i64 %34, 8 - store i64 %35, ptr %9, align 4 + %28 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %29 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %28, i32 0, i32 0 + store ptr @3, ptr %29, align 8 + %30 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %28, i32 0, i32 1 + store i64 5, ptr %30, align 4 + %31 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %28, align 8 + %32 = load i64, ptr %9, align 4 + %33 = or i64 %32, 4 + store i64 %33, ptr %9, align 4 store i64 1, ptr %10, align 4 br label %_llgo_4 _llgo_4: ; preds = %_llgo_3, %_llgo_2 - %36 = load i64, ptr %9, align 4 - %37 = and i64 %36, 8 - %38 = icmp ne i64 %37, 0 - br i1 %38, label %_llgo_7, label %_llgo_8 + %34 = load i64, ptr %9, align 4 + %35 = and i64 %34, 4 + %36 = icmp ne i64 %35, 0 + br i1 %36, label %_llgo_7, label %_llgo_8 -_llgo_5: ; preds = %_llgo_14 +_llgo_5: ; preds = %_llgo_12 ret i32 0 -_llgo_6: ; preds = %_llgo_14 +_llgo_6: ; preds = %_llgo_12 ret i32 0 _llgo_7: ; preds = %_llgo_4 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %33) + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %31) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_8 _llgo_8: ; preds = %_llgo_7, %_llgo_4 - %39 = and i64 %36, 4 - %40 = icmp ne i64 %39, 0 - br i1 %40, label %_llgo_9, label %_llgo_10 + %37 = and i64 %34, 2 + %38 = icmp ne i64 %37, 0 + br i1 %38, label %_llgo_9, label %_llgo_10 _llgo_9: ; preds = %_llgo_8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %27) + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %25) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_10 _llgo_10: ; preds = %_llgo_9, %_llgo_8 - %41 = and i64 %36, 2 - %42 = icmp ne i64 %41, 0 - br i1 %42, label %_llgo_11, label %_llgo_12 + %39 = and i64 %34, 1 + %40 = icmp ne i64 %39, 0 + br i1 %40, label %_llgo_11, label %_llgo_12 _llgo_11: ; preds = %_llgo_10 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %21) + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %19) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_12 _llgo_12: ; preds = %_llgo_11, %_llgo_10 - %43 = and i64 %36, 1 - %44 = icmp ne i64 %43, 0 - br i1 %44, label %_llgo_13, label %_llgo_14 - -_llgo_13: ; preds = %_llgo_12 call void @"main.main$1"() - br label %_llgo_14 - -_llgo_14: ; preds = %_llgo_13, %_llgo_12 - %45 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %4, align 8 - %46 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %45, 2 - %47 = call i32 @pthread_setspecific(ptr %2, ptr %46) - %48 = load i64, ptr %10, align 4 - switch i64 %48, label %_llgo_5 [ + %41 = load %"github.com/goplus/llgo/internal/runtime.Defer", ptr %4, align 8 + %42 = extractvalue %"github.com/goplus/llgo/internal/runtime.Defer" %41, 2 + %43 = call i32 @pthread_setspecific(ptr %2, ptr %42) + %44 = load i64, ptr %10, align 4 + switch i64 %44, label %_llgo_5 [ i64 1, label %_llgo_6 ] } diff --git a/cl/_testgo/strucintf/out.ll b/cl/_testgo/strucintf/out.ll index fada9257..131b5bb5 100644 --- a/cl/_testgo/strucintf/out.ll +++ b/cl/_testgo/strucintf/out.ll @@ -15,11 +15,11 @@ source_filename = "main" @2 = private unnamed_addr constant [5 x i8] c"main\00", align 1 @__llgo_argc = global ptr null @__llgo_argv = global ptr null +@3 = private unnamed_addr constant [12 x i8] c"Foo: not ok\00", align 1 @"_llgo_struct$K-dZ9QotZfVPz2a0YdRa9vmZUuDXPTqZOlMShKEDJtk" = linkonce global ptr null -@3 = private unnamed_addr constant [2 x i8] c"V\00", align 1 -@4 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 -@5 = private unnamed_addr constant [5 x i8] c"main\00", align 1 -@6 = private unnamed_addr constant [12 x i8] c"Foo: not ok\00", align 1 +@4 = private unnamed_addr constant [2 x i8] c"V\00", align 1 +@5 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@6 = private unnamed_addr constant [5 x i8] c"main\00", align 1 @7 = private unnamed_addr constant [12 x i8] c"Bar: not ok\00", align 1 @8 = private unnamed_addr constant [10 x i8] c"F: not ok\00", align 1 @@ -94,7 +94,7 @@ _llgo_2: ; preds = %_llgo_3, %_llgo_1 _llgo_3: ; preds = %_llgo_12 %16 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %17 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 0 - store ptr @6, ptr %17, align 8 + store ptr @3, ptr %17, align 8 %18 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %16, i32 0, i32 1 store i64 11, ptr %18, align 4 %19 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %16, align 8 @@ -299,20 +299,20 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 _llgo_3: ; preds = %_llgo_2 %28 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %29 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %28, i32 0, i32 0 - store ptr @3, ptr %29, align 8 + store ptr @4, ptr %29, align 8 %30 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %28, i32 0, i32 1 store i64 1, ptr %30, align 4 %31 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %28, align 8 %32 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %33 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %32, i32 0, i32 0 - store ptr @4, ptr %33, align 8 + store ptr @5, ptr %33, align 8 %34 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %32, i32 0, i32 1 store i64 0, ptr %34, align 4 %35 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %32, align 8 %36 = call %"github.com/goplus/llgo/internal/abi.StructField" @"github.com/goplus/llgo/internal/runtime.StructField"(%"github.com/goplus/llgo/internal/runtime.String" %31, ptr %25, i64 0, %"github.com/goplus/llgo/internal/runtime.String" %35, i1 false) %37 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 %38 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %37, i32 0, i32 0 - store ptr @5, ptr %38, align 8 + store ptr @6, ptr %38, align 8 %39 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %37, i32 0, i32 1 store i64 4, ptr %39, align 4 %40 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %37, align 8 @@ -351,8 +351,8 @@ declare void @"github.com/goplus/llgo/internal/runtime.PrintInt"(i64) declare void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8) -declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/cl/internal/foo.Bar"() - declare void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String") +declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/cl/internal/foo.Bar"() + declare %"github.com/goplus/llgo/internal/runtime.eface" @"github.com/goplus/llgo/cl/internal/foo.F"() diff --git a/cl/blocks/block.go b/cl/blocks/block.go new file mode 100644 index 00000000..21960751 --- /dev/null +++ b/cl/blocks/block.go @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package blocks + +import ( + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" +) + +type Info struct { + Kind llssa.DoAction + Next int +} + +// ----------------------------------------------------------------------------- + +type blockState struct { + self *ssa.BasicBlock + preds int + succs []int + loop bool + always bool + reach bool + fdel bool +} + +func (p *blockState) kind() llssa.DoAction { + if p.loop { + return llssa.DeferInLoop + } + if p.always { + return llssa.DeferAlways + } + return llssa.DeferInCond +} + +func newSuccs(succs []*ssa.BasicBlock) []int { + ret := make([]int, len(succs)) + for i, blk := range succs { + ret[i] = blk.Index + } + return ret +} + +func findLoop(states []*blockState, path []int, from, iblk int) []int { + path = append(path, iblk) + self := states[iblk] + for _, succ := range self.succs { + if states[succ].fdel { + continue + } + if pos := find(path, succ); pos >= 0 { + if pos > 0 { + continue + } + for _, i := range path { + s := states[i] + s.loop = true + s.fdel = true + } + return path + } + if ret := findLoop(states, path, from, succ); ret != nil { + return ret + } + } + return nil +} + +// https://en.wikipedia.org/wiki/Topological_sorting +func Infos(blks []*ssa.BasicBlock) []Info { + n := len(blks) + order := make([]int, 1, n+1) + order[0] = 0 + end, iend := 0, 0 + states := make([]*blockState, n) + for i, blk := range blks { + preds := len(blk.Preds) + if preds == 0 && i != 0 { + order = append(order, i) + } + if isEnd(blk) { + end++ + iend = i + } + states[i] = &blockState{ + self: blk, + preds: preds, + succs: newSuccs(blk.Succs), + } + } + + path := make([]int, 0, n) + if states[0].preds != 0 { + if loop := findLoop(states, path, 0, 0); len(loop) > 0 { + order = append(order, loop[1:]...) + } + } else { + states[0].always = true + } + if end == 1 { + states[iend].always = true + } + pos := 0 + +retry: + for pos < len(order) { + iblk := order[pos] + pos++ + state := states[iblk] + state.fdel = true + for _, succ := range state.succs { + s := states[succ] + if s.fdel { + continue + } + if s.preds--; s.preds == 0 { + order = append(order, succ) + } else { + s.reach = true + } + } + } + if pos < n { + for iblk, state := range states { + if state.fdel || !state.reach { + continue + } + if loop := findLoop(states, path, iblk, iblk); len(loop) > 0 { + order = append(order, loop...) + goto retry + } + } + panic("unreachable") + } + order = append(order, -1) + ret := make([]Info, n) + for i := 0; i < n; i++ { + iblk := order[i] + ret[iblk] = Info{states[iblk].kind(), order[i+1]} + } + return ret +} + +func isEnd(blk *ssa.BasicBlock) bool { + // Note: skip recover block + return len(blk.Succs) == 0 && (len(blk.Preds) > 0 || blk.Index == 0) +} + +func find(path []int, fv int) int { + for i, v := range path { + if v == fv { + return i + } + } + return -1 +} + +// ----------------------------------------------------------------------------- diff --git a/cl/blocks/block_test.go b/cl/blocks/block_test.go new file mode 100644 index 00000000..2cad3c86 --- /dev/null +++ b/cl/blocks/block_test.go @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package blocks + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "log" + "os" + "path" + "strings" + "testing" + + "github.com/goplus/gogen/packages" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + llssa "github.com/goplus/llgo/ssa" +) + +func TestTestdefer(t *testing.T) { + // debug = true + fromDir(t, "", "../_testdefer") +} + +func TestFirstLoop(t *testing.T) { + blk := &ssa.BasicBlock{} + blk.Index = 0 + blk.Preds = []*ssa.BasicBlock{blk} + blk.Succs = []*ssa.BasicBlock{blk} + infos := Infos([]*ssa.BasicBlock{blk}) + if infos[0].Kind != llssa.DeferInLoop { + t.Fatal("TestFirstLoop") + } +} + +func fromDir(t *testing.T, sel, relDir string) { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + dir = path.Join(dir, relDir) + fis, err := os.ReadDir(dir) + if err != nil { + t.Fatal("ReadDir failed:", err) + } + for _, fi := range fis { + name := fi.Name() + if !fi.IsDir() || strings.HasPrefix(name, "_") { + continue + } + t.Run(name, func(t *testing.T) { + testFrom(t, dir+"/"+name, sel) + }) + } +} + +func testFrom(t *testing.T, pkgDir, sel string) { + if sel != "" && !strings.Contains(pkgDir, sel) { + return + } + log.Println("Parsing", pkgDir) + in := pkgDir + "/in.go" + out := pkgDir + "/out.txt" + b, err := os.ReadFile(out) + if err != nil { + t.Fatal("ReadFile failed:", err) + } + expected := string(b) + testBlockInfo(t, nil, in, expected) +} + +func testBlockInfo(t *testing.T, src any, fname, expected string) { + t.Helper() + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, fname, src, parser.ParseComments) + if err != nil { + t.Fatal("ParseFile failed:", err) + } + files := []*ast.File{f} + name := f.Name.Name + pkg := types.NewPackage(name, name) + imp := packages.NewImporter(fset) + foo, _, err := ssautil.BuildPackage( + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions) + if err != nil { + t.Fatal("BuildPackage failed:", err) + } + foo.WriteTo(os.Stderr) + + for _, member := range foo.Members { + switch f := member.(type) { + case *ssa.Function: + if f.Name() == "main" { + f.WriteTo(os.Stderr) + infos := Infos(f.Blocks) + if v := resultOf(infos); v != expected { + t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) + } + return + } + } + } +} + +func resultOf(infos []Info) string { + var b bytes.Buffer + i := 0 + for { + fmt.Fprintf(&b, "%2d: %s\n", i, kinds[infos[i].Kind]) + if i = infos[i].Next; i < 0 { + break + } + } + return b.String() +} + +var kinds = [...]string{ + llssa.DeferAlways: "always", + llssa.DeferInCond: "cond", + llssa.DeferInLoop: "loop", +} diff --git a/cl/compile.go b/cl/compile.go index 199e10ba..469cd296 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -27,6 +27,7 @@ import ( "sort" "strings" + "github.com/goplus/llgo/cl/blocks" llssa "github.com/goplus/llgo/ssa" "golang.org/x/tools/go/ssa" ) @@ -149,7 +150,7 @@ type context struct { bvals map[ssa.Value]llssa.Expr // block values vargs map[*ssa.Alloc][]llssa.Expr // varargs - blkInfos []blockInfo + blkInfos []blocks.Info inits []func() phis []func() @@ -280,14 +281,14 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun for i, block := range f.Blocks { off[i] = p.compilePhis(b, block) } - p.blkInfos = blockInfos(f.Blocks) + p.blkInfos = blocks.Infos(f.Blocks) i := 0 for { block := f.Blocks[i] doMainInit := (i == 0 && name == "main") doModInit := (i == 1 && f.Name() == "init" && sig.Recv() == nil) p.compileBlock(b, block, off[i], doMainInit, doModInit) - if i = p.blkInfos[i].next; i < 0 { + if i = p.blkInfos[i].Next; i < 0 { break } } @@ -300,24 +301,6 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun return fn, nil, goFunc } -type blockInfo struct { - kind llssa.DoAction - next int -} - -func blockInfos(blks []*ssa.BasicBlock) []blockInfo { - n := len(blks) - infos := make([]blockInfo, n) - for i := range blks { - next := i + 1 - if next >= n { - next = -1 - } - infos[i] = blockInfo{kind: llssa.DeferInCond, next: next} - } - return infos -} - // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. // or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc. func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObjRef, ftype int) { @@ -829,7 +812,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { val := p.compileValue(b, v.Value) b.MapUpdate(m, key, val) case *ssa.Defer: - p.call(b, p.blkInfos[v.Block().Index].kind, &v.Call) + p.call(b, p.blkInfos[v.Block().Index].Kind, &v.Call) case *ssa.Go: p.call(b, llssa.Go, &v.Call) case *ssa.RunDefers: diff --git a/internal/abi/llgo_autogen.lla b/internal/abi/llgo_autogen.lla index eb0833a5..55b79262 100644 Binary files a/internal/abi/llgo_autogen.lla and b/internal/abi/llgo_autogen.lla differ diff --git a/internal/runtime/llgo_autogen.lla b/internal/runtime/llgo_autogen.lla index bd50b82b..f66c4e86 100644 Binary files a/internal/runtime/llgo_autogen.lla and b/internal/runtime/llgo_autogen.lla differ