Skip to content

Commit

Permalink
cmd/link,runtime: switch openbsd/amd64 to pthreads
Browse files Browse the repository at this point in the history
This switches openbsd/amd64 to thread creation via pthreads, rather than doing
direct system calls.

Update #36435

Change-Id: I1105d5c392aa3e4c445d99c8cb80b927712e3529
Reviewed-on: https://go-review.googlesource.com/c/go/+/250180
Trust: Joel Sing <[email protected]>
Run-TryBot: Joel Sing <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Reviewed-by: Cherry Zhang <[email protected]>
  • Loading branch information
4a6f656c committed Jan 19, 2021
1 parent 61debff commit d047c91
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 162 deletions.
4 changes: 2 additions & 2 deletions src/cmd/link/internal/ld/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) {
}

// We need to be able to reference dynimport symbols when linking against
// shared libraries, and Solaris, Darwin and AIX need it always
if !target.IsSolaris() && !target.IsDarwin() && !target.IsAIX() && rs != 0 && rst == sym.SDYNIMPORT && !target.IsDynlinkingGo() && !ldr.AttrSubSymbol(rs) {
// shared libraries, and AIX, Darwin, OpenBSD and Solaris always need it.
if !target.IsAIX() && !target.IsDarwin() && !target.IsSolaris() && !target.IsOpenbsd() && rs != 0 && rst == sym.SDYNIMPORT && !target.IsDynlinkingGo() && !ldr.AttrSubSymbol(rs) {
if !(target.IsPPC64() && target.IsExternal() && ldr.SymName(rs) == ".TOC.") {
st.err.Errorf(s, "unhandled relocation for %s (type %d (%s) rtype %d (%s))", ldr.SymName(rs), rst, rst, rt, sym.RelocName(target.Arch, rt))
}
Expand Down
1 change: 1 addition & 0 deletions src/cmd/link/internal/ld/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,7 @@ func (ctxt *Link) hostlink() {
}
case objabi.Hopenbsd:
argv = append(argv, "-Wl,-nopie")
argv = append(argv, "-pthread")
case objabi.Hwindows:
if windowsgui {
argv = append(argv, "-mwindows")
Expand Down
8 changes: 8 additions & 0 deletions src/cmd/link/internal/ld/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ func Main(arch *sys.Arch, theArch Arch) {

interpreter = *flagInterpreter

if *flagBuildid == "" && ctxt.Target.IsOpenbsd() {
// TODO(jsing): Remove once direct syscalls are no longer in use.
// OpenBSD 6.7 onwards will not permit direct syscalls from a
// dynamically linked binary unless it identifies the binary
// contains a .note.go.buildid ELF note. See issue #36435.
*flagBuildid = "go-openbsd"
}

// enable benchmarking
var bench *benchmark.Metrics
if len(*benchmarkFlag) != 0 {
Expand Down
4 changes: 4 additions & 0 deletions src/runtime/asm_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ needtls:
// skip TLS setup on Darwin
JMP ok
#endif
#ifdef GOOS_openbsd
// skip TLS setup on OpenBSD
JMP ok
#endif

LEAQ runtime·m0+m_tls(SB), DI
CALL runtime·settls(SB)
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/defs_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const (
SA_RESTART = C.SA_RESTART
SA_ONSTACK = C.SA_ONSTACK

PTHREAD_CREATE_DETACHED = C.PTHREAD_CREATE_DETACHED

SIGHUP = C.SIGHUP
SIGINT = C.SIGINT
SIGQUIT = C.SIGQUIT
Expand Down Expand Up @@ -129,3 +131,10 @@ type Timeval C.struct_timeval
type Itimerval C.struct_itimerval

type KeventT C.struct_kevent

type Pthread C.pthread_t
type PthreadAttr C.pthread_attr_t
type PthreadCond C.pthread_cond_t
type PthreadCondAttr C.pthread_condattr_t
type PthreadMutex C.pthread_mutex_t
type PthreadMutexAttr C.pthread_mutexattr_t
9 changes: 9 additions & 0 deletions src/runtime/defs_openbsd_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
_SA_RESTART = 0x2
_SA_ONSTACK = 0x1

_PTHREAD_CREATE_DETACHED = 0x1

_SIGHUP = 0x1
_SIGINT = 0x2
_SIGQUIT = 0x3
Expand Down Expand Up @@ -177,3 +179,10 @@ type keventt struct {
data int64
udata *byte
}

type pthread uintptr
type pthreadattr uintptr
type pthreadcond uintptr
type pthreadcondattr uintptr
type pthreadmutex uintptr
type pthreadmutexattr uintptr
34 changes: 0 additions & 34 deletions src/runtime/os_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package runtime

import (
"runtime/internal/atomic"
"runtime/internal/sys"
"unsafe"
)

Expand Down Expand Up @@ -47,9 +46,6 @@ func raiseproc(sig uint32)
func getthrid() int32
func thrkill(tid int32, sig int)

//go:noescape
func tfork(param *tforkt, psize uintptr, mm *m, gg *g, fn uintptr) int32

//go:noescape
func thrsleep(ident uintptr, clock_id int32, tsp *timespec, lock uintptr, abort *uint32) int32

Expand Down Expand Up @@ -183,36 +179,6 @@ func semawakeup(mp *m) {
}
}

// May run with m.p==nil, so write barriers are not allowed.
//go:nowritebarrier
func newosproc(mp *m) {
stk := unsafe.Pointer(mp.g0.stack.hi)
if false {
print("newosproc stk=", stk, " m=", mp, " g=", mp.g0, " id=", mp.id, " ostk=", &mp, "\n")
}

// Stack pointer must point inside stack area (as marked with MAP_STACK),
// rather than at the top of it.
param := tforkt{
tf_tcb: unsafe.Pointer(&mp.tls[0]),
tf_tid: nil, // minit will record tid
tf_stack: uintptr(stk) - sys.PtrSize,
}

var oset sigset
sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
ret := tfork(&param, unsafe.Sizeof(param), mp, mp.g0, funcPC(mstart))
sigprocmask(_SIG_SETMASK, &oset, nil)

if ret < 0 {
print("runtime: failed to create new OS thread (have ", mcount()-1, " already; errno=", -ret, ")\n")
if ret == -_EAGAIN {
println("runtime: may need to increase max user processes (ulimit -p)")
}
throw("runtime.newosproc")
}
}

func osinit() {
ncpu = getncpu()
physPageSize = getPageSize()
Expand Down
58 changes: 58 additions & 0 deletions src/runtime/os_openbsd_libc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build openbsd,amd64

package runtime

import (
"unsafe"
)

var failThreadCreate = []byte("runtime: failed to create new OS thread\n")

// mstart_stub provides glue code to call mstart from pthread_create.
func mstart_stub()

// May run with m.p==nil, so write barriers are not allowed.
//go:nowritebarrierrec
func newosproc(mp *m) {
if false {
print("newosproc m=", mp, " g=", mp.g0, " id=", mp.id, " ostk=", &mp, "\n")
}

// Initialize an attribute object.
var attr pthreadattr
if err := pthread_attr_init(&attr); err != 0 {
write(2, unsafe.Pointer(&failThreadCreate[0]), int32(len(failThreadCreate)))
exit(1)
}

// Find out OS stack size for our own stack guard.
var stacksize uintptr
if pthread_attr_getstacksize(&attr, &stacksize) != 0 {
write(2, unsafe.Pointer(&failThreadCreate[0]), int32(len(failThreadCreate)))
exit(1)
}
mp.g0.stack.hi = stacksize // for mstart

// Tell the pthread library we won't join with this thread.
if pthread_attr_setdetachstate(&attr, _PTHREAD_CREATE_DETACHED) != 0 {
write(2, unsafe.Pointer(&failThreadCreate[0]), int32(len(failThreadCreate)))
exit(1)
}

// Finally, create the thread. It starts at mstart_stub, which does some low-level
// setup and then calls mstart.
var oset sigset
sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
err := pthread_create(&attr, funcPC(mstart_stub), unsafe.Pointer(mp))
sigprocmask(_SIG_SETMASK, &oset, nil)
if err != 0 {
write(2, unsafe.Pointer(&failThreadCreate[0]), int32(len(failThreadCreate)))
exit(1)
}

pthread_attr_destroy(&attr)
}
45 changes: 45 additions & 0 deletions src/runtime/os_openbsd_syscall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build openbsd,!amd64

package runtime

import (
"runtime/internal/sys"
"unsafe"
)

//go:noescape
func tfork(param *tforkt, psize uintptr, mm *m, gg *g, fn uintptr) int32

// May run with m.p==nil, so write barriers are not allowed.
//go:nowritebarrier
func newosproc(mp *m) {
stk := unsafe.Pointer(mp.g0.stack.hi)
if false {
print("newosproc stk=", stk, " m=", mp, " g=", mp.g0, " id=", mp.id, " ostk=", &mp, "\n")
}

// Stack pointer must point inside stack area (as marked with MAP_STACK),
// rather than at the top of it.
param := tforkt{
tf_tcb: unsafe.Pointer(&mp.tls[0]),
tf_tid: nil, // minit will record tid
tf_stack: uintptr(stk) - sys.PtrSize,
}

var oset sigset
sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
ret := tfork(&param, unsafe.Sizeof(param), mp, mp.g0, funcPC(mstart))
sigprocmask(_SIG_SETMASK, &oset, nil)

if ret < 0 {
print("runtime: failed to create new OS thread (have ", mcount()-1, " already; errno=", -ret, ")\n")
if ret == -_EAGAIN {
println("runtime: may need to increase max user processes (ulimit -p)")
}
throw("runtime.newosproc")
}
}
5 changes: 5 additions & 0 deletions src/runtime/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,11 @@ func mStackIsSystemAllocated() bool {
switch GOOS {
case "aix", "darwin", "plan9", "illumos", "ios", "solaris", "windows":
return true
case "openbsd":
switch GOARCH {
case "amd64":
return true
}
}
return false
}
Expand Down
44 changes: 0 additions & 44 deletions src/runtime/sys_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,6 @@ package runtime

import "unsafe"

// Call fn with arg as its argument. Return what fn returns.
// fn is the raw pc value of the entry point of the desired function.
// Switches to the system stack, if not already there.
// Preserves the calling point as the location where a profiler traceback will begin.
//go:nosplit
func libcCall(fn, arg unsafe.Pointer) int32 {
// Leave caller's PC/SP/G around for traceback.
gp := getg()
var mp *m
if gp != nil {
mp = gp.m
}
if mp != nil && mp.libcallsp == 0 {
mp.libcallg.set(gp)
mp.libcallpc = getcallerpc()
// sp must be the last, because once async cpu profiler finds
// all three values to be non-zero, it will use them
mp.libcallsp = getcallersp()
} else {
// Make sure we don't reset libcallsp. This makes
// libcCall reentrant; We remember the g/pc/sp for the
// first call on an M, until that libcCall instance
// returns. Reentrance only matters for signals, as
// libc never calls back into Go. The tricky case is
// where we call libcX from an M and record g/pc/sp.
// Before that call returns, a signal arrives on the
// same M and the signal handling code calls another
// libc function. We don't want that second libcCall
// from within the handler to be recorded, and we
// don't want that call's completion to zero
// libcallsp.
// We don't need to set libcall* while we're in a sighandler
// (even if we're not currently in libc) because we block all
// signals while we're handling a signal. That includes the
// profile signal, which is the one that uses the libcall* info.
mp = nil
}
res := asmcgocall(fn, arg)
if mp != nil {
mp.libcallsp = 0
}
return res
}

// The X versions of syscall expect the libc call to return a 64-bit result.
// Otherwise (the non-X version) expects a 32-bit result.
// This distinction is required because an error is indicated by returning -1,
Expand Down
53 changes: 53 additions & 0 deletions src/runtime/sys_libc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin openbsd,amd64

package runtime

import "unsafe"

// Call fn with arg as its argument. Return what fn returns.
// fn is the raw pc value of the entry point of the desired function.
// Switches to the system stack, if not already there.
// Preserves the calling point as the location where a profiler traceback will begin.
//go:nosplit
func libcCall(fn, arg unsafe.Pointer) int32 {
// Leave caller's PC/SP/G around for traceback.
gp := getg()
var mp *m
if gp != nil {
mp = gp.m
}
if mp != nil && mp.libcallsp == 0 {
mp.libcallg.set(gp)
mp.libcallpc = getcallerpc()
// sp must be the last, because once async cpu profiler finds
// all three values to be non-zero, it will use them
mp.libcallsp = getcallersp()
} else {
// Make sure we don't reset libcallsp. This makes
// libcCall reentrant; We remember the g/pc/sp for the
// first call on an M, until that libcCall instance
// returns. Reentrance only matters for signals, as
// libc never calls back into Go. The tricky case is
// where we call libcX from an M and record g/pc/sp.
// Before that call returns, a signal arrives on the
// same M and the signal handling code calls another
// libc function. We don't want that second libcCall
// from within the handler to be recorded, and we
// don't want that call's completion to zero
// libcallsp.
// We don't need to set libcall* while we're in a sighandler
// (even if we're not currently in libc) because we block all
// signals while we're handling a signal. That includes the
// profile signal, which is the one that uses the libcall* info.
mp = nil
}
res := asmcgocall(fn, arg)
if mp != nil {
mp.libcallsp = 0
}
return res
}
Loading

0 comments on commit d047c91

Please sign in to comment.