cmd/compile: always construct typechecked closures

This CL extends ir.NewClosureFunc to take the signature type argument,
and to handle naming the closure and adding it to typecheck.Target.

It also removes the code for typechecking OCLOSURE and ODCLFUNC nodes,
by having them always constructed as typechecked. ODCLFUNC node
construction will be further simplified in the followup CL.

Change-Id: Iabde4557d33051ee470a3bc4fd49599490024cba
Reviewed-on: https://go-review.googlesource.com/c/go/+/520337
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2023-08-16 18:56:41 -07:00 committed by Gopher Robot
parent e453971005
commit 5c6fbd2c3e
12 changed files with 43 additions and 202 deletions

View file

@ -256,9 +256,8 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
} }
// Create a new no-argument function that we'll hand off to defer. // Create a new no-argument function that we'll hand off to defer.
fn := ir.NewClosureFunc(n.Pos(), true) fn := ir.NewClosureFunc(n.Pos(), n.Pos(), types.NewSignature(nil, nil, nil), e.curfn, typecheck.Target)
fn.SetWrapper(true) fn.SetWrapper(true)
fn.Nname.SetType(types.NewSignature(nil, nil, nil))
fn.SetEsc(escFuncTagged) // no params; effectively tagged already fn.SetEsc(escFuncTagged) // no params; effectively tagged already
fn.Body = []ir.Node{call} fn.Body = []ir.Node{call}
if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC { if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
@ -272,6 +271,7 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
} }
clo := fn.OClosure clo := fn.OClosure
if n.Op() == ir.OGO { if n.Op() == ir.OGO {
clo.IsGoWrap = true clo.IsGoWrap = true
} }

View file

@ -390,80 +390,37 @@ func closureName(outerfn *Func, pos src.XPos) *types.Sym {
return pkg.Lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen)) return pkg.Lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen))
} }
// NewClosureFunc creates a new Func to represent a function literal. // NewClosureFunc creates a new Func to represent a function literal
// If hidden is true, then the closure is marked hidden (i.e., as a // with the given type.
// function literal contained within another function, rather than a //
// package-scope variable initialization expression). // fpos the position used for the underlying ODCLFUNC and ONAME,
func NewClosureFunc(pos src.XPos, hidden bool) *Func { // whereas cpos is the position used for the OCLOSURE. They're
fn := NewFunc(pos) // separate because in the presence of inlining, the OCLOSURE node
fn.SetIsHiddenClosure(hidden) // should have an inline-adjusted position, whereas the ODCLFUNC and
// ONAME must not.
//
// outerfn is the enclosing function, if any. The returned function is
// appending to pkg.Funcs.
func NewClosureFunc(fpos, cpos src.XPos, typ *types.Type, outerfn *Func, pkg *Package) *Func {
fn := NewFunc(fpos)
fn.SetIsHiddenClosure(outerfn != nil)
fn.Nname = NewNameAt(pos, BlankNode.Sym(), nil) name := NewNameAt(fpos, closureName(outerfn, cpos), typ)
fn.Nname.Func = fn MarkFunc(name)
fn.Nname.Defn = fn name.Func = fn
name.Defn = fn
fn.Nname = name
fn.OClosure = &ClosureExpr{Func: fn} clo := &ClosureExpr{Func: fn}
fn.OClosure.op = OCLOSURE clo.op = OCLOSURE
fn.OClosure.pos = pos clo.pos = cpos
fn.OClosure = clo
fn.SetTypecheck(1)
clo.SetType(typ)
clo.SetTypecheck(1)
pkg.Funcs = append(pkg.Funcs, fn)
return fn return fn
} }
// NameClosure generates a unique for the given function literal,
// which must have appeared within outerfn.
func NameClosure(clo *ClosureExpr, outerfn *Func) {
fn := clo.Func
if fn.IsHiddenClosure() != (outerfn != nil) {
base.FatalfAt(clo.Pos(), "closure naming inconsistency: hidden %v, but outer %v", fn.IsHiddenClosure(), outerfn)
}
name := fn.Nname
if !IsBlank(name) {
base.FatalfAt(clo.Pos(), "closure already named: %v", name)
}
name.SetSym(closureName(outerfn, clo.Pos()))
MarkFunc(name)
}
// UseClosure checks that the given function literal has been setup
// correctly, and then returns it as an expression.
// It must be called after clo.Func.ClosureVars has been set.
func UseClosure(clo *ClosureExpr, pkg *Package) Node {
fn := clo.Func
name := fn.Nname
if IsBlank(name) {
base.FatalfAt(fn.Pos(), "unnamed closure func: %v", fn)
}
// Caution: clo.Typecheck() is still 0 when UseClosure is called by
// tcClosure.
if fn.Typecheck() != 1 || name.Typecheck() != 1 {
base.FatalfAt(fn.Pos(), "missed typecheck: %v", fn)
}
if clo.Type() == nil || name.Type() == nil {
base.FatalfAt(fn.Pos(), "missing types: %v", fn)
}
if !types.Identical(clo.Type(), name.Type()) {
base.FatalfAt(fn.Pos(), "mismatched types: %v", fn)
}
if base.Flag.W > 1 {
s := fmt.Sprintf("new closure func: %v", fn)
Dump(s, fn)
}
if pkg != nil {
pkg.Funcs = append(pkg.Funcs, fn)
}
if false && IsTrivialClosure(clo) {
// TODO(mdempsky): Investigate if we can/should optimize this
// case. walkClosure already handles it later, but it could be
// useful to recognize earlier (e.g., it might allow multiple
// inlined calls to a function to share a common trivial closure
// func, rather than cloning it for each inlined call).
}
return clo
}

View file

@ -755,6 +755,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
name.Func = ir.NewFunc(r.pos()) name.Func = ir.NewFunc(r.pos())
name.Func.Nname = name name.Func.Nname = name
name.Func.SetTypecheck(1)
if r.hasTypeParams() { if r.hasTypeParams() {
name.Func.SetDupok(true) name.Func.SetDupok(true)
@ -999,6 +1000,7 @@ func (r *reader) method(rext *reader) *types.Field {
name.Func = ir.NewFunc(r.pos()) name.Func = ir.NewFunc(r.pos())
name.Func.Nname = name name.Func.Nname = name
name.Func.SetTypecheck(1)
if r.hasTypeParams() { if r.hasTypeParams() {
name.Func.SetDupok(true) name.Func.SetDupok(true)
@ -1096,8 +1098,6 @@ func (r *reader) funcExt(name *ir.Name, method *types.Sym) {
} }
} }
typecheck.Func(fn)
if r.Bool() { if r.Bool() {
assert(name.Defn == nil) assert(name.Defn == nil)
@ -2722,15 +2722,9 @@ func (r *reader) syntheticClosure(origPos src.XPos, typ *types.Type, ifaceHack b
// position instead. See also the explanation in reader.funcLit. // position instead. See also the explanation in reader.funcLit.
inlPos := r.inlPos(origPos) inlPos := r.inlPos(origPos)
fn := ir.NewClosureFunc(origPos, r.curfn != nil) // TODO(mdempsky): Remove hard-coding of typecheck.Target.
fn := ir.NewClosureFunc(origPos, inlPos, typ, r.curfn, typecheck.Target)
fn.SetWrapper(true) fn.SetWrapper(true)
clo := fn.OClosure
clo.SetPos(inlPos)
ir.NameClosure(clo, r.curfn)
setType(fn.Nname, typ)
typecheck.Func(fn)
setType(clo, fn.Type())
var init ir.Nodes var init ir.Nodes
for i, n := range captures { for i, n := range captures {
@ -2767,8 +2761,7 @@ func (r *reader) syntheticClosure(origPos src.XPos, typ *types.Type, ifaceHack b
bodyReader[fn] = pri bodyReader[fn] = pri
pri.funcBody(fn) pri.funcBody(fn)
// TODO(mdempsky): Remove hard-coding of typecheck.Target. return ir.InitExpr(init, fn.OClosure)
return ir.InitExpr(init, ir.UseClosure(clo, typecheck.Target))
} }
// syntheticSig duplicates and returns the params and results lists // syntheticSig duplicates and returns the params and results lists
@ -3120,14 +3113,8 @@ func (r *reader) funcLit() ir.Node {
xtype2 := r.signature(nil) xtype2 := r.signature(nil)
r.suppressInlPos-- r.suppressInlPos--
fn := ir.NewClosureFunc(pos, r.curfn != nil) // TODO(mdempsky): Remove hard-coding of typecheck.Target.
clo := fn.OClosure fn := ir.NewClosureFunc(pos, r.inlPos(pos), xtype2, r.curfn, typecheck.Target)
clo.SetPos(r.inlPos(pos)) // see comment above
ir.NameClosure(clo, r.curfn)
setType(fn.Nname, xtype2)
typecheck.Func(fn)
setType(clo, fn.Type())
fn.ClosureVars = make([]*ir.Name, 0, r.Len()) fn.ClosureVars = make([]*ir.Name, 0, r.Len())
for len(fn.ClosureVars) < cap(fn.ClosureVars) { for len(fn.ClosureVars) < cap(fn.ClosureVars) {
@ -3141,8 +3128,7 @@ func (r *reader) funcLit() ir.Node {
r.addBody(fn, nil) r.addBody(fn, nil)
// TODO(mdempsky): Remove hard-coding of typecheck.Target. return fn.OClosure
return ir.UseClosure(clo, typecheck.Target)
} }
func (r *reader) exprList() []ir.Node { func (r *reader) exprList() []ir.Node {
@ -3463,6 +3449,7 @@ func unifiedInlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.Inlined
// TODO(mdempsky): This still feels clumsy. Can we do better? // TODO(mdempsky): This still feels clumsy. Can we do better?
tmpfn := ir.NewFunc(fn.Pos()) tmpfn := ir.NewFunc(fn.Pos())
tmpfn.Nname = ir.NewNameAt(fn.Nname.Pos(), callerfn.Sym(), fn.Type()) tmpfn.Nname = ir.NewNameAt(fn.Nname.Pos(), callerfn.Sym(), fn.Type())
tmpfn.SetTypecheck(1)
tmpfn.Closgen = callerfn.Closgen tmpfn.Closgen = callerfn.Closgen
defer func() { callerfn.Closgen = tmpfn.Closgen }() defer func() { callerfn.Closgen = tmpfn.Closgen }()
@ -3638,6 +3625,7 @@ func expandInline(fn *ir.Func, pri pkgReaderIndex) {
tmpfn := ir.NewFunc(fn.Pos()) tmpfn := ir.NewFunc(fn.Pos())
tmpfn.Nname = ir.NewNameAt(fn.Nname.Pos(), fn.Sym(), fn.Type()) tmpfn.Nname = ir.NewNameAt(fn.Nname.Pos(), fn.Sym(), fn.Type())
tmpfn.SetTypecheck(1)
tmpfn.ClosureVars = fn.ClosureVars tmpfn.ClosureVars = fn.ClosureVars
{ {
@ -3861,7 +3849,6 @@ func wrapMethodValue(recvType *types.Type, method *types.Field, target *ir.Packa
recv := ir.NewHiddenParam(pos, fn, typecheck.Lookup(".this"), recvType) recv := ir.NewHiddenParam(pos, fn, typecheck.Lookup(".this"), recvType)
if !needed { if !needed {
typecheck.Func(fn)
return return
} }
@ -3883,6 +3870,7 @@ func newWrapperFunc(pos src.XPos, sym *types.Sym, wrapper *types.Type, method *t
fn.Nname = name fn.Nname = name
setType(name, sig) setType(name, sig)
fn.SetTypecheck(1)
// TODO(mdempsky): De-duplicate with similar logic in funcargs. // TODO(mdempsky): De-duplicate with similar logic in funcargs.
defParams := func(class ir.Class, params *types.Type) { defParams := func(class ir.Class, params *types.Type) {
@ -3899,8 +3887,6 @@ func newWrapperFunc(pos src.XPos, sym *types.Sym, wrapper *types.Type, method *t
} }
func finishWrapperFunc(fn *ir.Func, target *ir.Package) { func finishWrapperFunc(fn *ir.Func, target *ir.Package) {
typecheck.Func(fn)
ir.WithFunc(fn, func() { ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body) typecheck.Stmts(fn.Body)
}) })

View file

@ -83,8 +83,6 @@ func unified(m posMap, noders []*noder) {
target := typecheck.Target target := typecheck.Target
typecheck.TypecheckAllowed = true
localPkgReader = newPkgReader(pkgbits.NewPkgDecoder(types.LocalPkg.Path, data)) localPkgReader = newPkgReader(pkgbits.NewPkgDecoder(types.LocalPkg.Path, data))
readPackage(localPkgReader, types.LocalPkg, true) readPackage(localPkgReader, types.LocalPkg, true)

View file

@ -52,7 +52,6 @@ func MakeInit() {
fn.Body = nf fn.Body = nf
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
typecheck.Func(fn)
ir.WithFunc(fn, func() { ir.WithFunc(fn, func() {
typecheck.Stmts(nf) typecheck.Stmts(nf)
}) })
@ -145,7 +144,6 @@ func MakeTask() {
fnInit.Body.Append(asancall) fnInit.Body.Append(asancall)
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
typecheck.Func(fnInit)
ir.CurFunc = fnInit ir.CurFunc = fnInit
typecheck.Stmts(fnInit.Body) typecheck.Stmts(fnInit.Body)
ir.CurFunc = nil ir.CurFunc = nil

View file

@ -237,7 +237,6 @@ func hashFunc(t *types.Type) *ir.Func {
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
fn.SetDupok(true) fn.SetDupok(true)
typecheck.Func(fn)
ir.WithFunc(fn, func() { ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body) typecheck.Stmts(fn.Body)
@ -623,7 +622,6 @@ func eqFunc(t *types.Type) *ir.Func {
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
fn.SetDupok(true) fn.SetDupok(true)
typecheck.Func(fn)
ir.WithFunc(fn, func() { ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body) typecheck.Stmts(fn.Body)

View file

@ -324,7 +324,6 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
typecheck.Func(fn)
ir.CurFunc = fn ir.CurFunc = fn
typecheck.Stmts(fn.Body) typecheck.Stmts(fn.Body)

View file

@ -1055,8 +1055,6 @@ func tryWrapGlobalMapInit(n ir.Node) (mapvar *ir.Name, genfn *ir.Func, call ir.N
newfn.Body = append(newfn.Body, as) newfn.Body = append(newfn.Body, as)
typecheck.FinishFuncBody() typecheck.FinishFuncBody()
typecheck.Func(newfn)
const no = ` const no = `
// Register new function with decls. // Register new function with decls.
typecheck.Target.Decls = append(typecheck.Target.Decls, newfn) typecheck.Target.Decls = append(typecheck.Target.Decls, newfn)

View file

@ -34,6 +34,7 @@ func DeclFunc(sym *types.Sym, recv *ir.Field, params, results []*ir.Field) *ir.F
checkdupfields("argument", typ.Recvs().FieldSlice(), typ.Params().FieldSlice(), typ.Results().FieldSlice()) checkdupfields("argument", typ.Recvs().FieldSlice(), typ.Params().FieldSlice(), typ.Results().FieldSlice())
fn.Nname.SetType(typ) fn.Nname.SetType(typ)
fn.Nname.SetTypecheck(1) fn.Nname.SetTypecheck(1)
fn.SetTypecheck(1)
return fn return fn
} }

View file

@ -146,62 +146,6 @@ func MethodValueType(n *ir.SelectorExpr) *types.Type {
return t return t
} }
// tcClosure typechecks an OCLOSURE node. It also creates the named
// function associated with the closure.
// TODO: This creation of the named function should probably really be done in a
// separate pass from type-checking.
func tcClosure(clo *ir.ClosureExpr, top int) ir.Node {
fn := clo.Func
// We used to allow IR builders to typecheck the underlying Func
// themselves, but that led to too much variety and inconsistency
// around who's responsible for naming the function, typechecking
// it, or adding it to Target.Decls.
//
// It's now all or nothing. Callers are still allowed to do these
// themselves, but then they assume responsibility for all of them.
if fn.Typecheck() == 1 {
base.FatalfAt(fn.Pos(), "underlying closure func already typechecked: %v", fn)
}
ir.NameClosure(clo, ir.CurFunc)
Func(fn)
// Type check the body now, but only if we're inside a function.
// At top level (in a variable initialization: curfn==nil) we're not
// ready to type check code yet; we'll check it later, because the
// underlying closure function we create is added to Target.Decls.
if ir.CurFunc != nil {
oldfn := ir.CurFunc
ir.CurFunc = fn
Stmts(fn.Body)
ir.CurFunc = oldfn
}
out := 0
for _, v := range fn.ClosureVars {
if v.Type() == nil {
// If v.Type is nil, it means v looked like it was going to be
// used in the closure, but isn't. This happens in struct
// literals like s{f: x} where we can't distinguish whether f is
// a field identifier or expression until resolving s.
continue
}
// type check closed variables outside the closure, so that the
// outer frame also captures them.
Expr(v.Outer)
fn.ClosureVars[out] = v
out++
}
fn.ClosureVars = fn.ClosureVars[:out]
clo.SetType(fn.Type())
return ir.UseClosure(clo, Target)
}
// type check function definition // type check function definition
// To be called by typecheck, not directly. // To be called by typecheck, not directly.
// (Call typecheck.Func instead.) // (Call typecheck.Func instead.)

View file

@ -63,7 +63,6 @@ func assign(stmt ir.Node, lhs, rhs []ir.Node) {
// so that the conversion below happens). // so that the conversion below happens).
checkLHS := func(i int, typ *types.Type) { checkLHS := func(i int, typ *types.Type) {
lhs[i] = Resolve(lhs[i])
if n := lhs[i]; typ != nil && ir.DeclaredBy(n, stmt) && n.Type() == nil { if n := lhs[i]; typ != nil && ir.DeclaredBy(n, stmt) && n.Type() == nil {
base.Assertf(typ.Kind() == types.TNIL, "unexpected untyped nil") base.Assertf(typ.Kind() == types.TNIL, "unexpected untyped nil")
n.SetType(defaultType(typ)) n.SetType(defaultType(typ))

View file

@ -21,10 +21,6 @@ import (
// to be included in the package-level init function. // to be included in the package-level init function.
var InitTodoFunc = ir.NewFunc(base.Pos) var InitTodoFunc = ir.NewFunc(base.Pos)
var inimport bool // set during import
var TypecheckAllowed bool
var ( var (
NeedRuntimeType = func(*types.Type) {} NeedRuntimeType = func(*types.Type) {}
) )
@ -105,20 +101,6 @@ const (
// marks variables that escape the local frame. // marks variables that escape the local frame.
// rewrites n.Op to be more specific in some cases. // rewrites n.Op to be more specific in some cases.
// Resolve resolves an ONONAME node to a definition, if any. If n is not an ONONAME node,
// Resolve returns n unchanged. If n is an ONONAME node and not in the same package,
// then n.Sym() is resolved using import data. Otherwise, Resolve returns
// n.Sym().Def. An ONONAME node can be created using ir.NewIdent(), so an imported
// symbol can be resolved via Resolve(ir.NewIdent(src.NoXPos, sym)).
func Resolve(n ir.Node) (res ir.Node) {
if n == nil || n.Op() != ir.ONONAME {
return n
}
base.Fatalf("unexpected NONAME node: %+v", n)
panic("unreachable")
}
func typecheckslice(l []ir.Node, top int) { func typecheckslice(l []ir.Node, top int) {
for i := range l { for i := range l {
l[i] = typecheck(l[i], top) l[i] = typecheck(l[i], top)
@ -203,23 +185,11 @@ func cycleTrace(cycle []ir.Node) string {
var typecheck_tcstack []ir.Node var typecheck_tcstack []ir.Node
func Func(fn *ir.Func) {
new := Stmt(fn)
if new != fn {
base.Fatalf("typecheck changed func")
}
}
// typecheck type checks node n. // typecheck type checks node n.
// The result of typecheck MUST be assigned back to n, e.g. // The result of typecheck MUST be assigned back to n, e.g.
// //
// n.Left = typecheck(n.Left, top) // n.Left = typecheck(n.Left, top)
func typecheck(n ir.Node, top int) (res ir.Node) { func typecheck(n ir.Node, top int) (res ir.Node) {
// cannot type check until all the source has been parsed
if !TypecheckAllowed {
base.Fatalf("early typecheck")
}
if n == nil { if n == nil {
return nil return nil
} }
@ -236,9 +206,6 @@ func typecheck(n ir.Node, top int) (res ir.Node) {
n = n.(*ir.ParenExpr).X n = n.(*ir.ParenExpr).X
} }
// Resolve definition of name and value of iota lazily.
n = Resolve(n)
// Skip typecheck if already done. // Skip typecheck if already done.
// But re-typecheck ONAME/OTYPE/OLITERAL/OPACK node in case context has changed. // But re-typecheck ONAME/OTYPE/OLITERAL/OPACK node in case context has changed.
if n.Typecheck() == 1 || n.Typecheck() == 3 { if n.Typecheck() == 1 || n.Typecheck() == 3 {
@ -681,10 +648,6 @@ func typecheck1(n ir.Node, top int) ir.Node {
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
return tcUnsafeData(n) return tcUnsafeData(n)
case ir.OCLOSURE:
n := n.(*ir.ClosureExpr)
return tcClosure(n, top)
case ir.OITAB: case ir.OITAB:
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
return tcITab(n) return tcITab(n)