Skip to content

Commit bbe5da4

Browse files
cmd/compile, syscall: add //go:uintptrescapes comment, and use it
This new comment can be used to declare that the uintptr arguments to a function may be converted from pointers, and that those pointers should be considered to escape. This is used for the Call methods in dll_windows.go that take uintptr arguments, because they call Syscall. We can't treat these functions as we do syscall.Syscall, because unlike Syscall they may cause the stack to grow. For Syscall we can assume that stack arguments can remain on the stack, but for these functions we need them to escape. Fixes #16035. Change-Id: Ia0e5b4068c04f8d303d95ab9ea394939f1f57454 Reviewed-on: https://go-review.googlesource.com/24551 Reviewed-by: David Chase <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 820e30f commit bbe5da4

File tree

8 files changed

+259
-12
lines changed

8 files changed

+259
-12
lines changed

src/cmd/compile/internal/gc/esc.go

+50-10
Original file line numberDiff line numberDiff line change
@@ -1551,10 +1551,12 @@ func esccall(e *EscState, n *Node, up *Node) {
15511551
}
15521552

15531553
var src *Node
1554+
note := ""
15541555
i := 0
15551556
lls := ll.Slice()
15561557
for t, it := IterFields(fntype.Params()); i < len(lls); i++ {
15571558
src = lls[i]
1559+
note = t.Note
15581560
if t.Isddd && !n.Isddd {
15591561
// Introduce ODDDARG node to represent ... allocation.
15601562
src = Nod(ODDDARG, nil, nil)
@@ -1566,7 +1568,7 @@ func esccall(e *EscState, n *Node, up *Node) {
15661568
}
15671569

15681570
if haspointers(t.Type) {
1569-
if escassignfromtag(e, t.Note, nE.Escretval, src) == EscNone && up.Op != ODEFER && up.Op != OPROC {
1571+
if escassignfromtag(e, note, nE.Escretval, src) == EscNone && up.Op != ODEFER && up.Op != OPROC {
15701572
a := src
15711573
for a.Op == OCONVNOP {
15721574
a = a.Left
@@ -1596,14 +1598,24 @@ func esccall(e *EscState, n *Node, up *Node) {
15961598
// This occurs when function parameter type Isddd and n not Isddd
15971599
break
15981600
}
1601+
1602+
if note == uintptrEscapesTag {
1603+
escassignSinkNilWhy(e, src, src, "escaping uintptr")
1604+
}
1605+
15991606
t = it.Next()
16001607
}
16011608

1609+
// Store arguments into slice for ... arg.
16021610
for ; i < len(lls); i++ {
16031611
if Debug['m'] > 3 {
16041612
fmt.Printf("%v::esccall:: ... <- %v\n", linestr(lineno), Nconv(lls[i], FmtShort))
16051613
}
1606-
escassignNilWhy(e, src, lls[i], "arg to ...") // args to slice
1614+
if note == uintptrEscapesTag {
1615+
escassignSinkNilWhy(e, src, lls[i], "arg to uintptrescapes ...")
1616+
} else {
1617+
escassignNilWhy(e, src, lls[i], "arg to ...")
1618+
}
16071619
}
16081620
}
16091621

@@ -1963,9 +1975,20 @@ recurse:
19631975
// lets us take the address below to get a *string.
19641976
var unsafeUintptrTag = "unsafe-uintptr"
19651977

1978+
// This special tag is applied to uintptr parameters of functions
1979+
// marked go:uintptrescapes.
1980+
const uintptrEscapesTag = "uintptr-escapes"
1981+
19661982
func esctag(e *EscState, func_ *Node) {
19671983
func_.Esc = EscFuncTagged
19681984

1985+
name := func(s *Sym, narg int) string {
1986+
if s != nil {
1987+
return s.Name
1988+
}
1989+
return fmt.Sprintf("arg#%d", narg)
1990+
}
1991+
19691992
// External functions are assumed unsafe,
19701993
// unless //go:noescape is given before the declaration.
19711994
if func_.Nbody.Len() == 0 {
@@ -1988,13 +2011,7 @@ func esctag(e *EscState, func_ *Node) {
19882011
narg++
19892012
if t.Type.Etype == TUINTPTR {
19902013
if Debug['m'] != 0 {
1991-
var name string
1992-
if t.Sym != nil {
1993-
name = t.Sym.Name
1994-
} else {
1995-
name = fmt.Sprintf("arg#%d", narg)
1996-
}
1997-
Warnl(func_.Lineno, "%v assuming %v is unsafe uintptr", funcSym(func_), name)
2014+
Warnl(func_.Lineno, "%v assuming %v is unsafe uintptr", funcSym(func_), name(t.Sym, narg))
19982015
}
19992016
t.Note = unsafeUintptrTag
20002017
}
@@ -2003,6 +2020,27 @@ func esctag(e *EscState, func_ *Node) {
20032020
return
20042021
}
20052022

2023+
if func_.Func.Pragma&UintptrEscapes != 0 {
2024+
narg := 0
2025+
for _, t := range func_.Type.Params().Fields().Slice() {
2026+
narg++
2027+
if t.Type.Etype == TUINTPTR {
2028+
if Debug['m'] != 0 {
2029+
Warnl(func_.Lineno, "%v marking %v as escaping uintptr", funcSym(func_), name(t.Sym, narg))
2030+
}
2031+
t.Note = uintptrEscapesTag
2032+
}
2033+
2034+
if t.Isddd && t.Type.Elem().Etype == TUINTPTR {
2035+
// final argument is ...uintptr.
2036+
if Debug['m'] != 0 {
2037+
Warnl(func_.Lineno, "%v marking %v as escaping ...uintptr", funcSym(func_), name(t.Sym, narg))
2038+
}
2039+
t.Note = uintptrEscapesTag
2040+
}
2041+
}
2042+
}
2043+
20062044
savefn := Curfn
20072045
Curfn = func_
20082046

@@ -2015,7 +2053,9 @@ func esctag(e *EscState, func_ *Node) {
20152053
case EscNone, // not touched by escflood
20162054
EscReturn:
20172055
if haspointers(ln.Type) { // don't bother tagging for scalars
2018-
ln.Name.Param.Field.Note = mktag(int(ln.Esc))
2056+
if ln.Name.Param.Field.Note != uintptrEscapesTag {
2057+
ln.Name.Param.Field.Note = mktag(int(ln.Esc))
2058+
}
20192059
}
20202060

20212061
case EscHeap, // touched by escflood, moved to heap

src/cmd/compile/internal/gc/lex.go

+14
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const (
7272
Nowritebarrier // emit compiler error instead of write barrier
7373
Nowritebarrierrec // error on write barrier in this or recursive callees
7474
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
75+
UintptrEscapes // pointers converted to uintptr escape
7576
)
7677

7778
type lexer struct {
@@ -930,6 +931,19 @@ func (l *lexer) getlinepragma() rune {
930931
l.pragma |= Nowritebarrierrec | Nowritebarrier // implies Nowritebarrier
931932
case "go:cgo_unsafe_args":
932933
l.pragma |= CgoUnsafeArgs
934+
case "go:uintptrescapes":
935+
// For the next function declared in the file
936+
// any uintptr arguments may be pointer values
937+
// converted to uintptr. This directive
938+
// ensures that the referenced allocated
939+
// object, if any, is retained and not moved
940+
// until the call completes, even though from
941+
// the types alone it would appear that the
942+
// object is no longer needed during the
943+
// call. The conversion to uintptr must appear
944+
// in the argument list.
945+
// Used in syscall/dll_windows.go.
946+
l.pragma |= UintptrEscapes
933947
}
934948
return c
935949
}

src/cmd/compile/internal/gc/order.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ func ordercall(n *Node, order *Order) {
373373
if t == nil {
374374
break
375375
}
376-
if t.Note == unsafeUintptrTag {
376+
if t.Note == unsafeUintptrTag || t.Note == uintptrEscapesTag {
377377
xp := n.List.Addr(i)
378378
for (*xp).Op == OCONVNOP && !(*xp).Type.IsPtr() {
379379
xp = &(*xp).Left
@@ -385,7 +385,11 @@ func ordercall(n *Node, order *Order) {
385385
*xp = x
386386
}
387387
}
388-
t = it.Next()
388+
next := it.Next()
389+
if next == nil && t.Isddd && t.Note == uintptrEscapesTag {
390+
next = t
391+
}
392+
t = next
389393
}
390394
}
391395
}

src/syscall/dll_windows.go

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ func (p *Proc) Addr() uintptr {
130130
return p.addr
131131
}
132132

133+
//go:uintptrescapes
134+
133135
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
134136
// are supplied.
135137
//
@@ -288,6 +290,8 @@ func (p *LazyProc) Addr() uintptr {
288290
return p.proc.Addr()
289291
}
290292

293+
//go:uintptrescapes
294+
291295
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
292296
// are supplied.
293297
//

test/uintptrescapes.dir/a.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
import (
8+
"unsafe"
9+
)
10+
11+
func recurse(i int, s []byte) byte {
12+
s[0] = byte(i)
13+
if i == 0 {
14+
return s[i]
15+
} else {
16+
var a [1024]byte
17+
r := recurse(i-1, a[:])
18+
return r + a[0]
19+
}
20+
}
21+
22+
//go:uintptrescapes
23+
func F1(a uintptr) {
24+
var s [16]byte
25+
recurse(4096, s[:])
26+
*(*int)(unsafe.Pointer(a)) = 42
27+
}
28+
29+
//go:uintptrescapes
30+
func F2(a ...uintptr) {
31+
var s [16]byte
32+
recurse(4096, s[:])
33+
*(*int)(unsafe.Pointer(a[0])) = 42
34+
}
35+
36+
type t struct{}
37+
38+
func GetT() *t {
39+
return &t{}
40+
}
41+
42+
//go:uintptrescapes
43+
func (*t) M1(a uintptr) {
44+
var s [16]byte
45+
recurse(4096, s[:])
46+
*(*int)(unsafe.Pointer(a)) = 42
47+
}
48+
49+
//go:uintptrescapes
50+
func (*t) M2(a ...uintptr) {
51+
var s [16]byte
52+
recurse(4096, s[:])
53+
*(*int)(unsafe.Pointer(a[0])) = 42
54+
}

test/uintptrescapes.dir/main.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"fmt"
9+
"os"
10+
"sync"
11+
"unsafe"
12+
13+
"./a"
14+
)
15+
16+
func F1() int {
17+
var buf [1024]int
18+
a.F1(uintptr(unsafe.Pointer(&buf[0])))
19+
return buf[0]
20+
}
21+
22+
func F2() int {
23+
var buf [1024]int
24+
a.F2(uintptr(unsafe.Pointer(&buf[0])))
25+
return buf[0]
26+
}
27+
28+
var t = a.GetT()
29+
30+
func M1() int {
31+
var buf [1024]int
32+
t.M1(uintptr(unsafe.Pointer(&buf[0])))
33+
return buf[0]
34+
}
35+
36+
func M2() int {
37+
var buf [1024]int
38+
t.M2(uintptr(unsafe.Pointer(&buf[0])))
39+
return buf[0]
40+
}
41+
42+
func main() {
43+
// Use different goroutines to force stack growth.
44+
var wg sync.WaitGroup
45+
wg.Add(4)
46+
c := make(chan bool, 4)
47+
48+
go func() {
49+
defer wg.Done()
50+
b := F1()
51+
if b != 42 {
52+
fmt.Printf("F1: got %d, expected 42\n", b)
53+
c <- false
54+
}
55+
}()
56+
57+
go func() {
58+
defer wg.Done()
59+
b := F2()
60+
if b != 42 {
61+
fmt.Printf("F2: got %d, expected 42\n", b)
62+
c <- false
63+
}
64+
}()
65+
66+
go func() {
67+
defer wg.Done()
68+
b := M1()
69+
if b != 42 {
70+
fmt.Printf("M1: got %d, expected 42\n", b)
71+
c <- false
72+
}
73+
}()
74+
75+
go func() {
76+
defer wg.Done()
77+
b := M2()
78+
if b != 42 {
79+
fmt.Printf("M2: got %d, expected 42\n", b)
80+
c <- false
81+
}
82+
}()
83+
84+
wg.Wait()
85+
86+
select {
87+
case <-c:
88+
os.Exit(1)
89+
default:
90+
}
91+
}

test/uintptrescapes.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// rundir
2+
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Test that the go:uintptrescapes comment works as expected.
8+
9+
package ignored

test/uintptrescapes2.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// errorcheck -0 -m -live
2+
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Test escape analysis and liveness inferred for uintptrescapes functions.
8+
9+
package p
10+
11+
import (
12+
"unsafe"
13+
)
14+
15+
//go:uintptrescapes
16+
//go:noinline
17+
func F1(a uintptr) {} // ERROR "escaping uintptr"
18+
19+
//go:uintptrescapes
20+
//go:noinline
21+
func F2(a ...uintptr) {} // ERROR "escaping ...uintptr" "live at entry" "a does not escape"
22+
23+
func G() {
24+
var t int // ERROR "moved to heap"
25+
F1(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to F1: autotmp" "&t escapes to heap"
26+
}
27+
28+
func H() {
29+
var v int // ERROR "moved to heap"
30+
F2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: autotmp" "live at call to F2: autotmp" "escapes to heap"
31+
}

0 commit comments

Comments
 (0)