diff --git a/cl/compile.go b/cl/compile.go index 5b6d7c9fbe..2bff1aae54 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -152,23 +152,24 @@ type pkgInfo struct { type none = struct{} type context struct { - prog llssa.Program - pkg llssa.Package - fn llssa.Function - goFn *ssa.Function - fset *token.FileSet - goProg *ssa.Program - goTyps *types.Package - goPkg *ssa.Package - pyMod string - skips map[string]none - loaded map[*types.Package]*pkgInfo // loaded packages - bvals map[ssa.Value]llssa.Expr // block values - vargs map[*ssa.Alloc][]llssa.Expr // varargs - funcs map[*ssa.Function]llssa.Function - stackDefers map[*ssa.Function]bool - anonDefers map[*ssa.Function]bool - paramDIVars map[*types.Var]llssa.DIVar + prog llssa.Program + pkg llssa.Package + fn llssa.Function + goFn *ssa.Function + fset *token.FileSet + goProg *ssa.Program + goTyps *types.Package + goPkg *ssa.Package + pyMod string + skips map[string]none + loaded map[*types.Package]*pkgInfo // loaded packages + bvals map[ssa.Value]llssa.Expr // block values + vargs map[*ssa.Alloc][]llssa.Expr // varargs + funcs map[*ssa.Function]llssa.Function + stackDefers map[*ssa.Function]bool + anonDefers map[*ssa.Function]bool + paramDIVars map[*types.Var]llssa.DIVar + recoverSlots map[*ssa.Alloc]none patches Patches blkInfos []blocks.Info @@ -402,10 +403,13 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } if fn == nil { fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx, isInstance(f)) - if disableInline { + if disableInline || functionUsesRecover(f) { fn.Inline(llssa.NoInline) } } + if functionUsesRecover(f) { + fn.Expr = fn.Expr.MarkMayRecover() + } p.funcs[f] = fn isCgo := isCgoExternSymbol(f) if nblk := len(f.Blocks); nblk > 0 { @@ -434,11 +438,18 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun dbgSymsEnabled := enableDbgSyms && (f == nil || f.Origin() == nil) p.inits = append(p.inits, func() { oldFn, oldGoFn := p.fn, p.goFn + oldRecoverSlots := p.recoverSlots p.fn = fn p.goFn = f p.state = state // restore pkgState when compiling funcBody + if f.Recover != nil { + p.recoverSlots = make(map[*ssa.Alloc]none) + } else { + p.recoverSlots = nil + } defer func() { p.fn, p.goFn = oldFn, oldGoFn + p.recoverSlots = oldRecoverSlots }() p.phis = nil if dbgSymsEnabled { @@ -772,6 +783,29 @@ func (p *context) checkVArgs(v *ssa.Alloc, t *types.Pointer) bool { return false } +func (p *context) markRecoverSlot(v *ssa.Alloc) { + if p.recoverSlots == nil || v.Heap { + return + } + p.recoverSlots[v] = none{} +} + +func (p *context) isRecoverSlotAddr(v ssa.Value) bool { + if p.recoverSlots == nil { + return false + } + switch v := v.(type) { + case *ssa.Alloc: + _, ok := p.recoverSlots[v] + return ok + case *ssa.FieldAddr: + return p.isRecoverSlotAddr(v.X) + case *ssa.IndexAddr: + return p.isRecoverSlotAddr(v.X) + } + return false +} + func isAllocVargs(ctx *context, v *ssa.Alloc) bool { refs := *v.Referrers() n := len(refs) @@ -905,6 +939,11 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue x := p.compileValue(b, v.X) if v.Op == token.ARROW { ret = b.Recv(x, v.CommaOk) + } else if v.Op == token.MUL { + ret = b.Load(x) + if p.isRecoverSlotAddr(v.X) { + ret = ret.SetVolatile(true) + } } else { ret = b.UnOp(v.Op, x) } @@ -926,6 +965,10 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue } elem := p.type_(t.Elem(), llssa.InGo) ret = b.Alloc(elem, v.Heap) + p.markRecoverSlot(v) + if p.isRecoverSlotAddr(v) { + b.Store(ret, p.prog.Zero(elem)).SetVolatile(true) + } case *ssa.IndexAddr: vx := v.X if _, ok := p.isVArgs(vx); ok { // varargs: this is a varargs index @@ -1126,7 +1169,10 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } ptr := p.compileValue(b, va) val := p.compileValue(b, v.Val) - b.Store(ptr, val) + store := b.Store(ptr, val) + if p.isRecoverSlotAddr(va) { + store.SetVolatile(true) + } case *ssa.Jump: jmpb := p.jumpTo(v) b.Jump(jmpb) @@ -1192,6 +1238,25 @@ func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.V return b.DIVarAuto(scope, pos, v.Name(), t) } +func functionUsesRecover(fn *ssa.Function) bool { + if fn == nil { + return false + } + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + call, ok := instr.(ssa.CallInstruction) + if !ok { + continue + } + builtin, ok := call.Common().Value.(*ssa.Builtin) + if ok && builtin.Name() == "recover" { + return true + } + } + } + return false +} + func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyObjRef, kind int) { // TODO(xsw) v.Pkg == nil: means auto generated function? if v.Pkg == p.goPkg || v.Pkg == nil { diff --git a/cl/instr.go b/cl/instr.go index 0184f1c7fd..b176b58a54 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -838,16 +838,43 @@ func (p *context) deferStackOwner(fn *ssa.Function) llssa.Function { return owner } -func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, ds *explicitDeferStack, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr { +func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, ds *explicitDeferStack, mayRecover bool, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr { if ds != nil { - b.DeferTo(ds.owner, ds.stack, fn, buildCall, args...) + b.DeferToRecover(ds.owner, ds.stack, mayRecover, fn, buildCall, args...) return llssa.Nil } - return b.Do(act, fn, buildCall, args...) + switch act { + case llssa.Call, llssa.Go: + return b.Do(act, fn, buildCall, args...) + default: + b.DeferRecover(act, mayRecover, fn, buildCall, args...) + return llssa.Nil + } +} + +func (p *context) callMayRecover(v ssa.Value) bool { + switch v := v.(type) { + case *ssa.Builtin: + return false + case *ssa.Function: + return functionUsesRecover(v) + case *ssa.MakeClosure: + if fn, ok := v.Fn.(*ssa.Function); ok { + return functionUsesRecover(fn) + } + return true + case *ssa.Call: + if fn := v.Call.StaticCallee(); fn != nil { + return functionUsesRecover(fn) + } + return true + } + return true } func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon, ds *explicitDeferStack) (ret llssa.Expr) { cv := call.Value + mayRecover := p.callMayRecover(cv) if mthd := call.Method; mthd != nil { o := p.compileValue(b, cv) fn := b.Imethod(o, mthd) @@ -856,7 +883,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm hasVArg = fnHasVArg } args := p.compileValues(b, call.Args, hasVArg) - ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, true, fn, llssa.Builder.Call, args...) return } kind := p.funcKind(cv) @@ -879,7 +906,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm } } args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Builtin(fn), llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, false, llssa.Builtin(fn), llssa.Builder.Call, args...) case *ssa.Function: aFn, pyFn, ftype := p.compileFunction(cv) // TODO(xsw): check ca != llssa.Call @@ -888,13 +915,13 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm p.inCFunc = true args := p.compileValues(b, args, kind) p.inCFunc = false - ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, aFn.Expr, llssa.Builder.Call, args...) case goFunc: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, aFn.Expr, llssa.Builder.Call, args...) case pyFunc: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, pyFn.Expr, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, pyFn.Expr, llssa.Builder.Call, args...) case llgoPyList: args := p.compileValues(b, args, fnHasVArg) ret = b.PyList(args...) @@ -968,33 +995,33 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm b.Unreachable() case llgoAtomicLoad: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicLoad(b, args) }, args...) case llgoAtomicStore: args := p.compileValues(b, args, kind) - p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicStore(b, args) }, args...) case llgoAtomicCmpXchg: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicCmpXchg(b, args) }, args...) case llgoAtomicCmpXchgOK: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomicCmpXchgOK(b, args) }, args...) case llgoAtomicAddReturnNew: args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return b.BinOp(token.ADD, p.atomic(b, llssa.OpAdd, args), args[1]) }, args...) default: if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { + ret = p.emitDo(b, act, ds, false, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr { return p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) }, args...) } else { @@ -1004,7 +1031,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm default: fn := p.compileValue(b, cv) args := p.compileValues(b, args, kind) - ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...) + ret = p.emitDo(b, act, ds, mayRecover, fn, llssa.Builder.Call, args...) } return } diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 35112239f6..55ccec357c 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -328,8 +328,8 @@ func TestEmitDoWithExplicitDeferStack(t *testing.T) { b.SetBlockEx(owner.Block(0), llssa.BeforeLast, true) ctx := &context{} - ctx.emitDo(b, llssa.DeferInLoop, &explicitDeferStack{stack: stack, owner: owner}, callee.Expr, llssa.Builder.Call) - ctx.emitDo(b, llssa.DeferAlways, nil, callee.Expr, llssa.Builder.Call) + ctx.emitDo(b, llssa.DeferInLoop, &explicitDeferStack{stack: stack, owner: owner}, false, callee.Expr, llssa.Builder.Call) + ctx.emitDo(b, llssa.DeferAlways, nil, false, callee.Expr, llssa.Builder.Call) b.DeferStackDrain() b.RunDefers() b.Return() @@ -465,7 +465,7 @@ func TestEmitDoWithoutExplicitDeferStack(t *testing.T) { b := fn.MakeBody(1) ctx := &context{} - got := ctx.emitDo(b, llssa.Call, nil, callee.Expr, llssa.Builder.Call) + got := ctx.emitDo(b, llssa.Call, nil, false, callee.Expr, llssa.Builder.Call) if got.IsNil() { t.Fatal("emitDo without explicit defer stack should return direct call result") } diff --git a/runtime/internal/runtime/z_default.go b/runtime/internal/runtime/z_default.go index cffe6cbf01..efcbfb5f85 100644 --- a/runtime/internal/runtime/z_default.go +++ b/runtime/internal/runtime/z_default.go @@ -3,6 +3,8 @@ package runtime import ( + "unsafe" + c "github.com/goplus/llgo/runtime/internal/clite" "github.com/goplus/llgo/runtime/internal/clite/debug" "github.com/goplus/llgo/runtime/internal/clite/pthread" @@ -16,11 +18,12 @@ var ( // Rethrow rethrows a panic. func Rethrow(link *Defer) { - if ptr := excepKey.Get(); ptr != nil { + if ptr := panicKey.Get(); ptr != nil { if link == nil { - TracePanic(*(*any)(ptr)) + node := (*panicNode)(ptr) + TracePanic(node.arg) debug.PrintStack(2) - c.Free(ptr) + c.Free(unsafe.Pointer(node)) c.Exit(2) } else { c.Siglongjmp(link.Addr, 1) diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index 3b17c951e1..a8c29e4777 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -36,30 +36,55 @@ type Defer struct { Args unsafe.Pointer // defer func and args links } +type panicNode struct { + prev unsafe.Pointer + arg any +} + // Recover recovers a panic. -func Recover() (ret any) { - ptr := excepKey.Get() +func Recover(token unsafe.Pointer) (ret any) { + if token == nil || token != recoverFrameKey.Get() { + return nil + } + ptr := panicKey.Get() if ptr != nil { - excepKey.Set(nil) - ret = *(*any)(ptr) - c.Free(ptr) + node := (*panicNode)(ptr) + panicKey.Set(node.prev) + recoverFrameKey.Set(nil) + ret = node.arg + c.Free(unsafe.Pointer(node)) } return } +// StartRecoverFrame enables direct recover calls made by the deferred function +// currently being invoked from frame. +func StartRecoverFrame(frame unsafe.Pointer) unsafe.Pointer { + old := recoverFrameKey.Get() + recoverFrameKey.Set(frame) + return old +} + +// EndRecoverFrame restores direct recover permission after a deferred call. +func EndRecoverFrame(frame unsafe.Pointer) { + recoverFrameKey.Set(frame) +} + // Panic panics with a value. func Panic(v any) { - ptr := c.Malloc(unsafe.Sizeof(v)) - *(*any)(ptr) = v - excepKey.Set(ptr) + ptr := (*panicNode)(c.Malloc(unsafe.Sizeof(panicNode{}))) + ptr.prev = panicKey.Get() + ptr.arg = v + panicKey.Set(unsafe.Pointer(ptr)) Rethrow((*Defer)(c.GoDeferData())) } var ( - excepKey pthread.Key - goexitKey pthread.Key - mainThread pthread.Thread + panicKey pthread.Key + recoverFrameKey pthread.Key + goexitKey pthread.Key + mainThread pthread.Thread ) func Goexit() { @@ -68,7 +93,8 @@ func Goexit() { } func init() { - excepKey.Create(nil) + panicKey.Create(nil) + recoverFrameKey.Create(nil) goexitKey.Create(nil) mainThread = pthread.Self() } diff --git a/ssa/eh.go b/ssa/eh.go index 0221d307e6..76c5d9f0da 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -148,11 +148,12 @@ type aDefer struct { // The id uniquely identifies the defer call site for dispatch during drain. // typ is the node struct type needed to decode the linked-list node. type loopDeferCase struct { - id Expr - typ Type - fn Expr - args []Expr - buildCall func(Builder, Expr, ...Expr) Expr + id Expr + typ Type + mayRecover bool + fn Expr + args []Expr + buildCall func(Builder, Expr, ...Expr) Expr } const ( @@ -302,6 +303,11 @@ func (b Builder) DeferStackDrain() { // Defer emits a defer instruction. func (b Builder) Defer(kind DoAction, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { + b.DeferRecover(kind, deferMayRecover(fn), fn, buildCall, args...) +} + +// DeferRecover emits a defer instruction with explicit recover capability. +func (b Builder) DeferRecover(kind DoAction, mayRecover bool, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { dbgInstrCall("Defer", fn, args) var prog Program var nextbit Expr @@ -329,14 +335,20 @@ func (b Builder) Defer(kind DoAction, fn Expr, buildCall func(Builder, Expr, ... } typ := b.saveDeferArgs(self, kind, id, fn, args) if kind == DeferInLoop { - loopCase := loopDeferCase{id: id, typ: typ, fn: fn, args: args, buildCall: buildCall} + loopCase := loopDeferCase{id: id, typ: typ, mayRecover: mayRecover, fn: fn, args: args, buildCall: buildCall} self.loopCases = append(self.loopCases, loopCase) } - b.appendDeferStmt(self, kind, typ, buildCall, fn, args, nextbit) + b.appendDeferStmt(self, kind, typ, mayRecover, buildCall, fn, args, nextbit) } // DeferTo emits a defer instruction into an explicit runtime defer stack. func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { + b.DeferToRecover(owner, stack, deferMayRecover(fn), fn, buildCall, args...) +} + +// DeferToRecover emits a defer instruction into an explicit runtime defer +// stack with explicit recover capability. +func (b Builder) DeferToRecover(owner Function, stack Expr, mayRecover bool, fn Expr, buildCall func(Builder, Expr, ...Expr) Expr, args ...Expr) { if debugInstr { logCall("DeferTo", fn, args) } @@ -350,11 +362,12 @@ func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Bui argsPtr := b.PtrCast(b.Prog.Pointer(b.Prog.VoidPtr()), stack) typ := b.saveDeferArgsTo(argsPtr, DeferInLoop, id, fn, args) loopCase := loopDeferCase{ - id: id, - typ: typ, - fn: fn, - args: args, - buildCall: buildCall, + id: id, + typ: typ, + mayRecover: mayRecover, + fn: fn, + args: args, + buildCall: buildCall, } if self == nil { owner.pendingLoopCases = append(owner.pendingLoopCases, loopCase) @@ -363,7 +376,7 @@ func (b Builder) DeferTo(owner Function, stack Expr, fn Expr, buildCall func(Bui self.loopCases = append(self.loopCases, loopCase) } -func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr, nextbit Expr) { +func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, mayRecover bool, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr, nextbit Expr) { self.stmts = append(self.stmts, func(bits Expr) { switch kind { case DeferInCond: @@ -374,13 +387,13 @@ func (b Builder) appendDeferStmt(self *aDefer, kind DoAction, typ Type, buildCal zero := prog.Val(uintptr(0)) has := b.BinOp(token.NEQ, b.BinOp(token.AND, bits, nextbit), zero) b.IfThen(has, func() { - b.callDefer(self, typ, buildCall, fn, args) + b.callDefer(self, typ, mayRecover, buildCall, fn, args) }) case DeferAlways: // Leaving a run of loop defers; allow the next loop-defer statement // (earlier in source order) to generate its own drainer. self.loopDrainerGenerated = false - b.callDefer(self, typ, buildCall, fn, args) + b.callDefer(self, typ, mayRecover, buildCall, fn, args) case DeferInLoop: b.loopDeferDrainer(self) } @@ -440,7 +453,7 @@ func (b Builder) loopDeferDrainer(self *aDefer) { b.SetBlockEx(caseBlks[i], AtEnd, true) b.Store(self.rethPtr, drainEntryAddr) - b.callDefer(self, c.typ, c.buildCall, c.fn, c.args) + b.callDefer(self, c.typ, c.mayRecover, c.buildCall, c.fn, c.args) b.Jump(condBlk) } @@ -495,9 +508,11 @@ func (b Builder) saveDeferArgsTo(argsPtr Expr, kind DoAction, id Expr, fn Expr, return typ } -func (b Builder) callDefer(self *aDefer, typ Type, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr) { +func (b Builder) callDefer(self *aDefer, typ Type, mayRecover bool, buildCall func(Builder, Expr, ...Expr) Expr, fn Expr, args []Expr) { if typ == nil { - buildCall(b, fn, args...) + b.callRecoverScopedDefer(fn, mayRecover, func() { + buildCall(b, fn, args...) + }) return } prog := b.Prog @@ -519,11 +534,70 @@ func (b Builder) callDefer(self *aDefer, typ Type, buildCall func(Builder, Expr, for i := 0; i < len(args); i++ { args[i] = b.getField(data, i+offset) } - buildCall(b, fn, args...) + b.callRecoverScopedDefer(fn, mayRecover, func() { + buildCall(b, fn, args...) + }) b.Call(b.Pkg.rtFunc("FreeDeferNode"), ptr) }) } +func (b Builder) callRecoverScopedDefer(fn Expr, mayRecover bool, call func()) { + if fn.IsNil() || fn.impl.IsNil() || isRecoverBuiltin(fn) { + call() + return + } + token := b.recoverDeferToken(fn, mayRecover) + if token.IsNil() { + call() + return + } + prev := b.Call(b.Pkg.rtFunc("StartRecoverFrame"), token) + call() + b.Call(b.Pkg.rtFunc("EndRecoverFrame"), prev) +} + +func (b Builder) recoverDeferToken(fn Expr, mayRecover bool) Expr { + switch fn.kind { + case vkClosure: + if !mayRecover { + return Nil + } + return b.PtrCast(b.Prog.VoidPtr(), b.Field(fn, 0)) + case vkFuncDecl: + if !mayRecover { + return Nil + } + return b.PtrCast(b.Prog.VoidPtr(), fn) + case vkFuncPtr: + return b.PtrCast(b.Prog.VoidPtr(), fn) + } + return Nil +} + +func deferMayRecover(fn Expr) bool { + if fn.IsNil() || fn.Type == nil { + return false + } + switch fn.kind { + case vkClosure, vkFuncDecl: + return fn.Type.mayRecover + case vkFuncPtr: + return true + } + return false +} + +func isRecoverBuiltin(fn Expr) bool { + if fn.IsNil() { + return false + } + if fn.kind != vkBuiltin { + return false + } + bi, ok := fn.raw.Type.(*builtinTy) + return ok && bi.name == "recover" +} + // RunDefers emits instructions to run deferred instructions. func (b Builder) RunDefers() { self := b.getDefer(DeferInCond) @@ -593,8 +667,7 @@ func (b Builder) Unreachable() { // Recover emits a recover instruction. func (b Builder) Recover() Expr { dbgInstrln("Recover") - // TODO(xsw): recover can't be a function call in Go - return b.Call(b.Pkg.rtFunc("Recover")) + return b.Call(b.Pkg.rtFunc("Recover"), b.PtrCast(b.Prog.VoidPtr(), b.Func.Expr)) } // Panic emits a panic instruction. diff --git a/ssa/expr.go b/ssa/expr.go index ecc19ca215..56d9a3dd71 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -59,6 +59,20 @@ func (v Expr) SetOrdering(ordering AtomicOrdering) Expr { return v } +// SetVolatile marks a load or store as volatile. +func (v Expr) SetVolatile(volatile bool) Expr { + v.impl.SetVolatile(volatile) + return v +} + +// MarkMayRecover marks a function or closure that may call recover directly. +func (v Expr) MarkMayRecover() Expr { + if v.Type != nil { + v.Type.mayRecover = true + } + return v +} + func (v Expr) SetName(alias string) Expr { v.impl.SetName(alias) return v diff --git a/ssa/package.go b/ssa/package.go index 0db719c157..670a4e63e7 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -514,7 +514,7 @@ func (p Program) AbiTypePtr() Type { // Void returns void type. func (p Program) Void() Type { if p.voidTy == nil { - p.voidTy = &aType{p.tyVoid(), rawType{types.Typ[types.Invalid]}, vkInvalid} + p.voidTy = &aType{ll: p.tyVoid(), raw: rawType{types.Typ[types.Invalid]}, kind: vkInvalid} } return p.voidTy } diff --git a/ssa/python.go b/ssa/python.go index 43629853c8..5f34712596 100644 --- a/ssa/python.go +++ b/ssa/python.go @@ -593,7 +593,7 @@ func (p Package) PyNewFunc(name string, sig *types.Signature, doInit bool) PyObj obj.InitNil() obj.impl.SetLinkage(llvm.LinkOnceAnyLinkage) } - ty := &aType{obj.ll, rawType{types.NewPointer(sig)}, vkPyFuncRef} + ty := &aType{ll: obj.ll, raw: rawType{types.NewPointer(sig)}, kind: vkPyFuncRef} expr := Expr{obj.impl, ty} ret := &aPyObjRef{expr, obj} p.pyobjs[name] = ret diff --git a/ssa/type.go b/ssa/type.go index e9f3aab4f2..d0018c4f16 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -163,9 +163,10 @@ type rawType struct { } type aType struct { - ll llvm.Type - raw rawType - kind valueKind // value kind of llvm.Type + ll llvm.Type + raw rawType + kind valueKind // value kind of llvm.Type + mayRecover bool // function/closure may call recover directly } type Type = *aType @@ -340,7 +341,7 @@ func (p Program) tyInt64() llvm.Type { /* func (p Program) toTuple(typ *types.Tuple) Type { - return &aType{p.toLLVMTuple(typ), rawType{typ}, vkTuple} + return &aType{ll: p.toLLVMTuple(typ), raw: rawType{typ}, kind: vkTuple} } */ @@ -353,66 +354,66 @@ func (p Program) toType(raw types.Type) Type { case *types.Basic: switch t.Kind() { case types.Int: - return &aType{p.tyInt(), typ, vkSigned} + return &aType{ll: p.tyInt(), raw: typ, kind: vkSigned} case types.Uint, types.Uintptr: - return &aType{p.tyInt(), typ, vkUnsigned} + return &aType{ll: p.tyInt(), raw: typ, kind: vkUnsigned} case types.Bool: - return &aType{p.tyInt1(), typ, vkBool} + return &aType{ll: p.tyInt1(), raw: typ, kind: vkBool} case types.Uint8: - return &aType{p.tyInt8(), typ, vkUnsigned} + return &aType{ll: p.tyInt8(), raw: typ, kind: vkUnsigned} case types.Int8: - return &aType{p.tyInt8(), typ, vkSigned} + return &aType{ll: p.tyInt8(), raw: typ, kind: vkSigned} case types.Int16: - return &aType{p.tyInt16(), typ, vkSigned} + return &aType{ll: p.tyInt16(), raw: typ, kind: vkSigned} case types.Uint16: - return &aType{p.tyInt16(), typ, vkUnsigned} + return &aType{ll: p.tyInt16(), raw: typ, kind: vkUnsigned} case types.Int32: - return &aType{p.tyInt32(), typ, vkSigned} + return &aType{ll: p.tyInt32(), raw: typ, kind: vkSigned} case types.Uint32: - return &aType{p.tyInt32(), typ, vkUnsigned} + return &aType{ll: p.tyInt32(), raw: typ, kind: vkUnsigned} case types.Int64: - return &aType{p.tyInt64(), typ, vkSigned} + return &aType{ll: p.tyInt64(), raw: typ, kind: vkSigned} case types.Uint64: - return &aType{p.tyInt64(), typ, vkUnsigned} + return &aType{ll: p.tyInt64(), raw: typ, kind: vkUnsigned} case types.Float32: - return &aType{p.ctx.FloatType(), typ, vkFloat} + return &aType{ll: p.ctx.FloatType(), raw: typ, kind: vkFloat} case types.Float64: - return &aType{p.ctx.DoubleType(), typ, vkFloat} + return &aType{ll: p.ctx.DoubleType(), raw: typ, kind: vkFloat} case types.Complex64: - return &aType{p.tyComplex64(), typ, vkComplex} + return &aType{ll: p.tyComplex64(), raw: typ, kind: vkComplex} case types.Complex128: - return &aType{p.tyComplex128(), typ, vkComplex} + return &aType{ll: p.tyComplex128(), raw: typ, kind: vkComplex} case types.String: - return &aType{p.rtString(), typ, vkString} + return &aType{ll: p.rtString(), raw: typ, kind: vkString} case types.UnsafePointer: - return &aType{p.tyVoidPtr(), typ, vkPtr} + return &aType{ll: p.tyVoidPtr(), raw: typ, kind: vkPtr} } case *types.Pointer: elem := p.rawType(t.Elem()) - return &aType{llvm.PointerType(elem.ll, 0), typ, vkPtr} + return &aType{ll: llvm.PointerType(elem.ll, 0), raw: typ, kind: vkPtr} case *types.Interface: if t.Empty() { - return &aType{p.rtEface(), typ, vkEface} + return &aType{ll: p.rtEface(), raw: typ, kind: vkEface} } - return &aType{p.rtIface(), typ, vkIface} + return &aType{ll: p.rtIface(), raw: typ, kind: vkIface} case *types.Slice: - return &aType{p.rtSlice(), typ, vkSlice} + return &aType{ll: p.rtSlice(), raw: typ, kind: vkSlice} case *types.Map: - return &aType{llvm.PointerType(p.rtMap(), 0), typ, vkMap} + return &aType{ll: llvm.PointerType(p.rtMap(), 0), raw: typ, kind: vkMap} case *types.Struct: ll, kind := p.toLLVMStruct(t) - return &aType{ll, typ, kind} + return &aType{ll: ll, raw: typ, kind: kind} case *types.Named: return p.toNamed(t) case *types.Signature: // represents a C function pointer in raw type - return &aType{p.toLLVMFuncPtr(t), typ, vkFuncPtr} + return &aType{ll: p.toLLVMFuncPtr(t), raw: typ, kind: vkFuncPtr} case *types.Tuple: - return &aType{p.toLLVMTuple(t), typ, vkTuple} + return &aType{ll: p.toLLVMTuple(t), raw: typ, kind: vkTuple} case *types.Array: elem := p.rawType(t.Elem()) - return &aType{llvm.ArrayType(elem.ll, int(t.Len())), typ, vkArray} + return &aType{ll: llvm.ArrayType(elem.ll, int(t.Len())), raw: typ, kind: vkArray} case *types.Chan: - return &aType{llvm.PointerType(p.rtChan(), 0), typ, vkChan} + return &aType{ll: llvm.PointerType(p.rtChan(), 0), raw: typ, kind: vkChan} case *types.Alias: return p.toType(types.Unalias(t)) case *types.TypeParam: @@ -423,7 +424,7 @@ func (p Program) toType(raw types.Type) Type { func (p Program) toLLVMNamedStruct(name string, raw *types.Named, st *types.Struct, kind valueKind) Type { t := p.ctx.StructCreateNamed(name) - typ := &aType{t, rawType{raw}, kind} + typ := &aType{ll: t, raw: rawType{raw}, kind: kind} p.named[name] = typ p.typs.Set(raw, typ) fields := p.toLLVMFields(st) @@ -506,7 +507,7 @@ func (p Program) retType(raw *types.Signature) Type { case 1: return p.rawType(out.At(0).Type()) default: - return &aType{p.toLLVMTuple(out), rawType{out}, vkTuple} + return &aType{ll: p.toLLVMTuple(out), raw: rawType{out}, kind: vkTuple} } } @@ -567,7 +568,7 @@ func (p Program) toNamed(raw *types.Named) Type { return p.toLLVMNamedStruct(name, raw, t, kind) default: typ := p.rawType(t) - return &aType{typ.ll, rawType{raw}, typ.kind} + return &aType{ll: typ.ll, raw: rawType{raw}, kind: typ.kind} } } diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index bd8ab36ed4..02050009fd 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -64,7 +64,7 @@ func (p Program) FuncDecl(sig *types.Signature, bg Background) Type { } else if recv != nil { // even in C, we need to add ctx for method sig = FuncAddCtx(recv, sig) } - return &aType{p.toLLVMFunc(sig), rawType{sig}, vkFuncDecl} + return &aType{ll: p.toLLVMFunc(sig), raw: rawType{sig}, kind: vkFuncDecl} } // Closure creates a closture type for a function. diff --git a/test/go/recover_defer_fixedbugs_test.go b/test/go/recover_defer_fixedbugs_test.go new file mode 100644 index 0000000000..e55075fa8c --- /dev/null +++ b/test/go/recover_defer_fixedbugs_test.go @@ -0,0 +1,119 @@ +package gotest + +import ( + "runtime" + "strings" + "testing" +) + +type fixedbug4066Panic struct{} + +func fixedbug4066NamedReturn() (val int) { + val = 0 + defer func() { + if x := recover(); x != nil { + _ = x.(fixedbug4066Panic) + } + }() + for { + val = 2 + fixedbug4066Throw() + } +} + +func fixedbug4066Throw() { + panic(fixedbug4066Panic{}) +} + +func TestRecoverFixedbug4066NamedReturn(t *testing.T) { + if got := fixedbug4066NamedReturn(); got != 2 { + t.Fatalf("named return after recover = %d, want 2", got) + } +} + +func TestRecoverFixedbugDirectDeferredFuncValue(t *testing.T) { + recovered := false + func() { + f := func() { + if recover() != nil { + recovered = true + } + } + defer f() + panic("direct deferred func value") + }() + if !recovered { + t.Fatal("direct deferred func value did not recover") + } +} + +var fixedbug73916Recovered bool + +func fixedbug73916CallRecover() { + if recover() != nil { + fixedbug73916Recovered = true + } +} + +func fixedbug73916Deferred(int) { + fixedbug73916CallRecover() +} + +func fixedbug73916MustPanic(t *testing.T, fn func()) any { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Fatal("deferred indirect recover swallowed panic") + } + }() + fn() + return nil +} + +func TestRecoverFixedbug73916IndirectRecoverDoesNotRecover(t *testing.T) { + skipBeforeGo126(t) + fixedbug73916Recovered = false + fixedbug73916MustPanic(t, func() { + defer fixedbug73916Deferred(1) + panic("fixedbug73916") + }) + if fixedbug73916Recovered { + t.Fatal("indirect recover returned non-nil") + } +} + +var fixedbug73916bRecovered bool + +func fixedbug73916bCallRecover() { + func() { + if recover() != nil { + fixedbug73916bRecovered = true + } + }() +} + +func fixedbug73916bDeferred() int { + fixedbug73916bCallRecover() + return 0 +} + +func TestRecoverFixedbug73916NestedRecoverDoesNotRecover(t *testing.T) { + skipBeforeGo126(t) + fixedbug73916bRecovered = false + fixedbug73916MustPanic(t, func() { + defer fixedbug73916bDeferred() + panic("fixedbug73916b") + }) + if fixedbug73916bRecovered { + t.Fatal("nested recover returned non-nil") + } +} + +func skipBeforeGo126(t *testing.T) { + t.Helper() + version := runtime.Version() + if strings.HasPrefix(version, "go1.26") || strings.HasPrefix(version, "devel") { + return + } + t.Skip("requires Go 1.26 recover semantics") +} diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index a5a66d10b4..4228fce18b 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -569,12 +569,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -947,12 +941,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.25 platform: darwin/arm64 directive: run @@ -1325,12 +1313,6 @@ timeouts: case: fixedbugs/issue40367.go timeout: 2m reason: issue40367 run exceeds the default timeout on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - timeout: 2m - reason: issue4066 run exceeds the default timeout on darwin/arm64 - version: go1.26 platform: darwin/arm64 directive: run @@ -1854,16 +1836,6 @@ xfails: directive: run case: fixedbugs/issue65417.go reason: current main goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73916.go - reason: go1.26 goroot run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue73916b.go - reason: go1.26 goroot run failure on darwin/arm64 - version: go1.26 platform: darwin/arm64 directive: run @@ -2222,16 +2194,6 @@ xfails: directive: run case: fixedbugs/issue72844.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73916.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue73916b.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -2691,16 +2653,6 @@ xfails: directive: run case: fixedbugs/issue33724.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.25 goroot run failure on darwin/arm64 - version: go1.25 platform: linux/amd64 directive: run @@ -3117,16 +3069,6 @@ xfails: directive: run case: fixedbugs/issue33724.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.24 goroot run failure on darwin/arm64 - version: go1.24 platform: linux/amd64 directive: run @@ -3367,16 +3309,6 @@ xfails: directive: run case: fixedbugs/issue33724.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue4066.go - reason: go1.26 goroot run failure on darwin/arm64 - version: go1.26 platform: linux/amd64 directive: run