diff --git a/chore/litgen/litgen.go b/chore/litgen/litgen.go index 34d51fe437..802cf2aab7 100644 --- a/chore/litgen/litgen.go +++ b/chore/litgen/litgen.go @@ -26,8 +26,11 @@ import ( "github.com/goplus/llgo/xtool/env/llvm" ) +var escapeChecks bool + func main() { llvm.SetupPath() + flag.BoolVar(&escapeChecks, "escape", false, "generate checks after escape analysis") flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [...]\n", filepath.Base(os.Args[0])) flag.PrintDefaults() diff --git a/chore/litgen/rewrite.go b/chore/litgen/rewrite.go index 209a1a3d7f..667791dec6 100644 --- a/chore/litgen/rewrite.go +++ b/chore/litgen/rewrite.go @@ -128,6 +128,9 @@ func genIR(target string) (ret string, err error) { } } }() + if escapeChecks { + return llgen.GenFromWithEscape(target), nil + } return llgen.GenFrom(target), nil } diff --git a/cl/_testdata/print/in.go b/cl/_testdata/print/in.go index b934f989bc..5864c7a2f7 100644 --- a/cl/_testdata/print/in.go +++ b/cl/_testdata/print/in.go @@ -58,6 +58,29 @@ type stringStruct struct { // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.Slice" %13 // CHECK-NEXT: } +// ESCAPE-LABEL: define %"{{.*}}/runtime/internal/runtime.Slice" @main.bytes(%"{{.*}}/runtime/internal/runtime.String" %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %1 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" %0, ptr %1, align 8 +// ESCAPE-NEXT: %.stack = alloca i8, i64 24, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 24, i1 false) +// ESCAPE-NEXT: %2 = call ptr @main.stringStructOf(ptr %1) +// ESCAPE-NEXT: %3 = getelementptr inbounds %main.stringStruct, ptr %2, i32 0, i32 0 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: %5 = getelementptr inbounds %main.slice, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: store ptr %4, ptr %5, align 8 +// ESCAPE-NEXT: %6 = getelementptr inbounds %main.stringStruct, ptr %2, i32 0, i32 1 +// ESCAPE-NEXT: %7 = load i64, ptr %6, align 8 +// ESCAPE-NEXT: %8 = getelementptr inbounds %main.slice, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: store i64 %7, ptr %8, align 8 +// ESCAPE-NEXT: %9 = getelementptr inbounds %main.stringStruct, ptr %2, i32 0, i32 1 +// ESCAPE-NEXT: %10 = load i64, ptr %9, align 8 +// ESCAPE-NEXT: %11 = getelementptr inbounds %main.slice, ptr %.stack, i32 0, i32 2 +// ESCAPE-NEXT: store i64 %10, ptr %11, align 8 +// ESCAPE-NEXT: %12 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %.stack, align 8 +// ESCAPE-NEXT: ret %"{{.*}}/runtime/internal/runtime.Slice" %12 +// ESCAPE-NEXT: } + func bytes(s string) (ret []byte) { rp := (*slice)(unsafe.Pointer(&ret)) sp := stringStructOf(&s) diff --git a/cl/_testgo/alias/in.go b/cl/_testgo/alias/in.go index 20d91abba6..6dbe039452 100644 --- a/cl/_testgo/alias/in.go +++ b/cl/_testgo/alias/in.go @@ -44,6 +44,27 @@ func (p *Point) Scale(factor float64) { p.y *= factor } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds %main.Point, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %1 = getelementptr inbounds %main.Point, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: store double 1.000000e+00, ptr %0, align 8 +// ESCAPE-NEXT: store double 2.000000e+00, ptr %1, align 8 +// ESCAPE-NEXT: call void @"main.(*Point).Scale"(ptr %.stack, double 2.000000e+00) +// ESCAPE-NEXT: call void @"main.(*Point).Move"(ptr %.stack, double 3.000000e+00, double 4.000000e+00) +// ESCAPE-NEXT: %2 = getelementptr inbounds %main.Point, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %3 = load double, ptr %2, align 8 +// ESCAPE-NEXT: %4 = getelementptr inbounds %main.Point, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %5 = load double, ptr %4, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintFloat"(double %3) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintFloat"(double %5) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { func main() { // CHECK: call ptr @"{{.*}}AllocZ"(i64 16) diff --git a/cl/_testgo/cgobasic/cgobasic.go b/cl/_testgo/cgobasic/cgobasic.go index 8017d92376..d6fdace353 100644 --- a/cl/_testgo/cgobasic/cgobasic.go +++ b/cl/_testgo/cgobasic/cgobasic.go @@ -66,6 +66,66 @@ import ( // CHECK-NEXT: ret double %4 // CHECK-NEXT: } +// ESCAPE-LABEL: define double @main._Cfunc_cos(double %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_cos, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call double %2(double %0) +// ESCAPE-NEXT: ret double %3 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define [0 x i8] @main._Cfunc_free(ptr %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_free, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call [0 x i8] %2(ptr %0) +// ESCAPE-NEXT: ret [0 x i8] %3 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define double @main._Cfunc_log(double %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_log, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call double %2(double %0) +// ESCAPE-NEXT: ret double %3 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define i32 @main._Cfunc_puts(ptr %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_puts, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call i32 %2(ptr %0) +// ESCAPE-NEXT: ret i32 %3 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define double @main._Cfunc_sin(double %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_sin, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call double %2(double %0) +// ESCAPE-NEXT: ret double %3 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define double @main._Cfunc_sqrt(double %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_sqrt, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call double %2(double %0) +// ESCAPE-NEXT: ret double %3 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.init(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = load i1, ptr @"main.init$guard", align 1 diff --git a/cl/_testgo/cgocfiles/cgocfiles.go b/cl/_testgo/cgocfiles/cgocfiles.go index 51d85ab1e8..8e4e67ccc8 100644 --- a/cl/_testgo/cgocfiles/cgocfiles.go +++ b/cl/_testgo/cgocfiles/cgocfiles.go @@ -16,6 +16,16 @@ import "fmt" // CHECK-NEXT: ret i32 %8 // CHECK-NEXT: } +// ESCAPE-LABEL: define i32 @main._Cfunc_test_structs(ptr %0, ptr %1, ptr %2, ptr %3, ptr %4){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %5 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_test_structs, align 8 +// ESCAPE-NEXT: %6 = load ptr, ptr %5, align 8 +// ESCAPE-NEXT: %7 = call i32 %6(ptr %0, ptr %1, ptr %2, ptr %3, ptr %4) +// ESCAPE-NEXT: ret i32 %7 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 4) diff --git a/cl/_testgo/cgodefer/cgodefer.go b/cl/_testgo/cgodefer/cgodefer.go index 8d9bca1306..5f1b9cb856 100644 --- a/cl/_testgo/cgodefer/cgodefer.go +++ b/cl/_testgo/cgodefer/cgodefer.go @@ -15,6 +15,16 @@ import "C" // CHECK-NEXT: ret [0 x i8] %4 // CHECK-NEXT: } +// ESCAPE-LABEL: define [0 x i8] @main._Cfunc_free(ptr %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_free, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call [0 x i8] %2(ptr %0) +// ESCAPE-NEXT: ret [0 x i8] %3 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 8) diff --git a/cl/_testgo/cgomacro/cgomacro.go b/cl/_testgo/cgomacro/cgomacro.go index c2c152d37d..ba1f6be461 100644 --- a/cl/_testgo/cgomacro/cgomacro.go +++ b/cl/_testgo/cgomacro/cgomacro.go @@ -92,6 +92,62 @@ import ( // CHECK-NEXT: ret ptr %2 // CHECK-NEXT: } +// ESCAPE-LABEL: define i32 @main._Cfunc_PyObject_Print(ptr %0, ptr %1, i32 %2){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %3 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_PyObject_Print, align 8 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: %5 = call i32 %4(ptr %0, ptr %1, i32 %2) +// ESCAPE-NEXT: ret i32 %5 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define i32 @main._Cfunc_fputs(ptr %0, ptr %1){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %2 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_fputs, align 8 +// ESCAPE-NEXT: %3 = load ptr, ptr %2, align 8 +// ESCAPE-NEXT: %4 = call i32 %3(ptr %0, ptr %1) +// ESCAPE-NEXT: ret i32 %4 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define ptr @main._Cmacro_Py_False(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = load ptr, ptr @main._cgo_{{.*}}_Cmacro_Py_False, align 8 +// ESCAPE-NEXT: %1 = load ptr, ptr %0, align 8 +// ESCAPE-NEXT: ret ptr %1 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define ptr @main._Cmacro_Py_None(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = load ptr, ptr @main._cgo_{{.*}}_Cmacro_Py_None, align 8 +// ESCAPE-NEXT: %1 = load ptr, ptr %0, align 8 +// ESCAPE-NEXT: ret ptr %1 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define ptr @main._Cmacro_Py_True(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = load ptr, ptr @main._cgo_{{.*}}_Cmacro_Py_True, align 8 +// ESCAPE-NEXT: %1 = load ptr, ptr %0, align 8 +// ESCAPE-NEXT: ret ptr %1 +// ESCAPE-NEXT: } + +// ESCAPE-LABEL: define ptr @main._Cmacro_stdout(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = load ptr, ptr @main._cgo_{{.*}}_Cmacro_stdout, align 8 +// ESCAPE-NEXT: %1 = load ptr, ptr %0, align 8 +// ESCAPE-NEXT: ret ptr %1 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.init(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = load i1, ptr @"main.init$guard", align 1 diff --git a/cl/_testgo/cgopython/cgopython.go b/cl/_testgo/cgopython/cgopython.go index aea2a0f615..88a245b2a3 100644 --- a/cl/_testgo/cgopython/cgopython.go +++ b/cl/_testgo/cgopython/cgopython.go @@ -16,6 +16,16 @@ import "C" // CHECK-NEXT: ret i32 %4 // CHECK-NEXT: } +// ESCAPE-LABEL: define i32 @main._Cfunc_PyRun_SimpleString(ptr %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_PyRun_SimpleString, align 8 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = call i32 %2(ptr %0) +// ESCAPE-NEXT: ret i32 %3 +// ESCAPE-NEXT: } + // CHECK-LABEL: define [0 x i8] @main._Cfunc_Py_Finalize(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = load ptr, ptr @main._cgo_{{.*}}_Cfunc_Py_Finalize, align 8 diff --git a/cl/_testgo/cursor/in.go b/cl/_testgo/cursor/in.go index d0a2fc24bf..e3a70cf809 100644 --- a/cl/_testgo/cursor/in.go +++ b/cl/_testgo/cursor/in.go @@ -805,6 +805,16 @@ const ( // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds %main.Cursor, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %1 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// ESCAPE-NEXT: store ptr %1, ptr %0, align 8 +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + // CHECK-LABEL: define i64 @main.maskOf(%"{{.*}}/runtime/internal/runtime.Slice" %0){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %1 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %0, 1 diff --git a/cl/_testgo/returnorder/in.go b/cl/_testgo/returnorder/in.go index de98fd2b9b..a7d8241ff5 100644 --- a/cl/_testgo/returnorder/in.go +++ b/cl/_testgo/returnorder/in.go @@ -36,6 +36,18 @@ func main() { // CHECK-NEXT: %4 = insertvalue { %main.state, i64 } undef, %main.state %3, 0 // CHECK-NEXT: %5 = insertvalue { %main.state, i64 } %4, i64 %2, 1 // CHECK-NEXT: ret { %main.state, i64 } %5 +// ESCAPE-LABEL: define { %main.state, i64 } @main.returnStateAndMut(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds %main.state, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: store i64 1, ptr %0, align 8 +// ESCAPE-NEXT: %1 = call i64 @"main.(*state).mutate"(ptr %.stack, i64 2) +// ESCAPE-NEXT: %2 = load %main.state, ptr %.stack, align 8 +// ESCAPE-NEXT: %3 = insertvalue { %main.state, i64 } undef, %main.state %2, 0 +// ESCAPE-NEXT: %4 = insertvalue { %main.state, i64 } %3, i64 %1, 1 +// ESCAPE-NEXT: ret { %main.state, i64 } %4 +// ESCAPE-NEXT: } func returnStateAndMut() (state, int) { x := state{v: 1} return x, x.mutate(2) diff --git a/cl/_testgo/tprecurfn/in.go b/cl/_testgo/tprecurfn/in.go index de9928916d..a36fa0925f 100644 --- a/cl/_testgo/tprecurfn/in.go +++ b/cl/_testgo/tprecurfn/in.go @@ -6,6 +6,25 @@ type My[T any] struct { next *My[T] } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 24, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 24, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds %"main.My[int]", ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %1 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// ESCAPE-NEXT: %2 = getelementptr inbounds %"main.My[int]", ptr %1, i32 0, i32 0 +// ESCAPE-NEXT: store { ptr, ptr } { ptr @"__llgo_stub.main.main$1", ptr null }, ptr %2, align 8 +// ESCAPE-NEXT: store ptr %1, ptr %0, align 8 +// ESCAPE-NEXT: %3 = getelementptr inbounds %"main.My[int]", ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: %5 = getelementptr inbounds %"main.My[int]", ptr %4, i32 0, i32 0 +// ESCAPE-NEXT: %6 = load { ptr, ptr }, ptr %5, align 8 +// ESCAPE-NEXT: %7 = extractvalue { ptr, ptr } %6, 1 +// ESCAPE-NEXT: %8 = extractvalue { ptr, ptr } %6, 0 +// ESCAPE-NEXT: call void %8(ptr %7, i64 100) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { func main() { // CHECK: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) diff --git a/cl/_testgo/tptypes/in.go b/cl/_testgo/tptypes/in.go index c6130848bd..03dfeabcc4 100644 --- a/cl/_testgo/tptypes/in.go +++ b/cl/_testgo/tptypes/in.go @@ -177,6 +177,133 @@ type ( // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %0 = alloca %"main.Data[int]", align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %0, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %1 = getelementptr inbounds %"main.Data[int]", ptr %0, i32 0, i32 0 +// ESCAPE-NEXT: store i64 1, ptr %1, align 8 +// ESCAPE-NEXT: %2 = load %"main.Data[int]", ptr %0, align 8 +// ESCAPE-NEXT: %3 = extractvalue %"main.Data[int]" %2, 0 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %3) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %4 = alloca %"main.Data[string]", align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %4, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %5 = getelementptr inbounds %"main.Data[string]", ptr %4, i32 0, i32 0 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 5 }, ptr %5, align 8 +// ESCAPE-NEXT: %6 = load %"main.Data[string]", ptr %4, align 8 +// ESCAPE-NEXT: %7 = extractvalue %"main.Data[string]" %6, 0 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %7) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %8 = alloca %"main.Data[int]", align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %8, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %9 = getelementptr inbounds %"main.Data[int]", ptr %8, i32 0, i32 0 +// ESCAPE-NEXT: store i64 100, ptr %9, align 8 +// ESCAPE-NEXT: %10 = load %"main.Data[int]", ptr %8, align 8 +// ESCAPE-NEXT: %11 = extractvalue %"main.Data[int]" %10, 0 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %11) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %12 = alloca %"main.Data[string]", align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %12, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %13 = getelementptr inbounds %"main.Data[string]", ptr %12, i32 0, i32 0 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 5 }, ptr %13, align 8 +// ESCAPE-NEXT: %14 = load %"main.Data[string]", ptr %12, align 8 +// ESCAPE-NEXT: %15 = extractvalue %"main.Data[string]" %14, 0 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %15) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 0) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %.stack = alloca i8, i64 24, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 24, i1 false) +// ESCAPE-NEXT: %16 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 8) +// ESCAPE-NEXT: %17 = getelementptr inbounds i64, ptr %16, i64 0 +// ESCAPE-NEXT: store i64 100, ptr %17, align 8 +// ESCAPE-NEXT: %18 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %16, 0 +// ESCAPE-NEXT: %19 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %18, i64 1, 1 +// ESCAPE-NEXT: %20 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %19, i64 1, 2 +// ESCAPE-NEXT: %21 = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[\]int,int\]}}).Append"(ptr %.stack, %"{{.*}}/runtime/internal/runtime.Slice" %20) +// ESCAPE-NEXT: %.stack1 = alloca i8, i64 24, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack1, i8 0, i64 24, i1 false) +// ESCAPE-NEXT: %22 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 16) +// ESCAPE-NEXT: %23 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.String", ptr %22, i64 0 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 5 }, ptr %23, align 8 +// ESCAPE-NEXT: %24 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %22, 0 +// ESCAPE-NEXT: %25 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %24, i64 1, 1 +// ESCAPE-NEXT: %26 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %25, i64 1, 2 +// ESCAPE-NEXT: %27 = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[\]string,string\]}}).Append"(ptr %.stack1, %"{{.*}}/runtime/internal/runtime.Slice" %26) +// ESCAPE-NEXT: %.stack2 = alloca i8, i64 24, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack2, i8 0, i64 24, i1 false) +// ESCAPE-NEXT: %28 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) +// ESCAPE-NEXT: %29 = getelementptr inbounds i64, ptr %28, i64 0 +// ESCAPE-NEXT: store i64 1, ptr %29, align 8 +// ESCAPE-NEXT: %30 = getelementptr inbounds i64, ptr %28, i64 1 +// ESCAPE-NEXT: store i64 2, ptr %30, align 8 +// ESCAPE-NEXT: %31 = getelementptr inbounds i64, ptr %28, i64 2 +// ESCAPE-NEXT: store i64 3, ptr %31, align 8 +// ESCAPE-NEXT: %32 = getelementptr inbounds i64, ptr %28, i64 3 +// ESCAPE-NEXT: store i64 4, ptr %32, align 8 +// ESCAPE-NEXT: %33 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %28, 0 +// ESCAPE-NEXT: %34 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %33, i64 4, 1 +// ESCAPE-NEXT: %35 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %34, i64 4, 2 +// ESCAPE-NEXT: %36 = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[\]int,int\]}}).Append"(ptr %.stack2, %"{{.*}}/runtime/internal/runtime.Slice" %35) +// ESCAPE-NEXT: %37 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) +// ESCAPE-NEXT: %38 = getelementptr inbounds i64, ptr %37, i64 0 +// ESCAPE-NEXT: store i64 1, ptr %38, align 8 +// ESCAPE-NEXT: %39 = getelementptr inbounds i64, ptr %37, i64 1 +// ESCAPE-NEXT: store i64 2, ptr %39, align 8 +// ESCAPE-NEXT: %40 = getelementptr inbounds i64, ptr %37, i64 2 +// ESCAPE-NEXT: store i64 3, ptr %40, align 8 +// ESCAPE-NEXT: %41 = getelementptr inbounds i64, ptr %37, i64 3 +// ESCAPE-NEXT: store i64 4, ptr %41, align 8 +// ESCAPE-NEXT: %42 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %37, 0 +// ESCAPE-NEXT: %43 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %42, i64 4, 1 +// ESCAPE-NEXT: %44 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %43, i64 4, 2 +// ESCAPE-NEXT: %45 = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[\]int,int\]}}).Append2"(ptr %.stack2, %"{{.*}}/runtime/internal/runtime.Slice" %44) +// ESCAPE-NEXT: %46 = getelementptr inbounds %"main.Slice{{\[\[\]int,int\]}}", ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %47 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %46, align 8 +// ESCAPE-NEXT: %48 = getelementptr inbounds %"main.Slice{{\[\[\]int,int\]}}", ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %49 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %48, align 8 +// ESCAPE-NEXT: %50 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %49, 0 +// ESCAPE-NEXT: %51 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %49, 1 +// ESCAPE-NEXT: %52 = icmp uge i64 0, %51 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.CheckIndexRange"(i1 %52, i64 0, i1 true, i64 %51) +// ESCAPE-NEXT: %53 = getelementptr inbounds i64, ptr %50, i64 0 +// ESCAPE-NEXT: %54 = load i64, ptr %53, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintSlice"(%"{{.*}}/runtime/internal/runtime.Slice" %47) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %54) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %55 = getelementptr inbounds %"main.Slice{{\[\[\]string,string\]}}", ptr %.stack1, i32 0, i32 0 +// ESCAPE-NEXT: %56 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %55, align 8 +// ESCAPE-NEXT: %57 = getelementptr inbounds %"main.Slice{{\[\[\]string,string\]}}", ptr %.stack1, i32 0, i32 0 +// ESCAPE-NEXT: %58 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %57, align 8 +// ESCAPE-NEXT: %59 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %58, 0 +// ESCAPE-NEXT: %60 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %58, 1 +// ESCAPE-NEXT: %61 = icmp uge i64 0, %60 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.CheckIndexRange"(i1 %61, i64 0, i1 true, i64 %60) +// ESCAPE-NEXT: %62 = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.String", ptr %59, i64 0 +// ESCAPE-NEXT: %63 = load %"{{.*}}/runtime/internal/runtime.String", ptr %62, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintSlice"(%"{{.*}}/runtime/internal/runtime.Slice" %56) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %63) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %64 = getelementptr inbounds %"main.Slice{{\[\[\]int,int\]}}", ptr %.stack2, i32 0, i32 0 +// ESCAPE-NEXT: %65 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %64, align 8 +// ESCAPE-NEXT: %66 = getelementptr inbounds %"main.Slice{{\[\[\]int,int\]}}", ptr %.stack2, i32 0, i32 0 +// ESCAPE-NEXT: %67 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %66, align 8 +// ESCAPE-NEXT: %68 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %67, 0 +// ESCAPE-NEXT: %69 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %67, 1 +// ESCAPE-NEXT: %70 = icmp uge i64 0, %69 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.CheckIndexRange"(i1 %70, i64 0, i1 true, i64 %69) +// ESCAPE-NEXT: %71 = getelementptr inbounds i64, ptr %68, i64 0 +// ESCAPE-NEXT: %72 = load i64, ptr %71, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintSlice"(%"{{.*}}/runtime/internal/runtime.Slice" %65) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %72) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + func main() { println(DataInt{1}.v) println(DataString{"hello"}.v) diff --git a/cl/_testlibc/atomic/in.go b/cl/_testlibc/atomic/in.go index f1681d9c38..2318a9ece9 100644 --- a/cl/_testlibc/atomic/in.go +++ b/cl/_testlibc/atomic/in.go @@ -6,6 +6,32 @@ import ( "github.com/goplus/lib/c/sync/atomic" ) +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: store atomic i64 100, ptr %.stack seq_cst, align 8 +// ESCAPE-NEXT: %0 = load atomic i64, ptr %.stack seq_cst, align 8 +// ESCAPE-NEXT: %1 = call i32 (ptr, ...) @printf(ptr @0, i64 %0) +// ESCAPE-NEXT: %2 = atomicrmw add ptr %.stack, i64 1 seq_cst, align 8 +// ESCAPE-NEXT: %3 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: %4 = call i32 (ptr, ...) @printf(ptr @1, i64 %2, i64 %3) +// ESCAPE-NEXT: %5 = cmpxchg ptr %.stack, i64 100, i64 102 seq_cst seq_cst, align 8 +// ESCAPE-NEXT: %6 = extractvalue { i64, i1 } %5, 0 +// ESCAPE-NEXT: %7 = extractvalue { i64, i1 } %5, 1 +// ESCAPE-NEXT: %8 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: %9 = call i32 (ptr, ...) @printf(ptr @2, i64 %6, i64 %8) +// ESCAPE-NEXT: %10 = cmpxchg ptr %.stack, i64 101, i64 102 seq_cst seq_cst, align 8 +// ESCAPE-NEXT: %11 = extractvalue { i64, i1 } %10, 0 +// ESCAPE-NEXT: %12 = extractvalue { i64, i1 } %10, 1 +// ESCAPE-NEXT: %13 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: %14 = call i32 (ptr, ...) @printf(ptr @3, i64 %11, i64 %13) +// ESCAPE-NEXT: %15 = atomicrmw sub ptr %.stack, i64 1 seq_cst, align 8 +// ESCAPE-NEXT: %16 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: %17 = call i32 (ptr, ...) @printf(ptr @4, i64 %15, i64 %16) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { func main() { var v int64 diff --git a/cl/_testlibgo/atomic/in.go b/cl/_testlibgo/atomic/in.go index d97635e548..55213eafe7 100644 --- a/cl/_testlibgo/atomic/in.go +++ b/cl/_testlibgo/atomic/in.go @@ -5,6 +5,63 @@ import ( "sync/atomic" ) +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: store atomic i64 100, ptr %.stack seq_cst, align 8 +// ESCAPE-NEXT: %0 = load atomic i64, ptr %.stack seq_cst, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 6 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %0) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %1 = atomicrmw add ptr %.stack, i64 1 seq_cst, align 8 +// ESCAPE-NEXT: %2 = add i64 %1, 1 +// ESCAPE-NEXT: %3 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 4 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %2) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 2 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %3) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %4 = cmpxchg ptr %.stack, i64 100, i64 102 seq_cst seq_cst, align 8 +// ESCAPE-NEXT: %5 = extractvalue { i64, i1 } %4, 1 +// ESCAPE-NEXT: %6 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @3, i64 4 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %5) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 2 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %6) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %7 = cmpxchg ptr %.stack, i64 101, i64 102 seq_cst seq_cst, align 8 +// ESCAPE-NEXT: %8 = extractvalue { i64, i1 } %7, 1 +// ESCAPE-NEXT: %9 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @3, i64 4 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %8) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 2 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %9) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %10 = atomicrmw add ptr %.stack, i64 -1 seq_cst, align 8 +// ESCAPE-NEXT: %11 = add i64 %10, -1 +// ESCAPE-NEXT: %12 = load i64, ptr %.stack, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 4 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %11) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @2, i64 2 }) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %12) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.main(){{.*}} { func main() { var v int64 diff --git a/cl/_testrt/abitype/in.go b/cl/_testrt/abitype/in.go index ced9c0c2d7..7e84ab3717 100644 --- a/cl/_testrt/abitype/in.go +++ b/cl/_testrt/abitype/in.go @@ -51,6 +51,51 @@ import ( // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 4) +// ESCAPE-NEXT: store i32 0, ptr %0, align 4 +// ESCAPE-NEXT: %1 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int32, ptr undef }, ptr %0, 1 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %1, ptr %.stack, align 8 +// ESCAPE-NEXT: %2 = getelementptr inbounds %main.eface, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %3 = load ptr, ptr %2, align 8 +// ESCAPE-NEXT: %4 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/runtime/abi.(*Type).String"(ptr %3) +// ESCAPE-NEXT: %5 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %4, %"{{.*}}/runtime/internal/runtime.String" { ptr @0, i64 5 }) +// ESCAPE-NEXT: %6 = xor i1 %5, true +// ESCAPE-NEXT: br i1 %6, label %_llgo_1, label %_llgo_2 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_1: ; preds = %_llgo_0 +// ESCAPE-NEXT: %7 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 14 }, ptr %7, align 8 +// ESCAPE-NEXT: %8 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %7, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %8) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_2: ; preds = %_llgo_0 +// ESCAPE-NEXT: %9 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 1) +// ESCAPE-NEXT: store i8 0, ptr %9, align 1 +// ESCAPE-NEXT: %10 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_uint8, ptr undef }, ptr %9, 1 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %10, ptr %.stack, align 8 +// ESCAPE-NEXT: %11 = getelementptr inbounds %main.eface, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %12 = load ptr, ptr %11, align 8 +// ESCAPE-NEXT: %13 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/runtime/abi.(*Type).String"(ptr %12) +// ESCAPE-NEXT: %14 = call i1 @"{{.*}}/runtime/internal/runtime.StringEqual"(%"{{.*}}/runtime/internal/runtime.String" %13, %"{{.*}}/runtime/internal/runtime.String" { ptr @3, i64 5 }) +// ESCAPE-NEXT: %15 = xor i1 %14, true +// ESCAPE-NEXT: br i1 %15, label %_llgo_3, label %_llgo_4 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_3: ; preds = %_llgo_2 +// ESCAPE-NEXT: %16 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @4, i64 14 }, ptr %16, align 8 +// ESCAPE-NEXT: %17 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %16, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %17) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_4: ; preds = %_llgo_2 +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + type eface struct { typ *abi.Type data unsafe.Pointer diff --git a/cl/_testrt/builtin/in.go b/cl/_testrt/builtin/in.go index fdc7ff8da0..dff734c1be 100644 --- a/cl/_testrt/builtin/in.go +++ b/cl/_testrt/builtin/in.go @@ -37,6 +37,15 @@ const ( // CHECK-NEXT: ret double %2 // CHECK-NEXT: } +// ESCAPE-LABEL: define double @main.Float64frombits(i64 %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: store i64 %0, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = load double, ptr %.stack, align 8 +// ESCAPE-NEXT: ret double %1 +// ESCAPE-NEXT: } + func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) } // CHECK-LABEL: define double @main.Inf(i64 %0){{.*}} { diff --git a/cl/_testrt/eface/in.go b/cl/_testrt/eface/in.go index 68b29347b3..5904566981 100644 --- a/cl/_testrt/eface/in.go +++ b/cl/_testrt/eface/in.go @@ -26,6 +26,16 @@ func (t *T) Invoke() { // CHECK-NEXT: call void @main.dumpTyp(ptr %3, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.dump(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %0, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = getelementptr inbounds %main.eface, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: call void @main.dumpTyp(ptr %2, %"{{.*}}/runtime/internal/runtime.String" zeroinitializer) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } func dump(v any) { e := (*eface)(unsafe.Pointer(&v)) dumpTyp(e._type, "") diff --git a/cl/_testrt/float2any/in.go b/cl/_testrt/float2any/in.go index ee0e1cf095..f15412a6ee 100644 --- a/cl/_testrt/float2any/in.go +++ b/cl/_testrt/float2any/in.go @@ -18,6 +18,61 @@ type u64parts struct { hi uint32 } +// ESCAPE-LABEL: define void @main.check32(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %0, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = load %"{{.*}}/runtime/internal/runtime.eface", ptr %.stack, align 8 +// ESCAPE-NEXT: %2 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 0 +// ESCAPE-NEXT: %3 = icmp eq ptr %2, @_llgo_float32 +// ESCAPE-NEXT: br i1 %3, label %_llgo_5, label %_llgo_6 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_1: ; preds = %_llgo_7 +// ESCAPE-NEXT: %4 = alloca %main.eface, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %4, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %5 = load %main.eface, ptr %.stack, align 8 +// ESCAPE-NEXT: store %main.eface %5, ptr %4, align 8 +// ESCAPE-NEXT: %6 = getelementptr inbounds %main.eface, ptr %4, i32 0, i32 1 +// ESCAPE-NEXT: %7 = load ptr, ptr %6, align 8 +// ESCAPE-NEXT: %8 = load i32, ptr %7, align 4 +// ESCAPE-NEXT: %9 = icmp ne i32 %8, 1078530011 +// ESCAPE-NEXT: br i1 %9, label %_llgo_3, label %_llgo_4 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_2: ; preds = %_llgo_7 +// ESCAPE-NEXT: %10 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @1, i64 14 }, ptr %10, align 8 +// ESCAPE-NEXT: %11 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %10, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %11) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_3: ; preds = %_llgo_1 +// ESCAPE-NEXT: %12 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @3, i64 14 }, ptr %12, align 8 +// ESCAPE-NEXT: %13 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %12, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %13) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_4: ; preds = %_llgo_1 +// ESCAPE-NEXT: ret void +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_5: ; preds = %_llgo_0 +// ESCAPE-NEXT: %14 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 1 +// ESCAPE-NEXT: %15 = load float, ptr %14, align 4 +// ESCAPE-NEXT: %16 = insertvalue { float, i1 } undef, float %15, 0 +// ESCAPE-NEXT: %17 = insertvalue { float, i1 } %16, i1 true, 1 +// ESCAPE-NEXT: br label %_llgo_7 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_6: ; preds = %_llgo_0 +// ESCAPE-NEXT: br label %_llgo_7 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_7: ; preds = %_llgo_6, %_llgo_5 +// ESCAPE-NEXT: %18 = phi { float, i1 } [ %17, %_llgo_5 ], [ zeroinitializer, %_llgo_6 ] +// ESCAPE-NEXT: %19 = extractvalue { float, i1 } %18, 0 +// ESCAPE-NEXT: %20 = extractvalue { float, i1 } %18, 1 +// ESCAPE-NEXT: br i1 %20, label %_llgo_1, label %_llgo_2 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.check32(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { // CHECK: icmp eq ptr {{.*}}, @_llgo_float32 // CHECK: load i32, ptr @@ -34,6 +89,72 @@ func check32(v any) { } } +// ESCAPE-LABEL: define void @main.check64(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %0, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = load %"{{.*}}/runtime/internal/runtime.eface", ptr %.stack, align 8 +// ESCAPE-NEXT: %2 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 0 +// ESCAPE-NEXT: %3 = icmp eq ptr %2, @_llgo_float64 +// ESCAPE-NEXT: br i1 %3, label %_llgo_6, label %_llgo_7 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_1: ; preds = %_llgo_8 +// ESCAPE-NEXT: %4 = alloca %main.eface, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %4, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %5 = load %main.eface, ptr %.stack, align 8 +// ESCAPE-NEXT: store %main.eface %5, ptr %4, align 8 +// ESCAPE-NEXT: %6 = alloca %main.u64parts, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %6, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %7 = getelementptr inbounds %main.eface, ptr %4, i32 0, i32 1 +// ESCAPE-NEXT: %8 = load ptr, ptr %7, align 8 +// ESCAPE-NEXT: %9 = load %main.u64parts, ptr %8, align 4 +// ESCAPE-NEXT: store %main.u64parts %9, ptr %6, align 4 +// ESCAPE-NEXT: %10 = getelementptr inbounds %main.u64parts, ptr %6, i32 0, i32 0 +// ESCAPE-NEXT: %11 = load i32, ptr %10, align 4 +// ESCAPE-NEXT: %12 = icmp ne i32 %11, 1405670641 +// ESCAPE-NEXT: br i1 %12, label %_llgo_3, label %_llgo_5 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_2: ; preds = %_llgo_8 +// ESCAPE-NEXT: %13 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 14 }, ptr %13, align 8 +// ESCAPE-NEXT: %14 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %13, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %14) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_3: ; preds = %_llgo_5, %_llgo_1 +// ESCAPE-NEXT: %15 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @6, i64 14 }, ptr %15, align 8 +// ESCAPE-NEXT: %16 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %15, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %16) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_4: ; preds = %_llgo_5 +// ESCAPE-NEXT: ret void +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_5: ; preds = %_llgo_1 +// ESCAPE-NEXT: %17 = getelementptr inbounds %main.u64parts, ptr %6, i32 0, i32 1 +// ESCAPE-NEXT: %18 = load i32, ptr %17, align 4 +// ESCAPE-NEXT: %19 = icmp ne i32 %18, 1074340347 +// ESCAPE-NEXT: br i1 %19, label %_llgo_3, label %_llgo_4 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_6: ; preds = %_llgo_0 +// ESCAPE-NEXT: %20 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %1, 1 +// ESCAPE-NEXT: %21 = load double, ptr %20, align 8 +// ESCAPE-NEXT: %22 = insertvalue { double, i1 } undef, double %21, 0 +// ESCAPE-NEXT: %23 = insertvalue { double, i1 } %22, i1 true, 1 +// ESCAPE-NEXT: br label %_llgo_8 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_7: ; preds = %_llgo_0 +// ESCAPE-NEXT: br label %_llgo_8 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_8: ; preds = %_llgo_7, %_llgo_6 +// ESCAPE-NEXT: %24 = phi { double, i1 } [ %23, %_llgo_6 ], [ zeroinitializer, %_llgo_7 ] +// ESCAPE-NEXT: %25 = extractvalue { double, i1 } %24, 0 +// ESCAPE-NEXT: %26 = extractvalue { double, i1 } %24, 1 +// ESCAPE-NEXT: br i1 %26, label %_llgo_1, label %_llgo_2 +// ESCAPE-NEXT: } + // CHECK-LABEL: define void @main.check64(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { // CHECK: icmp eq ptr {{.*}}, @_llgo_float64 // CHECK: load i32, ptr diff --git a/cl/_testrt/funcaddr/in.go b/cl/_testrt/funcaddr/in.go index 6457b31b93..566c193903 100644 --- a/cl/_testrt/funcaddr/in.go +++ b/cl/_testrt/funcaddr/in.go @@ -41,6 +41,30 @@ func add(a, b int) int { // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: store ptr @main.add, ptr %.stack, align 8 +// ESCAPE-NEXT: %.stack1 = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack1, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: store ptr @"main.main$1", ptr %.stack1, align 8 +// ESCAPE-NEXT: %0 = load ptr, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = icmp eq ptr @main.add, %0 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %1) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %2 = load ptr, ptr %.stack, align 8 +// ESCAPE-NEXT: %3 = load ptr, ptr %.stack, align 8 +// ESCAPE-NEXT: %4 = icmp eq ptr %2, %3 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %4) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %5 = load ptr, ptr %.stack1, align 8 +// ESCAPE-NEXT: %6 = load ptr, ptr %.stack1, align 8 +// ESCAPE-NEXT: %7 = icmp eq ptr %5, %6 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %7) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } func main() { var fn Add = add // CHECK-LABEL: define i64 @"main.main$1"(i64 %0, i64 %1){{.*}} { diff --git a/cl/_testrt/funcdecl/in.go b/cl/_testrt/funcdecl/in.go index 8efd3f4635..000c84adac 100644 --- a/cl/_testrt/funcdecl/in.go +++ b/cl/_testrt/funcdecl/in.go @@ -82,6 +82,18 @@ func check(fn func()) { // CHECK-NEXT: ret ptr %5 // CHECK-NEXT: } +// ESCAPE-LABEL: define ptr @main.closurePtr(%"{{.*}}/runtime/internal/runtime.eface" %0){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %0, ptr %.stack, align 8 +// ESCAPE-NEXT: %1 = getelementptr inbounds %main.rtype, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %2 = load ptr, ptr %1, align 8 +// ESCAPE-NEXT: %3 = getelementptr inbounds { ptr, ptr }, ptr %2, i32 0, i32 0 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: ret ptr %4 +// ESCAPE-NEXT: } + func closurePtr(a any) unsafe.Pointer { return (*rtype)(unsafe.Pointer(&a)).ptr.fn } diff --git a/cl/_testrt/index/in.go b/cl/_testrt/index/in.go index 8f19ac4768..310e6a798a 100644 --- a/cl/_testrt/index/in.go +++ b/cl/_testrt/index/in.go @@ -153,6 +153,135 @@ type S []int // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %0 = alloca %main.point, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %0, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %1 = alloca [3 x %main.point], align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %1, i8 0, i64 48, i1 false) +// ESCAPE-NEXT: %2 = getelementptr inbounds %main.point, ptr %1, i64 0 +// ESCAPE-NEXT: %3 = getelementptr inbounds %main.point, ptr %2, i32 0, i32 0 +// ESCAPE-NEXT: %4 = getelementptr inbounds %main.point, ptr %2, i32 0, i32 1 +// ESCAPE-NEXT: %5 = getelementptr inbounds %main.point, ptr %1, i64 1 +// ESCAPE-NEXT: %6 = getelementptr inbounds %main.point, ptr %5, i32 0, i32 0 +// ESCAPE-NEXT: %7 = getelementptr inbounds %main.point, ptr %5, i32 0, i32 1 +// ESCAPE-NEXT: %8 = getelementptr inbounds %main.point, ptr %1, i64 2 +// ESCAPE-NEXT: %9 = getelementptr inbounds %main.point, ptr %8, i32 0, i32 0 +// ESCAPE-NEXT: %10 = getelementptr inbounds %main.point, ptr %8, i32 0, i32 1 +// ESCAPE-NEXT: store i64 1, ptr %3, align 8 +// ESCAPE-NEXT: store i64 2, ptr %4, align 8 +// ESCAPE-NEXT: store i64 3, ptr %6, align 8 +// ESCAPE-NEXT: store i64 4, ptr %7, align 8 +// ESCAPE-NEXT: store i64 5, ptr %9, align 8 +// ESCAPE-NEXT: store i64 6, ptr %10, align 8 +// ESCAPE-NEXT: %11 = load [3 x %main.point], ptr %1, align 8 +// ESCAPE-NEXT: %12 = getelementptr inbounds %main.point, ptr %1, i64 2 +// ESCAPE-NEXT: %13 = load %main.point, ptr %12, align 8 +// ESCAPE-NEXT: store %main.point %13, ptr %0, align 8 +// ESCAPE-NEXT: %14 = getelementptr inbounds %main.point, ptr %0, i32 0, i32 0 +// ESCAPE-NEXT: %15 = load i64, ptr %14, align 8 +// ESCAPE-NEXT: %16 = getelementptr inbounds %main.point, ptr %0, i32 0, i32 1 +// ESCAPE-NEXT: %17 = load i64, ptr %16, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %15) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %17) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %18 = alloca [2 x i64], align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %18, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %19 = alloca [2 x [2 x i64]], align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %19, i8 0, i64 32, i1 false) +// ESCAPE-NEXT: %20 = getelementptr inbounds [2 x i64], ptr %19, i64 0 +// ESCAPE-NEXT: %21 = icmp eq ptr %20, null +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %21) +// ESCAPE-NEXT: %22 = getelementptr inbounds i64, ptr %20, i64 0 +// ESCAPE-NEXT: %23 = icmp eq ptr %20, null +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %23) +// ESCAPE-NEXT: %24 = getelementptr inbounds i64, ptr %20, i64 1 +// ESCAPE-NEXT: %25 = getelementptr inbounds [2 x i64], ptr %19, i64 1 +// ESCAPE-NEXT: %26 = icmp eq ptr %25, null +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %26) +// ESCAPE-NEXT: %27 = getelementptr inbounds i64, ptr %25, i64 0 +// ESCAPE-NEXT: %28 = icmp eq ptr %25, null +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %28) +// ESCAPE-NEXT: %29 = getelementptr inbounds i64, ptr %25, i64 1 +// ESCAPE-NEXT: store i64 1, ptr %22, align 8 +// ESCAPE-NEXT: store i64 2, ptr %24, align 8 +// ESCAPE-NEXT: store i64 3, ptr %27, align 8 +// ESCAPE-NEXT: store i64 4, ptr %29, align 8 +// ESCAPE-NEXT: %30 = load [2 x [2 x i64]], ptr %19, align 8 +// ESCAPE-NEXT: %31 = getelementptr inbounds [2 x i64], ptr %19, i64 1 +// ESCAPE-NEXT: %32 = load [2 x i64], ptr %31, align 8 +// ESCAPE-NEXT: store [2 x i64] %32, ptr %18, align 8 +// ESCAPE-NEXT: %33 = getelementptr inbounds i64, ptr %18, i64 0 +// ESCAPE-NEXT: %34 = load i64, ptr %33, align 8 +// ESCAPE-NEXT: %35 = getelementptr inbounds i64, ptr %18, i64 1 +// ESCAPE-NEXT: %36 = load i64, ptr %35, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %34) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %36) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %37 = alloca [5 x i64], align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %37, i8 0, i64 40, i1 false) +// ESCAPE-NEXT: %38 = getelementptr inbounds i64, ptr %37, i64 0 +// ESCAPE-NEXT: %39 = getelementptr inbounds i64, ptr %37, i64 1 +// ESCAPE-NEXT: %40 = getelementptr inbounds i64, ptr %37, i64 2 +// ESCAPE-NEXT: %41 = getelementptr inbounds i64, ptr %37, i64 3 +// ESCAPE-NEXT: %42 = getelementptr inbounds i64, ptr %37, i64 4 +// ESCAPE-NEXT: store i64 1, ptr %38, align 8 +// ESCAPE-NEXT: store i64 2, ptr %39, align 8 +// ESCAPE-NEXT: store i64 3, ptr %40, align 8 +// ESCAPE-NEXT: store i64 4, ptr %41, align 8 +// ESCAPE-NEXT: store i64 5, ptr %42, align 8 +// ESCAPE-NEXT: %43 = load [5 x i64], ptr %37, align 8 +// ESCAPE-NEXT: %44 = getelementptr inbounds i64, ptr %37, i64 2 +// ESCAPE-NEXT: %45 = load i64, ptr %44, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %45) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %46 = load i8, ptr getelementptr inbounds (i8, ptr @0, i64 2), align 1 +// ESCAPE-NEXT: %47 = zext i8 %46 to i64 +// ESCAPE-NEXT: %48 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/runtime/internal/runtime.StringFromUint64"(i64 %47) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %48) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %49 = load i8, ptr getelementptr inbounds (i8, ptr @0, i64 1), align 1 +// ESCAPE-NEXT: %50 = zext i8 %49 to i64 +// ESCAPE-NEXT: %51 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/runtime/internal/runtime.StringFromUint64"(i64 %50) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %51) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %.stack = alloca i8, i64 16, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 16, i1 false) +// ESCAPE-NEXT: %52 = getelementptr inbounds i64, ptr %.stack, i64 0 +// ESCAPE-NEXT: %53 = getelementptr inbounds i64, ptr %.stack, i64 1 +// ESCAPE-NEXT: store i64 1, ptr %52, align 8 +// ESCAPE-NEXT: store i64 2, ptr %53, align 8 +// ESCAPE-NEXT: %54 = getelementptr inbounds i64, ptr %.stack, i64 1 +// ESCAPE-NEXT: %55 = load i64, ptr %54, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %55) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %56 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) +// ESCAPE-NEXT: %57 = getelementptr inbounds i64, ptr %56, i64 0 +// ESCAPE-NEXT: store i64 1, ptr %57, align 8 +// ESCAPE-NEXT: %58 = getelementptr inbounds i64, ptr %56, i64 1 +// ESCAPE-NEXT: store i64 2, ptr %58, align 8 +// ESCAPE-NEXT: %59 = getelementptr inbounds i64, ptr %56, i64 2 +// ESCAPE-NEXT: store i64 3, ptr %59, align 8 +// ESCAPE-NEXT: %60 = getelementptr inbounds i64, ptr %56, i64 3 +// ESCAPE-NEXT: store i64 4, ptr %60, align 8 +// ESCAPE-NEXT: %61 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %56, 0 +// ESCAPE-NEXT: %62 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %61, i64 4, 1 +// ESCAPE-NEXT: %63 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %62, i64 4, 2 +// ESCAPE-NEXT: %64 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %63, 0 +// ESCAPE-NEXT: %65 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %63, 1 +// ESCAPE-NEXT: %66 = icmp uge i64 1, %65 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.CheckIndexRange"(i1 %66, i64 1, i1 true, i64 %65) +// ESCAPE-NEXT: %67 = getelementptr inbounds i64, ptr %64, i64 1 +// ESCAPE-NEXT: %68 = load i64, ptr %67, align 8 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %68) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 0) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } + func main() { a := [...]point{{1, 2}, {3, 4}, {5, 6}}[2] println(a.x, a.y) diff --git a/cl/_testrt/len/in.go b/cl/_testrt/len/in.go index 3bd00461b1..7bd5d2cf2b 100644 --- a/cl/_testrt/len/in.go +++ b/cl/_testrt/len/in.go @@ -98,6 +98,98 @@ type data struct { // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 56, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 56, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %1 = load %"{{.*}}/runtime/internal/runtime.String", ptr %0, align 8 +// ESCAPE-NEXT: %2 = extractvalue %"{{.*}}/runtime/internal/runtime.String" %1, 1 +// ESCAPE-NEXT: %3 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: %5 = call i64 @"{{.*}}/runtime/internal/runtime.ChanLen"(ptr %4) +// ESCAPE-NEXT: %6 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 2 +// ESCAPE-NEXT: %7 = load ptr, ptr %6, align 8 +// ESCAPE-NEXT: %8 = call i64 @"{{.*}}/runtime/internal/runtime.MapLen"(ptr %7) +// ESCAPE-NEXT: %9 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 3 +// ESCAPE-NEXT: %10 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %9, align 8 +// ESCAPE-NEXT: %11 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %10, 1 +// ESCAPE-NEXT: %12 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: %13 = load ptr, ptr %12, align 8 +// ESCAPE-NEXT: %14 = call i64 @"{{.*}}/runtime/internal/runtime.ChanCap"(ptr %13) +// ESCAPE-NEXT: %15 = getelementptr inbounds %main.data, ptr %.stack, i32 0, i32 3 +// ESCAPE-NEXT: %16 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %15, align 8 +// ESCAPE-NEXT: %17 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %16, 2 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %2) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %5) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %8) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %11) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %14) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %17) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: %.stack1 = alloca i8, i64 56, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack1, i8 0, i64 56, i1 false) +// ESCAPE-NEXT: %18 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 0 +// ESCAPE-NEXT: %19 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 1 +// ESCAPE-NEXT: %20 = call ptr @"{{.*}}/runtime/internal/runtime.NewChan"(i64 8, i64 2) +// ESCAPE-NEXT: %21 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 2 +// ESCAPE-NEXT: %22 = call ptr @"{{.*}}/runtime/internal/runtime.MakeMap"(ptr @"map[_llgo_int]_llgo_string", i64 1) +// ESCAPE-NEXT: %23 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) +// ESCAPE-NEXT: store i64 1, ptr %23, align 8 +// ESCAPE-NEXT: %24 = call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_int]_llgo_string", ptr %22, ptr %23) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @16, i64 5 }, ptr %24, align 8 +// ESCAPE-NEXT: %25 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 3 +// ESCAPE-NEXT: %26 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// ESCAPE-NEXT: %27 = getelementptr inbounds i64, ptr %26, i64 0 +// ESCAPE-NEXT: store i64 1, ptr %27, align 8 +// ESCAPE-NEXT: %28 = getelementptr inbounds i64, ptr %26, i64 1 +// ESCAPE-NEXT: store i64 2, ptr %28, align 8 +// ESCAPE-NEXT: %29 = getelementptr inbounds i64, ptr %26, i64 2 +// ESCAPE-NEXT: store i64 3, ptr %29, align 8 +// ESCAPE-NEXT: %30 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" undef, ptr %26, 0 +// ESCAPE-NEXT: %31 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %30, i64 3, 1 +// ESCAPE-NEXT: %32 = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %31, i64 3, 2 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @16, i64 5 }, ptr %18, align 8 +// ESCAPE-NEXT: store ptr %20, ptr %19, align 8 +// ESCAPE-NEXT: store ptr %22, ptr %21, align 8 +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.Slice" %32, ptr %25, align 8 +// ESCAPE-NEXT: %33 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 0 +// ESCAPE-NEXT: %34 = load %"{{.*}}/runtime/internal/runtime.String", ptr %33, align 8 +// ESCAPE-NEXT: %35 = extractvalue %"{{.*}}/runtime/internal/runtime.String" %34, 1 +// ESCAPE-NEXT: %36 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 1 +// ESCAPE-NEXT: %37 = load ptr, ptr %36, align 8 +// ESCAPE-NEXT: %38 = call i64 @"{{.*}}/runtime/internal/runtime.ChanLen"(ptr %37) +// ESCAPE-NEXT: %39 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 2 +// ESCAPE-NEXT: %40 = load ptr, ptr %39, align 8 +// ESCAPE-NEXT: %41 = call i64 @"{{.*}}/runtime/internal/runtime.MapLen"(ptr %40) +// ESCAPE-NEXT: %42 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 3 +// ESCAPE-NEXT: %43 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %42, align 8 +// ESCAPE-NEXT: %44 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %43, 1 +// ESCAPE-NEXT: %45 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 1 +// ESCAPE-NEXT: %46 = load ptr, ptr %45, align 8 +// ESCAPE-NEXT: %47 = call i64 @"{{.*}}/runtime/internal/runtime.ChanCap"(ptr %46) +// ESCAPE-NEXT: %48 = getelementptr inbounds %main.data, ptr %.stack1, i32 0, i32 3 +// ESCAPE-NEXT: %49 = load %"{{.*}}/runtime/internal/runtime.Slice", ptr %48, align 8 +// ESCAPE-NEXT: %50 = extractvalue %"{{.*}}/runtime/internal/runtime.Slice" %49, 2 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %35) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %38) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %41) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %44) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %47) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 %50) +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } func main() { d := &data{} println(len(d.s), len(d.c), len(d.m), len(d.a), cap(d.c), cap(d.a)) diff --git a/cl/_testrt/tpunsafe/in.go b/cl/_testrt/tpunsafe/in.go index a19bd33d6e..7a5a015410 100644 --- a/cl/_testrt/tpunsafe/in.go +++ b/cl/_testrt/tpunsafe/in.go @@ -24,6 +24,16 @@ type M[T any] struct { // CHECK-NEXT: call void @"main.(*M[int64]).check"(ptr %1, i64 8, i64 16, i64 8) // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 12, align 1 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 12, i1 false) +// ESCAPE-NEXT: call void @"main.(*M[bool]).check"(ptr %.stack, i64 1, i64 8, i64 1) +// ESCAPE-NEXT: %.stack1 = alloca i8, i64 32, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack1, i8 0, i64 32, i1 false) +// ESCAPE-NEXT: call void @"main.(*M[int64]).check"(ptr %.stack1, i64 8, i64 16, i64 8) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } func main() { m1 := M[bool]{} m1.check(1, 8, 1) diff --git a/cl/_testrt/typalias/in.go b/cl/_testrt/typalias/in.go index f35416ed36..0373fa10c5 100644 --- a/cl/_testrt/typalias/in.go +++ b/cl/_testrt/typalias/in.go @@ -46,6 +46,17 @@ func Print(p *Foo) { // CHECK-NEXT: call void @main.Print(ptr %0) // CHECK-NEXT: ret void // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 4 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: %0 = getelementptr inbounds { i32, i1 }, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %1 = getelementptr inbounds { i32, i1 }, ptr %.stack, i32 0, i32 1 +// ESCAPE-NEXT: store i32 100, ptr %0, align 4 +// ESCAPE-NEXT: store i1 true, ptr %1, align 1 +// ESCAPE-NEXT: call void @main.Print(ptr %.stack) +// ESCAPE-NEXT: ret void +// ESCAPE-NEXT: } func main() { foo := &Foo{100, true} Print(foo) diff --git a/cl/_testrt/vamethod/in.go b/cl/_testrt/vamethod/in.go index a7ef707b26..4ef7e50c11 100644 --- a/cl/_testrt/vamethod/in.go +++ b/cl/_testrt/vamethod/in.go @@ -117,6 +117,70 @@ type IFmt interface { // CHECK-NEXT: %38 = extractvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } %36, 1 // CHECK-NEXT: br i1 %38, label %_llgo_2, label %_llgo_1 // CHECK-NEXT: } +// ESCAPE-LABEL: define void @main.main(){{.*}} { +// ESCAPE-NEXT: _llgo_0: +// ESCAPE-NEXT: %.stack = alloca i8, i64 8, align 8 +// ESCAPE-NEXT: call void @llvm.memset.p0.i64(ptr %.stack, i8 0, i64 8, i1 false) +// ESCAPE-NEXT: call void @"main.(*CFmt).SetFormat"(ptr %.stack, ptr @0) +// ESCAPE-NEXT: %0 = getelementptr inbounds %main.CFmt, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %1 = load ptr, ptr %0, align 8 +// ESCAPE-NEXT: %2 = call i32 (ptr, ...) @printf(ptr %1, ptr @1, i64 100) +// ESCAPE-NEXT: call void @"main.(*CFmt).SetFormat"(ptr %.stack, ptr @2) +// ESCAPE-NEXT: %3 = getelementptr inbounds %main.CFmt, ptr %.stack, i32 0, i32 0 +// ESCAPE-NEXT: %4 = load ptr, ptr %3, align 8 +// ESCAPE-NEXT: %5 = call i32 (ptr, ...) @printf(ptr %4, i64 200, ptr @3) +// ESCAPE-NEXT: %6 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 8) +// ESCAPE-NEXT: %7 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @"*_llgo_main.CFmt", ptr undef }, ptr %6, 1 +// ESCAPE-NEXT: %8 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %7, 0 +// ESCAPE-NEXT: %9 = call i1 @"{{.*}}/runtime/internal/runtime.Implements"(ptr @_llgo_main.IFmt, ptr %8) +// ESCAPE-NEXT: br i1 %9, label %_llgo_3, label %_llgo_4 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_1: ; preds = %_llgo_5 +// ESCAPE-NEXT: %10 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 16) +// ESCAPE-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @20, i64 5 }, ptr %10, align 8 +// ESCAPE-NEXT: %11 = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_string, ptr undef }, ptr %10, 1 +// ESCAPE-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %11) +// ESCAPE-NEXT: unreachable +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_2: ; preds = %_llgo_5 +// ESCAPE-NEXT: %12 = call ptr @"{{.*}}/runtime/internal/runtime.IfacePtrData"(%"{{.*}}/runtime/internal/runtime.iface" %36) +// ESCAPE-NEXT: %13 = extractvalue %"{{.*}}/runtime/internal/runtime.iface" %36, 0 +// ESCAPE-NEXT: %14 = getelementptr ptr, ptr %13, i64 4 +// ESCAPE-NEXT: %15 = load ptr, ptr %14, align 8 +// ESCAPE-NEXT: %16 = insertvalue { ptr, ptr } undef, ptr %15, 0 +// ESCAPE-NEXT: %17 = insertvalue { ptr, ptr } %16, ptr %12, 1 +// ESCAPE-NEXT: %18 = extractvalue { ptr, ptr } %17, 1 +// ESCAPE-NEXT: %19 = extractvalue { ptr, ptr } %17, 0 +// ESCAPE-NEXT: call void %19(ptr %18, ptr @18) +// ESCAPE-NEXT: %20 = call ptr @"{{.*}}/runtime/internal/runtime.IfacePtrData"(%"{{.*}}/runtime/internal/runtime.iface" %36) +// ESCAPE-NEXT: %21 = extractvalue %"{{.*}}/runtime/internal/runtime.iface" %36, 0 +// ESCAPE-NEXT: %22 = getelementptr ptr, ptr %21, i64 3 +// ESCAPE-NEXT: %23 = load ptr, ptr %22, align 8 +// ESCAPE-NEXT: %24 = insertvalue { ptr, ptr } undef, ptr %23, 0 +// ESCAPE-NEXT: %25 = insertvalue { ptr, ptr } %24, ptr %20, 1 +// ESCAPE-NEXT: %26 = extractvalue { ptr, ptr } %25, 1 +// ESCAPE-NEXT: %27 = extractvalue { ptr, ptr } %25, 0 +// ESCAPE-NEXT: %28 = call i32 (ptr, ...) %27(ptr %26, ptr @19, i64 100, i64 200) +// ESCAPE-NEXT: ret void +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_3: ; preds = %_llgo_0 +// ESCAPE-NEXT: %29 = extractvalue %"{{.*}}/runtime/internal/runtime.eface" %7, 1 +// ESCAPE-NEXT: %30 = call ptr @"{{.*}}/runtime/internal/runtime.NewItab"(ptr @"_llgo_iface$a85zs5wWQQoPIERm_en8plssh4spdIeeXZPC-E0TDh0", ptr %8) +// ESCAPE-NEXT: %31 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" undef, ptr %30, 0 +// ESCAPE-NEXT: %32 = insertvalue %"{{.*}}/runtime/internal/runtime.iface" %31, ptr %29, 1 +// ESCAPE-NEXT: %33 = insertvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } undef, %"{{.*}}/runtime/internal/runtime.iface" %32, 0 +// ESCAPE-NEXT: %34 = insertvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } %33, i1 true, 1 +// ESCAPE-NEXT: br label %_llgo_5 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_4: ; preds = %_llgo_0 +// ESCAPE-NEXT: br label %_llgo_5 +// ESCAPE-EMPTY: +// ESCAPE-NEXT: _llgo_5: ; preds = %_llgo_4, %_llgo_3 +// ESCAPE-NEXT: %35 = phi { %"{{.*}}/runtime/internal/runtime.iface", i1 } [ %34, %_llgo_3 ], [ zeroinitializer, %_llgo_4 ] +// ESCAPE-NEXT: %36 = extractvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } %35, 0 +// ESCAPE-NEXT: %37 = extractvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } %35, 1 +// ESCAPE-NEXT: br i1 %37, label %_llgo_2, label %_llgo_1 +// ESCAPE-NEXT: } func main() { cfmt := &CFmt{} cfmt.SetFormat(c.Str("%s (%d)\n")) diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 4a1fc60bef..849e6992e7 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -261,6 +261,7 @@ func testFrom(t *testing.T, pkgDir, sel string) { _ = os.WriteFile(pkgDir+"/result.txt", []byte(v), 0644) t.Fatal(err) } + checkEscapeIR(t, pkgDir, spec) return } if test.Diff(t, pkgDir+"/result.txt", []byte(v), []byte(spec.Text)) { @@ -351,6 +352,19 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption _ = os.WriteFile(filepath.Join(pkgDir, "result.txt"), []byte(*capturedIR), 0644) t.Fatal(err) } + checkEscapeIR(t, pkgDir, irSpec) +} + +func checkEscapeIR(t *testing.T, pkgDir string, spec littest.Spec) { + t.Helper() + var ir string + withFuncInfoDisabled(func() { + ir = llgen.GenFromWithEscape(pkgDir) + }) + if err := littest.Check(spec, ir, "ESCAPE"); err != nil { + _ = os.WriteFile(filepath.Join(pkgDir, "escape-result.txt"), []byte(ir), 0644) + t.Fatal(err) + } } func assertExpectedMeta(t *testing.T, pkgDir, relPkg string, capturedMeta *string) { diff --git a/cmd/internal/compile/compile.go b/cmd/internal/compile/compile.go index e9b4b12833..c7c10b24db 100644 --- a/cmd/internal/compile/compile.go +++ b/cmd/internal/compile/compile.go @@ -163,6 +163,7 @@ func runCmd(_ *base.Command, args []string) { conf.NoErrorColumn = opts.noColumns.value != 0 conf.AllowNoBody = !opts.complete conf.DisableBoundsChecks = opts.noBounds.value != 0 + conf.EscapeDiagnostics = opts.showOpt.value != 0 var loaderCompilerFlags []string if opts.allErrors.value != 0 { loaderCompilerFlags = append(loaderCompilerFlags, "-e") @@ -183,6 +184,11 @@ func runCmd(_ *base.Command, args []string) { conf.OptLevel = optlevel.O0 } pkgs, err := build.Do(files, conf) + if len(pkgs) != 0 { + for _, message := range pkgs[0].EscapeDiagnosticMessages { + fmt.Fprintln(os.Stderr, message) + } + } if len(pkgs) != 0 && pkgs[0].LPkg != nil { pkgs[0].LPkg.Prog.Dispose() } @@ -200,7 +206,6 @@ func (opts *options) unsupported() []string { } } appendFlag(opts.dynlink, "-dynlink") - appendFlag(opts.showOpt.value != 0, "-m") appendFlag(opts.live, "-live") appendFlag(opts.race, "-race") appendFlag(opts.smallFrames, "-smallframes") diff --git a/cmd/internal/compile/compile_test.go b/cmd/internal/compile/compile_test.go index 5b2988aa22..501a1cadc8 100644 --- a/cmd/internal/compile/compile_test.go +++ b/cmd/internal/compile/compile_test.go @@ -19,6 +19,7 @@ func TestGoCompilerFlagNamesAndTypes(t *testing.T) { "-C", "-e", "-l=4", + "-m=2", "-lang=go1.17", "-d=panic,ssa/check/on", "-p=p", @@ -28,7 +29,7 @@ func TestGoCompilerFlagNamesAndTypes(t *testing.T) { if err != nil { t.Fatal(err) } - if opts.noBounds.value != 1 || opts.concurrency != 2 || opts.noColumns.value != 1 || opts.allErrors.value != 1 || opts.noInline.value != 4 { + if opts.noBounds.value != 1 || opts.concurrency != 2 || opts.noColumns.value != 1 || opts.allErrors.value != 1 || opts.noInline.value != 4 || opts.showOpt.value != 2 { t.Fatalf("parsed flags: %+v", opts) } if opts.lang != "go1.17" || opts.pkgPath != "p" || opts.importCfg != "importcfg" { @@ -105,7 +106,7 @@ func TestUnsupportedCompilerFlags(t *testing.T) { writeBar: countFlag{set: true}, debug: stringListFlag{"panic,libfuzzer", "ssa/check/on,ssa/check/seed=1,wb"}, } - want := []string{"-dynlink", "-m", "-live", "-race", "-smallframes", "-+", "-wb", "-d=libfuzzer", "-d=wb"} + want := []string{"-dynlink", "-live", "-race", "-smallframes", "-+", "-wb", "-d=libfuzzer", "-d=wb"} if got := opts.unsupported(); !reflect.DeepEqual(got, want) { t.Fatalf("unsupported=%v, want %v", got, want) } @@ -182,6 +183,45 @@ func TestRunCmdBuildsAndReportsErrors(t *testing.T) { } } +func TestRunCmdEscapeDiagnostics(t *testing.T) { + dir := t.TempDir() + source := dir + "/escape.go" + if err := os.WriteFile(source, []byte(`package escape +var sink *int +func noescape(p *int) { _ = *p } +func leak(p *int) { sink = p } +func content(p **int) { sink = *p } +func result(p *int) *int { return p } +func through(p *int) *int { return result(p) } +func discard(p *int) { _ = result(p) } +func mutate(p *int) { *p = 1 } +`), 0o644); err != nil { + t.Fatal(err) + } + + _, stderr, code := runCompileCommand(t, []string{"-C", "-m", "-l", source}) + if code != 0 { + t.Fatalf("-m compile exit code = %d, stderr=%q", code, stderr) + } + for _, want := range []string{ + // TODO: Re-enable these diagnostics after the summary traversal handles + // compiler-generated `icmp eq ptr, null` checks without recording a heap + // flow. HeapToStack cannot generally discard the comparison because the + // current AllocZ/AllocU runtime contracts do not both guarantee non-nil. + // "escape.go:3: p does not escape", + "escape.go:4: leaking param: p", + // "escape.go:5: leaking param content: p", + "escape.go:6: leaking param: p to result ~r0 level=0", + "escape.go:7: leaking param: p to result ~r0 level=0", + "escape.go:8: p does not escape", + "escape.go:9: p does not escape", + } { + if !strings.Contains(stderr, want) { + t.Errorf("-m stderr missing %q:\n%s", want, stderr) + } + } +} + func runCompileCommand(t *testing.T, args []string) (stdout, stderr string, exitCode int) { t.Helper() outFile, err := os.CreateTemp(t.TempDir(), "stdout") diff --git a/internal/build/build.go b/internal/build/build.go index 9607c9d00a..f010bfaf60 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -38,6 +38,7 @@ import ( "sync/atomic" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "github.com/goplus/llgo/cl" llabi "github.com/goplus/llgo/internal/abi" @@ -48,6 +49,7 @@ import ( "github.com/goplus/llgo/internal/dcepass" "github.com/goplus/llgo/internal/deadcode" "github.com/goplus/llgo/internal/env" + llescape "github.com/goplus/llgo/internal/escape" "github.com/goplus/llgo/internal/firmware" "github.com/goplus/llgo/internal/flash" "github.com/goplus/llgo/internal/goembed" @@ -188,6 +190,10 @@ type Config struct { // DisableBoundsChecks disables index, slice, and slice-to-array conversion // bounds checks while retaining required integer conversions and nil checks. DisableBoundsChecks bool + // DisableEscapeAnalysis skips the heap-to-stack transform. + DisableEscapeAnalysis bool + // EscapeDiagnostics requests source-positioned parameter escape summaries. + EscapeDiagnostics bool // PthreadStackSize sets a custom stack size, in bytes, for pthread-backed // goroutines. A zero value keeps the platform pthread default. @@ -1833,6 +1839,12 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { } ctx.cTransformer.SetSkipFuncs(cabiSkipFuncsForPlan9Asm(ctx, pkgPath, ret.Module())) + if !ctx.buildConf.DisableEscapeAnalysis { + result := llescape.TransformModule(ret.Module(), ctx.buildConf.EscapeDiagnostics) + if ctx.buildConf.EscapeDiagnostics { + aPkg.EscapeDiagnosticMessages = escapeDiagnosticMessages(aPkg, result, ctx.buildConf.NoErrorColumn) + } + } llabi.LowerLargeAggregates(ctx.prog.TargetData(), ret.Module()) ctx.cTransformer.TransformModule(ret.Path(), ret.Module()) ctx.cTransformer.SetSkipFuncs(nil) @@ -1908,6 +1920,59 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { return nil } +func escapeDiagnosticMessages(pkg *aPackage, result llescape.Result, noColumn bool) []string { + functions := make(map[string]*ssa.Function) + for fn := range ssautil.AllFunctions(pkg.SSA.Prog) { + if fn == nil || fn.Pkg != pkg.SSA || fn.Parent() != nil || fn.Synthetic != "" || fn.Origin() != nil || fn.Signature.Recv() != nil { + continue + } + functions[fn.String()] = fn + } + + var messages []string + for _, summary := range result.Parameters { + fn := functions[summary.Function] + if fn == nil || summary.Parameter >= len(fn.Params) { + continue + } + param := fn.Params[summary.Parameter] + name := param.Name() + pos := pkg.Fset.Position(param.Pos()) + if name == "" || !pos.IsValid() { + continue + } + + var reports []string + if summary.HeapLevel == 0 { + reports = append(reports, "leaking param: "+name) + } else if summary.HeapLevel > 0 { + reports = append(reports, "leaking param content: "+name) + } + for _, leak := range summary.Results { + if leak.Result >= fn.Signature.Results().Len() { + continue + } + resultName := fn.Signature.Results().At(leak.Result).Name() + if resultName == "" { + resultName = fmt.Sprintf("~r%d", leak.Result) + } + reports = append(reports, fmt.Sprintf("leaking param: %s to result %s level=%d", name, resultName, leak.Level)) + } + if len(reports) == 0 { + reports = append(reports, name+" does not escape") + } + + position := fmt.Sprintf("%s:%d", pos.Filename, pos.Line) + if !noColumn { + position += fmt.Sprintf(":%d", pos.Column) + } + for _, report := range reports { + messages = append(messages, position+": "+report) + } + } + return messages +} + func printCompiledPackage(conf *Config, pkg *aPackage) { if conf.PrintPackages && !pkg.CacheHit { fmt.Fprintln(os.Stderr, pkg.PkgPath) @@ -2169,6 +2234,8 @@ type aPackage struct { Meta *meta.PackageMeta rewriteVars map[string]string + EscapeDiagnosticMessages []string + // Cache related fields Fingerprint string // fingerprint digest Manifest string // manifest text content diff --git a/internal/build/build_test.go b/internal/build/build_test.go index 5d67a571b2..178fd25c6b 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -85,6 +85,22 @@ func TestConfigCloneDoesNotAliasInput(t *testing.T) { } } +func TestDisableEscapeAnalysis(t *testing.T) { + conf := NewDefaultConf(ModeGen) + conf.DisableEscapeAnalysis = true + pkgs, err := Do([]string{"./testdata/escape_phase1"}, conf) + if err != nil { + t.Fatal(err) + } + if len(pkgs) != 1 || pkgs[0].LPkg == nil { + t.Fatalf("generated packages = %#v", pkgs) + } + defer pkgs[0].LPkg.Prog.Dispose() + if ir := pkgs[0].LPkg.String(); !strings.Contains(ir, `call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"`) { + t.Fatalf("disabled escape analysis changed heap allocation:\n%s", ir) + } +} + func TestResolveBuildConfigDefaultsAndValidation(t *testing.T) { resolved, err := resolveBuildConfig(&Config{ BuildMode: BuildModeCArchive, diff --git a/internal/build/testdata/escape_phase1/escape.go b/internal/build/testdata/escape_phase1/escape.go new file mode 100644 index 0000000000..20b124b181 --- /dev/null +++ b/internal/build/testdata/escape_phase1/escape.go @@ -0,0 +1,10 @@ +package escapephase1 + +func read(*int) int { + return 7 +} + +func Local() int { + p := new(int) + return read(p) +} diff --git a/internal/escape/escape.go b/internal/escape/escape.go new file mode 100644 index 0000000000..dd855b5143 --- /dev/null +++ b/internal/escape/escape.go @@ -0,0 +1,821 @@ +// Package escape implements LLGo's LLVM-IR escape analysis and heap-to-stack +// transform. +package escape + +import ( + "math" + "sort" + "strings" + + llabi "github.com/goplus/llgo/internal/abi" + "github.com/xgo-dev/llvm" +) + +const ( + runtimePrefix = "github.com/goplus/llgo/runtime/internal/runtime." + runtimeAllocZ = runtimePrefix + "AllocZ" + runtimeAllocU = runtimePrefix + "AllocU" + + // github.com/xgo-dev/llvm does not expose these LLVM 19 C opcode names. + opcodeAtomicCmpXchg = llvm.Opcode(56) + opcodeAtomicRMW = llvm.Opcode(57) + opcodeAddrSpaceCast = llvm.Opcode(60) + opcodeFreeze = llvm.Opcode(68) +) + +type paramKey struct { + fn llvm.Value + param int +} + +type locationEdgeKind uint8 + +const ( + locationFlow locationEdgeKind = iota + locationDeref + locationHeap + locationMutator + locationCallee +) + +type location struct { + value llvm.Value + result int +} + +type locationEdge struct { + to location + kind locationEdgeKind +} + +// locationGraph records diagnostic pointer flows but is never queried by the +// optimization analyses. +type locationGraph struct { + edges map[location]map[locationEdge]struct{} +} + +func newLocationGraph() locationGraph { + return locationGraph{edges: make(map[location]map[locationEdge]struct{})} +} + +func valueLocation(value llvm.Value) location { + return location{value: value, result: -1} +} + +func resultLocation(fn llvm.Value, result int) location { + return location{value: fn, result: result} +} + +func (g *locationGraph) add(from location, edge locationEdge) { + edges := g.edges[from] + if edges == nil { + edges = make(map[locationEdge]struct{}) + g.edges[from] = edges + } + edges[edge] = struct{}{} +} + +func (g *locationGraph) addFlow(from, to llvm.Value) { + g.add(valueLocation(from), locationEdge{to: valueLocation(to), kind: locationFlow}) +} + +func (g *locationGraph) addDeref(from, to llvm.Value) { + g.add(valueLocation(from), locationEdge{to: valueLocation(to), kind: locationDeref}) +} + +func (g *locationGraph) addHeap(from llvm.Value) { + g.add(valueLocation(from), locationEdge{kind: locationHeap}) +} + +func (g *locationGraph) addMutator(from llvm.Value) { + g.add(valueLocation(from), locationEdge{kind: locationMutator}) +} + +func (g *locationGraph) addCallee(from llvm.Value) { + g.add(valueLocation(from), locationEdge{kind: locationCallee}) +} + +func (g *locationGraph) addResult(from, fn llvm.Value, result int) { + g.add(valueLocation(from), locationEdge{to: resultLocation(fn, result), kind: locationFlow}) +} + +func (g *locationGraph) addCallResult(fn llvm.Value, result int, call llvm.Value) { + g.add(resultLocation(fn, result), locationEdge{to: valueLocation(call), kind: locationFlow}) +} + +const ( + leakHeap = iota + leakMutator + leakCallee + leakResult0 + numEscResults = 5 +) + +type leaks [leakResult0 + numEscResults]uint8 + +func (l leaks) get(index int) int { + return int(l[index]) - 1 +} + +func (l *leaks) add(index, level int) { + if old := l.get(index); old >= 0 && old <= level { + return + } + value := level + 1 + if value > math.MaxUint8 { + value = math.MaxUint8 + } + l[index] = uint8(value) +} + +func (l *leaks) optimize() { + heap := l.get(leakHeap) + if heap < 0 { + return + } + for index := leakMutator; index < len(l); index++ { + if l.get(index) >= heap { + l[index] = 0 + } + } +} + +func (g *locationGraph) summarize(root paramKey) leaks { + var summary leaks + start := valueLocation(root.fn.Param(root.param)) + best := map[location]int{start: 0} + worklist := []location{start} + + for len(worklist) != 0 { + current := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + level := best[current] + for edge := range g.edges[current] { + switch edge.kind { + case locationFlow, locationDeref: + nextLevel := level + if edge.kind == locationDeref { + nextLevel++ + } + if edge.to.result >= 0 && edge.to.value == root.fn { + if edge.to.result < numEscResults { + summary.add(leakResult0+edge.to.result, nextLevel) + } + continue + } + if old, ok := best[edge.to]; !ok || nextLevel < old { + best[edge.to] = nextLevel + worklist = append(worklist, edge.to) + } + case locationHeap: + summary.add(leakHeap, level) + case locationMutator: + summary.add(leakMutator, level) + case locationCallee: + summary.add(leakCallee, level) + } + } + } + summary.optimize() + return summary +} + +type analyzer struct { + noCapture map[paramKey]bool + noCaptureKeys []paramKey + locations *locationGraph + copies *copyAnalysis +} + +func newAnalyzer(mod llvm.Module, diagnostics bool) *analyzer { + a := &analyzer{ + noCapture: make(map[paramKey]bool), + copies: newCopyAnalysis(mod), + } + if diagnostics { + locations := newLocationGraph() + a.locations = &locations + } + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if fn.IsDeclaration() || isRuntimeFunction(fn) { + continue + } + for index, param := range fn.Params() { + if !isPointer(param.Type()) { + continue + } + key := paramKey{fn: fn, param: index} + a.noCapture[key] = true + a.noCaptureKeys = append(a.noCaptureKeys, key) + } + } + return a +} + +func isRuntimeFunction(fn llvm.Value) bool { + return strings.HasPrefix(fn.Name(), runtimePrefix) +} + +func isPointer(typ llvm.Type) bool { + return typ.TypeKind() == llvm.PointerTypeKind +} + +func (a *analyzer) solveNoCapture() { + for { + changed := false + for _, key := range a.noCaptureKeys { + if a.noCapture[key] && !a.parameterNoCapture(key) { + a.noCapture[key] = false + changed = true + } + } + if !changed { + return + } + } +} + +type useState struct { + user llvm.Value + operand int +} + +type walkResult struct { + escaped bool + alignment int +} + +func (a *analyzer) addUses(worklist *[]useState, value llvm.Value) { + for use := value.FirstUse(); !use.IsNil(); use = use.NextUse() { + user := use.User() + for operand := 0; operand < user.OperandsCount(); operand++ { + if user.Operand(operand) == value { + state := useState{user: user, operand: operand} + *worklist = append(*worklist, state) + } + } + } +} + +type useAction uint8 + +const ( + useSafe useAction = iota + useFollow + useCapture +) + +func (a *analyzer) checkForAllUses(root llvm.Value, classify func(useState) useAction) bool { + worklist := make([]useState, 0, 8) + a.addUses(&worklist, root) + visited := make(map[useState]struct{}) + valid := true + + for len(worklist) != 0 { + state := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + if _, ok := visited[state]; ok { + continue + } + visited[state] = struct{}{} + + if state.user.InstructionOpcode() == llvm.Store && state.operand == 0 { + copies, ok := a.copies.getPotentialCopiesOfStoredValue(state.user) + if ok { + for _, copy := range copies { + a.addUses(&worklist, copy) + } + continue + } + } + + switch classify(state) { + case useFollow: + a.addUses(&worklist, state.user) + case useCapture: + valid = false + } + } + return valid +} + +func (a *analyzer) parameterNoCapture(key paramKey) bool { + return a.checkForAllUses(key.fn.Param(key.param), a.classifyNoCaptureUse) +} + +func callCalleeOperand(call llvm.Value) int { + called := call.CalledValue() + index := -1 + for operand := 0; operand < call.OperandsCount(); operand++ { + if call.Operand(operand) == called { + index = operand + } + } + return index +} + +func definedCallParameter(call llvm.Value, argument int) (paramKey, bool) { + fn := call.CalledValue().IsAFunction() + if fn.IsNil() || fn.IntrinsicID() != 0 || fn.IsDeclaration() || isRuntimeFunction(fn) || argument >= fn.ParamsCount() { + return paramKey{}, false + } + return paramKey{fn: fn, param: argument}, true +} + +func (a *analyzer) classifyNoCaptureUse(state useState) useAction { + user := state.user + switch user.InstructionOpcode() { + case llvm.Call, llvm.Invoke: + if a.callArgumentNoCapture(state) { + return useSafe + } + case llvm.Load: + return useSafe + case llvm.Store: + if state.operand == 1 { + return useSafe + } + case opcodeAtomicRMW: + if state.operand == 0 { + return useSafe + } + case opcodeAtomicCmpXchg: + if state.operand == 0 { + return useSafe + } + case llvm.VAArg: + return useSafe + case llvm.GetElementPtr: + if state.operand == 0 && user.Type().TypeKind() != llvm.VectorTypeKind { + return useFollow + } + case llvm.BitCast, llvm.PHI, opcodeAddrSpaceCast: + return useFollow + case llvm.Select: + if state.operand == 1 || state.operand == 2 { + return useFollow + } + case opcodeFreeze: + if state.operand == 0 { + return useFollow + } + } + return useCapture +} + +func (a *analyzer) requiredAlignment(root paramKey) int { + alignment := 0 + worklist := []paramKey{root} + visited := make(map[paramKey]struct{}) + + for len(worklist) != 0 { + key := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + if _, ok := visited[key]; ok { + continue + } + visited[key] = struct{}{} + + a.checkForAllUses(key.fn.Param(key.param), func(state useState) useAction { + user := state.user + switch user.InstructionOpcode() { + case llvm.Load: + if user.Alignment() > alignment { + alignment = user.Alignment() + } + case llvm.Store: + if state.operand == 1 && user.Alignment() > alignment { + alignment = user.Alignment() + } + case llvm.Call, llvm.Invoke: + if callee, ok := definedCallParameter(user, state.operand); ok && a.noCapture[callee] { + worklist = append(worklist, callee) + } + case llvm.GetElementPtr: + if state.operand == 0 { + return useFollow + } + case llvm.BitCast, llvm.PHI, opcodeAddrSpaceCast: + return useFollow + case llvm.Select: + if state.operand == 1 || state.operand == 2 { + return useFollow + } + case opcodeFreeze: + if state.operand == 0 { + return useFollow + } + case opcodeAtomicCmpXchg, opcodeAtomicRMW: + if state.operand == 0 && user.Alignment() > alignment { + alignment = user.Alignment() + } + } + return useSafe + }) + } + return alignment +} + +func (a *analyzer) allocationUses(root llvm.Value) walkResult { + result := walkResult{} + result.escaped = !a.checkForAllUses(root, func(state useState) useAction { + user := state.user + switch user.InstructionOpcode() { + case llvm.Load: + if user.Alignment() > result.alignment { + result.alignment = user.Alignment() + } + return useSafe + case llvm.Store: + if state.operand == 1 { + if user.Alignment() > result.alignment { + result.alignment = user.Alignment() + } + return useSafe + } + case llvm.Call, llvm.Invoke: + if a.callArgumentNoCapture(state) { + if key, ok := definedCallParameter(user, state.operand); ok { + if alignment := a.requiredAlignment(key); alignment > result.alignment { + result.alignment = alignment + } + } + return useSafe + } + case llvm.GetElementPtr: + if state.operand == 0 { + return useFollow + } + case llvm.BitCast, llvm.PHI: + return useFollow + case llvm.Select: + if state.operand == 1 || state.operand == 2 { + return useFollow + } + case opcodeFreeze: + if state.operand == 0 { + return useFollow + } + case opcodeAtomicCmpXchg: + if state.operand == 0 { + if user.Alignment() > result.alignment { + result.alignment = user.Alignment() + } + return useSafe + } + case opcodeAtomicRMW: + if state.operand == 0 { + if user.Alignment() > result.alignment { + result.alignment = user.Alignment() + } + return useSafe + } + } + return useCapture + }) + return result +} + +func (a *analyzer) callArgumentNoCapture(state useState) bool { + call := state.user + calleeOperand := callCalleeOperand(call) + if state.operand == calleeOperand { + return true + } + + callee := call.CalledValue() + if !callee.IsAInlineAsm().IsNil() { + return false + } + fn := callee.IsAFunction() + if fn.IsNil() { + return false + } + if fn.IntrinsicID() != 0 { + return intrinsicArgumentNoCapture(call, fn, state.operand) + } + key, ok := definedCallParameter(call, state.operand) + if !ok { + return false + } + + return a.noCapture[key] +} + +func (a *analyzer) recordLocations() { + for _, key := range a.noCaptureKeys { + a.recordParameterLocations(key) + } +} + +func (a *analyzer) recordParameterLocations(root paramKey) { + worklist := make([]useState, 0, 8) + a.addUses(&worklist, root.fn.Param(root.param)) + visited := make(map[useState]struct{}) + + for len(worklist) != 0 { + state := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + if _, ok := visited[state]; ok { + continue + } + visited[state] = struct{}{} + from := state.user.Operand(state.operand) + + if state.user.InstructionOpcode() == llvm.Store && state.operand == 0 { + copies, ok := a.copies.getPotentialCopiesOfStoredValue(state.user) + if ok { + for _, copy := range copies { + a.locations.addFlow(from, copy) + a.addUses(&worklist, copy) + } + continue + } + } + + user := state.user + switch user.InstructionOpcode() { + case llvm.Load: + if state.operand == 0 && isPointer(user.Type()) { + a.locations.addDeref(from, user) + a.addUses(&worklist, user) + } + case llvm.Store: + if state.operand == 0 { + a.locations.addHeap(from) + } else if state.operand == 1 { + a.locations.addMutator(from) + } + case llvm.Call, llvm.Invoke: + calleeOperand := callCalleeOperand(user) + if state.operand == calleeOperand { + a.locations.addCallee(from) + continue + } + if callee, ok := definedCallParameter(user, state.operand); ok { + a.locations.addFlow(from, callee.fn.Param(callee.param)) + if isPointer(user.Type()) { + a.locations.addCallResult(callee.fn, 0, user) + a.addUses(&worklist, user) + } + continue + } + fn := user.CalledValue().IsAFunction() + if fn.IsNil() || fn.IntrinsicID() == 0 || !intrinsicArgumentNoCapture(user, fn, state.operand) { + a.locations.addHeap(from) + } + case llvm.Ret: + a.locations.addResult(from, root.fn, 0) + case llvm.GetElementPtr: + if state.operand == 0 && user.Type().TypeKind() != llvm.VectorTypeKind { + a.locations.addFlow(from, user) + a.addUses(&worklist, user) + } else { + a.locations.addHeap(from) + } + case llvm.BitCast, llvm.PHI, opcodeAddrSpaceCast: + a.locations.addFlow(from, user) + a.addUses(&worklist, user) + case llvm.Select: + if state.operand == 1 || state.operand == 2 { + a.locations.addFlow(from, user) + a.addUses(&worklist, user) + } else { + a.locations.addHeap(from) + } + case opcodeFreeze: + if state.operand == 0 { + a.locations.addFlow(from, user) + a.addUses(&worklist, user) + } else { + a.locations.addHeap(from) + } + case opcodeAtomicRMW, opcodeAtomicCmpXchg: + if state.operand == 0 { + a.locations.addMutator(from) + } else { + a.locations.addHeap(from) + } + case llvm.VAArg: + default: + a.locations.addHeap(from) + } + } +} + +// Result contains diagnostic facts computed independently from heap-to-stack +// decisions. +type Result struct { + Parameters []ParameterSummary +} + +// ParameterSummary contains the Go-compatible leak summary for one LLVM formal +// parameter. +type ParameterSummary struct { + Function string + Parameter int + HeapLevel int + MutatorLevel int + CalleeLevel int + Results []ResultLeak +} + +// ResultLeak describes a parameter flow to one direct LLVM result. +type ResultLeak struct { + Result int + Level int +} + +func (a *analyzer) result() Result { + if a.locations == nil { + return Result{} + } + result := Result{Parameters: make([]ParameterSummary, 0, len(a.noCaptureKeys))} + for _, key := range a.noCaptureKeys { + leaks := a.locations.summarize(key) + summary := ParameterSummary{ + Function: key.fn.Name(), + Parameter: key.param, + HeapLevel: leaks.get(leakHeap), + MutatorLevel: leaks.get(leakMutator), + CalleeLevel: leaks.get(leakCallee), + } + for resultIndex := 0; resultIndex < numEscResults; resultIndex++ { + if level := leaks.get(leakResult0 + resultIndex); level >= 0 { + summary.Results = append(summary.Results, ResultLeak{Result: resultIndex, Level: level}) + } + } + result.Parameters = append(result.Parameters, summary) + } + sort.Slice(result.Parameters, func(i, j int) bool { + left, right := result.Parameters[i], result.Parameters[j] + if left.Function != right.Function { + return left.Function < right.Function + } + return left.Parameter < right.Parameter + }) + return result +} + +func intrinsicArgumentNoCapture(call, fn llvm.Value, argument int) bool { + name := fn.Name() + if argument < 0 { + return false + } + if strings.HasPrefix(name, "llvm.dbg.") || + strings.HasPrefix(name, "llvm.lifetime.start.") || + strings.HasPrefix(name, "llvm.lifetime.end.") || + strings.HasPrefix(name, "llvm.objectsize.") { + return true + } + kind := llvm.AttributeKindID("nocapture") + attributeIndex := argument + 1 + if attr := call.GetCallSiteEnumAttribute(attributeIndex, kind); !attr.IsNil() { + return true + } + return !fn.GetEnumAttributeAtIndex(attributeIndex, kind).IsNil() +} + +type allocationPlan struct { + call llvm.Value + size llvm.Value + zero bool + alignment int +} + +// TransformModule analyzes eligible LLGo allocations and rewrites proven-local +// AllocZ and AllocU calls. Diagnostic summaries are computed only when +// diagnostics is true. +func TransformModule(mod llvm.Module, diagnostics bool) Result { + a := newAnalyzer(mod, diagnostics) + defer a.copies.dispose() + a.solveNoCapture() + if a.locations != nil { + a.recordLocations() + } + result := a.result() + + var plans []allocationPlan + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if fn.IsDeclaration() { + continue + } + for block := fn.FirstBasicBlock(); !block.IsNil(); block = llvm.NextBasicBlock(block) { + for instr := block.FirstInstruction(); !instr.IsNil(); instr = llvm.NextInstruction(instr) { + plan, ok := a.planAllocation(instr) + if ok { + plans = append(plans, plan) + } + } + } + } + + for _, plan := range plans { + rewriteAllocation(mod.Context(), plan) + } + return result +} + +func (a *analyzer) planAllocation(instr llvm.Value) (allocationPlan, bool) { + call := instr.IsACallInst() + if call.IsNil() || call.OperandsCount() < 2 || !isPointer(call.Type()) { + return allocationPlan{}, false + } + callee := call.CalledValue().IsAFunction() + if callee.IsNil() { + return allocationPlan{}, false + } + name := callee.Name() + if name != runtimeAllocZ && name != runtimeAllocU { + return allocationPlan{}, false + } + size := call.Operand(0).IsAConstantInt() + if size.IsNil() { + return allocationPlan{}, false + } + sizeBytes := size.ZExtValue() + if sizeBytes == 0 || sizeBytes > llabi.MaxImplicitStackVarSize || blockInCycle(call.InstructionParent()) { + return allocationPlan{}, false + } + + result := a.allocationUses(call) + if result.escaped { + return allocationPlan{}, false + } + alignment := callResultAlignment(call, callee) + if result.alignment > alignment { + alignment = result.alignment + } + if alignment == 0 { + alignment = 1 + } + if alignment&(alignment-1) != 0 || alignment > int(llabi.MaxImplicitStackVarSize) { + return allocationPlan{}, false + } + return allocationPlan{call: call, size: size, zero: name == runtimeAllocZ, alignment: alignment}, true +} + +func callResultAlignment(call, callee llvm.Value) int { + kind := llvm.AttributeKindID("align") + align := 0 + if attr := call.GetCallSiteEnumAttribute(0, kind); !attr.IsNil() { + align = int(attr.GetEnumValue()) + } + if attr := callee.GetEnumAttributeAtIndex(0, kind); !attr.IsNil() && int(attr.GetEnumValue()) > align { + align = int(attr.GetEnumValue()) + } + return align +} + +func blockInCycle(start llvm.BasicBlock) bool { + visited := make(map[llvm.BasicBlock]bool) + var visit func(llvm.BasicBlock) bool + visit = func(block llvm.BasicBlock) bool { + if block == start { + return true + } + if visited[block] { + return false + } + visited[block] = true + terminator := block.LastInstruction() + for index := 0; index < terminator.SuccessorsCount(); index++ { + if visit(terminator.Successor(index)) { + return true + } + } + return false + } + + terminator := start.LastInstruction() + for index := 0; index < terminator.SuccessorsCount(); index++ { + if visit(terminator.Successor(index)) { + return true + } + } + return false +} + +func rewriteAllocation(ctx llvm.Context, plan allocationPlan) bool { + builder := ctx.NewBuilder() + defer builder.Dispose() + builder.SetInsertPointBefore(plan.call) + stack := llvm.CreateArrayAlloca(builder, ctx.Int8Type(), plan.size) + if stack.Type().PointerAddressSpace() != plan.call.Type().PointerAddressSpace() { + stack.EraseFromParentAsInstruction() + return false + } + stack.SetName(plan.call.Name() + ".stack") + stack.SetAlignment(plan.alignment) + if plan.zero { + builder.CreateIntrinsic(ctx.VoidType(), llvm.LookupIntrinsicID("llvm.memset"), []llvm.Value{ + stack, + llvm.ConstInt(ctx.Int8Type(), 0, false), + plan.size, + llvm.ConstInt(ctx.Int1Type(), 0, false), + }, "") + } + plan.call.ReplaceAllUsesWith(stack) + plan.call.EraseFromParentAsInstruction() + return true +} diff --git a/internal/escape/escape_test.go b/internal/escape/escape_test.go new file mode 100644 index 0000000000..d399f0013a --- /dev/null +++ b/internal/escape/escape_test.go @@ -0,0 +1,77 @@ +//go:build !llgo + +package escape + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/goplus/llgo/internal/littest" + "github.com/xgo-dev/llvm" +) + +func TestTransformModule(t *testing.T) { + entries, err := os.ReadDir("testdata") + if err != nil { + t.Fatal(err) + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + t.Run(entry.Name(), func(t *testing.T) { + dir := filepath.Join("testdata", entry.Name()) + input := filepath.Join(dir, "in.txt") + buffer, err := llvm.NewMemoryBufferFromFile(input) + if err != nil { + t.Fatal(err) + } + ctx := llvm.NewContext() + defer ctx.Dispose() + mod, err := ctx.ParseIR(buffer) + if err != nil { + t.Fatalf("parse %s: %v", input, err) + } + defer mod.Dispose() + result := TransformModule(mod, true) + + output := filepath.Join(dir, "out.txt") + want, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + spec := littest.Spec{Path: output, Text: string(want), Mode: littest.ModeLiteral} + if err := littest.Check(spec, mod.String()); err != nil { + t.Fatalf("%v\n%s", err, mod.String()) + } + + diagnostics := filepath.Join(dir, "diagnostics.txt") + wantDiagnostics, err := os.ReadFile(diagnostics) + if os.IsNotExist(err) { + return + } + if err != nil { + t.Fatal(err) + } + spec = littest.Spec{Path: diagnostics, Text: string(wantDiagnostics), Mode: littest.ModeLiteral} + if got := formatParameterSummaries(result); littest.Check(spec, got) != nil { + t.Fatalf("diagnostics mismatch:\n%s", got) + } + }) + } +} + +func formatParameterSummaries(result Result) string { + var out strings.Builder + for _, param := range result.Parameters { + fmt.Fprintf(&out, "%s param=%d heap=%d mutator=%d callee=%d", param.Function, param.Parameter, param.HeapLevel, param.MutatorLevel, param.CalleeLevel) + for _, leak := range param.Results { + fmt.Fprintf(&out, " result=%d level=%d", leak.Result, leak.Level) + } + out.WriteByte('\n') + } + return out.String() +} diff --git a/internal/escape/pointer_info.go b/internal/escape/pointer_info.go new file mode 100644 index 0000000000..598c2f36dd --- /dev/null +++ b/internal/escape/pointer_info.go @@ -0,0 +1,804 @@ +package escape + +import ( + "math" + "sort" + "strings" + + "github.com/xgo-dev/llvm" +) + +const ( + rangeUnassigned = int64(math.MinInt32) + rangeUnknown = int64(math.MaxInt32) +) + +type memoryRange struct { + offset int64 + size int64 +} + +func unknownMemoryRange() memoryRange { + return memoryRange{offset: rangeUnknown, size: rangeUnknown} +} + +func (r memoryRange) isUnassigned() bool { + return r.offset == rangeUnassigned && r.size == rangeUnassigned +} + +func (r memoryRange) isUnknown() bool { + return r.offset == rangeUnknown || r.size == rangeUnknown +} + +func (r memoryRange) mayOverlap(other memoryRange) bool { + if r.isUnknown() || other.isUnknown() { + return true + } + return other.offset+other.size > r.offset && other.offset < r.offset+r.size +} + +func (r *memoryRange) merge(other memoryRange) { + if other.isUnassigned() { + return + } + if r.isUnassigned() { + *r = other + return + } + if r.offset == rangeUnknown || other.offset == rangeUnknown { + r.offset = rangeUnknown + } + if r.size == rangeUnknown || other.size == rangeUnknown { + r.size = rangeUnknown + } + if r.offset == rangeUnknown && r.size == rangeUnknown { + return + } + if r.offset == rangeUnknown { + if other.size > r.size { + r.size = other.size + } + return + } + if r.size == rangeUnknown { + if other.offset < r.offset { + r.offset = other.offset + } + return + } + r.offset = min(r.offset, other.offset) + r.size = max(r.offset+r.size, other.offset+other.size) - r.offset +} + +type accessKind uint8 + +const ( + accessMust accessKind = 1 << iota + accessMay + accessRead + accessWrite +) + +type memoryAccess struct { + local llvm.Value + remote llvm.Value + ranges []memoryRange + kind accessKind +} + +func (a memoryAccess) isRead() bool { return a.kind&accessRead != 0 } +func (a memoryAccess) isWrite() bool { return a.kind&accessWrite != 0 } +func (a memoryAccess) isMust() bool { return a.kind&accessMust != 0 } + +type offsetInfo struct { + offsets []int64 +} + +func unknownOffsetInfo() offsetInfo { + return offsetInfo{offsets: []int64{rangeUnknown}} +} + +func (o offsetInfo) isUnknown() bool { + return len(o.offsets) == 1 && o.offsets[0] == rangeUnknown +} + +func (o offsetInfo) equal(other offsetInfo) bool { + if len(o.offsets) != len(other.offsets) { + return false + } + for index := range o.offsets { + if o.offsets[index] != other.offsets[index] { + return false + } + } + return true +} + +func (o *offsetInfo) merge(other offsetInfo) bool { + if o.isUnknown() { + return false + } + if other.isUnknown() { + *o = unknownOffsetInfo() + return true + } + seen := make(map[int64]struct{}, len(o.offsets)+len(other.offsets)) + for _, offset := range o.offsets { + seen[offset] = struct{}{} + } + changed := false + for _, offset := range other.offsets { + if _, ok := seen[offset]; ok { + continue + } + seen[offset] = struct{}{} + o.offsets = append(o.offsets, offset) + changed = true + } + if changed { + sort.Slice(o.offsets, func(i, j int) bool { return o.offsets[i] < o.offsets[j] }) + } + return changed +} + +type pointerInfo struct { + valid bool + accesses []memoryAccess +} + +type pointerInfoState uint8 + +const ( + pointerInfoUnseen pointerInfoState = iota + pointerInfoBuilding + pointerInfoReady +) + +type copyAnalysis struct { + mod llvm.Module + td llvm.TargetData + infos map[llvm.Value]*pointerInfo + states map[llvm.Value]pointerInfoState + callSites map[llvm.Value][]llvm.Value +} + +func newCopyAnalysis(mod llvm.Module) *copyAnalysis { + a := ©Analysis{ + mod: mod, + td: llvm.NewTargetData(mod.DataLayout()), + infos: make(map[llvm.Value]*pointerInfo), + states: make(map[llvm.Value]pointerInfoState), + callSites: make(map[llvm.Value][]llvm.Value), + } + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if fn.IsDeclaration() { + continue + } + for block := fn.FirstBasicBlock(); !block.IsNil(); block = llvm.NextBasicBlock(block) { + for instr := block.FirstInstruction(); !instr.IsNil(); instr = llvm.NextInstruction(instr) { + if call := instr.IsACallInst(); !call.IsNil() { + if callee := call.CalledValue().IsAFunction(); !callee.IsNil() { + a.callSites[callee] = append(a.callSites[callee], call) + } + } + } + } + } + return a +} + +func (a *copyAnalysis) dispose() { + a.td.Dispose() +} + +func (a *copyAnalysis) pointerInfo(root llvm.Value) (*pointerInfo, bool) { + switch a.states[root] { + case pointerInfoReady: + info := a.infos[root] + return info, info.valid + case pointerInfoBuilding: + return nil, false + } + + info := &pointerInfo{valid: true} + a.infos[root] = info + a.states[root] = pointerInfoBuilding + info.valid = a.buildPointerInfo(root, info) + a.states[root] = pointerInfoReady + return info, info.valid +} + +func (a *copyAnalysis) buildPointerInfo(root llvm.Value, info *pointerInfo) bool { + offsets := map[llvm.Value]offsetInfo{root: {offsets: []int64{0}}} + worklist := []llvm.Value{root} + inWorklist := map[llvm.Value]bool{root: true} + + enqueue := func(value llvm.Value, incoming offsetInfo) { + current, ok := offsets[value] + if ok && !current.merge(incoming) { + return + } + if !ok { + current = offsetInfo{} + current.merge(incoming) + offsets[value] = current + } else { + offsets[value] = current + } + if !inWorklist[value] { + worklist = append(worklist, value) + inWorklist[value] = true + } + } + + for len(worklist) != 0 { + value := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + inWorklist[value] = false + valueOffsets := offsets[value] + + for use := value.FirstUse(); !use.IsNil(); use = use.NextUse() { + user := use.User() + for operand := 0; operand < user.OperandsCount(); operand++ { + if user.Operand(operand) != value { + continue + } + + if user.InstructionOpcode() == llvm.Store && operand == 0 { + copies, ok := a.getPotentialCopiesOfStoredValue(user) + if !ok { + return false + } + for _, copy := range copies { + enqueue(copy, valueOffsets) + } + continue + } + + switch user.InstructionOpcode() { + case llvm.GetElementPtr: + if operand != 0 || user.Type().TypeKind() == llvm.VectorTypeKind { + return false + } + enqueue(user, a.gepOffsets(user, valueOffsets)) + case llvm.BitCast, opcodeAddrSpaceCast: + enqueue(user, valueOffsets) + case llvm.Select: + if operand != 1 && operand != 2 { + return false + } + enqueue(user, valueOffsets) + case llvm.PHI: + current, exists := offsets[user] + if exists && blockInCycle(user.InstructionParent()) && !current.equal(valueOffsets) { + enqueue(user, unknownOffsetInfo()) + } else { + enqueue(user, valueOffsets) + } + case llvm.Ret: + fn := user.InstructionParent().Parent() + for _, call := range a.callSites[fn] { + enqueue(call, valueOffsets) + } + case llvm.Load: + kind := accessRead | a.accessCertainty(root, value) + a.addAccess(info, user, user, valueOffsets, int64(a.td.TypeStoreSize(user.Type())), kind) + case llvm.Store: + if operand != 1 { + return false + } + kind := accessWrite | a.accessCertainty(root, value) + a.addAccess(info, user, user, valueOffsets, int64(a.td.TypeStoreSize(user.Operand(0).Type())), kind) + case opcodeAtomicRMW, opcodeAtomicCmpXchg: + if operand != 0 { + return false + } + kind := accessRead | accessWrite | a.accessCertainty(root, value) + a.addAccess(info, user, user, valueOffsets, int64(a.td.TypeStoreSize(user.Operand(1).Type())), kind) + case llvm.Call, llvm.Invoke: + if !a.addCallAccesses(info, root, value, valueOffsets, user, operand) { + return false + } + case llvm.VAArg: + case opcodeFreeze: + if operand != 0 { + return false + } + enqueue(user, valueOffsets) + default: + return false + } + } + } + } + return true +} + +func (a *copyAnalysis) accessCertainty(root, pointer llvm.Value) accessKind { + objects := a.underlyingObjects(pointer) + if len(objects) == 1 && objects[0] == root { + return accessMust + } + return accessMay +} + +func (a *copyAnalysis) addAccess(info *pointerInfo, local, remote llvm.Value, offsets offsetInfo, size int64, kind accessKind) { + ranges := make([]memoryRange, 0, len(offsets.offsets)) + if offsets.isUnknown() || size == rangeUnknown { + ranges = append(ranges, unknownMemoryRange()) + } else { + for _, offset := range offsets.offsets { + ranges = append(ranges, memoryRange{offset: offset, size: size}) + } + } + if len(ranges) != 1 { + kind &^= accessMust + kind |= accessMay + } + for index := range info.accesses { + access := &info.accesses[index] + if access.local != local || access.remote != remote { + continue + } + for _, candidate := range ranges { + found := false + for rangeIndex := range access.ranges { + if access.ranges[rangeIndex].offset == candidate.offset { + access.ranges[rangeIndex].merge(candidate) + found = true + break + } + } + if !found { + access.ranges = append(access.ranges, candidate) + } + } + if len(access.ranges) != 1 { + access.kind &^= accessMust + access.kind |= accessMay + } + access.kind |= kind + if access.kind&accessMay != 0 { + access.kind &^= accessMust + } + return + } + info.accesses = append(info.accesses, memoryAccess{local: local, remote: remote, ranges: ranges, kind: kind}) +} + +func (a *copyAnalysis) gepOffsets(gep llvm.Value, base offsetInfo) offsetInfo { + if base.isUnknown() { + return unknownOffsetInfo() + } + current := append([]int64(nil), base.offsets...) + typ := gep.GEPSourceElementType() + for operand := 1; operand < gep.OperandsCount(); operand++ { + indices, ok := a.potentialConstants(gep.Operand(operand), make(map[llvm.Value]bool)) + if !ok || len(indices) == 0 { + return unknownOffsetInfo() + } + var additions []int64 + if operand == 1 { + scale := int64(a.td.TypeAllocSize(typ)) + for _, index := range indices { + additions = append(additions, index*scale) + } + } else { + switch typ.TypeKind() { + case llvm.StructTypeKind: + if len(indices) != 1 || indices[0] < 0 || indices[0] >= int64(typ.StructElementTypesCount()) { + return unknownOffsetInfo() + } + field := int(indices[0]) + additions = append(additions, int64(a.td.ElementOffset(typ, field))) + typ = typ.StructElementTypes()[field] + current = addOffsetProduct(current, additions) + continue + case llvm.ArrayTypeKind, llvm.VectorTypeKind: + elem := typ.ElementType() + scale := int64(a.td.TypeAllocSize(elem)) + for _, index := range indices { + additions = append(additions, index*scale) + } + typ = elem + default: + return unknownOffsetInfo() + } + } + current = addOffsetProduct(current, additions) + } + return offsetInfo{offsets: uniqueSortedOffsets(current)} +} + +func addOffsetProduct(base, additions []int64) []int64 { + result := make([]int64, 0, len(base)*len(additions)) + for _, value := range base { + for _, addition := range additions { + result = append(result, value+addition) + } + } + return result +} + +func uniqueSortedOffsets(offsets []int64) []int64 { + sort.Slice(offsets, func(i, j int) bool { return offsets[i] < offsets[j] }) + result := offsets[:0] + for _, offset := range offsets { + if len(result) == 0 || result[len(result)-1] != offset { + result = append(result, offset) + } + } + return result +} + +func (a *copyAnalysis) potentialConstants(value llvm.Value, visiting map[llvm.Value]bool) ([]int64, bool) { + if constant := value.IsAConstantInt(); !constant.IsNil() { + return []int64{constant.SExtValue()}, true + } + if visiting[value] { + return nil, false + } + visiting[value] = true + defer delete(visiting, value) + + var values []int64 + switch value.InstructionOpcode() { + case llvm.Select: + for operand := 1; operand <= 2; operand++ { + set, ok := a.potentialConstants(value.Operand(operand), visiting) + if !ok { + return nil, false + } + values = append(values, set...) + } + case llvm.PHI: + for index := 0; index < value.IncomingCount(); index++ { + set, ok := a.potentialConstants(value.IncomingValue(index), visiting) + if !ok { + return nil, false + } + values = append(values, set...) + } + default: + return nil, false + } + return uniqueSortedOffsets(values), true +} + +func (a *copyAnalysis) underlyingObjects(value llvm.Value) []llvm.Value { + objects := make(map[llvm.Value]struct{}) + var visit func(llvm.Value, map[llvm.Value]bool) + visit = func(current llvm.Value, path map[llvm.Value]bool) { + if path[current] { + return + } + nextPath := make(map[llvm.Value]bool, len(path)+1) + for seen := range path { + nextPath[seen] = true + } + nextPath[current] = true + + switch current.InstructionOpcode() { + case llvm.GetElementPtr, llvm.BitCast, opcodeAddrSpaceCast: + visit(current.Operand(0), nextPath) + case llvm.Select: + visit(current.Operand(1), nextPath) + visit(current.Operand(2), nextPath) + case llvm.PHI: + for index := 0; index < current.IncomingCount(); index++ { + visit(current.IncomingValue(index), nextPath) + } + default: + objects[current] = struct{}{} + } + } + visit(value, make(map[llvm.Value]bool)) + result := make([]llvm.Value, 0, len(objects)) + for object := range objects { + result = append(result, object) + } + return result +} + +func (a *copyAnalysis) addCallAccesses(info *pointerInfo, root, pointer llvm.Value, offsets offsetInfo, call llvm.Value, operand int) bool { + calleeOperand := callCalleeOperand(call) + if operand == calleeOperand { + return true + } + if strings.HasPrefix(call.CalledValue().Name(), "llvm.lifetime.") { + return true + } + if !call.IsAMemCpyInst().IsNil() || !call.IsAMemMoveInst().IsNil() || !call.IsAMemSetInst().IsNil() { + if operand > 1 { + return true + } + size := int64(rangeUnknown) + if call.OperandsCount() > 2 { + if length := call.Operand(2).IsAConstantInt(); !length.IsNil() { + size = int64(length.SExtValue()) + } + } + kind := accessMust | accessWrite + if operand == 1 { + kind = accessMust | accessRead + } + a.addAccess(info, call, call, offsets, size, kind) + return true + } + + callee := call.CalledValue().IsAFunction() + if callee.IsNil() || operand >= callee.ParamsCount() { + return false + } + if !callee.IsDeclaration() { + calleeInfo, ok := a.pointerInfo(callee.Param(operand)) + if !ok { + return false + } + for _, access := range calleeInfo.accesses { + translated := access + translated.local = call + translated.ranges = translateRanges(access.ranges, offsets) + if len(translated.ranges) != 1 { + translated.kind &^= accessMust + translated.kind |= accessMay + } + info.accesses = append(info.accesses, translated) + } + return true + } + + if !callArgumentHasAttribute(call, callee, operand, "nocapture") { + return false + } + if callArgumentHasAttribute(call, callee, operand, "readnone") { + return true + } + kind := accessMay | accessRead | accessWrite + if callArgumentHasAttribute(call, callee, operand, "readonly") { + kind = accessMay | accessRead + } + a.addAccess(info, call, call, unknownOffsetInfo(), rangeUnknown, kind) + return true +} + +func translateRanges(ranges []memoryRange, offsets offsetInfo) []memoryRange { + if offsets.isUnknown() { + return []memoryRange{unknownMemoryRange()} + } + var result []memoryRange + for _, base := range offsets.offsets { + for _, current := range ranges { + if current.isUnknown() { + return []memoryRange{unknownMemoryRange()} + } + result = append(result, memoryRange{offset: base + current.offset, size: current.size}) + } + } + return result +} + +func callArgumentHasAttribute(call, callee llvm.Value, argument int, name string) bool { + kind := llvm.AttributeKindID(name) + index := argument + 1 + if attr := call.GetCallSiteEnumAttribute(index, kind); !attr.IsNil() { + return true + } + return !callee.GetEnumAttributeAtIndex(index, kind).IsNil() +} + +func (a *copyAnalysis) getPotentialCopiesOfStoredValue(store llvm.Value) ([]llvm.Value, bool) { + objects := a.underlyingObjects(store.Operand(1)) + potentialCopies := make(map[llvm.Value]struct{}) + + for _, object := range objects { + if !object.IsAUndefValue().IsNil() { + continue + } + if !object.IsAConstantPointerNull().IsNil() { + if store.Operand(1) == object && !functionNullPointerIsValid(store.InstructionParent().Parent()) { + continue + } + return nil, false + } + if !a.supportedUnderlyingObject(object) { + return nil, false + } + info, ok := a.pointerInfo(object) + if !ok { + return nil, false + } + copies, ok := a.interferingLoadCopies(store, object, info) + if !ok { + return nil, false + } + for _, copy := range copies { + potentialCopies[copy] = struct{}{} + } + } + + result := make([]llvm.Value, 0, len(potentialCopies)) + for copy := range potentialCopies { + result = append(result, copy) + } + return result, true +} + +func functionNullPointerIsValid(fn llvm.Value) bool { + return !fn.GetStringAttributeAtIndex(-1, "null-pointer-is-valid").IsNil() +} + +func (a *copyAnalysis) supportedUnderlyingObject(object llvm.Value) bool { + if !object.IsAAllocaInst().IsNil() { + return true + } + if global := object.IsAGlobalVariable(); !global.IsNil() { + linkage := global.Linkage() + return linkage == llvm.InternalLinkage || linkage == llvm.PrivateLinkage || global.IsGlobalConstant() && !global.Initializer().IsNil() + } + if call := object.IsACallInst(); !call.IsNil() { + callee := call.CalledValue().IsAFunction() + if callee.IsNil() { + return false + } + kind := llvm.AttributeKindID("noalias") + return !call.GetCallSiteEnumAttribute(0, kind).IsNil() || !callee.GetEnumAttributeAtIndex(0, kind).IsNil() + } + return false +} + +func (a *copyAnalysis) interferingLoadCopies(store, object llvm.Value, info *pointerInfo) ([]llvm.Value, bool) { + query := memoryRange{offset: rangeUnassigned, size: rangeUnassigned} + for _, access := range info.accesses { + if access.local != store && access.remote != store { + continue + } + for _, current := range access.ranges { + query.merge(current) + } + } + if query.isUnassigned() { + return nil, false + } + + blockers := make(map[llvm.Value]bool) + for _, access := range info.accesses { + if !access.isWrite() || !access.isMust() || access.remote == store { + continue + } + for _, current := range access.ranges { + if current == query && !query.isUnknown() { + blockers[access.remote] = true + } + } + } + + copies := make(map[llvm.Value]struct{}) + threadLocal := !object.IsAAllocaInst().IsNil() || object.IsThreadLocal() + for _, access := range info.accesses { + if !access.isRead() { + continue + } + for _, current := range access.ranges { + if !query.mayOverlap(current) { + continue + } + if threadLocal && !a.potentiallyReachable(store, access.remote, blockers) { + continue + } + if query.isUnknown() || current.isUnknown() || current != query { + return nil, false + } + if access.remote.IsALoadInst().IsNil() { + return nil, false + } + copies[access.remote] = struct{}{} + } + } + + result := make([]llvm.Value, 0, len(copies)) + for copy := range copies { + result = append(result, copy) + } + return result, true +} + +func (a *copyAnalysis) potentiallyReachable(from, to llvm.Value, blockers map[llvm.Value]bool) bool { + fromFn := from.InstructionParent().Parent() + toFn := to.InstructionParent().Parent() + if fromFn == toFn { + if intraFunctionReachable(from, to, blockers) { + return true + } + if !canReachFunctionReturn(from, blockers) { + return false + } + norecurse := llvm.AttributeKindID("norecurse") + return fromFn.GetEnumFunctionAttribute(norecurse).IsNil() + } + + worklist := []llvm.Value{from} + visited := make(map[llvm.Value]bool) + for len(worklist) != 0 { + current := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + if current == to { + return true + } + if visited[current] || current != from && blockers[current] { + continue + } + visited[current] = true + worklist = append(worklist, a.instructionSuccessors(current)...) + } + return false +} + +func intraFunctionReachable(from, to llvm.Value, blockers map[llvm.Value]bool) bool { + worklist := []llvm.Value{from} + visited := make(map[llvm.Value]bool) + for len(worklist) != 0 { + current := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + if current == to { + return true + } + if visited[current] || current != from && blockers[current] { + continue + } + visited[current] = true + if next := llvm.NextInstruction(current); !next.IsNil() { + worklist = append(worklist, next) + continue + } + terminator := current.InstructionParent().LastInstruction() + for index := 0; index < terminator.SuccessorsCount(); index++ { + if first := terminator.Successor(index).FirstInstruction(); !first.IsNil() { + worklist = append(worklist, first) + } + } + } + return false +} + +func canReachFunctionReturn(from llvm.Value, blockers map[llvm.Value]bool) bool { + fn := from.InstructionParent().Parent() + for block := fn.FirstBasicBlock(); !block.IsNil(); block = llvm.NextBasicBlock(block) { + terminator := block.LastInstruction() + if terminator.InstructionOpcode() == llvm.Ret && intraFunctionReachable(from, terminator, blockers) { + return true + } + } + return false +} + +func (a *copyAnalysis) instructionSuccessors(instr llvm.Value) []llvm.Value { + var result []llvm.Value + if next := llvm.NextInstruction(instr); !next.IsNil() { + result = append(result, next) + } else { + terminator := instr.InstructionParent().LastInstruction() + for index := 0; index < terminator.SuccessorsCount(); index++ { + if first := terminator.Successor(index).FirstInstruction(); !first.IsNil() { + result = append(result, first) + } + } + } + + if call := instr.IsACallInst(); !call.IsNil() { + if callee := call.CalledValue().IsAFunction(); !callee.IsNil() && !callee.IsDeclaration() { + if first := callee.EntryBasicBlock().FirstInstruction(); !first.IsNil() { + result = append(result, first) + } + } + } + if instr.InstructionOpcode() == llvm.Ret { + fn := instr.InstructionParent().Parent() + for _, call := range a.callSites[fn] { + if next := llvm.NextInstruction(call); !next.IsNil() { + result = append(result, next) + } + } + } + return result +} diff --git a/internal/escape/testdata/address-space/in.txt b/internal/escape/testdata/address-space/in.txt new file mode 100644 index 0000000000..51fdb46b6a --- /dev/null +++ b/internal/escape/testdata/address-space/in.txt @@ -0,0 +1,10 @@ +target datalayout = "e-p:64:64-A5" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define void @wrong_address_space() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p + ret void +} diff --git a/internal/escape/testdata/address-space/out.txt b/internal/escape/testdata/address-space/out.txt new file mode 100644 index 0000000000..8199e4256b --- /dev/null +++ b/internal/escape/testdata/address-space/out.txt @@ -0,0 +1,12 @@ +; ModuleID = 'testdata/address-space/in.txt' +source_filename = "testdata/address-space/in.txt" +target datalayout = "e-p:64:64-A5" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define void @wrong_address_space() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p, align 1 + ret void +} diff --git a/internal/escape/testdata/calls/in.txt b/internal/escape/testdata/calls/in.txt new file mode 100644 index 0000000000..b4c02e9505 --- /dev/null +++ b/internal/escape/testdata/calls/in.txt @@ -0,0 +1,273 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null +@atomic_slot = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) +declare void @external(ptr) +declare void @assert_nil(i1) + +define void @read(ptr %p) { +entry: + %v = load i8, ptr %p, align 8 + ret void +} + +define void @write(ptr %p) { +entry: + store i8 1, ptr %p, align 8 + ret void +} + +define i8 @nil_checked_read(ptr %p) { +entry: + %nil = icmp eq ptr %p, null + call void @assert_nil(i1 %nil) + %v = load i8, ptr %p, align 1 + ret i8 %v +} + +define void @save(ptr %p) { +entry: + store ptr %p, ptr @escaped + ret void +} + +define ptr @identity(ptr %p) { +entry: + ret ptr %p +} + +define void @leak_content(ptr %p) { +entry: + %q = load ptr, ptr %p, align 8 + store ptr %q, ptr @escaped + ret void +} + +define void @self_safe(ptr %p) { +entry: + call void @self_safe(ptr %p) + ret void +} + +define void @self_sink(ptr %p) { +entry: + call void @self_sink(ptr %p) + store ptr %p, ptr @escaped + ret void +} + +define void @mutual_safe_a(ptr %p) { +entry: + call void @mutual_safe_b(ptr %p) + ret void +} + +define void @mutual_safe_b(ptr %p) { +entry: + call void @mutual_safe_a(ptr %p) + ret void +} + +define void @mutual_sink_a(ptr %p) { +entry: + call void @mutual_sink_b(ptr %p) + ret void +} + +define void @mutual_sink_b(ptr %p) { +entry: + call void @mutual_sink_a(ptr %p) + store ptr %p, ptr @escaped + ret void +} + +define void @call_parameter(ptr %fn) { +entry: + call void %fn() + ret void +} + +define void @derived(ptr %p, i1 %cond) { +entry: + %g = getelementptr i8, ptr %p, i64 1 + %b = bitcast ptr %g to ptr + %s = select i1 %cond, ptr %b, ptr %p + %f = freeze ptr %s + %same = icmp eq ptr %f, null + store volatile i8 1, ptr %f, align 1 + %v = load volatile i8, ptr %f, align 1 + ret void +} + +define void @phi_addrspace(ptr %p, i1 %cond) { +entry: + br i1 %cond, label %left, label %right +left: + %q = addrspacecast ptr %p to ptr addrspace(1) + %r = addrspacecast ptr addrspace(1) %q to ptr + br label %join +right: + br label %join +join: + %x = phi ptr [ %r, %left ], [ %p, %right ] + %v = load i8, ptr %x, align 1 + ret void +} + +define void @atomic_use(ptr %p) { +entry: + store atomic i32 1, ptr %p monotonic, align 4 + %v = load atomic i32, ptr %p monotonic, align 4 + %cx = cmpxchg ptr @atomic_slot, ptr %p, ptr null monotonic monotonic, align 8 + %rmw = atomicrmw add ptr %p, i32 1 monotonic, align 4 + ret void +} + +define void @atomic_sink(ptr %p) { +entry: + %cx = cmpxchg ptr @atomic_slot, ptr null, ptr %p monotonic monotonic, align 8 + ret void +} + +define void @unknown_param(ptr %p) { +entry: + call void @external(ptr %p) + ret void +} + +define void @recursive_load(ptr %p) { +entry: + %q = load ptr, ptr %p, align 8 + call void @recursive_load(ptr %q) + ret void +} + +define void @known_no_leak() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @read(ptr %p) + ret void +} + +define void @known_heap_leak() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @save(ptr %p) + ret void +} + +define void @known_mutator() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @write(ptr %p) + ret void +} + +define i8 @known_nil_checked_read() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %v = call i8 @nil_checked_read(ptr %p) + ret i8 %v +} + +define void @known_content_leak() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @leak_content(ptr %p) + ret void +} + +define void @result_local() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @identity(ptr %p) + store i8 1, ptr %q + ret void +} + +define void @result_escape() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @identity(ptr %p) + store ptr %q, ptr @escaped + ret void +} + +define void @direct_recursion_safe() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @self_safe(ptr %p) + ret void +} + +define void @direct_recursion_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @self_sink(ptr %p) + ret void +} + +define void @mutual_recursion_safe() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @mutual_safe_a(ptr %p) + ret void +} + +define void @mutual_recursion_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @mutual_sink_a(ptr %p) + ret void +} + +define void @callee_effect() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @call_parameter(ptr %p) + ret void +} + +define void @known_derived(i1 %cond) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @derived(ptr %p, i1 %cond) + ret void +} + +define void @known_phi_addrspace(i1 %cond) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @phi_addrspace(ptr %p, i1 %cond) + ret void +} + +define void @known_atomic() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @atomic_use(ptr %p) + ret void +} + +define void @known_atomic_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @atomic_sink(ptr %p) + ret void +} + +define void @known_unknown_param() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @unknown_param(ptr %p) + ret void +} + +define void @recursive_alignment() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @recursive_load(ptr %p) + ret void +} diff --git a/internal/escape/testdata/calls/out.txt b/internal/escape/testdata/calls/out.txt new file mode 100644 index 0000000000..03ebae6efb --- /dev/null +++ b/internal/escape/testdata/calls/out.txt @@ -0,0 +1,280 @@ +; ModuleID = 'testdata/calls/in.txt' +source_filename = "testdata/calls/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null +@atomic_slot = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +declare void @external(ptr) + +declare void @assert_nil(i1) + +define void @read(ptr %p) { +entry: + %v = load i8, ptr %p, align 8 + ret void +} + +define void @write(ptr %p) { +entry: + store i8 1, ptr %p, align 8 + ret void +} + +define i8 @nil_checked_read(ptr %p) { +entry: + %nil = icmp eq ptr %p, null + call void @assert_nil(i1 %nil) + %v = load i8, ptr %p, align 1 + ret i8 %v +} + +define void @save(ptr %p) { +entry: + store ptr %p, ptr @escaped, align 8 + ret void +} + +define ptr @identity(ptr %p) { +entry: + ret ptr %p +} + +define void @leak_content(ptr %p) { +entry: + %q = load ptr, ptr %p, align 8 + store ptr %q, ptr @escaped, align 8 + ret void +} + +define void @self_safe(ptr %p) { +entry: + call void @self_safe(ptr %p) + ret void +} + +define void @self_sink(ptr %p) { +entry: + call void @self_sink(ptr %p) + store ptr %p, ptr @escaped, align 8 + ret void +} + +define void @mutual_safe_a(ptr %p) { +entry: + call void @mutual_safe_b(ptr %p) + ret void +} + +define void @mutual_safe_b(ptr %p) { +entry: + call void @mutual_safe_a(ptr %p) + ret void +} + +define void @mutual_sink_a(ptr %p) { +entry: + call void @mutual_sink_b(ptr %p) + ret void +} + +define void @mutual_sink_b(ptr %p) { +entry: + call void @mutual_sink_a(ptr %p) + store ptr %p, ptr @escaped, align 8 + ret void +} + +define void @call_parameter(ptr %fn) { +entry: + call void %fn() + ret void +} + +define void @derived(ptr %p, i1 %cond) { +entry: + %g = getelementptr i8, ptr %p, i64 1 + %b = bitcast ptr %g to ptr + %s = select i1 %cond, ptr %b, ptr %p + %f = freeze ptr %s + %same = icmp eq ptr %f, null + store volatile i8 1, ptr %f, align 1 + %v = load volatile i8, ptr %f, align 1 + ret void +} + +define void @phi_addrspace(ptr %p, i1 %cond) { +entry: + br i1 %cond, label %left, label %right + +left: ; preds = %entry + %q = addrspacecast ptr %p to ptr addrspace(1) + %r = addrspacecast ptr addrspace(1) %q to ptr + br label %join + +right: ; preds = %entry + br label %join + +join: ; preds = %right, %left + %x = phi ptr [ %r, %left ], [ %p, %right ] + %v = load i8, ptr %x, align 1 + ret void +} + +define void @atomic_use(ptr %p) { +entry: + store atomic i32 1, ptr %p monotonic, align 4 + %v = load atomic i32, ptr %p monotonic, align 4 + %cx = cmpxchg ptr @atomic_slot, ptr %p, ptr null monotonic monotonic, align 8 + %rmw = atomicrmw add ptr %p, i32 1 monotonic, align 4 + ret void +} + +define void @atomic_sink(ptr %p) { +entry: + %cx = cmpxchg ptr @atomic_slot, ptr null, ptr %p monotonic monotonic, align 8 + ret void +} + +define void @unknown_param(ptr %p) { +entry: + call void @external(ptr %p) + ret void +} + +define void @recursive_load(ptr %p) { +entry: + %q = load ptr, ptr %p, align 8 + call void @recursive_load(ptr %q) + ret void +} + +define void @known_no_leak() { +entry: + %p.stack = alloca i8, i64 8, align 8 + call void @read(ptr %p.stack) + ret void +} + +define void @known_heap_leak() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @save(ptr %p) + ret void +} + +define void @known_mutator() { +entry: + %p.stack = alloca i8, i64 8, align 8 + call void @write(ptr %p.stack) + ret void +} + +define i8 @known_nil_checked_read() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %v = call i8 @nil_checked_read(ptr %p) + ret i8 %v +} + +define void @known_content_leak() { +entry: + %p.stack = alloca i8, i64 8, align 8 + call void @leak_content(ptr %p.stack) + ret void +} + +define void @result_local() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @identity(ptr %p) + store i8 1, ptr %q, align 1 + ret void +} + +define void @result_escape() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @identity(ptr %p) + store ptr %q, ptr @escaped, align 8 + ret void +} + +define void @direct_recursion_safe() { +entry: + %p.stack = alloca i8, i64 8, align 1 + call void @self_safe(ptr %p.stack) + ret void +} + +define void @direct_recursion_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @self_sink(ptr %p) + ret void +} + +define void @mutual_recursion_safe() { +entry: + %p.stack = alloca i8, i64 8, align 1 + call void @mutual_safe_a(ptr %p.stack) + ret void +} + +define void @mutual_recursion_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @mutual_sink_a(ptr %p) + ret void +} + +define void @callee_effect() { +entry: + %p.stack = alloca i8, i64 8, align 1 + call void @call_parameter(ptr %p.stack) + ret void +} + +define void @known_derived(i1 %cond) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @derived(ptr %p, i1 %cond) + ret void +} + +define void @known_phi_addrspace(i1 %cond) { +entry: + %p.stack = alloca i8, i64 8, align 1 + call void @phi_addrspace(ptr %p.stack, i1 %cond) + ret void +} + +define void @known_atomic() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @atomic_use(ptr %p) + ret void +} + +define void @known_atomic_sink() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @atomic_sink(ptr %p) + ret void +} + +define void @known_unknown_param() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @unknown_param(ptr %p) + ret void +} + +define void @recursive_alignment() { +entry: + %p.stack = alloca i8, i64 8, align 8 + call void @recursive_load(ptr %p.stack) + ret void +} diff --git a/internal/escape/testdata/diagnostics/diagnostics.txt b/internal/escape/testdata/diagnostics/diagnostics.txt new file mode 100644 index 0000000000..8ffaa7e766 --- /dev/null +++ b/internal/escape/testdata/diagnostics/diagnostics.txt @@ -0,0 +1,12 @@ +call_parameter param=0 heap=-1 mutator=-1 callee=0 +content_result param=0 heap=-1 mutator=-1 callee=-1 result=0 level=1 +discard_result param=0 heap=-1 mutator=-1 callee=-1 +identity param=0 heap=-1 mutator=-1 callee=-1 result=0 level=0 +mutate param=0 heap=-1 mutator=0 callee=-1 +summaries param=0 heap=-1 mutator=-1 callee=-1 +summaries param=1 heap=0 mutator=-1 callee=-1 +summaries param=2 heap=1 mutator=-1 callee=-1 +summaries param=3 heap=-1 mutator=-1 callee=-1 result=0 level=0 +summaries param=4 heap=0 mutator=-1 callee=-1 +through_content_result param=0 heap=-1 mutator=-1 callee=-1 result=0 level=1 +through_result param=0 heap=-1 mutator=-1 callee=-1 result=0 level=0 diff --git a/internal/escape/testdata/diagnostics/in.txt b/internal/escape/testdata/diagnostics/in.txt new file mode 100644 index 0000000000..0a8934f29b --- /dev/null +++ b/internal/escape/testdata/diagnostics/in.txt @@ -0,0 +1,54 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +define ptr @summaries(ptr %safe, ptr %heap, ptr %content, ptr %result, ptr %mixed) { +entry: + %value = load i8, ptr %safe + store ptr %heap, ptr @escaped + %loaded = load ptr, ptr %content + store ptr %loaded, ptr @escaped + store ptr %mixed, ptr @escaped + ret ptr %result +} + +define ptr @content_result(ptr %p) { +entry: + %loaded = load ptr, ptr %p + ret ptr %loaded +} + +define ptr @identity(ptr %p) { +entry: + ret ptr %p +} + +define ptr @through_result(ptr %p) { +entry: + %result = call ptr @identity(ptr %p) + ret ptr %result +} + +define ptr @through_content_result(ptr %p) { +entry: + %result = call ptr @content_result(ptr %p) + ret ptr %result +} + +define void @discard_result(ptr %p) { +entry: + %result = call ptr @identity(ptr %p) + ret void +} + +define void @mutate(ptr %p) { +entry: + store i8 1, ptr %p + ret void +} + +define void @call_parameter(ptr %fn) { +entry: + call void %fn() + ret void +} diff --git a/internal/escape/testdata/diagnostics/out.txt b/internal/escape/testdata/diagnostics/out.txt new file mode 100644 index 0000000000..6b004c4009 --- /dev/null +++ b/internal/escape/testdata/diagnostics/out.txt @@ -0,0 +1,56 @@ +; ModuleID = 'testdata/diagnostics/in.txt' +source_filename = "testdata/diagnostics/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +define ptr @summaries(ptr %safe, ptr %heap, ptr %content, ptr %result, ptr %mixed) { +entry: + %value = load i8, ptr %safe, align 1 + store ptr %heap, ptr @escaped, align 8 + %loaded = load ptr, ptr %content, align 8 + store ptr %loaded, ptr @escaped, align 8 + store ptr %mixed, ptr @escaped, align 8 + ret ptr %result +} + +define ptr @content_result(ptr %p) { +entry: + %loaded = load ptr, ptr %p, align 8 + ret ptr %loaded +} + +define ptr @identity(ptr %p) { +entry: + ret ptr %p +} + +define ptr @through_result(ptr %p) { +entry: + %result = call ptr @identity(ptr %p) + ret ptr %result +} + +define ptr @through_content_result(ptr %p) { +entry: + %result = call ptr @content_result(ptr %p) + ret ptr %result +} + +define void @discard_result(ptr %p) { +entry: + %result = call ptr @identity(ptr %p) + ret void +} + +define void @mutate(ptr %p) { +entry: + store i8 1, ptr %p, align 1 + ret void +} + +define void @call_parameter(ptr %fn) { +entry: + call void %fn() + ret void +} diff --git a/internal/escape/testdata/escaping-uses/in.txt b/internal/escape/testdata/escaping-uses/in.txt new file mode 100644 index 0000000000..460a64ef47 --- /dev/null +++ b/internal/escape/testdata/escaping-uses/in.txt @@ -0,0 +1,46 @@ +target datalayout = "e-p:64:64-p1:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define i64 @ptr_to_int() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %n = ptrtoint ptr %p to i64 + ret i64 %n +} + +define void @address_space_cast() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = addrspacecast ptr %p to ptr addrspace(1) + ret void +} + +define ptr @returned() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + ret ptr %p +} + +define void @atomic_store_value() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store atomic ptr %p, ptr @escaped release, align 8 + ret void +} + +define void @cmpxchg_replacement() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = cmpxchg ptr @escaped, ptr null, ptr %p monotonic monotonic, align 8 + ret void +} + +define void @atomicrmw_value() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = atomicrmw xchg ptr @escaped, ptr %p monotonic, align 8 + ret void +} diff --git a/internal/escape/testdata/escaping-uses/out.txt b/internal/escape/testdata/escaping-uses/out.txt new file mode 100644 index 0000000000..c3f8ca9318 --- /dev/null +++ b/internal/escape/testdata/escaping-uses/out.txt @@ -0,0 +1,48 @@ +; ModuleID = 'testdata/escaping-uses/in.txt' +source_filename = "testdata/escaping-uses/in.txt" +target datalayout = "e-p:64:64-p1:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define i64 @ptr_to_int() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %n = ptrtoint ptr %p to i64 + ret i64 %n +} + +define void @address_space_cast() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = addrspacecast ptr %p to ptr addrspace(1) + ret void +} + +define ptr @returned() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + ret ptr %p +} + +define void @atomic_store_value() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store atomic ptr %p, ptr @escaped release, align 8 + ret void +} + +define void @cmpxchg_replacement() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = cmpxchg ptr @escaped, ptr null, ptr %p monotonic monotonic, align 8 + ret void +} + +define void @atomicrmw_value() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = atomicrmw xchg ptr @escaped, ptr %p monotonic, align 8 + ret void +} diff --git a/internal/escape/testdata/exact-copy/in.txt b/internal/escape/testdata/exact-copy/in.txt new file mode 100644 index 0000000000..e7f85def06 --- /dev/null +++ b/internal/escape/testdata/exact-copy/in.txt @@ -0,0 +1,246 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +%Pair = type { i32, ptr } + +@escaped = global ptr null +@local_slot = internal global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) +declare void @clobber(ptr) +declare noalias ptr @slot_alloc(i64) +declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg) + +define void @read_slot(ptr %slot) { +entry: + %p = load ptr, ptr %slot + store i8 1, ptr %p + ret void +} + +define void @exact_copy() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + %q = load ptr, ptr %slot + store i8 1, ptr %q + ret void +} + +define void @alias() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + %alias = getelementptr ptr, ptr %slot, i64 0 + store ptr %p, ptr %slot + %q = load ptr, ptr %alias + store i8 1, ptr %q + ret void +} + +define void @slot_address_escape() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + store ptr %slot, ptr @escaped + ret void +} + +define void @unknown_clobber() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + call void @clobber(ptr %slot) + ret void +} + +define void @unsupported_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + %q = load i64, ptr %slot + ret void +} + +define void @other_store() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + store ptr null, ptr %slot + ret void +} + +define void @global_destination() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr @escaped + ret void +} + +define void @nonlocal_destination(ptr %dst) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr %dst + ret void +} + +define void @struct_field() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %object = alloca %Pair + %field1 = getelementptr %Pair, ptr %object, i32 0, i32 1 + store ptr %p, ptr %field1 + %field2 = getelementptr %Pair, ptr %object, i32 0, i32 1 + %q = load ptr, ptr %field2 + store i8 1, ptr %q + ret void +} + +define void @array_element() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %array = alloca [2 x ptr] + %element1 = getelementptr [2 x ptr], ptr %array, i32 0, i32 1 + store ptr %p, ptr %element1 + %element2 = getelementptr [2 x ptr], ptr %array, i32 0, i32 1 + %q = load ptr, ptr %element2 + store i8 1, ptr %q + ret void +} + +define void @whole_struct_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %object = alloca %Pair + %field = getelementptr %Pair, ptr %object, i32 0, i32 1 + store ptr %p, ptr %field + %whole = load %Pair, ptr %object + %q = extractvalue %Pair %whole, 1 + store i8 1, ptr %q + ret void +} + +define void @read_before_store() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr null, ptr %slot + %before = load ptr, ptr %slot + store ptr %before, ptr @escaped + store ptr %p, ptr %slot + ret void +} + +define void @overwritten_copy() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + store ptr null, ptr %slot + %q = load ptr, ptr %slot + store ptr %q, ptr @escaped + ret void +} + +define void @selected_destination(i1 %condition) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + %selected = select i1 %condition, ptr %slot, ptr %slot + store ptr %p, ptr %selected + %q = load ptr, ptr %slot + store i8 1, ptr %q + ret void +} + +define void @callee_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr %p, ptr %slot + call void @read_slot(ptr %slot) + ret void +} + +define void @read_before_store_norecurse() norecurse { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr + store ptr null, ptr %slot + %before = load ptr, ptr %slot + store ptr %before, ptr @escaped + store ptr %p, ptr %slot + ret void +} + +define void @two_underlying_objects(i1 %condition) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot1 = alloca ptr + %slot2 = alloca ptr + %selected = select i1 %condition, ptr %slot1, ptr %slot2 + store ptr %p, ptr %selected + %q1 = load ptr, ptr %slot1 + %q2 = load ptr, ptr %slot2 + store i8 1, ptr %q1 + store i8 2, ptr %q2 + ret void +} + +define void @finite_dynamic_offset(i1 %condition) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %array = alloca [2 x ptr] + %index = select i1 %condition, i64 0, i64 1 + %element = getelementptr [2 x ptr], ptr %array, i64 0, i64 %index + store ptr %p, ptr %element + %q = load ptr, ptr %element + store i8 1, ptr %q + ret void +} + +define void @unknown_dynamic_offset(i64 %index) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %array = alloca [2 x ptr] + %element = getelementptr [2 x ptr], ptr %array, i64 0, i64 %index + store ptr %p, ptr %element + %q = load ptr, ptr %element + store i8 1, ptr %q + ret void +} + +define void @memcpy_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %source = alloca ptr + %destination = alloca ptr + store ptr %p, ptr %source + call void @llvm.memcpy.p0.p0.i64(ptr %destination, ptr %source, i64 8, i1 false) + %q = load ptr, ptr %destination + store i8 1, ptr %q + ret void +} + +define void @internal_global_copy() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr @local_slot + %q = load ptr, ptr @local_slot + store i8 1, ptr %q + ret void +} + +define void @noalias_object_copy() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = call noalias ptr @slot_alloc(i64 8) + store ptr %p, ptr %slot + %q = load ptr, ptr %slot + store i8 1, ptr %q + ret void +} diff --git a/internal/escape/testdata/exact-copy/out.txt b/internal/escape/testdata/exact-copy/out.txt new file mode 100644 index 0000000000..aa6394d109 --- /dev/null +++ b/internal/escape/testdata/exact-copy/out.txt @@ -0,0 +1,256 @@ +; ModuleID = 'testdata/exact-copy/in.txt' +source_filename = "testdata/exact-copy/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +%Pair = type { i32, ptr } + +@escaped = global ptr null +@local_slot = internal global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +declare void @clobber(ptr) + +declare noalias ptr @slot_alloc(i64) + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0 + +define void @read_slot(ptr %slot) { +entry: + %p = load ptr, ptr %slot, align 8 + store i8 1, ptr %p, align 1 + ret void +} + +define void @exact_copy() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr %p.stack, ptr %slot, align 8 + %q = load ptr, ptr %slot, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @alias() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + %alias = getelementptr ptr, ptr %slot, i64 0 + store ptr %p.stack, ptr %slot, align 8 + %q = load ptr, ptr %alias, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @slot_address_escape() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr, align 8 + store ptr %p, ptr %slot, align 8 + store ptr %slot, ptr @escaped, align 8 + ret void +} + +define void @unknown_clobber() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr, align 8 + store ptr %p, ptr %slot, align 8 + call void @clobber(ptr %slot) + ret void +} + +define void @unsupported_reader() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr %p.stack, ptr %slot, align 8 + %q = load i64, ptr %slot, align 8 + ret void +} + +define void @other_store() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr %p.stack, ptr %slot, align 8 + store ptr null, ptr %slot, align 8 + ret void +} + +define void @global_destination() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr @escaped, align 8 + ret void +} + +define void @nonlocal_destination(ptr %dst) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr %dst, align 8 + ret void +} + +define void @struct_field() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %object = alloca %Pair, align 8 + %field1 = getelementptr %Pair, ptr %object, i32 0, i32 1 + store ptr %p.stack, ptr %field1, align 8 + %field2 = getelementptr %Pair, ptr %object, i32 0, i32 1 + %q = load ptr, ptr %field2, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @array_element() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %array = alloca [2 x ptr], align 8 + %element1 = getelementptr [2 x ptr], ptr %array, i32 0, i32 1 + store ptr %p.stack, ptr %element1, align 8 + %element2 = getelementptr [2 x ptr], ptr %array, i32 0, i32 1 + %q = load ptr, ptr %element2, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @whole_struct_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %object = alloca %Pair, align 8 + %field = getelementptr %Pair, ptr %object, i32 0, i32 1 + store ptr %p, ptr %field, align 8 + %whole = load %Pair, ptr %object, align 8 + %q = extractvalue %Pair %whole, 1 + store i8 1, ptr %q, align 1 + ret void +} + +define void @read_before_store() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %slot = alloca ptr, align 8 + store ptr null, ptr %slot, align 8 + %before = load ptr, ptr %slot, align 8 + store ptr %before, ptr @escaped, align 8 + store ptr %p, ptr %slot, align 8 + ret void +} + +define void @overwritten_copy() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr %p.stack, ptr %slot, align 8 + store ptr null, ptr %slot, align 8 + %q = load ptr, ptr %slot, align 8 + store ptr %q, ptr @escaped, align 8 + ret void +} + +define void @selected_destination(i1 %condition) { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + %selected = select i1 %condition, ptr %slot, ptr %slot + store ptr %p.stack, ptr %selected, align 8 + %q = load ptr, ptr %slot, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @callee_reader() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr %p.stack, ptr %slot, align 8 + call void @read_slot(ptr %slot) + ret void +} + +; Function Attrs: norecurse +define void @read_before_store_norecurse() #1 { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = alloca ptr, align 8 + store ptr null, ptr %slot, align 8 + %before = load ptr, ptr %slot, align 8 + store ptr %before, ptr @escaped, align 8 + store ptr %p.stack, ptr %slot, align 8 + ret void +} + +define void @two_underlying_objects(i1 %condition) { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot1 = alloca ptr, align 8 + %slot2 = alloca ptr, align 8 + %selected = select i1 %condition, ptr %slot1, ptr %slot2 + store ptr %p.stack, ptr %selected, align 8 + %q1 = load ptr, ptr %slot1, align 8 + %q2 = load ptr, ptr %slot2, align 8 + store i8 1, ptr %q1, align 1 + store i8 2, ptr %q2, align 1 + ret void +} + +define void @finite_dynamic_offset(i1 %condition) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %array = alloca [2 x ptr], align 8 + %index = select i1 %condition, i64 0, i64 1 + %element = getelementptr [2 x ptr], ptr %array, i64 0, i64 %index + store ptr %p, ptr %element, align 8 + %q = load ptr, ptr %element, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @unknown_dynamic_offset(i64 %index) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %array = alloca [2 x ptr], align 8 + %element = getelementptr [2 x ptr], ptr %array, i64 0, i64 %index + store ptr %p, ptr %element, align 8 + %q = load ptr, ptr %element, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @memcpy_reader() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %source = alloca ptr, align 8 + %destination = alloca ptr, align 8 + store ptr %p, ptr %source, align 8 + call void @llvm.memcpy.p0.p0.i64(ptr %destination, ptr %source, i64 8, i1 false) + %q = load ptr, ptr %destination, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @internal_global_copy() { +entry: + %p.stack = alloca i8, i64 8, align 1 + store ptr %p.stack, ptr @local_slot, align 8 + %q = load ptr, ptr @local_slot, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +define void @noalias_object_copy() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %slot = call noalias ptr @slot_alloc(i64 8) + store ptr %p.stack, ptr %slot, align 8 + %q = load ptr, ptr %slot, align 8 + store i8 1, ptr %q, align 1 + ret void +} + +attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #1 = { norecurse } diff --git a/internal/escape/testdata/invoke/in.txt b/internal/escape/testdata/invoke/in.txt new file mode 100644 index 0000000000..3134bfd7e0 --- /dev/null +++ b/internal/escape/testdata/invoke/in.txt @@ -0,0 +1,33 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) +declare i32 @personality(...) +declare void @external(ptr) + +define void @read_invoke(ptr %p) { +entry: + %v = load i8, ptr %p + ret void +} + +define void @known_invoke() personality ptr @personality { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + invoke void @read_invoke(ptr %p) to label %done unwind label %failed +done: + ret void +failed: + %landing = landingpad { ptr, i32 } cleanup + resume { ptr, i32 } %landing +} + +define void @unknown_invoke() personality ptr @personality { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + invoke void @external(ptr %p) to label %done unwind label %failed +done: + ret void +failed: + %landing = landingpad { ptr, i32 } cleanup + resume { ptr, i32 } %landing +} diff --git a/internal/escape/testdata/invoke/out.txt b/internal/escape/testdata/invoke/out.txt new file mode 100644 index 0000000000..e0ec352310 --- /dev/null +++ b/internal/escape/testdata/invoke/out.txt @@ -0,0 +1,45 @@ +; ModuleID = 'testdata/invoke/in.txt' +source_filename = "testdata/invoke/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +declare i32 @personality(...) + +declare void @external(ptr) + +define void @read_invoke(ptr %p) { +entry: + %v = load i8, ptr %p, align 1 + ret void +} + +define void @known_invoke() personality ptr @personality { +entry: + %p.stack = alloca i8, i64 8, align 1 + invoke void @read_invoke(ptr %p.stack) + to label %done unwind label %failed + +done: ; preds = %entry + ret void + +failed: ; preds = %entry + %landing = landingpad { ptr, i32 } + cleanup + resume { ptr, i32 } %landing +} + +define void @unknown_invoke() personality ptr @personality { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + invoke void @external(ptr %p) + to label %done unwind label %failed + +done: ; preds = %entry + ret void + +failed: ; preds = %entry + %landing = landingpad { ptr, i32 } + cleanup + resume { ptr, i32 } %landing +} diff --git a/internal/escape/testdata/rewrite-policy/in.txt b/internal/escape/testdata/rewrite-policy/in.txt new file mode 100644 index 0000000000..22011df609 --- /dev/null +++ b/internal/escape/testdata/rewrite-policy/in.txt @@ -0,0 +1,73 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"(i64) +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define void @alloc_z() { +entry: + %p = call align 16 ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"(i64 8) + store i8 1, ptr %p + ret void +} + +define void @alloc_u() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p + ret void +} + +define void @escaping() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr @escaped + ret void +} + +define void @loop(i1 %again) { +entry: + br label %body +body: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p + br i1 %again, label %body, label %exit +exit: + ret void +} + +define void @dynamic_size(i64 %size) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 %size) + store i8 1, ptr %p + ret void +} + +define void @too_large() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 65537) + store i8 1, ptr %p + ret void +} + +define void @at_limit() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 65536) + store i8 1, ptr %p + ret void +} + +define void @zero_size() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0) + %same = icmp eq ptr %p, null + ret void +} + +define void @excessive_alignment() { +entry: + %p = call align 131072 ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p + ret void +} diff --git a/internal/escape/testdata/rewrite-policy/out.txt b/internal/escape/testdata/rewrite-policy/out.txt new file mode 100644 index 0000000000..a2bd36c7eb --- /dev/null +++ b/internal/escape/testdata/rewrite-policy/out.txt @@ -0,0 +1,84 @@ +; ModuleID = 'testdata/rewrite-policy/in.txt' +source_filename = "testdata/rewrite-policy/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@escaped = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"(i64) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +define void @alloc_z() { +entry: + %p.stack = alloca i8, i64 8, align 16 + call void @llvm.memset.p0.i64(ptr %p.stack, i8 0, i64 8, i1 false) + store i8 1, ptr %p.stack, align 1 + ret void +} + +define void @alloc_u() { +entry: + %p.stack = alloca i8, i64 8, align 1 + store i8 1, ptr %p.stack, align 1 + ret void +} + +define void @escaping() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store ptr %p, ptr @escaped, align 8 + ret void +} + +define void @loop(i1 %again) { +entry: + br label %body + +body: ; preds = %body, %entry + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p, align 1 + br i1 %again, label %body, label %exit + +exit: ; preds = %body + ret void +} + +define void @dynamic_size(i64 %size) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 %size) + store i8 1, ptr %p, align 1 + ret void +} + +define void @too_large() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 65537) + store i8 1, ptr %p, align 1 + ret void +} + +define void @at_limit() { +entry: + %p.stack = alloca i8, i64 65536, align 1 + store i8 1, ptr %p.stack, align 1 + ret void +} + +define void @zero_size() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0) + %same = icmp eq ptr %p, null + ret void +} + +define void @excessive_alignment() { +entry: + %p = call align 131072 ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i8 1, ptr %p, align 1 + ret void +} + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #0 + +attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: write) } diff --git a/internal/escape/testdata/safe-uses/in.txt b/internal/escape/testdata/safe-uses/in.txt new file mode 100644 index 0000000000..1b21ff8fca --- /dev/null +++ b/internal/escape/testdata/safe-uses/in.txt @@ -0,0 +1,104 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) +declare void @llvm.memset.p0.i64(ptr writeonly, i8, i64, i1 immarg) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly, ptr noalias readonly, i64, i1 immarg) + +@bytes = global [8 x i8] zeroinitializer +@escaped_pointer = global ptr null + +define i64 @load_store() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store i64 7, ptr %p, align 8 + %v = load i64, ptr %p, align 8 + ret i64 %v +} + +define void @gep() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = getelementptr i8, ptr %p, i64 1 + store i8 7, ptr %q, align 1 + ret void +} + +define void @bitcast() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = bitcast ptr %p to ptr + store i8 7, ptr %q, align 1 + ret void +} + +define void @select_value(i1 %cond) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = select i1 %cond, ptr %p, ptr %p + store i8 7, ptr %q, align 1 + ret void +} + +define void @phi_cycle(i1 %again) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + br label %loop +loop: + %q = phi ptr [ %p, %entry ], [ %q, %loop ] + store i8 7, ptr %q, align 1 + br i1 %again, label %loop, label %exit +exit: + ret void +} + +define i1 @compare() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %same = icmp eq ptr %p, null + ret i1 %same +} + +define void @atomic_addresses() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store atomic i32 1, ptr %p monotonic, align 4 + %v = load atomic i32, ptr %p monotonic, align 4 + %cx = cmpxchg ptr %p, i32 %v, i32 2 monotonic monotonic, align 4 + %rmw = atomicrmw add ptr %p, i32 1 monotonic, align 4 + ret void +} + +define void @freeze_value() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = freeze ptr %p + store i8 7, ptr %q + ret void +} + +define void @volatile_addresses() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + store volatile i8 7, ptr %p + %v = load volatile i8, ptr %p + ret void +} + +define void @safe_intrinsics() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @llvm.lifetime.start.p0(i64 8, ptr %p) + call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 8, i1 false) + call void @llvm.memcpy.p0.p0.i64(ptr @bytes, ptr %p, i64 8, i1 false) + call void @llvm.lifetime.end.p0(i64 8, ptr %p) + ret void +} + +define void @cmpxchg_expected() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = cmpxchg ptr @escaped_pointer, ptr %p, ptr null monotonic monotonic, align 8 + ret void +} diff --git a/internal/escape/testdata/safe-uses/out.txt b/internal/escape/testdata/safe-uses/out.txt new file mode 100644 index 0000000000..c7d6fc3967 --- /dev/null +++ b/internal/escape/testdata/safe-uses/out.txt @@ -0,0 +1,120 @@ +; ModuleID = 'testdata/safe-uses/in.txt' +source_filename = "testdata/safe-uses/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +@bytes = global [8 x i8] zeroinitializer +@escaped_pointer = global ptr null + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #0 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #0 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #1 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2 + +define i64 @load_store() { +entry: + %p.stack = alloca i8, i64 8, align 8 + store i64 7, ptr %p.stack, align 8 + %v = load i64, ptr %p.stack, align 8 + ret i64 %v +} + +define void @gep() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %q = getelementptr i8, ptr %p.stack, i64 1 + store i8 7, ptr %q, align 1 + ret void +} + +define void @bitcast() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %q = bitcast ptr %p.stack to ptr + store i8 7, ptr %q, align 1 + ret void +} + +define void @select_value(i1 %cond) { +entry: + %p.stack = alloca i8, i64 8, align 1 + %q = select i1 %cond, ptr %p.stack, ptr %p.stack + store i8 7, ptr %q, align 1 + ret void +} + +define void @phi_cycle(i1 %again) { +entry: + %p.stack = alloca i8, i64 8, align 1 + br label %loop + +loop: ; preds = %loop, %entry + %q = phi ptr [ %p.stack, %entry ], [ %q, %loop ] + store i8 7, ptr %q, align 1 + br i1 %again, label %loop, label %exit + +exit: ; preds = %loop + ret void +} + +define i1 @compare() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %same = icmp eq ptr %p, null + ret i1 %same +} + +define void @atomic_addresses() { +entry: + %p.stack = alloca i8, i64 8, align 4 + store atomic i32 1, ptr %p.stack monotonic, align 4 + %v = load atomic i32, ptr %p.stack monotonic, align 4 + %cx = cmpxchg ptr %p.stack, i32 %v, i32 2 monotonic monotonic, align 4 + %rmw = atomicrmw add ptr %p.stack, i32 1 monotonic, align 4 + ret void +} + +define void @freeze_value() { +entry: + %p.stack = alloca i8, i64 8, align 1 + %q = freeze ptr %p.stack + store i8 7, ptr %q, align 1 + ret void +} + +define void @volatile_addresses() { +entry: + %p.stack = alloca i8, i64 8, align 1 + store volatile i8 7, ptr %p.stack, align 1 + %v = load volatile i8, ptr %p.stack, align 1 + ret void +} + +define void @safe_intrinsics() { +entry: + %p.stack = alloca i8, i64 8, align 1 + call void @llvm.lifetime.start.p0(i64 8, ptr %p.stack) + call void @llvm.memset.p0.i64(ptr %p.stack, i8 0, i64 8, i1 false) + call void @llvm.memcpy.p0.p0.i64(ptr @bytes, ptr %p.stack, i64 8, i1 false) + call void @llvm.lifetime.end.p0(i64 8, ptr %p.stack) + ret void +} + +define void @cmpxchg_expected() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %old = cmpxchg ptr @escaped_pointer, ptr %p, ptr null monotonic monotonic, align 8 + ret void +} + +attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: write) } +attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } diff --git a/internal/escape/testdata/unknown-calls/in.txt b/internal/escape/testdata/unknown-calls/in.txt new file mode 100644 index 0000000000..3d3f74744d --- /dev/null +++ b/internal/escape/testdata/unknown-calls/in.txt @@ -0,0 +1,41 @@ +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) +declare void @c_function(ptr) +declare void @"github.com/goplus/llgo/runtime/internal/runtime.ChanSend"(ptr) +declare ptr @llvm.launder.invariant.group.p0(ptr) + +define void @bodyless_c() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @c_function(ptr %p) + ret void +} + +define void @runtime_call() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @"github.com/goplus/llgo/runtime/internal/runtime.ChanSend"(ptr %p) + ret void +} + +define void @indirect_call(ptr %fn) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void %fn(ptr %p) + ret void +} + +define void @inline_asm() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void asm sideeffect "", "r"(ptr %p) + ret void +} + +define void @unknown_intrinsic() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @llvm.launder.invariant.group.p0(ptr %p) + ret void +} diff --git a/internal/escape/testdata/unknown-calls/out.txt b/internal/escape/testdata/unknown-calls/out.txt new file mode 100644 index 0000000000..6aa9970710 --- /dev/null +++ b/internal/escape/testdata/unknown-calls/out.txt @@ -0,0 +1,49 @@ +; ModuleID = 'testdata/unknown-calls/in.txt' +source_filename = "testdata/unknown-calls/in.txt" +target datalayout = "e-p:64:64-i64:64-n32:64-S128" + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +declare void @c_function(ptr) + +declare void @"github.com/goplus/llgo/runtime/internal/runtime.ChanSend"(ptr) + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(inaccessiblemem: readwrite) +declare ptr @llvm.launder.invariant.group.p0(ptr) #0 + +define void @bodyless_c() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @c_function(ptr %p) + ret void +} + +define void @runtime_call() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void @"github.com/goplus/llgo/runtime/internal/runtime.ChanSend"(ptr %p) + ret void +} + +define void @indirect_call(ptr %fn) { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void %fn(ptr %p) + ret void +} + +define void @inline_asm() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + call void asm sideeffect "", "r"(ptr %p) + ret void +} + +define void @unknown_intrinsic() { +entry: + %p = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8) + %q = call ptr @llvm.launder.invariant.group.p0(ptr %p) + ret void +} + +attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(inaccessiblemem: readwrite) } diff --git a/internal/littest/littest.go b/internal/littest/littest.go index 0147603631..989e3d686b 100644 --- a/internal/littest/littest.go +++ b/internal/littest/littest.go @@ -62,13 +62,41 @@ func LoadSpec(pkgDir string) (Spec, error) { return Spec{Path: path, Text: string(data), Mode: ModeLiteral}, nil } -func Check(spec Spec, actual string) error { +func Check(spec Spec, actual string, prefixes ...string) error { switch spec.Mode { case ModeSkip: return nil case ModeFileCheck: + if len(prefixes) != 0 { + data, err := os.ReadFile(spec.Path) + if err != nil { + return err + } + lines := strings.Split(string(data), "\n") + used := make([]string, 0, len(prefixes)) + for _, prefix := range prefixes { + for _, line := range lines { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "//") { + continue + } + directive := strings.TrimSpace(strings.TrimPrefix(line, "//")) + if strings.HasPrefix(directive, prefix+":") || strings.HasPrefix(directive, prefix+"-") { + used = append(used, prefix) + break + } + } + } + if len(used) == 0 { + return nil + } + return filecheck.MatchWithPrefixes(spec.Path, actual, used...) + } return filecheck.Match(spec.Path, actual) case ModeLiteral: + if len(prefixes) != 0 { + return nil + } if actual != spec.Text { return fmt.Errorf("%s: literal LLVM IR mismatch", spec.Path) } diff --git a/internal/littest/littest_test.go b/internal/littest/littest_test.go index d02d899375..4042cc8988 100644 --- a/internal/littest/littest_test.go +++ b/internal/littest/littest_test.go @@ -195,16 +195,22 @@ func TestHasMarker(t *testing.T) { } func TestCheck(t *testing.T) { - checkPath := filepath.Join(t.TempDir(), "check.go") + dir := t.TempDir() + checkPath := filepath.Join(dir, "check.go") if err := os.WriteFile(checkPath, []byte("// CHECK: ok\n"), 0644); err != nil { t.Fatal(err) } + escapeCheckPath := filepath.Join(dir, "escape.go") + if err := os.WriteFile(escapeCheckPath, []byte("// ESCAPE: escaped\n"), 0644); err != nil { + t.Fatal(err) + } cases := []struct { - name string - spec Spec - text string - want string + name string + spec Spec + text string + prefixes []string + want string }{ { name: "skip", @@ -226,6 +232,23 @@ func TestCheck(t *testing.T) { spec: Spec{Path: checkPath, Mode: ModeFileCheck}, text: "ok\n", }, + { + name: "filecheck prefix match", + spec: Spec{Path: escapeCheckPath, Mode: ModeFileCheck}, + text: "escaped\n", + prefixes: []string{"ESCAPE"}, + }, + { + name: "filecheck unused prefix", + spec: Spec{Path: checkPath, Mode: ModeFileCheck}, + prefixes: []string{"ESCAPE"}, + }, + { + name: "literal ignores prefix", + spec: Spec{Path: "literal", Text: "ok", Mode: ModeLiteral}, + text: "escaped", + prefixes: []string{"ESCAPE"}, + }, { name: "invalid mode", spec: Spec{Path: "bad", Mode: Mode(99)}, @@ -234,7 +257,7 @@ func TestCheck(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - err := Check(tc.spec, tc.text) + err := Check(tc.spec, tc.text, tc.prefixes...) if tc.want == "" { if err != nil { t.Fatal(err) diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 2629328a05..dcb0402859 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -28,7 +28,15 @@ import ( ) func GenFrom(fileOrPkg string) string { - pkg, err := genFrom(fileOrPkg, 0) + return genStringFrom(fileOrPkg, true) +} + +func GenFromWithEscape(fileOrPkg string) string { + return genStringFrom(fileOrPkg, false) +} + +func genStringFrom(fileOrPkg string, disableEscapeAnalysis bool) string { + pkg, err := genFrom(fileOrPkg, 0, disableEscapeAnalysis) check(err) out := pkg.LPkg.String() // Release the compile's LLVM context: golden suites call GenFrom for @@ -38,11 +46,12 @@ func GenFrom(fileOrPkg string) string { return out } -func genFrom(pkgPath string, abiMode build.AbiMode) (build.Package, error) { +func genFrom(pkgPath string, abiMode build.AbiMode, disableEscapeAnalysis bool) (build.Package, error) { conf := &build.Config{ - Mode: build.ModeGen, - AbiMode: abiMode, - GenLL: true, + Mode: build.ModeGen, + AbiMode: abiMode, + GenLL: true, + DisableEscapeAnalysis: disableEscapeAnalysis, } if err := applyGoBuildFlagsFile(conf, filepath.Join(pkgPath, "flags.txt")); err != nil { return nil, err @@ -91,7 +100,7 @@ func SmartDoFile(pkgPath string) { } func SmartDoFileEx(pkgPath string, abiMode build.AbiMode) { - pkg, err := genFrom(pkgPath, abiMode) + pkg, err := genFrom(pkgPath, abiMode, false) check(err) const autgenFile = "llgo_autogen.ll"