]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/internal/obj, runtime: preempt & restart some instruction sequences
authorCherry Zhang <cherryyz@google.com>
Wed, 20 Nov 2019 22:10:34 +0000 (17:10 -0500)
committerCherry Zhang <cherryyz@google.com>
Wed, 6 May 2020 15:41:12 +0000 (15:41 +0000)
On some architectures, for async preemption the injected call
needs to clobber a register (usually REGTMP) in order to return
to the preempted function. As a consequence, the PC ranges where
REGTMP is live are not preemptible.

The uses of REGTMP are usually generated by the assembler, where
it needs to load or materialize a large constant or offset that
doesn't fit into the instruction. In those cases, REGTMP is not
live at the start of the instruction sequence. Instead of giving
up preemption in those cases, we could preempt it and restart the
sequence when resuming the execution. Basically, this is like
reissuing an interrupted instruction, except that here the
"instruction" is a Prog that consists of multiple machine
instructions. For this to work, we need to generate PC data to
mark the start of the Prog.

Currently this is only done for ARM64.

TODO: the split-stack function prologue is currently not async
preemptible. We could use this mechanism, preempt it and restart
at the function entry.

Change-Id: I37cb282f8e606e7ab6f67b3edfdc6063097b4bd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/208126
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
20 files changed:
src/cmd/internal/obj/arm64/asm7.go
src/cmd/internal/obj/mips/asm0.go
src/cmd/internal/obj/plist.go
src/cmd/internal/obj/riscv/obj.go
src/cmd/internal/obj/s390x/asmz.go
src/cmd/internal/obj/x86/asm6.go
src/cmd/internal/objabi/funcdata.go
src/runtime/os_windows.go
src/runtime/preempt.go
src/runtime/signal_386.go
src/runtime/signal_amd64.go
src/runtime/signal_arm.go
src/runtime/signal_arm64.go
src/runtime/signal_linux_s390x.go
src/runtime/signal_mips64x.go
src/runtime/signal_mipsx.go
src/runtime/signal_ppc64x.go
src/runtime/signal_riscv64.go
src/runtime/signal_unix.go
src/runtime/symtab.go

index 8e5b598084936e8ec46abaf2d86a63eb405a7f2c..9a1908a6551ff963a21d827072294db5223a59a1 100644 (file)
@@ -287,8 +287,8 @@ var optab = []Optab{
        {AADD, C_BITCON, C_RSP, C_NONE, C_RSP, 62, 8, 0, 0, 0},
        {AADD, C_BITCON, C_NONE, C_NONE, C_RSP, 62, 8, 0, 0, 0},
        {ACMP, C_BITCON, C_RSP, C_NONE, C_NONE, 62, 8, 0, 0, 0},
-       {AADD, C_ADDCON2, C_RSP, C_NONE, C_RSP, 48, 8, 0, 0, 0},
-       {AADD, C_ADDCON2, C_NONE, C_NONE, C_RSP, 48, 8, 0, 0, 0},
+       {AADD, C_ADDCON2, C_RSP, C_NONE, C_RSP, 48, 8, 0, NOTUSETMP, 0},
+       {AADD, C_ADDCON2, C_NONE, C_NONE, C_RSP, 48, 8, 0, NOTUSETMP, 0},
        {AADD, C_MOVCON2, C_RSP, C_NONE, C_RSP, 13, 12, 0, 0, 0},
        {AADD, C_MOVCON2, C_NONE, C_NONE, C_RSP, 13, 12, 0, 0, 0},
        {AADD, C_MOVCON3, C_RSP, C_NONE, C_RSP, 13, 16, 0, 0, 0},
@@ -1072,16 +1072,29 @@ func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
        // We use REGTMP as a scratch register during call injection,
        // so instruction sequences that use REGTMP are unsafe to
        // preempt asynchronously.
-       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint)
+       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint, c.isRestartable)
 }
 
-// Return whether p is an unsafe point.
+// isUnsafePoint returns whether p is an unsafe point.
 func (c *ctxt7) isUnsafePoint(p *obj.Prog) bool {
-       if p.From.Reg == REGTMP || p.To.Reg == REGTMP || p.Reg == REGTMP {
-               return true
+       // If p explicitly uses REGTMP, it's unsafe to preempt, because the
+       // preemption sequence clobbers REGTMP.
+       return p.From.Reg == REGTMP || p.To.Reg == REGTMP || p.Reg == REGTMP
+}
+
+// isRestartable returns whether p is a multi-instruction sequence that,
+// if preempted, can be restarted.
+func (c *ctxt7) isRestartable(p *obj.Prog) bool {
+       if c.isUnsafePoint(p) {
+               return false
        }
-       // Most of the multi-instruction sequence uses REGTMP, except
-       // ones marked safe.
+       // If p is a multi-instruction sequence with uses REGTMP inserted by
+       // the assembler in order to materialize a large constant/offset, we
+       // can restart p (at the start of the instruction sequence), recompute
+       // the content of REGTMP, upon async preemption. Currently, all cases
+       // of assembler-inserted REGTMP fall into this category.
+       // If p doesn't use REGTMP, it can be simply preempted, so we don't
+       // mark it.
        o := c.oplook(p)
        return o.size > 4 && o.flag&NOTUSETMP == 0
 }
@@ -3831,6 +3844,8 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
                o1 |= fields | uint32(rs&31)<<16 | uint32(rb&31)<<5 | uint32(rt&31)
 
        case 48: /* ADD $C_ADDCON2, Rm, Rd */
+               // NOTE: this case does not use REGTMP. If it ever does,
+               // remove the NOTUSETMP flag in optab.
                op := c.opirr(p, p.As)
                if op&Sbit != 0 {
                        c.ctxt.Diag("can not break addition/subtraction when S bit is set", p)
index 13d875ed3abaa164384dbea0723e389eb6fe4350..957f2d5c93d04fa4034f1fd47fec5b312e277099 100644 (file)
@@ -526,7 +526,7 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
        // We use REGTMP as a scratch register during call injection,
        // so instruction sequences that use REGTMP are unsafe to
        // preempt asynchronously.
-       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint)
+       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint, nil)
 }
 
 // Return whether p is an unsafe point.
index 44ec4602decf107684ec9d4e78caceb89ca25c4f..73b6e8b1a1701319bf831f3336ae14cff8626394 100644 (file)
@@ -236,13 +236,13 @@ func (ctxt *Link) StartUnsafePoint(p *Prog, newprog ProgAlloc) *Prog {
        pcdata.From.Type = TYPE_CONST
        pcdata.From.Offset = objabi.PCDATA_RegMapIndex
        pcdata.To.Type = TYPE_CONST
-       pcdata.To.Offset = -2 // pcdata -2 marks unsafe point
+       pcdata.To.Offset = objabi.PCDATA_RegMapUnsafe
 
        return pcdata
 }
 
 // EndUnsafePoint generates PCDATA Progs after p to mark the end of an
-// unsafe point, restoring the stack map index to oldval.
+// unsafe point, restoring the register map index to oldval.
 // The unsafe point ends right after p.
 // It returns the last Prog generated.
 func (ctxt *Link) EndUnsafePoint(p *Prog, newprog ProgAlloc, oldval int64) *Prog {
@@ -253,23 +253,33 @@ func (ctxt *Link) EndUnsafePoint(p *Prog, newprog ProgAlloc, oldval int64) *Prog
        pcdata.To.Type = TYPE_CONST
        pcdata.To.Offset = oldval
 
-       // TODO: register map?
-
        return pcdata
 }
 
-// MarkUnsafePoints inserts PCDATAs to mark nonpreemptible instruction
-// sequences, based on isUnsafePoint predicate. p0 is the start of the
-// instruction stream.
-func MarkUnsafePoints(ctxt *Link, p0 *Prog, newprog ProgAlloc, isUnsafePoint func(*Prog) bool) {
+// MarkUnsafePoints inserts PCDATAs to mark nonpreemptible and restartable
+// instruction sequences, based on isUnsafePoint and isRestartable predicate.
+// p0 is the start of the instruction stream.
+// isUnsafePoint(p) returns true if p is not safe for async preemption.
+// isRestartable(p) returns true if we can restart at the start of p (this Prog)
+// upon async preemption. (Currently multi-Prog restartable sequence is not
+// supported.)
+// isRestartable can be nil. In this case it is treated as always returning false.
+// If isUnsafePoint(p) and isRestartable(p) are both true, it is treated as
+// an unsafe point.
+func MarkUnsafePoints(ctxt *Link, p0 *Prog, newprog ProgAlloc, isUnsafePoint, isRestartable func(*Prog) bool) {
+       if isRestartable == nil {
+               // Default implementation: nothing is restartable.
+               isRestartable = func(*Prog) bool { return false }
+       }
        prev := p0
-       oldval := int64(-1) // entry pcdata
+       prevPcdata := int64(-1) // entry PC data value
+       prevRestart := int64(0)
        for p := prev.Link; p != nil; p, prev = p.Link, p {
                if p.As == APCDATA && p.From.Offset == objabi.PCDATA_RegMapIndex {
-                       oldval = p.To.Offset
+                       prevPcdata = p.To.Offset
                        continue
                }
-               if oldval == -2 {
+               if prevPcdata == objabi.PCDATA_RegMapUnsafe {
                        continue // already unsafe
                }
                if isUnsafePoint(p) {
@@ -283,7 +293,39 @@ func MarkUnsafePoints(ctxt *Link, p0 *Prog, newprog ProgAlloc, isUnsafePoint fun
                        if p.Link == nil {
                                break // Reached the end, don't bother marking the end
                        }
-                       p = ctxt.EndUnsafePoint(p, newprog, oldval)
+                       p = ctxt.EndUnsafePoint(p, newprog, prevPcdata)
+                       p.Pc = p.Link.Pc
+                       continue
+               }
+               if isRestartable(p) {
+                       val := int64(objabi.PCDATA_Restart1)
+                       if val == prevRestart {
+                               val = objabi.PCDATA_Restart2
+                       }
+                       prevRestart = val
+                       q := Appendp(prev, newprog)
+                       q.As = APCDATA
+                       q.From.Type = TYPE_CONST
+                       q.From.Offset = objabi.PCDATA_RegMapIndex
+                       q.To.Type = TYPE_CONST
+                       q.To.Offset = val
+                       q.Pc = p.Pc
+                       q.Link = p
+
+                       if p.Link == nil {
+                               break // Reached the end, don't bother marking the end
+                       }
+                       if isRestartable(p.Link) {
+                               // Next Prog is also restartable. No need to mark the end
+                               // of this sequence. We'll just go ahead mark the next one.
+                               continue
+                       }
+                       p = Appendp(p, newprog)
+                       p.As = APCDATA
+                       p.From.Type = TYPE_CONST
+                       p.From.Offset = objabi.PCDATA_RegMapIndex
+                       p.To.Type = TYPE_CONST
+                       p.To.Offset = prevPcdata
                        p.Pc = p.Link.Pc
                }
        }
index 6fcde2d67eb83f156c8004228b356c51963d5df2..2eb2935b318edb92f0afd32e3050f8cb579a8b24 100644 (file)
@@ -2005,7 +2005,7 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
                ctxt.Arch.ByteOrder.PutUint32(p, symcode[i])
        }
 
-       obj.MarkUnsafePoints(ctxt, cursym.Func.Text, newprog, isUnsafePoint)
+       obj.MarkUnsafePoints(ctxt, cursym.Func.Text, newprog, isUnsafePoint, nil)
 }
 
 func isUnsafePoint(p *obj.Prog) bool {
index 30c0738c33dcdc3e4b5b90d189750205c2e00f19..29182ea8059274bf4187183eb62aff4f1aaa05db 100644 (file)
@@ -500,7 +500,7 @@ func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
        // We use REGTMP as a scratch register during call injection,
        // so instruction sequences that use REGTMP are unsafe to
        // preempt asynchronously.
-       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint)
+       obj.MarkUnsafePoints(c.ctxt, c.cursym.Func.Text, c.newprog, c.isUnsafePoint, nil)
 }
 
 // Return whether p is an unsafe point.
index 3eaed2ab5481e6005a6c05af7512f7586fd75b82..f7d81dc2f7b88a299c39f519b1ca1c319a15acc1 100644 (file)
@@ -2226,7 +2226,7 @@ func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
                        // the first instruction.)
                        return p.From.Index == REG_TLS
                }
-               obj.MarkUnsafePoints(ctxt, s.Func.Text, newprog, useTLS)
+               obj.MarkUnsafePoints(ctxt, s.Func.Text, newprog, useTLS, nil)
        }
 }
 
index 2a51816cbd75509bcb56dd12c4f317df0ce46353..d5bacb5900bf8937940f11d7596af5a40ef0f2e3 100644 (file)
@@ -40,4 +40,15 @@ const (
        // PCDATA_UnsafePoint values.
        PCDATA_UnsafePointSafe   = -1 // Safe for async preemption
        PCDATA_UnsafePointUnsafe = -2 // Unsafe for async preemption
+
+       // PCDATA_Restart1(2) apply on a sequence of instructions, within
+       // which if an async preemption happens, we should back off the PC
+       // to the start of the sequence when resuming.
+       // We need two so we can distinguish the start/end of the sequence
+       // in case that two sequences are next to each other.
+       PCDATA_Restart1 = -3
+       PCDATA_Restart2 = -4
+
+       // Like PCDATA_Restart1, but back to function entry if async preempted.
+       PCDATA_RestartAtEntry = -5
 )
index 7baba83817f0d1ff4daf62be468b9a40d94c71dd..a584ada702736c6dbed6b38f387476290d85e730 100644 (file)
@@ -1192,23 +1192,24 @@ func preemptM(mp *m) {
 
        // Does it want a preemption and is it safe to preempt?
        gp := gFromTLS(mp)
-       if wantAsyncPreempt(gp) && isAsyncSafePoint(gp, c.ip(), c.sp(), c.lr()) {
-               // Inject call to asyncPreempt
-               targetPC := funcPC(asyncPreempt)
-               switch GOARCH {
-               default:
-                       throw("unsupported architecture")
-               case "386", "amd64":
-                       // Make it look like the thread called targetPC.
-                       pc := c.ip()
-                       sp := c.sp()
-                       sp -= sys.PtrSize
-                       *(*uintptr)(unsafe.Pointer(sp)) = pc
-                       c.set_sp(sp)
-                       c.set_ip(targetPC)
-               }
+       if wantAsyncPreempt(gp) {
+               if ok, newpc := isAsyncSafePoint(gp, c.ip(), c.sp(), c.lr()); ok {
+                       // Inject call to asyncPreempt
+                       targetPC := funcPC(asyncPreempt)
+                       switch GOARCH {
+                       default:
+                               throw("unsupported architecture")
+                       case "386", "amd64":
+                               // Make it look like the thread called targetPC.
+                               sp := c.sp()
+                               sp -= sys.PtrSize
+                               *(*uintptr)(unsafe.Pointer(sp)) = newpc
+                               c.set_sp(sp)
+                               c.set_ip(targetPC)
+                       }
 
-               stdcall2(_SetThreadContext, thread, uintptr(unsafe.Pointer(c)))
+                       stdcall2(_SetThreadContext, thread, uintptr(unsafe.Pointer(c)))
+               }
        }
 
        atomic.Store(&mp.preemptExtLock, 0)
index 41a32fa6505f9294cfd87706c332124d74d37666..761856576a9c9300ef2a1b5459c1ba54f522f026 100644 (file)
@@ -61,6 +61,8 @@ import (
 // Keep in sync with cmd/compile/internal/gc/plive.go:go115ReduceLiveness.
 const go115ReduceLiveness = true
 
+const go115RestartSeq = go115ReduceLiveness && true // enable restartable sequences
+
 type suspendGState struct {
        g *g
 
@@ -359,31 +361,35 @@ func wantAsyncPreempt(gp *g) bool {
 // 3. It's generally safe to interact with the runtime, even if we're
 // in a signal handler stopped here. For example, there are no runtime
 // locks held, so acquiring a runtime lock won't self-deadlock.
-func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) bool {
+//
+// In some cases the PC is safe for asynchronous preemption but it
+// also needs to adjust the resumption PC. The new PC is returned in
+// the second result.
+func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) {
        mp := gp.m
 
        // Only user Gs can have safe-points. We check this first
        // because it's extremely common that we'll catch mp in the
        // scheduler processing this G preemption.
        if mp.curg != gp {
-               return false
+               return false, 0
        }
 
        // Check M state.
        if mp.p == 0 || !canPreemptM(mp) {
-               return false
+               return false, 0
        }
 
        // Check stack space.
        if sp < gp.stack.lo || sp-gp.stack.lo < asyncPreemptStack {
-               return false
+               return false, 0
        }
 
        // Check if PC is an unsafe-point.
        f := findfunc(pc)
        if !f.valid() {
                // Not Go code.
-               return false
+               return false, 0
        }
        if (GOARCH == "mips" || GOARCH == "mipsle" || GOARCH == "mips64" || GOARCH == "mips64le") && lr == pc+8 && funcspdelta(f, pc, nil) == 0 {
                // We probably stopped at a half-executed CALL instruction,
@@ -394,23 +400,25 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) bool {
                // stack for unwinding, not the LR value. But if this is a
                // call to morestack, we haven't created the frame, and we'll
                // use the LR for unwinding, which will be bad.
-               return false
+               return false, 0
        }
+       var up int32
+       var startpc uintptr
        if !go115ReduceLiveness {
                smi := pcdatavalue(f, _PCDATA_RegMapIndex, pc, nil)
                if smi == -2 {
                        // Unsafe-point marked by compiler. This includes
                        // atomic sequences (e.g., write barrier) and nosplit
                        // functions (except at calls).
-                       return false
+                       return false, 0
                }
        } else {
-               up := pcdatavalue(f, _PCDATA_UnsafePoint, pc, nil)
+               up, startpc = pcdatavalue2(f, _PCDATA_UnsafePoint, pc)
                if up != _PCDATA_UnsafePointSafe {
                        // Unsafe-point marked by compiler. This includes
                        // atomic sequences (e.g., write barrier) and nosplit
                        // functions (except at calls).
-                       return false
+                       return false, 0
                }
        }
        if fd := funcdata(f, _FUNCDATA_LocalsPointerMaps); fd == nil || fd == unsafe.Pointer(&no_pointers_stackmap) {
@@ -422,7 +430,7 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) bool {
                //
                // TODO: Are there cases that are safe but don't have a
                // locals pointer map, like empty frame functions?
-               return false
+               return false, 0
        }
        name := funcname(f)
        if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
@@ -445,10 +453,29 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) bool {
                //
                // TODO(austin): We should improve this, or opt things
                // in incrementally.
-               return false
+               return false, 0
        }
-
-       return true
+       if go115RestartSeq {
+               switch up {
+               case _PCDATA_Restart1, _PCDATA_Restart2:
+                       // Restartable instruction sequence. Back off PC to
+                       // the start PC.
+                       if startpc == 0 || startpc > pc || pc-startpc > 20 {
+                               throw("bad restart PC")
+                       }
+                       return true, startpc
+               case _PCDATA_RestartAtEntry:
+                       // Restart from the function entry at resumption.
+                       return true, f.entry
+               }
+       } else {
+               switch up {
+               case _PCDATA_Restart1, _PCDATA_Restart2, _PCDATA_RestartAtEntry:
+                       // go115RestartSeq is not enabled. Treat it as unsafe point.
+                       return false, 0
+               }
+       }
+       return true, pc
 }
 
 var no_pointers_stackmap uint64 // defined in assembly, for NO_LOCAL_POINTERS macro
index 95749d2cb25397291082c39557ab5711cd0fcdc8..065aff48d30af568226b606f472d84bb3979b8be 100644 (file)
@@ -41,19 +41,18 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        sp := uintptr(c.esp())
 
        if shouldPushSigpanic(gp, pc, *(*uintptr)(unsafe.Pointer(sp))) {
-               c.pushCall(funcPC(sigpanic))
+               c.pushCall(funcPC(sigpanic), pc)
        } else {
                // Not safe to push the call. Just clobber the frame.
                c.set_eip(uint32(funcPC(sigpanic)))
        }
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
-       // Make it look like the signaled instruction called target.
-       pc := uintptr(c.eip())
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
+       // Make it look like we called target at resumePC.
        sp := uintptr(c.esp())
        sp -= sys.PtrSize
-       *(*uintptr)(unsafe.Pointer(sp)) = pc
+       *(*uintptr)(unsafe.Pointer(sp)) = resumePC
        c.set_esp(uint32(sp))
        c.set_eip(uint32(targetPC))
 }
index 63ffedbc87077103a49939a712f4b7717e6e1f54..6ab1f758c2293542396062811536109be2964ccb 100644 (file)
@@ -66,19 +66,18 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        sp := uintptr(c.rsp())
 
        if shouldPushSigpanic(gp, pc, *(*uintptr)(unsafe.Pointer(sp))) {
-               c.pushCall(funcPC(sigpanic))
+               c.pushCall(funcPC(sigpanic), pc)
        } else {
                // Not safe to push the call. Just clobber the frame.
                c.set_rip(uint64(funcPC(sigpanic)))
        }
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
-       // Make it look like the signaled instruction called target.
-       pc := uintptr(c.rip())
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
+       // Make it look like we called target at resumePC.
        sp := uintptr(c.rsp())
        sp -= sys.PtrSize
-       *(*uintptr)(unsafe.Pointer(sp)) = pc
+       *(*uintptr)(unsafe.Pointer(sp)) = resumePC
        c.set_rsp(uint64(sp))
        c.set_rip(uint64(targetPC))
 }
index b4b3ca458fcc2f4f6ed5d8ac0f7d578178d4a96a..156d9d384c7d4dc987717b4e81258019fbb25b3c 100644 (file)
@@ -63,7 +63,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint32(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -72,7 +72,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint32)(unsafe.Pointer(uintptr(sp))) = c.lr()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_lr(c.pc())
+       // calls targetPC at resumePC.
+       c.set_lr(uint32(resumePC))
        c.set_pc(uint32(targetPC))
 }
index ef65f92aa3e1332e350fb466a6ea02b1f70bd905..3c20139c99fdb5ae7a56f6f680c1df18000ff2cc 100644 (file)
@@ -79,7 +79,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint64(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -88,7 +88,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint64)(unsafe.Pointer(uintptr(sp))) = c.lr()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_lr(c.pc())
+       // calls targetPC at resumePC.
+       c.set_lr(uint64(resumePC))
        c.set_pc(uint64(targetPC))
 }
index 15f50351bb24b931d255a6d3eeca50d9fd7ab166..12d5c315931c89c5764e6813edbf55485b9c58be 100644 (file)
@@ -110,7 +110,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint64(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -119,7 +119,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint64)(unsafe.Pointer(uintptr(sp))) = c.link()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_link(c.pc())
+       // calls targetPC at resumePC.
+       c.set_link(uint64(resumePC))
        c.set_pc(uint64(targetPC))
 }
index 6110b1c0232ba7694db1a8041432c392427c09e8..040c959f0423eb2c1f4ea99c3fbc709c0d997a52 100644 (file)
@@ -85,7 +85,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(sigpanicPC)
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -94,7 +94,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint64)(unsafe.Pointer(uintptr(sp))) = c.link()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_link(c.pc())
+       // calls targetPC at resumePC.
+       c.set_link(uint64(resumePC))
        c.set_pc(uint64(targetPC))
 }
index cdbe193501bd354990530c29e67f6e4ad6a58263..8c29f59bd10b97f02f6a40c5809b0c04b489995d 100644 (file)
@@ -80,7 +80,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint32(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -89,7 +89,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint32)(unsafe.Pointer(uintptr(sp))) = c.link()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_link(c.pc())
+       // calls targetPC at resumePC.
+       c.set_link(uint32(resumePC))
        c.set_pc(uint32(targetPC))
 }
index 2da09d378ae5a8036283a259ab829318e6b5de9a..5de93a330ac243b8c8f8d54162bae734ecc310ef 100644 (file)
@@ -86,7 +86,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint64(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -104,8 +104,8 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        *(*uint64)(unsafe.Pointer(uintptr(sp) + 8)) = c.r2()
        *(*uint64)(unsafe.Pointer(uintptr(sp) + 16)) = c.r12()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_link(c.pc())
+       // calls targetPC at resumePC.
+       c.set_link(uint64(resumePC))
        c.set_r12(uint64(targetPC))
        c.set_pc(uint64(targetPC))
 }
index e2edaf3735add9acbdff2bdc914958d2a909f295..93363a4746d8b0a9108fa35a5855c9016efb40a7 100644 (file)
@@ -78,7 +78,7 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
        c.set_pc(uint64(funcPC(sigpanic)))
 }
 
-func (c *sigctxt) pushCall(targetPC uintptr) {
+func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
        // Push the LR to stack, as we'll clobber it in order to
        // push the call. The function being pushed is responsible
        // for restoring the LR and setting the SP back.
@@ -87,7 +87,7 @@ func (c *sigctxt) pushCall(targetPC uintptr) {
        c.set_sp(sp)
        *(*uint64)(unsafe.Pointer(uintptr(sp))) = c.ra()
        // Set up PC and LR to pretend the function being signaled
-       // calls targetPC at the faulting PC.
-       c.set_ra(c.pc())
+       // calls targetPC at resumePC.
+       c.set_ra(uint64(resumePC))
        c.set_pc(uint64(targetPC))
 }
index f5d79e561c8c1d056ac154a3d4a7efb7f51cc2e3..5aedbf777831c8787f3265e75709a83618809c5a 100644 (file)
@@ -326,9 +326,11 @@ func sigpipe() {
 func doSigPreempt(gp *g, ctxt *sigctxt) {
        // Check if this G wants to be preempted and is safe to
        // preempt.
-       if wantAsyncPreempt(gp) && isAsyncSafePoint(gp, ctxt.sigpc(), ctxt.sigsp(), ctxt.siglr()) {
-               // Inject a call to asyncPreempt.
-               ctxt.pushCall(funcPC(asyncPreempt))
+       if wantAsyncPreempt(gp) {
+               if ok, newpc := isAsyncSafePoint(gp, ctxt.sigpc(), ctxt.sigsp(), ctxt.siglr()); ok {
+                       // Adjust the PC and inject a call to asyncPreempt.
+                       ctxt.pushCall(funcPC(asyncPreempt), newpc)
+               }
        }
 
        // Acknowledge the preemption.
index 04aa90e0779a70504f81cfe7a543e4fd2be9f68b..ce2ec6dd1db0cd54776e8d78c64b657b5cb226dd 100644 (file)
@@ -287,6 +287,18 @@ const (
        // PCDATA_UnsafePoint values.
        _PCDATA_UnsafePointSafe   = -1 // Safe for async preemption
        _PCDATA_UnsafePointUnsafe = -2 // Unsafe for async preemption
+
+       // _PCDATA_Restart1(2) apply on a sequence of instructions, within
+       // which if an async preemption happens, we should back off the PC
+       // to the start of the sequence when resume.
+       // We need two so we can distinguish the start/end of the sequence
+       // in case that two sequences are next to each other.
+       _PCDATA_Restart1 = -3
+       _PCDATA_Restart2 = -4
+
+       // Like _PCDATA_RestartAtEntry, but back to function entry if async
+       // preempted.
+       _PCDATA_RestartAtEntry = -5
 )
 
 // A FuncID identifies particular functions that need to be treated
@@ -708,9 +720,11 @@ func pcvalueCacheKey(targetpc uintptr) uintptr {
        return (targetpc / sys.PtrSize) % uintptr(len(pcvalueCache{}.entries))
 }
 
-func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
+// Returns the PCData value, and the PC where this value starts.
+// TODO: the start PC is returned only when cache is nil.
+func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) {
        if off == 0 {
-               return -1
+               return -1, 0
        }
 
        // Check the cache. This speeds up walks of deep stacks, which
@@ -729,7 +743,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
                        // fail in the first clause.
                        ent := &cache.entries[x][i]
                        if ent.off == off && ent.targetpc == targetpc {
-                               return ent.val
+                               return ent.val, 0
                        }
                }
        }
@@ -739,11 +753,12 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
                        print("runtime: no module data for ", hex(f.entry), "\n")
                        throw("no module data")
                }
-               return -1
+               return -1, 0
        }
        datap := f.datap
        p := datap.pclntable[off:]
        pc := f.entry
+       prevpc := pc
        val := int32(-1)
        for {
                var ok bool
@@ -770,14 +785,15 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
                                }
                        }
 
-                       return val
+                       return val, prevpc
                }
+               prevpc = pc
        }
 
        // If there was a table, it should have covered all program counters.
        // If not, something is wrong.
        if panicking != 0 || !strict {
-               return -1
+               return -1, 0
        }
 
        print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n")
@@ -795,7 +811,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
        }
 
        throw("invalid runtime symbol table")
-       return -1
+       return -1, 0
 }
 
 func cfuncname(f funcInfo) *byte {
@@ -833,9 +849,9 @@ func funcline1(f funcInfo, targetpc uintptr, strict bool) (file string, line int
        if !f.valid() {
                return "?", 0
        }
-       fileno := int(pcvalue(f, f.pcfile, targetpc, nil, strict))
-       line = pcvalue(f, f.pcln, targetpc, nil, strict)
-       if fileno == -1 || line == -1 || fileno >= len(datap.filetab) {
+       fileno, _ := pcvalue(f, f.pcfile, targetpc, nil, strict)
+       line, _ = pcvalue(f, f.pcln, targetpc, nil, strict)
+       if fileno == -1 || line == -1 || int(fileno) >= len(datap.filetab) {
                // print("looking for ", hex(targetpc), " in ", funcname(f), " got file=", fileno, " line=", lineno, "\n")
                return "?", 0
        }
@@ -848,7 +864,7 @@ func funcline(f funcInfo, targetpc uintptr) (file string, line int32) {
 }
 
 func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 {
-       x := pcvalue(f, f.pcsp, targetpc, cache, true)
+       x, _ := pcvalue(f, f.pcsp, targetpc, cache, true)
        if x&(sys.PtrSize-1) != 0 {
                print("invalid spdelta ", funcname(f), " ", hex(f.entry), " ", hex(targetpc), " ", hex(f.pcsp), " ", x, "\n")
        }
@@ -882,14 +898,25 @@ func pcdatavalue(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache)
        if table < 0 || table >= f.npcdata {
                return -1
        }
-       return pcvalue(f, pcdatastart(f, table), targetpc, cache, true)
+       r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true)
+       return r
 }
 
 func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
        if table < 0 || table >= f.npcdata {
                return -1
        }
-       return pcvalue(f, pcdatastart(f, table), targetpc, cache, strict)
+       r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict)
+       return r
+}
+
+// Like pcdatavalue, but also return the start PC of this PCData value.
+// It doesn't take a cache.
+func pcdatavalue2(f funcInfo, table int32, targetpc uintptr) (int32, uintptr) {
+       if table < 0 || table >= f.npcdata {
+               return -1, 0
+       }
+       return pcvalue(f, pcdatastart(f, table), targetpc, nil, true)
 }
 
 func funcdata(f funcInfo, i uint8) unsafe.Pointer {