diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index d4b8297732..83a4af362c 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -120,6 +120,43 @@ func TestCGo(t *testing.T) { } } +func TestCGoPragmas(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "testdata/symbols.go", nil, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + files, _, _, _, _, errs := Process([]*ast.File{f}, "testdata", "main", fset, []string{"--target=armv6m-unknown-unknown-eabi"}, "linux") + if len(errs) != 0 { + t.Fatal(errs) + } + for _, file := range files { + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Name.Name != "_Cgo_variadicNoescape" { + continue + } + if fn.Body != nil { + t.Error("generated C function has a body") + } + if fn.Doc == nil { + t.Fatal("generated C function has no pragma comments") + } + var pragmas []string + for _, comment := range fn.Doc.List { + if strings.HasPrefix(comment.Text, "//go:") { + pragmas = append(pragmas, comment.Text) + } + } + if len(pragmas) != 2 || pragmas[0] != "//go:variadic" || pragmas[1] != "//go:noescape" { + t.Errorf("expected separate variadic and noescape comments, got %q", pragmas) + } + return + } + } + t.Fatal("missing generated variadicNoescape declaration") +} + func Test_cgoPackage_isEquivalentAST(t *testing.T) { fieldA := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "a"}} fieldB := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "b"}} diff --git a/cgo/libclang.go b/cgo/libclang.go index 5a434ce8db..5a23bf1de4 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -272,10 +272,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { doc = append(doc, "//go:noescape") f.noescapingFuncs[name].used = true } - if len(doc) != 0 { + for _, text := range doc { decl.Doc.List = append(decl.Doc.List, &ast.Comment{ Slash: pos - 1, - Text: strings.Join(doc, "\n"), + Text: text, }) } for i := range numArgs { diff --git a/cgo/testdata/symbols.go b/cgo/testdata/symbols.go index c8029a1481..0cd688523f 100644 --- a/cgo/testdata/symbols.go +++ b/cgo/testdata/symbols.go @@ -5,6 +5,7 @@ package main int foo(int a, int b); void variadic0(); void variadic2(int x, int y, ...); +void variadicNoescape(int *a, ...); static void staticfunc(int x); // Global variable signatures. @@ -13,6 +14,7 @@ extern int someValue; void notEscapingFunction(int *a); #cgo noescape notEscapingFunction +#cgo noescape variadicNoescape */ import "C" @@ -21,6 +23,7 @@ func accessFunctions() { C.foo(3, 4) C.variadic0() C.variadic2(3, 5) + C.variadicNoescape(nil) C.staticfunc(3) C.notEscapingFunction(nil) } diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index 8a603cfd7e..c2647db903 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -70,6 +70,13 @@ func _Cgo_variadic2(x _Cgo_int, y _Cgo_int) var _Cgo_variadic2$funcaddr unsafe.Pointer +//export variadicNoescape +//go:variadic +//go:noescape +func _Cgo_variadicNoescape(a *_Cgo_int) + +var _Cgo_variadicNoescape$funcaddr unsafe.Pointer + //export _Cgo_static_173c95a79b6df1980521_staticfunc func _Cgo_staticfunc!symbols.go(x _Cgo_int) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 30c73fb37c..7647938d22 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/transform" @@ -156,6 +157,29 @@ func TestCGoGlobalOrder(t *testing.T) { } } +func TestCGoPragmas(t *testing.T) { + mod, errs := testCompilePackage(t, &compileopts.Options{Target: "wasm"}, "cgo-pragmas.go") + if len(errs) != 0 { + t.Fatal(errs) + } + defer mod.Dispose() + + fn := mod.NamedFunction("variadicNoescape") + if fn.IsNil() { + t.Fatal("missing variadicNoescape declaration") + } + if !fn.GlobalValueType().IsFunctionVarArg() { + t.Error("C function declaration is not variadic") + } + nocapture := fn.GetEnumAttributeAtIndex(1, llvm.AttributeKindID(llvmutil.NoCaptureAttrName())) + if !llvmutil.IsNoCapture(nocapture) { + t.Error("C function pointer parameter can escape") + } + if err := llvm.VerifyModule(mod, llvm.ReturnStatusAction); err != nil { + t.Fatal(err) + } +} + func TestOptimizedLargeAggregateABI(t *testing.T) { options := &compileopts.Options{Target: "wasm"} mod, errs := testCompilePackage(t, options, "large-optimized.go") diff --git a/compiler/testdata/cgo-pragmas.go b/compiler/testdata/cgo-pragmas.go new file mode 100644 index 0000000000..c1d3de7e44 --- /dev/null +++ b/compiler/testdata/cgo-pragmas.go @@ -0,0 +1,11 @@ +package main + +/* +void variadicNoescape(int *a, ...); +#cgo noescape variadicNoescape +*/ +import "C" + +func callVariadicNoescape(a *C.int) { + C.variadicNoescape(a) +}