cmd/compile: ensure first instruction in a function is not inlined

People are using this to get the name of the function from a function type:

runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()

Unfortunately, this technique falls down when the first instruction
of the function is from an inlined callee. Then the expression above
gets you the name of the inlined function instead of the function itself.

To fix this, ensure that the first instruction is never from an inlinee.
Normally functions have prologs so those are already fine. In just the
cases where a function is a leaf with no local variables, and an instruction
from an inlinee appears first in the prog list, add a nop at the start
of the function to hold a non-inlined position.

Consider the nop a "mini-prolog" for leaf functions.

Fixes #58300

Change-Id: Ie37092f4ac3167fe8e5ef4a2207b14abc1786897
Reviewed-on: https://go-review.googlesource.com/c/go/+/465076
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 48f813a..f31cf29 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -7120,6 +7120,8 @@
 	}
 
 	if inlMarks != nil {
+		hasCall := false
+
 		// We have some inline marks. Try to find other instructions we're
 		// going to emit anyway, and use those instructions instead of the
 		// inline marks.
@@ -7137,6 +7139,9 @@
 				// whether they will be zero-sized or not yet.
 				continue
 			}
+			if p.As == obj.ACALL || p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO {
+				hasCall = true
+			}
 			pos := p.Pos.AtColumn1()
 			s := inlMarksByPos[pos]
 			if len(s) == 0 {
@@ -7162,6 +7167,45 @@
 				pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[p])
 			}
 		}
+
+		if e.stksize == 0 && !hasCall {
+			// Frameless leaf function. It doesn't need any preamble,
+			// so make sure its first instruction isn't from an inlined callee.
+			// If it is, add a nop at the start of the function with a position
+			// equal to the start of the function.
+			// This ensures that runtime.FuncForPC(uintptr(reflect.ValueOf(fn).Pointer())).Name()
+			// returns the right answer. See issue 58300.
+			for p := pp.Text; p != nil; p = p.Link {
+				if p.As == obj.AFUNCDATA || p.As == obj.APCDATA || p.As == obj.ATEXT {
+					continue
+				}
+				if base.Ctxt.PosTable.Pos(p.Pos).Base().InliningIndex() >= 0 {
+					// Make a real (not 0-sized) nop.
+					nop := Arch.Ginsnop(pp)
+					nop.Pos = e.curfn.Pos().WithIsStmt()
+
+					// Unfortunately, Ginsnop puts the instruction at the
+					// end of the list. Move it up to just before p.
+
+					// Unlink from the current list.
+					for x := pp.Text; x != nil; x = x.Link {
+						if x.Link == nop {
+							x.Link = nop.Link
+							break
+						}
+					}
+					// Splice in right before p.
+					for x := pp.Text; x != nil; x = x.Link {
+						if x.Link == p {
+							nop.Link = p
+							x.Link = nop
+							break
+						}
+					}
+				}
+				break
+			}
+		}
 	}
 
 	if base.Ctxt.Flag_locationlists {
diff --git a/test/fixedbugs/issue58300.go b/test/fixedbugs/issue58300.go
new file mode 100644
index 0000000..fff3d211f
--- /dev/null
+++ b/test/fixedbugs/issue58300.go
@@ -0,0 +1,29 @@
+// run
+
+// Copyright 2023 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 main
+
+import (
+	"reflect"
+	"runtime"
+)
+
+func f(n int) int {
+	return n % 2
+}
+
+func g(n int) int {
+	return f(n)
+}
+
+func name(fn any) (res string) {
+	return runtime.FuncForPC(uintptr(reflect.ValueOf(fn).Pointer())).Name()
+}
+
+func main() {
+	println(name(f))
+	println(name(g))
+}
diff --git a/test/fixedbugs/issue58300.out b/test/fixedbugs/issue58300.out
new file mode 100644
index 0000000..f347287
--- /dev/null
+++ b/test/fixedbugs/issue58300.out
@@ -0,0 +1,2 @@
+main.f
+main.g