Commit 15d6ab69 authored by Austin Clements's avatar Austin Clements

runtime: make systemstack tail call if already switched

Currently systemstack always calls its argument, even if we're already
on the system stack. Unfortunately, traceback with _TraceJump stops at
the first systemstack it sees, which often cuts off runtime stacks
early in profiles.

Fix this by performing a tail call if we're already on the system
stack. This eliminates it from the traceback entirely, so it won't
stop prematurely (or all get mushed into a single node in the profile
graph).

Change-Id: Ibc69e8765e899f8d3806078517b8c7314da196f4
Reviewed-on: https://go-review.googlesource.com/74050Reviewed-by: 's avatarCherry Zhang <cherryyz@google.com>
Reviewed-by: 's avatarKeith Randall <khr@golang.org>
parent 67a7d5d8
...@@ -474,11 +474,12 @@ switch: ...@@ -474,11 +474,12 @@ switch:
RET RET
noswitch: noswitch:
// already on system stack, just call directly // already on system stack; tail call the function
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVL DI, DX MOVL DI, DX
MOVL 0(DI), DI MOVL 0(DI), DI
CALL DI JMP DI
RET
/* /*
* support for morestack * support for morestack
......
...@@ -419,11 +419,12 @@ switch: ...@@ -419,11 +419,12 @@ switch:
RET RET
noswitch: noswitch:
// already on m stack, just call directly // already on m stack; tail call the function
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVQ DI, DX MOVQ DI, DX
MOVQ 0(DI), DI MOVQ 0(DI), DI
CALL DI JMP DI
RET
/* /*
* support for morestack * support for morestack
......
...@@ -306,10 +306,11 @@ switch: ...@@ -306,10 +306,11 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVL DI, DX MOVL DI, DX
MOVL 0(DI), DI MOVL 0(DI), DI
CALL DI JMP DI
RET
/* /*
* support for morestack * support for morestack
......
...@@ -358,10 +358,12 @@ switch: ...@@ -358,10 +358,12 @@ switch:
RET RET
noswitch: noswitch:
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVW R0, R7 MOVW R0, R7
MOVW 0(R0), R0 MOVW 0(R0), R0
BL (R0) MOVW.P 4(R13), R14 // restore LR
RET B (R0)
/* /*
* support for morestack * support for morestack
......
...@@ -239,9 +239,11 @@ switch: ...@@ -239,9 +239,11 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVD 0(R26), R3 // code pointer MOVD 0(R26), R3 // code pointer
BL (R3) MOVD.P 16(RSP), R30 // restore LR
RET B (R3)
/* /*
* support for morestack * support for morestack
......
...@@ -214,9 +214,12 @@ switch: ...@@ -214,9 +214,12 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVV 0(REGCTXT), R4 // code pointer MOVV 0(REGCTXT), R4 // code pointer
JAL (R4) MOVV 0(R29), R31 // restore LR
RET ADDV $8, R29
JMP (R4)
/* /*
* support for morestack * support for morestack
......
...@@ -215,9 +215,12 @@ switch: ...@@ -215,9 +215,12 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVW 0(REGCTXT), R4 // code pointer MOVW 0(REGCTXT), R4 // code pointer
JAL (R4) MOVW 0(R29), R31 // restore LR
RET ADD $4, R29
JMP (R4)
/* /*
* support for morestack * support for morestack
......
...@@ -265,6 +265,9 @@ switch: ...@@ -265,6 +265,9 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// On other arches we do a tail call here, but it appears to be
// impossible to tail call a function pointer in shared mode on
// ppc64 because the caller is responsible for restoring the TOC.
MOVD 0(R11), R12 // code pointer MOVD 0(R11), R12 // code pointer
MOVD R12, CTR MOVD R12, CTR
BL (CTR) BL (CTR)
......
...@@ -224,9 +224,12 @@ switch: ...@@ -224,9 +224,12 @@ switch:
noswitch: noswitch:
// already on m stack, just call directly // already on m stack, just call directly
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVD 0(R12), R3 // code pointer MOVD 0(R12), R3 // code pointer
BL (R3) MOVD 0(R15), LR // restore LR
RET ADD $8, R15
BR (R3)
/* /*
* support for morestack * support for morestack
......
...@@ -395,3 +395,16 @@ func LockOSCounts() (external, internal uint32) { ...@@ -395,3 +395,16 @@ func LockOSCounts() (external, internal uint32) {
} }
return g.m.lockedExt, g.m.lockedInt return g.m.lockedExt, g.m.lockedInt
} }
//go:noinline
func TracebackSystemstack(stk []uintptr, i int) int {
if i == 0 {
pc, sp := getcallerpc(), getcallersp(unsafe.Pointer(&stk))
return gentraceback(pc, sp, 0, getg(), 0, &stk[0], len(stk), nil, nil, _TraceJumpStack)
}
n := 0
systemstack(func() {
n = TracebackSystemstack(stk, i-1)
})
return n
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package runtime_test package runtime_test
import ( import (
"bytes"
"fmt" "fmt"
. "runtime" . "runtime"
"strings" "strings"
...@@ -685,3 +686,35 @@ func TestStackWrapperStack(t *testing.T) { ...@@ -685,3 +686,35 @@ func TestStackWrapperStack(t *testing.T) {
t.Fatalf("<autogenerated> appears in stack trace:\n%s", stk) t.Fatalf("<autogenerated> appears in stack trace:\n%s", stk)
} }
} }
func TestTracebackSystemstack(t *testing.T) {
if GOARCH == "ppc64" || GOARCH == "ppc64le" {
t.Skip("systemstack tail call not implemented on ppc64x")
}
// Test that profiles correctly jump over systemstack,
// including nested systemstack calls.
pcs := make([]uintptr, 20)
pcs = pcs[:TracebackSystemstack(pcs, 5)]
// Check that runtime.TracebackSystemstack appears five times
// and that we see TestTracebackSystemstack.
countIn, countOut := 0, 0
frames := CallersFrames(pcs)
var tb bytes.Buffer
for {
frame, more := frames.Next()
fmt.Fprintf(&tb, "\n%s+0x%x %s:%d", frame.Function, frame.PC-frame.Entry, frame.File, frame.Line)
switch frame.Function {
case "runtime.TracebackSystemstack":
countIn++
case "runtime_test.TestTracebackSystemstack":
countOut++
}
if !more {
break
}
}
if countIn != 5 || countOut != 1 {
t.Fatalf("expected 5 calls to TracebackSystemstack and 1 call to TestTracebackSystemstack, got:%s", tb.String())
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment