]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.simd] cmd/compile: generate function body for bodyless intrinsics
authorCherry Mui <cherryyz@google.com>
Fri, 20 Jun 2025 20:28:14 +0000 (16:28 -0400)
committerCherry Mui <cherryyz@google.com>
Mon, 23 Jun 2025 14:27:39 +0000 (07:27 -0700)
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>

src/cmd/compile/internal/gc/compile.go
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/ir/expr.go
src/cmd/compile/internal/ssagen/abi.go
src/cmd/compile/internal/ssagen/intrinsics.go

index 1a40df9a84ff046c863267cbd4fdf3288ee629e5..1eb4b8cc37c30c18a8362049d12e0dc4c728b99d 100644 (file)
@@ -29,7 +29,7 @@ var (
        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)
        }
@@ -49,22 +49,30 @@ func enqueueFunc(fn *ir.Func) {
        }
 
        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()
index 253ec3257a1a3be432963c2a9a6b191f08de40f5..c486920f5b2b721d182beebe18f97447a67966f5 100644 (file)
@@ -188,6 +188,7 @@ func Main(archInit func(*ssagen.ArchInfo)) {
 
        ir.EscFmt = escape.Fmt
        ir.IsIntrinsicCall = ssagen.IsIntrinsicCall
+       ir.IsIntrinsicSym = ssagen.IsIntrinsicSym
        inline.SSADumpInline = ssagen.DumpInline
        ssagen.InitEnv()
        ssagen.InitTables()
@@ -304,7 +305,7 @@ func Main(archInit func(*ssagen.ArchInfo)) {
                }
 
                if nextFunc < len(typecheck.Target.Funcs) {
-                       enqueueFunc(typecheck.Target.Funcs[nextFunc])
+                       enqueueFunc(typecheck.Target.Funcs[nextFunc], symABIs)
                        nextFunc++
                        continue
                }
index 702adfdd84ef5ee6bd595accd3f456cd949e56da..e27e4336c973ff085c5ff01da0f4ee4f646014f6 100644 (file)
@@ -1022,6 +1022,9 @@ func StaticCalleeName(n Node) *Name {
 // 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
@@ -1140,6 +1143,14 @@ func ParamNames(ft *types.Type) []Node {
        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.
 //
index 3d50155cf36d10ba3b30079fb1a1b6521a1a17d1..0e8dbd944540e0e8b265886597ee896f10c1f957 100644 (file)
@@ -99,6 +99,18 @@ func (s *SymABIs) ReadSymABIs(file string) {
        }
 }
 
+// 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() {
index 186cfc4865ed182d188f704d77dc05debb3829d3..660047df1f22996c8d0500f0aa1c7c1370344d88 100644 (file)
@@ -12,6 +12,7 @@ import (
        "cmd/compile/internal/base"
        "cmd/compile/internal/ir"
        "cmd/compile/internal/ssa"
+       "cmd/compile/internal/typecheck"
        "cmd/compile/internal/types"
        "cmd/internal/sys"
 )
@@ -1751,5 +1752,65 @@ func IsIntrinsicCall(n *ir.CallExpr) bool {
        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
 }