Merge pull request #1348 from cpunion/feature/defer-loop
defer: enable loop lowering
This commit is contained in:
@@ -261,7 +261,7 @@ All Go syntax (including `cgo`) is already supported. Here are some examples:
|
|||||||
|
|
||||||
### Defer
|
### Defer
|
||||||
|
|
||||||
LLGo `defer` does not support usage in loops. This is not a bug but a feature, because we think that using `defer` in a loop is a very unrecommended practice.
|
LLGo now supports `defer` within loops, matching Go's semantics of executing defers in LIFO order for every iteration. The usual caveat from Go still applies: be mindful of loop-heavy defer usage because it allocates per iteration.
|
||||||
|
|
||||||
|
|
||||||
### Garbage Collection (GC)
|
### Garbage Collection (GC)
|
||||||
|
|||||||
@@ -300,6 +300,9 @@ github_com_goplus_llgo_runtime_internal_clite_pthread_sync_init(void) GO_SYMBOL_
|
|||||||
void
|
void
|
||||||
github_com_goplus_llgo_runtime_internal_clite_signal_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/signal.init")
|
github_com_goplus_llgo_runtime_internal_clite_signal_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/signal.init")
|
||||||
|
|
||||||
|
void
|
||||||
|
github_com_goplus_llgo_runtime_internal_clite_tls_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/tls.init")
|
||||||
|
|
||||||
void
|
void
|
||||||
github_com_goplus_llgo_runtime_internal_runtime_goarch_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/goarch.init")
|
github_com_goplus_llgo_runtime_internal_runtime_goarch_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/goarch.init")
|
||||||
|
|
||||||
|
|||||||
51
cl/_testgo/defercomplex/in.go
Normal file
51
cl/_testgo/defercomplex/in.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for _, label := range complexOrder() {
|
||||||
|
println(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func complexOrder() (res []string) {
|
||||||
|
record := func(label string) { res = append(res, label) }
|
||||||
|
|
||||||
|
defer record(label1("cleanup-final", 0))
|
||||||
|
defer record(label1("cleanup-before-loop", 0))
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
defer record(label1("exit-outer", i))
|
||||||
|
for j := 0; j < 2; j++ {
|
||||||
|
if j == 0 {
|
||||||
|
defer record(label2("branch-even", i, j))
|
||||||
|
} else {
|
||||||
|
defer record(label2("branch-odd", i, j))
|
||||||
|
}
|
||||||
|
for k := 0; k < 2; k++ {
|
||||||
|
nested := label3("nested", i, j, k)
|
||||||
|
defer record(nested)
|
||||||
|
if k == 1 {
|
||||||
|
defer record(label3("nested-tail", i, j, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer record(label1("post-loop", 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func label1(prefix string, a int) string {
|
||||||
|
return prefix + "-" + digit(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func label2(prefix string, a, b int) string {
|
||||||
|
return prefix + "-" + digit(a) + "-" + digit(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func label3(prefix string, a, b, c int) string {
|
||||||
|
return prefix + "-" + digit(a) + "-" + digit(b) + "-" + digit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func digit(n int) string {
|
||||||
|
return string(rune('0' + n))
|
||||||
|
}
|
||||||
1
cl/_testgo/defercomplex/out.ll
Normal file
1
cl/_testgo/defercomplex/out.ll
Normal file
@@ -0,0 +1 @@
|
|||||||
|
;
|
||||||
7
cl/_testgo/deferloop/in.go
Normal file
7
cl/_testgo/deferloop/in.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
defer println("loop", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
cl/_testgo/deferloop/out.ll
Normal file
1
cl/_testgo/deferloop/out.ll
Normal file
@@ -0,0 +1 @@
|
|||||||
|
;
|
||||||
@@ -98,18 +98,19 @@ type pkgInfo struct {
|
|||||||
type none = struct{}
|
type none = struct{}
|
||||||
|
|
||||||
type context struct {
|
type context struct {
|
||||||
prog llssa.Program
|
prog llssa.Program
|
||||||
pkg llssa.Package
|
pkg llssa.Package
|
||||||
fn llssa.Function
|
fn llssa.Function
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
goProg *ssa.Program
|
goProg *ssa.Program
|
||||||
goTyps *types.Package
|
goTyps *types.Package
|
||||||
goPkg *ssa.Package
|
goPkg *ssa.Package
|
||||||
pyMod string
|
pyMod string
|
||||||
skips map[string]none
|
skips map[string]none
|
||||||
loaded map[*types.Package]*pkgInfo // loaded packages
|
loaded map[*types.Package]*pkgInfo // loaded packages
|
||||||
bvals map[ssa.Value]llssa.Expr // block values
|
bvals map[ssa.Value]llssa.Expr // block values
|
||||||
vargs map[*ssa.Alloc][]llssa.Expr // varargs
|
vargs map[*ssa.Alloc][]llssa.Expr // varargs
|
||||||
|
paramDIVars map[*types.Var]llssa.DIVar
|
||||||
|
|
||||||
patches Patches
|
patches Patches
|
||||||
blkInfos []blocks.Info
|
blkInfos []blocks.Info
|
||||||
@@ -263,6 +264,8 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
|
|||||||
if f.Recover != nil { // set recover block
|
if f.Recover != nil { // set recover block
|
||||||
fn.SetRecover(fn.Block(f.Recover.Index))
|
fn.SetRecover(fn.Block(f.Recover.Index))
|
||||||
}
|
}
|
||||||
|
dbgEnabled := enableDbg && (f == nil || f.Origin() == nil)
|
||||||
|
dbgSymsEnabled := enableDbgSyms && (f == nil || f.Origin() == nil)
|
||||||
p.inits = append(p.inits, func() {
|
p.inits = append(p.inits, func() {
|
||||||
p.fn = fn
|
p.fn = fn
|
||||||
p.state = state // restore pkgState when compiling funcBody
|
p.state = state // restore pkgState when compiling funcBody
|
||||||
@@ -270,6 +273,11 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
|
|||||||
p.fn = nil
|
p.fn = nil
|
||||||
}()
|
}()
|
||||||
p.phis = nil
|
p.phis = nil
|
||||||
|
if dbgSymsEnabled {
|
||||||
|
p.paramDIVars = make(map[*types.Var]llssa.DIVar)
|
||||||
|
} else {
|
||||||
|
p.paramDIVars = nil
|
||||||
|
}
|
||||||
if debugGoSSA {
|
if debugGoSSA {
|
||||||
f.WriteTo(os.Stderr)
|
f.WriteTo(os.Stderr)
|
||||||
}
|
}
|
||||||
@@ -277,7 +285,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
|
|||||||
log.Println("==> FuncBody", name)
|
log.Println("==> FuncBody", name)
|
||||||
}
|
}
|
||||||
b := fn.NewBuilder()
|
b := fn.NewBuilder()
|
||||||
if enableDbg {
|
if dbgEnabled {
|
||||||
pos := p.goProg.Fset.Position(f.Pos())
|
pos := p.goProg.Fset.Position(f.Pos())
|
||||||
bodyPos := p.getFuncBodyPos(f)
|
bodyPos := p.getFuncBodyPos(f)
|
||||||
b.DebugFunction(fn, pos, bodyPos)
|
b.DebugFunction(fn, pos, bodyPos)
|
||||||
@@ -371,6 +379,9 @@ func (p *context) debugParams(b llssa.Builder, f *ssa.Function) {
|
|||||||
ty := param.Type()
|
ty := param.Type()
|
||||||
argNo := i + 1
|
argNo := i + 1
|
||||||
div := b.DIVarParam(p.fn, pos, param.Name(), p.type_(ty, llssa.InGo), argNo)
|
div := b.DIVarParam(p.fn, pos, param.Name(), p.type_(ty, llssa.InGo), argNo)
|
||||||
|
if p.paramDIVars != nil {
|
||||||
|
p.paramDIVars[variable] = div
|
||||||
|
}
|
||||||
b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0))
|
b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +399,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do
|
|||||||
b.Printf("call " + fn.Name() + "\n\x00")
|
b.Printf("call " + fn.Name() + "\n\x00")
|
||||||
}
|
}
|
||||||
// place here to avoid wrong current-block
|
// place here to avoid wrong current-block
|
||||||
if enableDbgSyms && block.Index == 0 {
|
if enableDbgSyms && block.Parent().Origin() == nil && block.Index == 0 {
|
||||||
p.debugParams(b, block.Parent())
|
p.debugParams(b, block.Parent())
|
||||||
}
|
}
|
||||||
if doModInit {
|
if doModInit {
|
||||||
@@ -783,7 +794,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
|||||||
p.compileInstrOrValue(b, iv, false)
|
p.compileInstrOrValue(b, iv, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if enableDbg {
|
if enableDbg && instr.Parent().Origin() == nil {
|
||||||
scope := p.getDebugLocScope(instr.Parent(), instr.Pos())
|
scope := p.getDebugLocScope(instr.Parent(), instr.Pos())
|
||||||
if scope != nil {
|
if scope != nil {
|
||||||
diScope := b.DIScope(p.fn, scope)
|
diScope := b.DIScope(p.fn, scope)
|
||||||
@@ -846,7 +857,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
|||||||
x := p.compileValue(b, v.X)
|
x := p.compileValue(b, v.X)
|
||||||
b.Send(ch, x)
|
b.Send(ch, x)
|
||||||
case *ssa.DebugRef:
|
case *ssa.DebugRef:
|
||||||
if enableDbgSyms {
|
if enableDbgSyms && v.Parent().Origin() == nil {
|
||||||
p.debugRef(b, v)
|
p.debugRef(b, v)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -855,14 +866,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar {
|
func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar {
|
||||||
pos := p.fset.Position(v.Pos())
|
if p.paramDIVars != nil {
|
||||||
t := p.type_(v.Type(), llssa.InGo)
|
if div, ok := p.paramDIVars[v]; ok {
|
||||||
for i, param := range fn.Params {
|
return div
|
||||||
if param.Object().(*types.Var) == v {
|
|
||||||
argNo := i + 1
|
|
||||||
return b.DIVarParam(p.fn, pos, v.Name(), t, argNo)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pos := p.fset.Position(v.Pos())
|
||||||
|
t := p.type_(v.Type(), llssa.InGo)
|
||||||
scope := b.DIScope(p.fn, v.Parent())
|
scope := b.DIScope(p.fn, v.Parent())
|
||||||
return b.DIVarAuto(scope, pos, v.Name(), t)
|
return b.DIVarAuto(scope, pos, v.Name(), t)
|
||||||
}
|
}
|
||||||
|
|||||||
32
doc/defer-tls-gc.md
Normal file
32
doc/defer-tls-gc.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Defer Loop GC Integration
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
`defer` chains are stored in a per-thread TLS slot so that unwind paths can locate the active `*runtime.Defer`. With the default allocator (`AllocU`) backed by Boehm GC (bdwgc), those TLS-resident pointers were invisible to the collector. In stress scenarios—e.g. `TestDeferLoopStress` with 1,000,000 defers—the collector reclaimed the defer nodes, leaving dangling pointers and causing crashes inside the deferred closures.
|
||||||
|
|
||||||
|
Prior experiments (`test-defer-dont-free` branch) confirmed the crash disappeared when allocations bypassed GC (plain `malloc` without `free`), pointing to a root-registration gap rather than logical corruption.
|
||||||
|
|
||||||
|
## Solution Overview
|
||||||
|
|
||||||
|
1. **GC-aware TLS slot helper** *(from PR [#1347](https://github.com/goplus/llgo/pull/1347))*
|
||||||
|
- Added `runtime/internal/clite/tls`, which exposes `tls.Alloc` to create per-thread storage that is automatically registered as a Boehm GC root.
|
||||||
|
- `SetThreadDefer` delegates to this helper so every thread reuses the same GC-safe slot without bespoke plumbing.
|
||||||
|
- The package handles TLS key creation, root registration/removal, and invokes an optional destructor when a thread exits.
|
||||||
|
|
||||||
|
2. **SSA codegen synchronization**
|
||||||
|
- `ssa/eh.go` now calls `runtime.SetThreadDefer` whenever it updates the TLS pointer (on first allocation and when restoring the previous link during unwind).
|
||||||
|
- Defer argument nodes and the `runtime.Defer` struct itself are allocated with `aggregateAllocU`, ensuring new memory comes from GC-managed heaps, and nodes are released via `runtime.FreeDeferNode`.
|
||||||
|
|
||||||
|
3. **Non-GC builds**
|
||||||
|
- The `tls` helper falls back to a malloc-backed TLS slot without GC registration, while `FreeDeferNode` continues to release nodes via `c.Free` when building with `-tags nogc`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the stress and regression suites to validate the integration:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./llgo.sh test ./test -run TestDeferLoopStress
|
||||||
|
./llgo.sh test ./test
|
||||||
|
```
|
||||||
|
|
||||||
|
The updated `TestDeferLoopStress` now asserts 1,000,000 loop defers execute without failure, catching regressions in GC root tracking.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !llgo
|
||||||
|
|
||||||
package libc
|
package libc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,622 +0,0 @@
|
|||||||
// Copyright 2021 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.
|
|
||||||
|
|
||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const debugResolve = false
|
|
||||||
|
|
||||||
// resolveFile walks the given file to resolve identifiers within the file
|
|
||||||
// scope, updating ast.Ident.Obj fields with declaration information.
|
|
||||||
//
|
|
||||||
// If declErr is non-nil, it is used to report declaration errors during
|
|
||||||
// resolution. tok is used to format position in error messages.
|
|
||||||
func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, string)) {
|
|
||||||
pkgScope := ast.NewScope(nil)
|
|
||||||
r := &resolver{
|
|
||||||
handle: handle,
|
|
||||||
declErr: declErr,
|
|
||||||
topScope: pkgScope,
|
|
||||||
pkgScope: pkgScope,
|
|
||||||
depth: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, decl := range file.Decls {
|
|
||||||
ast.Walk(r, decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.closeScope()
|
|
||||||
assert(r.topScope == nil, "unbalanced scopes")
|
|
||||||
assert(r.labelScope == nil, "unbalanced label scopes")
|
|
||||||
|
|
||||||
// resolve global identifiers within the same file
|
|
||||||
i := 0
|
|
||||||
for _, ident := range r.unresolved {
|
|
||||||
// i <= index for current ident
|
|
||||||
assert(ident.Obj == unresolved, "object already resolved")
|
|
||||||
ident.Obj = r.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel
|
|
||||||
if ident.Obj == nil {
|
|
||||||
r.unresolved[i] = ident
|
|
||||||
i++
|
|
||||||
} else if debugResolve {
|
|
||||||
pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos()
|
|
||||||
r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.Scope = r.pkgScope
|
|
||||||
file.Unresolved = r.unresolved[0:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxScopeDepth int = 1e3
|
|
||||||
|
|
||||||
type resolver struct {
|
|
||||||
handle *token.File
|
|
||||||
declErr func(token.Pos, string)
|
|
||||||
|
|
||||||
// Ordinary identifier scopes
|
|
||||||
pkgScope *ast.Scope // pkgScope.Outer == nil
|
|
||||||
topScope *ast.Scope // top-most scope; may be pkgScope
|
|
||||||
unresolved []*ast.Ident // unresolved identifiers
|
|
||||||
depth int // scope depth
|
|
||||||
|
|
||||||
// Label scopes
|
|
||||||
// (maintained by open/close LabelScope)
|
|
||||||
labelScope *ast.Scope // label scope for current function
|
|
||||||
targetStack [][]*ast.Ident // stack of unresolved labels
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) trace(format string, args ...any) {
|
|
||||||
fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) sprintf(format string, args ...any) string {
|
|
||||||
for i, arg := range args {
|
|
||||||
switch arg := arg.(type) {
|
|
||||||
case token.Pos:
|
|
||||||
args[i] = r.handle.Position(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) openScope(pos token.Pos) {
|
|
||||||
r.depth++
|
|
||||||
if r.depth > maxScopeDepth {
|
|
||||||
panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"})
|
|
||||||
}
|
|
||||||
if debugResolve {
|
|
||||||
r.trace("opening scope @%v", pos)
|
|
||||||
}
|
|
||||||
r.topScope = ast.NewScope(r.topScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) closeScope() {
|
|
||||||
r.depth--
|
|
||||||
if debugResolve {
|
|
||||||
r.trace("closing scope")
|
|
||||||
}
|
|
||||||
r.topScope = r.topScope.Outer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) openLabelScope() {
|
|
||||||
r.labelScope = ast.NewScope(r.labelScope)
|
|
||||||
r.targetStack = append(r.targetStack, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) closeLabelScope() {
|
|
||||||
// resolve labels
|
|
||||||
n := len(r.targetStack) - 1
|
|
||||||
scope := r.labelScope
|
|
||||||
for _, ident := range r.targetStack[n] {
|
|
||||||
ident.Obj = scope.Lookup(ident.Name)
|
|
||||||
if ident.Obj == nil && r.declErr != nil {
|
|
||||||
r.declErr(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// pop label scope
|
|
||||||
r.targetStack = r.targetStack[0:n]
|
|
||||||
r.labelScope = r.labelScope.Outer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
|
|
||||||
for _, ident := range idents {
|
|
||||||
if ident.Obj != nil {
|
|
||||||
panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
|
|
||||||
}
|
|
||||||
obj := ast.NewObj(kind, ident.Name)
|
|
||||||
// remember the corresponding declaration for redeclaration
|
|
||||||
// errors and global variable resolution/typechecking phase
|
|
||||||
obj.Decl = decl
|
|
||||||
obj.Data = data
|
|
||||||
// Identifiers (for receiver type parameters) are written to the scope, but
|
|
||||||
// never set as the resolved object. See go.dev/issue/50956.
|
|
||||||
if _, ok := decl.(*ast.Ident); !ok {
|
|
||||||
ident.Obj = obj
|
|
||||||
}
|
|
||||||
if ident.Name != "_" {
|
|
||||||
if debugResolve {
|
|
||||||
r.trace("declaring %s@%v", ident.Name, ident.Pos())
|
|
||||||
}
|
|
||||||
if alt := scope.Insert(obj); alt != nil && r.declErr != nil {
|
|
||||||
prevDecl := ""
|
|
||||||
if pos := alt.Pos(); pos.IsValid() {
|
|
||||||
prevDecl = r.sprintf("\n\tprevious declaration at %v", pos)
|
|
||||||
}
|
|
||||||
r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) shortVarDecl(decl *ast.AssignStmt) {
|
|
||||||
// Go spec: A short variable declaration may redeclare variables
|
|
||||||
// provided they were originally declared in the same block with
|
|
||||||
// the same type, and at least one of the non-blank variables is new.
|
|
||||||
n := 0 // number of new variables
|
|
||||||
for _, x := range decl.Lhs {
|
|
||||||
if ident, isIdent := x.(*ast.Ident); isIdent {
|
|
||||||
assert(ident.Obj == nil, "identifier already declared or resolved")
|
|
||||||
obj := ast.NewObj(ast.Var, ident.Name)
|
|
||||||
// remember corresponding assignment for other tools
|
|
||||||
obj.Decl = decl
|
|
||||||
ident.Obj = obj
|
|
||||||
if ident.Name != "_" {
|
|
||||||
if debugResolve {
|
|
||||||
r.trace("declaring %s@%v", ident.Name, ident.Pos())
|
|
||||||
}
|
|
||||||
if alt := r.topScope.Insert(obj); alt != nil {
|
|
||||||
ident.Obj = alt // redeclaration
|
|
||||||
} else {
|
|
||||||
n++ // new declaration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n == 0 && r.declErr != nil {
|
|
||||||
r.declErr(decl.Lhs[0].Pos(), "no new variables on left side of :=")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The unresolved object is a sentinel to mark identifiers that have been added
|
|
||||||
// to the list of unresolved identifiers. The sentinel is only used for verifying
|
|
||||||
// internal consistency.
|
|
||||||
var unresolved = new(ast.Object)
|
|
||||||
|
|
||||||
// If x is an identifier, resolve attempts to resolve x by looking up
|
|
||||||
// the object it denotes. If no object is found and collectUnresolved is
|
|
||||||
// set, x is marked as unresolved and collected in the list of unresolved
|
|
||||||
// identifiers.
|
|
||||||
func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) {
|
|
||||||
if ident.Obj != nil {
|
|
||||||
panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
|
|
||||||
}
|
|
||||||
// '_' should never refer to existing declarations, because it has special
|
|
||||||
// handling in the spec.
|
|
||||||
if ident.Name == "_" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for s := r.topScope; s != nil; s = s.Outer {
|
|
||||||
if obj := s.Lookup(ident.Name); obj != nil {
|
|
||||||
if debugResolve {
|
|
||||||
r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj)
|
|
||||||
}
|
|
||||||
assert(obj.Name != "", "obj with no name")
|
|
||||||
// Identifiers (for receiver type parameters) are written to the scope,
|
|
||||||
// but never set as the resolved object. See go.dev/issue/50956.
|
|
||||||
if _, ok := obj.Decl.(*ast.Ident); !ok {
|
|
||||||
ident.Obj = obj
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// all local scopes are known, so any unresolved identifier
|
|
||||||
// must be found either in the file scope, package scope
|
|
||||||
// (perhaps in another file), or universe scope --- collect
|
|
||||||
// them so that they can be resolved later
|
|
||||||
if collectUnresolved {
|
|
||||||
ident.Obj = unresolved
|
|
||||||
r.unresolved = append(r.unresolved, ident)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkExprs(list []ast.Expr) {
|
|
||||||
for _, node := range list {
|
|
||||||
ast.Walk(r, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Unparen(e ast.Expr) ast.Expr {
|
|
||||||
for {
|
|
||||||
paren, ok := e.(*ast.ParenExpr)
|
|
||||||
if !ok {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
e = paren.X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkLHS(list []ast.Expr) {
|
|
||||||
for _, expr := range list {
|
|
||||||
expr := Unparen(expr)
|
|
||||||
if _, ok := expr.(*ast.Ident); !ok && expr != nil {
|
|
||||||
ast.Walk(r, expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkStmts(list []ast.Stmt) {
|
|
||||||
for _, stmt := range list {
|
|
||||||
ast.Walk(r, stmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) Visit(node ast.Node) ast.Visitor {
|
|
||||||
if debugResolve && node != nil {
|
|
||||||
r.trace("node %T@%v", node, node.Pos())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch n := node.(type) {
|
|
||||||
|
|
||||||
// Expressions.
|
|
||||||
case *ast.Ident:
|
|
||||||
r.resolve(n, true)
|
|
||||||
|
|
||||||
case *ast.FuncLit:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkFuncType(n.Type)
|
|
||||||
r.walkBody(n.Body)
|
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
ast.Walk(r, n.X)
|
|
||||||
// Note: don't try to resolve n.Sel, as we don't support qualified
|
|
||||||
// resolution.
|
|
||||||
|
|
||||||
case *ast.StructType:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkFieldList(n.Fields, ast.Var)
|
|
||||||
|
|
||||||
case *ast.FuncType:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkFuncType(n)
|
|
||||||
|
|
||||||
case *ast.CompositeLit:
|
|
||||||
if n.Type != nil {
|
|
||||||
ast.Walk(r, n.Type)
|
|
||||||
}
|
|
||||||
for _, e := range n.Elts {
|
|
||||||
if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
|
|
||||||
// See go.dev/issue/45160: try to resolve composite lit keys, but don't
|
|
||||||
// collect them as unresolved if resolution failed. This replicates
|
|
||||||
// existing behavior when resolving during parsing.
|
|
||||||
if ident, _ := kv.Key.(*ast.Ident); ident != nil {
|
|
||||||
r.resolve(ident, false)
|
|
||||||
} else {
|
|
||||||
ast.Walk(r, kv.Key)
|
|
||||||
}
|
|
||||||
ast.Walk(r, kv.Value)
|
|
||||||
} else {
|
|
||||||
ast.Walk(r, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.InterfaceType:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkFieldList(n.Methods, ast.Fun)
|
|
||||||
|
|
||||||
// Statements
|
|
||||||
case *ast.LabeledStmt:
|
|
||||||
r.declare(n, nil, r.labelScope, ast.Lbl, n.Label)
|
|
||||||
ast.Walk(r, n.Stmt)
|
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
r.walkExprs(n.Rhs)
|
|
||||||
if n.Tok == token.DEFINE {
|
|
||||||
r.shortVarDecl(n)
|
|
||||||
} else {
|
|
||||||
r.walkExprs(n.Lhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BranchStmt:
|
|
||||||
// add to list of unresolved targets
|
|
||||||
if n.Tok != token.FALLTHROUGH && n.Label != nil {
|
|
||||||
depth := len(r.targetStack) - 1
|
|
||||||
r.targetStack[depth] = append(r.targetStack[depth], n.Label)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BlockStmt:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkStmts(n.List)
|
|
||||||
|
|
||||||
case *ast.IfStmt:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
if n.Init != nil {
|
|
||||||
ast.Walk(r, n.Init)
|
|
||||||
}
|
|
||||||
ast.Walk(r, n.Cond)
|
|
||||||
ast.Walk(r, n.Body)
|
|
||||||
if n.Else != nil {
|
|
||||||
ast.Walk(r, n.Else)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.CaseClause:
|
|
||||||
r.walkExprs(n.List)
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
r.walkStmts(n.Body)
|
|
||||||
|
|
||||||
case *ast.SwitchStmt:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
if n.Init != nil {
|
|
||||||
ast.Walk(r, n.Init)
|
|
||||||
}
|
|
||||||
if n.Tag != nil {
|
|
||||||
// The scope below reproduces some unnecessary behavior of the parser,
|
|
||||||
// opening an extra scope in case this is a type switch. It's not needed
|
|
||||||
// for expression switches.
|
|
||||||
// TODO: remove this once we've matched the parser resolution exactly.
|
|
||||||
if n.Init != nil {
|
|
||||||
r.openScope(n.Tag.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
}
|
|
||||||
ast.Walk(r, n.Tag)
|
|
||||||
}
|
|
||||||
if n.Body != nil {
|
|
||||||
r.walkStmts(n.Body.List)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.TypeSwitchStmt:
|
|
||||||
if n.Init != nil {
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
ast.Walk(r, n.Init)
|
|
||||||
}
|
|
||||||
r.openScope(n.Assign.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
ast.Walk(r, n.Assign)
|
|
||||||
// s.Body consists only of case clauses, so does not get its own
|
|
||||||
// scope.
|
|
||||||
if n.Body != nil {
|
|
||||||
r.walkStmts(n.Body.List)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.CommClause:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
if n.Comm != nil {
|
|
||||||
ast.Walk(r, n.Comm)
|
|
||||||
}
|
|
||||||
r.walkStmts(n.Body)
|
|
||||||
|
|
||||||
case *ast.SelectStmt:
|
|
||||||
// as for switch statements, select statement bodies don't get their own
|
|
||||||
// scope.
|
|
||||||
if n.Body != nil {
|
|
||||||
r.walkStmts(n.Body.List)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ForStmt:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
if n.Init != nil {
|
|
||||||
ast.Walk(r, n.Init)
|
|
||||||
}
|
|
||||||
if n.Cond != nil {
|
|
||||||
ast.Walk(r, n.Cond)
|
|
||||||
}
|
|
||||||
if n.Post != nil {
|
|
||||||
ast.Walk(r, n.Post)
|
|
||||||
}
|
|
||||||
ast.Walk(r, n.Body)
|
|
||||||
|
|
||||||
case *ast.RangeStmt:
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
ast.Walk(r, n.X)
|
|
||||||
var lhs []ast.Expr
|
|
||||||
if n.Key != nil {
|
|
||||||
lhs = append(lhs, n.Key)
|
|
||||||
}
|
|
||||||
if n.Value != nil {
|
|
||||||
lhs = append(lhs, n.Value)
|
|
||||||
}
|
|
||||||
if len(lhs) > 0 {
|
|
||||||
if n.Tok == token.DEFINE {
|
|
||||||
// Note: we can't exactly match the behavior of object resolution
|
|
||||||
// during the parsing pass here, as it uses the position of the RANGE
|
|
||||||
// token for the RHS OpPos. That information is not contained within
|
|
||||||
// the AST.
|
|
||||||
as := &ast.AssignStmt{
|
|
||||||
Lhs: lhs,
|
|
||||||
Tok: token.DEFINE,
|
|
||||||
TokPos: n.TokPos,
|
|
||||||
Rhs: []ast.Expr{&ast.UnaryExpr{Op: token.RANGE, X: n.X}},
|
|
||||||
}
|
|
||||||
// TODO(rFindley): this walkLHS reproduced the parser resolution, but
|
|
||||||
// is it necessary? By comparison, for a normal AssignStmt we don't
|
|
||||||
// walk the LHS in case there is an invalid identifier list.
|
|
||||||
r.walkLHS(lhs)
|
|
||||||
r.shortVarDecl(as)
|
|
||||||
} else {
|
|
||||||
r.walkExprs(lhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast.Walk(r, n.Body)
|
|
||||||
|
|
||||||
// Declarations
|
|
||||||
case *ast.GenDecl:
|
|
||||||
switch n.Tok {
|
|
||||||
case token.CONST, token.VAR:
|
|
||||||
for i, spec := range n.Specs {
|
|
||||||
spec := spec.(*ast.ValueSpec)
|
|
||||||
kind := ast.Con
|
|
||||||
if n.Tok == token.VAR {
|
|
||||||
kind = ast.Var
|
|
||||||
}
|
|
||||||
r.walkExprs(spec.Values)
|
|
||||||
if spec.Type != nil {
|
|
||||||
ast.Walk(r, spec.Type)
|
|
||||||
}
|
|
||||||
r.declare(spec, i, r.topScope, kind, spec.Names...)
|
|
||||||
}
|
|
||||||
case token.TYPE:
|
|
||||||
for _, spec := range n.Specs {
|
|
||||||
spec := spec.(*ast.TypeSpec)
|
|
||||||
// Go spec: The scope of a type identifier declared inside a function begins
|
|
||||||
// at the identifier in the TypeSpec and ends at the end of the innermost
|
|
||||||
// containing block.
|
|
||||||
r.declare(spec, nil, r.topScope, ast.Typ, spec.Name)
|
|
||||||
if spec.TypeParams != nil {
|
|
||||||
r.openScope(spec.Pos())
|
|
||||||
r.walkTParams(spec.TypeParams)
|
|
||||||
r.closeScope()
|
|
||||||
}
|
|
||||||
ast.Walk(r, spec.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
// Open the function scope.
|
|
||||||
r.openScope(n.Pos())
|
|
||||||
defer r.closeScope()
|
|
||||||
|
|
||||||
r.walkRecv(n.Recv)
|
|
||||||
|
|
||||||
// Type parameters are walked normally: they can reference each other, and
|
|
||||||
// can be referenced by normal parameters.
|
|
||||||
if n.Type.TypeParams != nil {
|
|
||||||
r.walkTParams(n.Type.TypeParams)
|
|
||||||
// TODO(rFindley): need to address receiver type parameters.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve and declare parameters in a specific order to get duplicate
|
|
||||||
// declaration errors in the correct location.
|
|
||||||
r.resolveList(n.Type.Params)
|
|
||||||
r.resolveList(n.Type.Results)
|
|
||||||
r.declareList(n.Recv, ast.Var)
|
|
||||||
r.declareList(n.Type.Params, ast.Var)
|
|
||||||
r.declareList(n.Type.Results, ast.Var)
|
|
||||||
|
|
||||||
r.walkBody(n.Body)
|
|
||||||
if n.Recv == nil && n.Name.Name != "init" {
|
|
||||||
r.declare(n, nil, r.pkgScope, ast.Fun, n.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkFuncType(typ *ast.FuncType) {
|
|
||||||
// typ.TypeParams must be walked separately for FuncDecls.
|
|
||||||
r.resolveList(typ.Params)
|
|
||||||
r.resolveList(typ.Results)
|
|
||||||
r.declareList(typ.Params, ast.Var)
|
|
||||||
r.declareList(typ.Results, ast.Var)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) resolveList(list *ast.FieldList) {
|
|
||||||
if list == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, f := range list.List {
|
|
||||||
if f.Type != nil {
|
|
||||||
ast.Walk(r, f.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) {
|
|
||||||
if list == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, f := range list.List {
|
|
||||||
r.declare(f, nil, r.topScope, kind, f.Names...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkRecv(recv *ast.FieldList) {
|
|
||||||
// If our receiver has receiver type parameters, we must declare them before
|
|
||||||
// trying to resolve the rest of the receiver, and avoid re-resolving the
|
|
||||||
// type parameter identifiers.
|
|
||||||
if recv == nil || len(recv.List) == 0 {
|
|
||||||
return // nothing to do
|
|
||||||
}
|
|
||||||
typ := recv.List[0].Type
|
|
||||||
if ptr, ok := typ.(*ast.StarExpr); ok {
|
|
||||||
typ = ptr.X
|
|
||||||
}
|
|
||||||
|
|
||||||
var declareExprs []ast.Expr // exprs to declare
|
|
||||||
var resolveExprs []ast.Expr // exprs to resolve
|
|
||||||
switch typ := typ.(type) {
|
|
||||||
case *ast.IndexExpr:
|
|
||||||
declareExprs = []ast.Expr{typ.Index}
|
|
||||||
resolveExprs = append(resolveExprs, typ.X)
|
|
||||||
case *ast.IndexListExpr:
|
|
||||||
declareExprs = typ.Indices
|
|
||||||
resolveExprs = append(resolveExprs, typ.X)
|
|
||||||
default:
|
|
||||||
resolveExprs = append(resolveExprs, typ)
|
|
||||||
}
|
|
||||||
for _, expr := range declareExprs {
|
|
||||||
if id, _ := expr.(*ast.Ident); id != nil {
|
|
||||||
r.declare(expr, nil, r.topScope, ast.Typ, id)
|
|
||||||
} else {
|
|
||||||
// The receiver type parameter expression is invalid, but try to resolve
|
|
||||||
// it anyway for consistency.
|
|
||||||
resolveExprs = append(resolveExprs, expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, expr := range resolveExprs {
|
|
||||||
if expr != nil {
|
|
||||||
ast.Walk(r, expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The receiver is invalid, but try to resolve it anyway for consistency.
|
|
||||||
for _, f := range recv.List[1:] {
|
|
||||||
if f.Type != nil {
|
|
||||||
ast.Walk(r, f.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) {
|
|
||||||
if list == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.resolveList(list)
|
|
||||||
r.declareList(list, kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkTParams is like walkFieldList, but declares type parameters eagerly so
|
|
||||||
// that they may be resolved in the constraint expressions held in the field
|
|
||||||
// Type.
|
|
||||||
func (r *resolver) walkTParams(list *ast.FieldList) {
|
|
||||||
r.declareList(list, ast.Typ)
|
|
||||||
r.resolveList(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolver) walkBody(body *ast.BlockStmt) {
|
|
||||||
if body == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.openLabelScope()
|
|
||||||
defer r.closeLabelScope()
|
|
||||||
r.walkStmts(body.List)
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package testing
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
34
runtime/internal/clite/tls/tls_stub.go
Normal file
34
runtime/internal/clite/tls/tls_stub.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//go:build !llgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
// Handle is a no-op TLS handle used when building without the llgo tag.
|
||||||
|
type Handle[T any] struct{}
|
||||||
|
|
||||||
|
// Alloc returns a stub TLS handle that ignores all operations.
|
||||||
|
func Alloc[T any](func(*T)) Handle[T] { return Handle[T]{} }
|
||||||
|
|
||||||
|
// Get always returns the zero value.
|
||||||
|
func (Handle[T]) Get() (zero T) { return zero }
|
||||||
|
|
||||||
|
// Set is a no-op.
|
||||||
|
func (Handle[T]) Set(T) {}
|
||||||
|
|
||||||
|
// Clear is a no-op.
|
||||||
|
func (Handle[T]) Clear() {}
|
||||||
40
runtime/internal/runtime/defer_tls.go
Normal file
40
runtime/internal/runtime/defer_tls.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "github.com/goplus/llgo/runtime/internal/clite/tls"
|
||||||
|
|
||||||
|
var deferTLS = tls.Alloc[*Defer](func(head **Defer) {
|
||||||
|
if head != nil {
|
||||||
|
*head = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// SetThreadDefer associates the current thread with the given defer chain.
|
||||||
|
func SetThreadDefer(head *Defer) {
|
||||||
|
deferTLS.Set(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThreadDefer returns the current thread's defer chain head.
|
||||||
|
func GetThreadDefer() *Defer {
|
||||||
|
return deferTLS.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearThreadDefer resets the current thread's defer chain to nil.
|
||||||
|
func ClearThreadDefer() {
|
||||||
|
deferTLS.Clear()
|
||||||
|
}
|
||||||
33
runtime/internal/runtime/z_defer_gc.go
Normal file
33
runtime/internal/runtime/z_defer_gc.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//go:build !nogc
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||||
|
"github.com/goplus/llgo/runtime/internal/clite/bdwgc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FreeDeferNode releases a defer argument node allocated from the Boehm heap.
|
||||||
|
func FreeDeferNode(ptr unsafe.Pointer) {
|
||||||
|
if ptr != nil {
|
||||||
|
bdwgc.Free(c.Pointer(ptr))
|
||||||
|
}
|
||||||
|
}
|
||||||
32
runtime/internal/runtime/z_defer_nogc.go
Normal file
32
runtime/internal/runtime/z_defer_nogc.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build nogc
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FreeDeferNode releases the defer node when GC integration is disabled.
|
||||||
|
func FreeDeferNode(ptr unsafe.Pointer) {
|
||||||
|
if ptr != nil {
|
||||||
|
c.Free(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,27 +7,11 @@ import (
|
|||||||
//go:embed _overlay/runtime/runtime.go
|
//go:embed _overlay/runtime/runtime.go
|
||||||
var fakeRuntime string
|
var fakeRuntime string
|
||||||
|
|
||||||
//go:embed _overlay/go/parser/resolver.go
|
|
||||||
var go_parser_resolver string
|
|
||||||
|
|
||||||
//go:embed _overlay/testing/testing.go
|
|
||||||
var testing_testing string
|
|
||||||
|
|
||||||
//go:embed _overlay/testing/testing_go123.go
|
|
||||||
var testing_testing_go123 string
|
|
||||||
|
|
||||||
//go:embed _overlay/testing/testing_go124.go
|
|
||||||
var testing_testing_go124 string
|
|
||||||
|
|
||||||
//go:embed _overlay/net/textproto/textproto.go
|
//go:embed _overlay/net/textproto/textproto.go
|
||||||
var net_textproto string
|
var net_textproto string
|
||||||
|
|
||||||
var OverlayFiles = map[string]string{
|
var OverlayFiles = map[string]string{
|
||||||
"math/exp_amd64.go": "package math;",
|
"math/exp_amd64.go": "package math;",
|
||||||
"go/parser/resolver.go": go_parser_resolver,
|
|
||||||
"testing/testing.go": testing_testing,
|
|
||||||
"testing/testing_go123.go": testing_testing_go123,
|
|
||||||
"testing/testing_go124.go": testing_testing_go124,
|
|
||||||
"net/textproto/textproto.go": net_textproto,
|
"net/textproto/textproto.go": net_textproto,
|
||||||
"runtime/runtime.go": fakeRuntime,
|
"runtime/runtime.go": fakeRuntime,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ func (p Function) NewBuilder() Builder {
|
|||||||
// TODO(xsw): Finalize may cause panic, so comment it.
|
// TODO(xsw): Finalize may cause panic, so comment it.
|
||||||
// b.Finalize()
|
// b.Finalize()
|
||||||
return &aBuilder{b, nil, p, p.Pkg, prog,
|
return &aBuilder{b, nil, p, p.Pkg, prog,
|
||||||
make(map[Expr]dbgExpr), make(map[*types.Scope]DIScope)}
|
make(map[*types.Scope]DIScope)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasBody reports whether the function has a body.
|
// HasBody reports whether the function has a body.
|
||||||
|
|||||||
12
ssa/di.go
12
ssa/di.go
@@ -662,14 +662,8 @@ func (b diBuilder) createExpression(ops []uint64) DIExpression {
|
|||||||
|
|
||||||
// Copy to alloca'd memory to get declareable address.
|
// Copy to alloca'd memory to get declareable address.
|
||||||
func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) {
|
func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) {
|
||||||
if v, ok := b.dbgVars[v]; ok {
|
|
||||||
return v.ptr, v.val, true
|
|
||||||
}
|
|
||||||
t := v.Type.RawType().Underlying()
|
t := v.Type.RawType().Underlying()
|
||||||
dbgPtr, dbgVal = b.doConstructDebugAddr(v, t)
|
dbgPtr, dbgVal = b.doConstructDebugAddr(v, t)
|
||||||
dbgExpr := dbgExpr{dbgPtr, dbgVal}
|
|
||||||
b.dbgVars[v] = dbgExpr
|
|
||||||
b.dbgVars[dbgVal] = dbgExpr
|
|
||||||
return dbgPtr, dbgVal, false
|
return dbgPtr, dbgVal, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,11 +868,7 @@ func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Pos
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b Builder) Param(idx int) Expr {
|
func (b Builder) Param(idx int) Expr {
|
||||||
p := b.Func.Param(idx)
|
return b.Func.Param(idx)
|
||||||
if v, ok := b.dbgVars[p]; ok {
|
|
||||||
return v.val
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
112
ssa/eh.go
112
ssa/eh.go
@@ -149,10 +149,6 @@ func (b Builder) Longjmp(jb, retval Expr) {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
const (
|
|
||||||
deferKey = "__llgo_defer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
|
func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
|
||||||
b = p.NewBuilder()
|
b = p.NewBuilder()
|
||||||
next = b.setBlockMoveLast(p.blks[0])
|
next = b.setBlockMoveLast(p.blks[0])
|
||||||
@@ -162,7 +158,6 @@ func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
|
|||||||
|
|
||||||
type aDefer struct {
|
type aDefer struct {
|
||||||
nextBit int // next defer bit
|
nextBit int // next defer bit
|
||||||
key Expr // pthread TLS key
|
|
||||||
data Expr // pointer to runtime.Defer
|
data Expr // pointer to runtime.Defer
|
||||||
bitsPtr Expr // pointer to defer bits
|
bitsPtr Expr // pointer to defer bits
|
||||||
rethPtr Expr // next block of Rethrow
|
rethPtr Expr // next block of Rethrow
|
||||||
@@ -174,30 +169,6 @@ type aDefer struct {
|
|||||||
stmts []func(bits Expr)
|
stmts []func(bits Expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Package) keyInit(name string) {
|
|
||||||
keyVar := p.VarOf(name)
|
|
||||||
if keyVar == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prog := p.Prog
|
|
||||||
keyVar.InitNil()
|
|
||||||
keyVar.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
|
|
||||||
|
|
||||||
b := p.afterBuilder()
|
|
||||||
eq := b.BinOp(token.EQL, b.Load(keyVar.Expr), prog.IntVal(0, prog.CInt()))
|
|
||||||
b.IfThen(eq, func() {
|
|
||||||
b.pthreadKeyCreate(keyVar.Expr, prog.Nil(prog.VoidPtr()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Package) newKey(name string) Global {
|
|
||||||
return p.NewVarEx(name, p.Prog.CIntPtr())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Builder) deferKey() Expr {
|
|
||||||
return b.Load(b.Pkg.newKey(deferKey).Expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 0: addr sigjmpbuf
|
// 0: addr sigjmpbuf
|
||||||
// 1: bits uintptr
|
// 1: bits uintptr
|
||||||
@@ -230,17 +201,19 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
|
|||||||
blks := self.MakeBlocks(2)
|
blks := self.MakeBlocks(2)
|
||||||
procBlk, rethrowBlk := blks[0], blks[1]
|
procBlk, rethrowBlk := blks[0], blks[1]
|
||||||
|
|
||||||
key := b.deferKey()
|
|
||||||
zero := prog.Val(uintptr(0))
|
zero := prog.Val(uintptr(0))
|
||||||
link := Expr{b.pthreadGetspecific(key).impl, prog.DeferPtr()}
|
link := b.Call(b.Pkg.rtFunc("GetThreadDefer"))
|
||||||
jb := b.AllocaSigjmpBuf()
|
jb := b.AllocaSigjmpBuf()
|
||||||
ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl)
|
ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl)
|
||||||
deferData := Expr{ptr, prog.DeferPtr()}
|
deferData := Expr{ptr, prog.DeferPtr()}
|
||||||
b.pthreadSetspecific(key, deferData)
|
b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData)
|
||||||
bitsPtr := b.FieldAddr(deferData, deferBits)
|
bitsPtr := b.FieldAddr(deferData, deferBits)
|
||||||
rethPtr := b.FieldAddr(deferData, deferRethrow)
|
rethPtr := b.FieldAddr(deferData, deferRethrow)
|
||||||
rundPtr := b.FieldAddr(deferData, deferRunDefers)
|
rundPtr := b.FieldAddr(deferData, deferRunDefers)
|
||||||
argsPtr := b.FieldAddr(deferData, deferArgs)
|
argsPtr := b.FieldAddr(deferData, deferArgs)
|
||||||
|
// Initialize the args list so later guards (e.g. DeferAlways/DeferInLoop)
|
||||||
|
// can safely detect an empty chain without a prior push.
|
||||||
|
b.Store(argsPtr, prog.Nil(prog.VoidPtr()))
|
||||||
|
|
||||||
czero := prog.IntVal(0, prog.CInt())
|
czero := prog.IntVal(0, prog.CInt())
|
||||||
retval := b.Sigsetjmp(jb, czero)
|
retval := b.Sigsetjmp(jb, czero)
|
||||||
@@ -253,7 +226,6 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
|
|||||||
b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk)
|
b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk)
|
||||||
|
|
||||||
self.defer_ = &aDefer{
|
self.defer_ = &aDefer{
|
||||||
key: key,
|
|
||||||
data: deferData,
|
data: deferData,
|
||||||
bitsPtr: bitsPtr,
|
bitsPtr: bitsPtr,
|
||||||
rethPtr: rethPtr,
|
rethPtr: rethPtr,
|
||||||
@@ -278,8 +250,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
|
|||||||
|
|
||||||
// DeferData returns the defer data (*runtime.Defer).
|
// DeferData returns the defer data (*runtime.Defer).
|
||||||
func (b Builder) DeferData() Expr {
|
func (b Builder) DeferData() Expr {
|
||||||
key := b.deferKey()
|
return b.Call(b.Pkg.rtFunc("GetThreadDefer"))
|
||||||
return Expr{b.pthreadGetspecific(key).impl, b.Prog.DeferPtr()}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer emits a defer instruction.
|
// Defer emits a defer instruction.
|
||||||
@@ -294,14 +265,17 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) {
|
|||||||
case DeferInCond:
|
case DeferInCond:
|
||||||
prog = b.Prog
|
prog = b.Prog
|
||||||
next := self.nextBit
|
next := self.nextBit
|
||||||
|
if uintptr(next) >= unsafe.Sizeof(uintptr(0))*8 {
|
||||||
|
panic("too many conditional defers")
|
||||||
|
}
|
||||||
self.nextBit++
|
self.nextBit++
|
||||||
bits := b.Load(self.bitsPtr)
|
bits := b.Load(self.bitsPtr)
|
||||||
nextbit = prog.Val(uintptr(1 << next))
|
nextbit = prog.Val(uintptr(1 << next))
|
||||||
b.Store(self.bitsPtr, b.BinOp(token.OR, bits, nextbit))
|
b.Store(self.bitsPtr, b.BinOp(token.OR, bits, nextbit))
|
||||||
case DeferAlways:
|
case DeferAlways:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
default:
|
case DeferInLoop:
|
||||||
panic("todo: DeferInLoop is not supported - " + b.Func.Name())
|
// Loop defers rely on a dedicated drain loop inserted below.
|
||||||
}
|
}
|
||||||
typ := b.saveDeferArgs(self, fn, args)
|
typ := b.saveDeferArgs(self, fn, args)
|
||||||
self.stmts = append(self.stmts, func(bits Expr) {
|
self.stmts = append(self.stmts, func(bits Expr) {
|
||||||
@@ -314,6 +288,29 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) {
|
|||||||
})
|
})
|
||||||
case DeferAlways:
|
case DeferAlways:
|
||||||
b.callDefer(self, typ, fn, args)
|
b.callDefer(self, typ, fn, args)
|
||||||
|
case DeferInLoop:
|
||||||
|
prog := b.Prog
|
||||||
|
condBlk := b.Func.MakeBlock()
|
||||||
|
bodyBlk := b.Func.MakeBlock()
|
||||||
|
exitBlk := b.Func.MakeBlock()
|
||||||
|
// Control flow:
|
||||||
|
// condBlk: check argsPtr for non-nil to see if there's work to drain.
|
||||||
|
// bodyBlk: execute a single defer node, then jump back to condBlk.
|
||||||
|
// exitBlk: reached when the list is empty (argsPtr == nil).
|
||||||
|
// This mirrors runtime's linked-list unwinding semantics for loop defers.
|
||||||
|
|
||||||
|
// jump to condition check before executing
|
||||||
|
b.Jump(condBlk)
|
||||||
|
b.SetBlockEx(condBlk, AtEnd, true)
|
||||||
|
list := b.Load(self.argsPtr)
|
||||||
|
has := b.BinOp(token.NEQ, list, prog.Nil(prog.VoidPtr()))
|
||||||
|
b.If(has, bodyBlk, exitBlk)
|
||||||
|
|
||||||
|
b.SetBlockEx(bodyBlk, AtEnd, true)
|
||||||
|
b.callDefer(self, typ, fn, args)
|
||||||
|
b.Jump(condBlk)
|
||||||
|
|
||||||
|
b.SetBlockEx(exitBlk, AtEnd, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -354,7 +351,7 @@ func (b Builder) saveDeferArgs(self *aDefer, fn Expr, args []Expr) Type {
|
|||||||
flds[i+offset] = arg.impl
|
flds[i+offset] = arg.impl
|
||||||
}
|
}
|
||||||
typ := prog.Struct(typs...)
|
typ := prog.Struct(typs...)
|
||||||
ptr := Expr{b.aggregateMalloc(typ, flds...), prog.VoidPtr()}
|
ptr := Expr{b.aggregateAllocU(typ, flds...), prog.VoidPtr()}
|
||||||
b.Store(self.argsPtr, ptr)
|
b.Store(self.argsPtr, ptr)
|
||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
@@ -365,19 +362,28 @@ func (b Builder) callDefer(self *aDefer, typ Type, fn Expr, args []Expr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
prog := b.Prog
|
prog := b.Prog
|
||||||
ptr := b.Load(self.argsPtr)
|
zero := prog.Nil(prog.VoidPtr())
|
||||||
data := b.Load(Expr{ptr.impl, prog.Pointer(typ)})
|
list := b.Load(self.argsPtr)
|
||||||
offset := 1
|
has := b.BinOp(token.NEQ, list, zero)
|
||||||
b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()})
|
// The guard is required because callDefer is reused by endDefer() after the
|
||||||
if fn.kind == vkClosure {
|
// list has been drained. Without this check we would dereference a nil
|
||||||
fn = b.getField(data, 1)
|
// pointer when no loop defers were recorded.
|
||||||
offset++
|
b.IfThen(has, func() {
|
||||||
}
|
ptr := b.Load(self.argsPtr)
|
||||||
for i := 0; i < len(args); i++ {
|
data := b.Load(Expr{ptr.impl, prog.Pointer(typ)})
|
||||||
args[i] = b.getField(data, i+offset)
|
offset := 1
|
||||||
}
|
b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()})
|
||||||
b.Call(fn, args...)
|
callFn := fn
|
||||||
b.free(ptr)
|
if callFn.kind == vkClosure {
|
||||||
|
callFn = b.getField(data, 1)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
args[i] = b.getField(data, i+offset)
|
||||||
|
}
|
||||||
|
b.Call(callFn, args...)
|
||||||
|
b.Call(b.Pkg.rtFunc("FreeDeferNode"), ptr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunDefers emits instructions to run deferred instructions.
|
// RunDefers emits instructions to run deferred instructions.
|
||||||
@@ -431,7 +437,7 @@ func (p Function) endDefer(b Builder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
link := b.getField(b.Load(self.data), deferLink)
|
link := b.getField(b.Load(self.data), deferLink)
|
||||||
b.pthreadSetspecific(self.key, link)
|
b.Call(b.Pkg.rtFunc("SetThreadDefer"), link)
|
||||||
b.IndirectJump(b.Load(rundPtr), nexts)
|
b.IndirectJump(b.Load(rundPtr), nexts)
|
||||||
|
|
||||||
b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow
|
b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow
|
||||||
|
|||||||
53
ssa/eh_loop_test.go
Normal file
53
ssa/eh_loop_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//go:build !llgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ssa_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/ssa"
|
||||||
|
"github.com/goplus/llgo/ssa/ssatest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeferInLoopIR(t *testing.T) {
|
||||||
|
prog := ssatest.NewProgram(t, nil)
|
||||||
|
pkg := prog.NewPackage("foo", "foo")
|
||||||
|
|
||||||
|
callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo)
|
||||||
|
cb := callee.MakeBody(1)
|
||||||
|
cb.Return()
|
||||||
|
cb.EndBuild()
|
||||||
|
|
||||||
|
fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo)
|
||||||
|
b := fn.MakeBody(1)
|
||||||
|
fn.SetRecover(fn.MakeBlock())
|
||||||
|
|
||||||
|
// Ensure entry block has a terminator like real codegen
|
||||||
|
b.Return()
|
||||||
|
b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true)
|
||||||
|
|
||||||
|
b.Defer(ssa.DeferInLoop, callee.Expr)
|
||||||
|
b.EndBuild()
|
||||||
|
|
||||||
|
ir := pkg.Module().String()
|
||||||
|
if !strings.Contains(ir, "icmp ne ptr") {
|
||||||
|
t.Fatalf("expected loop defer condition in IR, got:\n%s", ir)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -798,7 +798,6 @@ func (p Package) afterBuilder() Builder {
|
|||||||
|
|
||||||
// AfterInit is called after the package is initialized (init all packages that depends on).
|
// AfterInit is called after the package is initialized (init all packages that depends on).
|
||||||
func (p Package) AfterInit(b Builder, ret BasicBlock) {
|
func (p Package) AfterInit(b Builder, ret BasicBlock) {
|
||||||
p.keyInit(deferKey)
|
|
||||||
doAfterb := p.afterb != nil
|
doAfterb := p.afterb != nil
|
||||||
doPyLoadModSyms := p.pyHasModSyms()
|
doPyLoadModSyms := p.pyHasModSyms()
|
||||||
if doAfterb || doPyLoadModSyms {
|
if doAfterb || doPyLoadModSyms {
|
||||||
|
|||||||
@@ -60,6 +60,42 @@ func TestUnsafeString(t *testing.T) {
|
|||||||
b.Return()
|
b.Return()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTooManyConditionalDefers(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Chdir("../../runtime")
|
||||||
|
defer os.Chdir(wd)
|
||||||
|
|
||||||
|
prog := NewProgram(nil)
|
||||||
|
prog.SetRuntime(func() *types.Package {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
imp := packages.NewImporter(fset)
|
||||||
|
pkg, _ := imp.Import(PkgRuntime)
|
||||||
|
return pkg
|
||||||
|
})
|
||||||
|
|
||||||
|
pkg := prog.NewPackage("foo", "foo")
|
||||||
|
target := pkg.NewFunc("f", NoArgsNoRet, InGo)
|
||||||
|
fn := pkg.NewFunc("main", NoArgsNoRet, InGo)
|
||||||
|
fn.SetRecover(fn.MakeBlock())
|
||||||
|
b := fn.MakeBody(1)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Fatal("expected panic: too many conditional defers")
|
||||||
|
} else if r != "too many conditional defers" {
|
||||||
|
t.Fatalf("unexpected panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b.Return()
|
||||||
|
for i := 0; i < 65; i++ {
|
||||||
|
b.Defer(DeferInCond, target.Expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPointerSize(t *testing.T) {
|
func TestPointerSize(t *testing.T) {
|
||||||
expected := unsafe.Sizeof(uintptr(0))
|
expected := unsafe.Sizeof(uintptr(0))
|
||||||
if size := NewProgram(nil).PointerSize(); size != int(expected) {
|
if size := NewProgram(nil).PointerSize(); size != int(expected) {
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ func (p BasicBlock) Addr() Expr {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
type dbgExpr struct {
|
|
||||||
ptr Expr
|
|
||||||
val Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
type aBuilder struct {
|
type aBuilder struct {
|
||||||
impl llvm.Builder
|
impl llvm.Builder
|
||||||
blk BasicBlock
|
blk BasicBlock
|
||||||
@@ -69,7 +64,6 @@ type aBuilder struct {
|
|||||||
Pkg Package
|
Pkg Package
|
||||||
Prog Program
|
Prog Program
|
||||||
|
|
||||||
dbgVars map[Expr]dbgExpr // save copied address and values for debug info
|
|
||||||
diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s)
|
diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
326
test/defer_test.go
Normal file
326
test/defer_test.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runLoopDefers exercises a defer statement inside a loop and relies on
|
||||||
|
// defers executing after the loop but before the function returns.
|
||||||
|
func runLoopDefers() (result []int) {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
v := i
|
||||||
|
defer func() {
|
||||||
|
result = append(result, v)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoopDeferCount(n int) (count int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
defer func() {
|
||||||
|
count++
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDeferRecover() (recovered any, ran bool) {
|
||||||
|
defer func() {
|
||||||
|
recovered = recover()
|
||||||
|
ran = true
|
||||||
|
}()
|
||||||
|
panic("defer recover sentinel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNestedLoopDeferOrder() (order []string) {
|
||||||
|
outerNestedLoop(&order)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func outerNestedLoop(order *[]string) {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
v := i
|
||||||
|
label := "outer:" + strconv.Itoa(v)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
middleNestedLoop(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
func middleNestedLoop(order *[]string) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
v := i
|
||||||
|
label := "middle:" + strconv.Itoa(v)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
innerNestedLoop(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerNestedLoop(order *[]string) {
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
v := i
|
||||||
|
label := "inner:" + strconv.Itoa(v)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferInLoopOrder(t *testing.T) {
|
||||||
|
got := runLoopDefers()
|
||||||
|
want := []int{2, 1, 0}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("unexpected defer order: got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferLoopStress(t *testing.T) {
|
||||||
|
const n = 1_000_000
|
||||||
|
if got := runLoopDeferCount(n); got != n {
|
||||||
|
t.Fatalf("unexpected count: got %d, want %d", got, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferRecoverHandlesPanic(t *testing.T) {
|
||||||
|
got, ran := runDeferRecover()
|
||||||
|
want := "defer recover sentinel"
|
||||||
|
if !ran {
|
||||||
|
t.Fatalf("recover defer not executed")
|
||||||
|
}
|
||||||
|
str, ok := got.(string)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("recover returned %T, want string", got)
|
||||||
|
}
|
||||||
|
if str != want {
|
||||||
|
t.Fatalf("unexpected recover value: got %q, want %q", str, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedDeferLoops(t *testing.T) {
|
||||||
|
got := runNestedLoopDeferOrder()
|
||||||
|
want := []string{
|
||||||
|
"inner:3", "inner:2", "inner:1", "inner:0",
|
||||||
|
"middle:1", "middle:0",
|
||||||
|
"outer:2", "outer:1", "outer:0",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("unexpected nested order: got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNestedConditionalDeferWithRecover() (order []string, recovered any) {
|
||||||
|
defer func() { recovered = recover() }()
|
||||||
|
nestedCondOuter(&order)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedCondOuter(order *[]string) {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
v := i
|
||||||
|
label := "outer:" + strconv.Itoa(v)
|
||||||
|
if v%2 == 0 {
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
nestedCondMiddle(order, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedCondMiddle(order *[]string, v int) {
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
u := j
|
||||||
|
label := "middle:" + strconv.Itoa(u)
|
||||||
|
if u < 2 {
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
nestedCondInner(order)
|
||||||
|
}
|
||||||
|
if v == 1 {
|
||||||
|
panic("nested-conditional-boom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nestedCondInner(order *[]string) {
|
||||||
|
for k := 0; k < 2; k++ {
|
||||||
|
w := k
|
||||||
|
label := "inner:" + strconv.Itoa(w)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedConditionalDeferWithRecover(t *testing.T) {
|
||||||
|
gotOrder, gotRecovered := runNestedConditionalDeferWithRecover()
|
||||||
|
wantRecovered := "nested-conditional-boom"
|
||||||
|
if s, ok := gotRecovered.(string); !ok || s != wantRecovered {
|
||||||
|
t.Fatalf("unexpected recover value: got %v, want %q", gotRecovered, wantRecovered)
|
||||||
|
}
|
||||||
|
wantOrder := []string{
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"middle:1", "middle:0",
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"middle:1", "middle:0",
|
||||||
|
"outer:0",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotOrder, wantOrder) {
|
||||||
|
t.Fatalf("unexpected nested conditional order: got %v, want %v", gotOrder, wantOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func callWithRecover(fn func()) (recovered any) {
|
||||||
|
defer func() { recovered = recover() }()
|
||||||
|
fn()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loopBranchEven(order *[]string, i int) {
|
||||||
|
label := "even:" + strconv.Itoa(i)
|
||||||
|
defer func() { *order = append(*order, label) }()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loopBranchOddNoRecover(order *[]string, i int) {
|
||||||
|
label := "odd-wrap:" + strconv.Itoa(i)
|
||||||
|
defer func() { *order = append(*order, label) }()
|
||||||
|
panic("odd-no-recover")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loopBranchOddLocalRecover(order *[]string, i int) {
|
||||||
|
label := "odd-local:" + strconv.Itoa(i)
|
||||||
|
defer func() { *order = append(*order, label) }()
|
||||||
|
defer func() { _ = recover() }()
|
||||||
|
panic("odd-local-recover")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoopBranchRecoverMixed(n int) (order []string, recoveredVals []any) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
loopBranchEven(&order, i)
|
||||||
|
} else if i%4 == 3 {
|
||||||
|
rec := callWithRecover(func() { loopBranchOddNoRecover(&order, i) })
|
||||||
|
recoveredVals = append(recoveredVals, rec)
|
||||||
|
} else {
|
||||||
|
loopBranchOddLocalRecover(&order, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoopBranchRecoverMixed(t *testing.T) {
|
||||||
|
order, recovered := runLoopBranchRecoverMixed(6)
|
||||||
|
wantOrder := []string{
|
||||||
|
"even:0",
|
||||||
|
"odd-local:1",
|
||||||
|
"even:2",
|
||||||
|
"odd-wrap:3",
|
||||||
|
"even:4",
|
||||||
|
"odd-local:5",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(order, wantOrder) {
|
||||||
|
t.Fatalf("unexpected loop/branch order: got %v, want %v", order, wantOrder)
|
||||||
|
}
|
||||||
|
if len(recovered) != 1 {
|
||||||
|
t.Fatalf("unexpected recovered count: got %d, want %d", len(recovered), 1)
|
||||||
|
}
|
||||||
|
if s, ok := recovered[0].(string); !ok || s != "odd-no-recover" {
|
||||||
|
t.Fatalf("unexpected recovered value: got %v, want %q", recovered[0], "odd-no-recover")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepInner(order *[]string) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
idx := i
|
||||||
|
label := "inner:" + strconv.Itoa(idx)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
if idx == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
panic("deep-boom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepMiddle(order *[]string) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
idx := i
|
||||||
|
label := "middle:" + strconv.Itoa(idx)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
if idx == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if rec := recover(); rec != nil {
|
||||||
|
panic(rec)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
deepInner(order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepOuter(order *[]string) (recovered any) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
idx := i
|
||||||
|
label := "outer:" + strconv.Itoa(idx)
|
||||||
|
defer func(label string) {
|
||||||
|
*order = append(*order, label)
|
||||||
|
}(label)
|
||||||
|
if idx == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if rec := recover(); rec != nil {
|
||||||
|
recovered = rec
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
deepMiddle(order)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicCrossTwoFunctionsRecover(t *testing.T) {
|
||||||
|
var order []string
|
||||||
|
recovered := deepOuter(&order)
|
||||||
|
if s, ok := recovered.(string); !ok || s != "deep-boom" {
|
||||||
|
t.Fatalf("unexpected recovered value: got %v, want %q", recovered, "deep-boom")
|
||||||
|
}
|
||||||
|
wantOrder := []string{
|
||||||
|
"inner:1", "inner:0",
|
||||||
|
"middle:1", "middle:0",
|
||||||
|
"outer:1", "outer:0",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(order, wantOrder) {
|
||||||
|
t.Fatalf("unexpected cross-function defer order: got %v, want %v", order, wantOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user