For a compiler intrinsic, if it is used in a non-call context, e.g.
as a function pointer, currently it requires fallback
implementation (e.g. assembly code for atomic operations),
otherwise it will result in a build failure. The fallback
implementation needs to be maintained and tested, albeit rarely
used in practice.
Also, for SIMD, we're currently adding a large number of compiler
intrinsics without providing fallback implementations (we might in
the future). As methods, it is not unlikely that they are used in
a non-call context, e.g. referenced from the type descriptor.
This CL lets the compiler generate the function body for
bodyless intrinsics. The compiler already recognizes a call to
the function as an intrinsic and can directly generate code for it.
So we just fill in the body with a call to the same function.
Change-Id: I2636e3128f28301c9abaf2b48bc962ab56e7d1a9
Reviewed-on: https://go-review.googlesource.com/c/go/+/683096
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
compilequeue []*ir.Func // functions waiting to be compiled
)
-func enqueueFunc(fn *ir.Func) {
+func enqueueFunc(fn *ir.Func, symABIs *ssagen.SymABIs) {
if ir.CurFunc != nil {
base.FatalfAt(fn.Pos(), "enqueueFunc %v inside %v", fn, ir.CurFunc)
}
}
if len(fn.Body) == 0 {
- // Initialize ABI wrappers if necessary.
- ir.InitLSym(fn, false)
- types.CalcSize(fn.Type())
- a := ssagen.AbiForBodylessFuncStackMap(fn)
- abiInfo := a.ABIAnalyzeFuncType(fn.Type()) // abiInfo has spill/home locations for wrapper
- if fn.ABI == obj.ABI0 {
- // The current args_stackmap generation assumes the function
- // is ABI0, and only ABI0 assembly function can have a FUNCDATA
- // reference to args_stackmap (see cmd/internal/obj/plist.go:Flushplist).
- // So avoid introducing an args_stackmap if the func is not ABI0.
- liveness.WriteFuncMap(fn, abiInfo)
-
- x := ssagen.EmitArgInfo(fn, abiInfo)
- objw.Global(x, int32(len(x.P)), obj.RODATA|obj.LOCAL)
+ if ir.IsIntrinsicSym(fn.Sym()) && fn.Sym().Linkname == "" && !symABIs.HasDef(fn.Sym()) {
+ // Generate the function body for a bodyless intrinsic, in case it
+ // is used in a non-call context (e.g. as a function pointer).
+ // We skip functions defined in assembly, or has a linkname (which
+ // could be defined in another package).
+ ssagen.GenIntrinsicBody(fn)
+ } else {
+ // Initialize ABI wrappers if necessary.
+ ir.InitLSym(fn, false)
+ types.CalcSize(fn.Type())
+ a := ssagen.AbiForBodylessFuncStackMap(fn)
+ abiInfo := a.ABIAnalyzeFuncType(fn.Type()) // abiInfo has spill/home locations for wrapper
+ if fn.ABI == obj.ABI0 {
+ // The current args_stackmap generation assumes the function
+ // is ABI0, and only ABI0 assembly function can have a FUNCDATA
+ // reference to args_stackmap (see cmd/internal/obj/plist.go:Flushplist).
+ // So avoid introducing an args_stackmap if the func is not ABI0.
+ liveness.WriteFuncMap(fn, abiInfo)
+
+ x := ssagen.EmitArgInfo(fn, abiInfo)
+ objw.Global(x, int32(len(x.P)), obj.RODATA|obj.LOCAL)
+ }
+ return
}
- return
}
errorsBefore := base.Errors()
ir.EscFmt = escape.Fmt
ir.IsIntrinsicCall = ssagen.IsIntrinsicCall
+ ir.IsIntrinsicSym = ssagen.IsIntrinsicSym
inline.SSADumpInline = ssagen.DumpInline
ssagen.InitEnv()
ssagen.InitTables()
}
if nextFunc < len(typecheck.Target.Funcs) {
- enqueueFunc(typecheck.Target.Funcs[nextFunc])
+ enqueueFunc(typecheck.Target.Funcs[nextFunc], symABIs)
nextFunc++
continue
}
// IsIntrinsicCall reports whether the compiler back end will treat the call as an intrinsic operation.
var IsIntrinsicCall = func(*CallExpr) bool { return false }
+// IsIntrinsicSym reports whether the compiler back end will treat a call to this symbol as an intrinsic operation.
+var IsIntrinsicSym = func(*types.Sym) bool { return false }
+
// SameSafeExpr checks whether it is safe to reuse one of l and r
// instead of computing both. SameSafeExpr assumes that l and r are
// used in the same statement or expression. In order for it to be
return args
}
+func RecvParamNames(ft *types.Type) []Node {
+ args := make([]Node, ft.NumRecvs()+ft.NumParams())
+ for i, f := range ft.RecvParams() {
+ args[i] = f.Nname.(*Name)
+ }
+ return args
+}
+
// MethodSym returns the method symbol representing a method name
// associated with a specific receiver type.
//
}
}
+// HasDef returns whether the given symbol has an assembly definition.
+func (s *SymABIs) HasDef(sym *types.Sym) bool {
+ symName := sym.Linkname
+ if symName == "" {
+ symName = sym.Pkg.Prefix + "." + sym.Name
+ }
+ symName = s.canonicalize(symName)
+
+ _, hasDefABI := s.defs[symName]
+ return hasDefABI
+}
+
// GenABIWrappers applies ABI information to Funcs and generates ABI
// wrapper functions where necessary.
func (s *SymABIs) GenABIWrappers() {
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/ssa"
+ "cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/sys"
)
if !ok {
return false
}
- return findIntrinsic(name.Sym()) != nil
+ return IsIntrinsicSym(name.Sym())
+}
+
+func IsIntrinsicSym(sym *types.Sym) bool {
+ return findIntrinsic(sym) != nil
+}
+
+// GenIntrinsicBody generates the function body for a bodyless intrinsic.
+// This is used when the intrinsic is used in a non-call context, e.g.
+// as a function pointer, or (for a method) being referenced from the type
+// descriptor.
+//
+// The compiler already recognizes a call to fn as an intrinsic and can
+// directly generate code for it. So we just fill in the body with a call
+// to fn.
+func GenIntrinsicBody(fn *ir.Func) {
+ if ir.CurFunc != nil {
+ base.FatalfAt(fn.Pos(), "enqueueFunc %v inside %v", fn, ir.CurFunc)
+ }
+
+ if base.Flag.LowerR != 0 {
+ fmt.Println("generate intrinsic for", ir.FuncName(fn))
+ }
+
+ pos := fn.Pos()
+ ft := fn.Type()
+ var ret ir.Node
+
+ // For a method, it usually starts with an ODOTMETH (pre-typecheck) or
+ // OMETHEXPR (post-typecheck) referencing the method symbol without the
+ // receiver type, and Walk rewrites it to a call directly to the
+ // type-qualified method symbol, moving the receiver to an argument.
+ // Here fn has already the type-qualified method symbol, and it is hard
+ // to get the unqualified symbol. So we just generate the post-Walk form
+ // and mark it typechecked and Walked.
+ call := ir.NewCallExpr(pos, ir.OCALLFUNC, fn.Nname, nil)
+ call.Args = ir.RecvParamNames(ft)
+ call.IsDDD = ft.IsVariadic()
+ typecheck.Exprs(call.Args)
+ call.SetTypecheck(1)
+ call.SetWalked(true)
+ ret = call
+ if ft.NumResults() > 0 {
+ if ft.NumResults() == 1 {
+ call.SetType(ft.Result(0).Type)
+ } else {
+ call.SetType(ft.ResultsTuple())
+ }
+ n := ir.NewReturnStmt(base.Pos, nil)
+ n.Results = []ir.Node{call}
+ ret = n
+ }
+ fn.Body.Append(ret)
+
+ if base.Flag.LowerR != 0 {
+ ir.DumpList("generate intrinsic body", fn.Body)
+ }
+
+ ir.CurFunc = fn
+ typecheck.Stmts(fn.Body)
+ ir.CurFunc = nil // we know CurFunc is nil at entry
}