Skip to content

Commit 332719f

Browse files
runtime: don't call lockOSThread for every cgo call
For a trivial benchmark with a do-nothing cgo call: name old time/op new time/op delta Call-4 64.5ns ± 7% 63.0ns ± 6% -2.25% (p=0.027 n=20+16) Because Windows uses the cgocall mechanism to make system calls, and passes arguments in a struct held in the m, we need to do the lockOSThread/unlockOSThread in that code. Because deferreturn was getting a nosplit stack overflow error, change it to avoid calling typedmemmove. Updates #21827. Change-Id: I9b1d61434c44faeb29805b46b409c812c9acadc2 Reviewed-on: https://go-review.googlesource.com/64070 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]> Reviewed-by: David Crawshaw <[email protected]>
1 parent 9daee93 commit 332719f

File tree

5 files changed

+82
-35
lines changed

5 files changed

+82
-35
lines changed

src/runtime/cgocall.go

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
// runtime.cgocall(_cgo_Cfunc_f, frame), where _cgo_Cfunc_f is a
99
// gcc-compiled function written by cgo.
1010
//
11-
// runtime.cgocall (below) locks g to m, calls entersyscall
12-
// so as not to block other goroutines or the garbage collector,
13-
// and then calls runtime.asmcgocall(_cgo_Cfunc_f, frame).
11+
// runtime.cgocall (below) calls entersyscall so as not to block
12+
// other goroutines or the garbage collector, and then calls
13+
// runtime.asmcgocall(_cgo_Cfunc_f, frame).
1414
//
1515
// runtime.asmcgocall (in asm_$GOARCH.s) switches to the m->g0 stack
1616
// (assumed to be an operating system-allocated stack, so safe to run
@@ -104,13 +104,9 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
104104
racereleasemerge(unsafe.Pointer(&racecgosync))
105105
}
106106

107-
// Lock g to m to ensure we stay on the same stack if we do a
108-
// cgo callback. In case of panic, unwindm calls endcgo.
109-
lockOSThread()
110107
mp := getg().m
111108
mp.ncgocall++
112109
mp.ncgo++
113-
mp.incgo = true
114110

115111
// Reset traceback.
116112
mp.cgoCallers[0] = 0
@@ -130,7 +126,14 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
130126
// and then re-enter the "system call" reusing the PC and SP
131127
// saved by entersyscall here.
132128
entersyscall(0)
129+
130+
mp.incgo = true
133131
errno := asmcgocall(fn, arg)
132+
133+
// Call endcgo before exitsyscall because exitsyscall may
134+
// reschedule us on to a different M.
135+
endcgo(mp)
136+
134137
exitsyscall(0)
135138

136139
// From the garbage collector's perspective, time can move
@@ -145,8 +148,8 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
145148
// GC by forcing them to stay live across this time warp.
146149
KeepAlive(fn)
147150
KeepAlive(arg)
151+
KeepAlive(mp)
148152

149-
endcgo(mp)
150153
return errno
151154
}
152155

@@ -158,8 +161,6 @@ func endcgo(mp *m) {
158161
if raceenabled {
159162
raceacquire(unsafe.Pointer(&racecgosync))
160163
}
161-
162-
unlockOSThread() // invalidates mp
163164
}
164165

165166
// Call from C back to Go.
@@ -171,6 +172,12 @@ func cgocallbackg(ctxt uintptr) {
171172
exit(2)
172173
}
173174

175+
// The call from C is on gp.m's g0 stack, so we must ensure
176+
// that we stay on that M. We have to do this before calling
177+
// exitsyscall, since it would otherwise be free to move us to
178+
// a different M. The call to unlockOSThread is in unwindm.
179+
lockOSThread()
180+
174181
// Save current syscall parameters, so m.syscall can be
175182
// used again if callback decide to make syscall.
176183
syscall := gp.m.syscall
@@ -186,6 +193,10 @@ func cgocallbackg(ctxt uintptr) {
186193

187194
cgocallbackg1(ctxt)
188195

196+
// At this point unlockOSThread has been called.
197+
// The following code must not change to a different m.
198+
// This is enforced by checking incgo in the schedule function.
199+
189200
gp.m.incgo = true
190201
// going back to cgo call
191202
reentersyscall(savedpc, uintptr(savedsp))
@@ -321,32 +332,35 @@ func cgocallbackg1(ctxt uintptr) {
321332
}
322333

323334
func unwindm(restore *bool) {
324-
if !*restore {
325-
return
326-
}
327-
// Restore sp saved by cgocallback during
328-
// unwind of g's stack (see comment at top of file).
329-
mp := acquirem()
330-
sched := &mp.g0.sched
331-
switch GOARCH {
332-
default:
333-
throw("unwindm not implemented")
334-
case "386", "amd64", "arm", "ppc64", "ppc64le", "mips64", "mips64le", "s390x", "mips", "mipsle":
335-
sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + sys.MinFrameSize))
336-
case "arm64":
337-
sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + 16))
338-
}
335+
if *restore {
336+
// Restore sp saved by cgocallback during
337+
// unwind of g's stack (see comment at top of file).
338+
mp := acquirem()
339+
sched := &mp.g0.sched
340+
switch GOARCH {
341+
default:
342+
throw("unwindm not implemented")
343+
case "386", "amd64", "arm", "ppc64", "ppc64le", "mips64", "mips64le", "s390x", "mips", "mipsle":
344+
sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + sys.MinFrameSize))
345+
case "arm64":
346+
sched.sp = *(*uintptr)(unsafe.Pointer(sched.sp + 16))
347+
}
339348

340-
// Call endcgo to do the accounting that cgocall will not have a
341-
// chance to do during an unwind.
342-
//
343-
// In the case where a Go call originates from C, ncgo is 0
344-
// and there is no matching cgocall to end.
345-
if mp.ncgo > 0 {
346-
endcgo(mp)
349+
// Call endcgo to do the accounting that cgocall will not have a
350+
// chance to do during an unwind.
351+
//
352+
// In the case where a Go call originates from C, ncgo is 0
353+
// and there is no matching cgocall to end.
354+
if mp.ncgo > 0 {
355+
endcgo(mp)
356+
}
357+
358+
releasem(mp)
347359
}
348360

349-
releasem(mp)
361+
// Undo the call to lockOSThread in cgocallbackg.
362+
// We must still stay on the same m.
363+
unlockOSThread()
350364
}
351365

352366
// called from assembly

src/runtime/panic.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,17 @@ func freedefer(d *_defer) {
273273
unlock(&sched.deferlock)
274274
})
275275
}
276-
*d = _defer{}
276+
277+
// These lines used to be simply `*d = _defer{}` but that
278+
// started causing a nosplit stack overflow via typedmemmove.
279+
d.siz = 0
280+
d.started = false
281+
d.sp = 0
282+
d.pc = 0
283+
d.fn = nil
284+
d._panic = nil
285+
d.link = nil
286+
277287
pp.deferpool[sc] = append(pp.deferpool[sc], d)
278288
}
279289

src/runtime/proc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,6 +2221,12 @@ func schedule() {
22212221
execute(_g_.m.lockedg.ptr(), false) // Never returns.
22222222
}
22232223

2224+
// We should not schedule away from a g that is executing a cgo call,
2225+
// since the cgo call is using the m's g0 stack.
2226+
if _g_.m.incgo {
2227+
throw("schedule: in cgo")
2228+
}
2229+
22242230
top:
22252231
if sched.gcwaiting != 0 {
22262232
gcstopm()

src/runtime/runtime2.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,8 @@ func extendRandom(r []byte, n int) {
675675
}
676676
}
677677

678-
// deferred subroutine calls
678+
// A _defer holds an entry on the list of deferred calls.
679+
// If you add a field here, add code to clear it in freedefer.
679680
type _defer struct {
680681
siz int32
681682
started bool

src/runtime/syscall_windows.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
9393
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
9494
//go:nosplit
9595
func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
96+
lockOSThread()
97+
defer unlockOSThread()
9698
c := &getg().m.syscall
9799

98100
if useLoadLibraryEx {
@@ -126,6 +128,8 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
126128
//go:linkname syscall_loadlibrary syscall.loadlibrary
127129
//go:nosplit
128130
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
131+
lockOSThread()
132+
defer unlockOSThread()
129133
c := &getg().m.syscall
130134
c.fn = getLoadLibrary()
131135
c.n = 1
@@ -141,6 +145,8 @@ func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
141145
//go:linkname syscall_getprocaddress syscall.getprocaddress
142146
//go:nosplit
143147
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
148+
lockOSThread()
149+
defer unlockOSThread()
144150
c := &getg().m.syscall
145151
c.fn = getGetProcAddress()
146152
c.n = 2
@@ -156,6 +162,8 @@ func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uint
156162
//go:linkname syscall_Syscall syscall.Syscall
157163
//go:nosplit
158164
func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
165+
lockOSThread()
166+
defer unlockOSThread()
159167
c := &getg().m.syscall
160168
c.fn = fn
161169
c.n = nargs
@@ -167,6 +175,8 @@ func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
167175
//go:linkname syscall_Syscall6 syscall.Syscall6
168176
//go:nosplit
169177
func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
178+
lockOSThread()
179+
defer unlockOSThread()
170180
c := &getg().m.syscall
171181
c.fn = fn
172182
c.n = nargs
@@ -178,6 +188,8 @@ func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err ui
178188
//go:linkname syscall_Syscall9 syscall.Syscall9
179189
//go:nosplit
180190
func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
191+
lockOSThread()
192+
defer unlockOSThread()
181193
c := &getg().m.syscall
182194
c.fn = fn
183195
c.n = nargs
@@ -189,6 +201,8 @@ func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1
189201
//go:linkname syscall_Syscall12 syscall.Syscall12
190202
//go:nosplit
191203
func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2, err uintptr) {
204+
lockOSThread()
205+
defer unlockOSThread()
192206
c := &getg().m.syscall
193207
c.fn = fn
194208
c.n = nargs
@@ -200,6 +214,8 @@ func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
200214
//go:linkname syscall_Syscall15 syscall.Syscall15
201215
//go:nosplit
202216
func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
217+
lockOSThread()
218+
defer unlockOSThread()
203219
c := &getg().m.syscall
204220
c.fn = fn
205221
c.n = nargs

0 commit comments

Comments
 (0)