// Copyright 2022 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 runtime

import (
	"internal/abi"
	"internal/goarch"
	"runtime/internal/sys"
	"unsafe"
)

// A stkframe holds information about a single physical stack frame.
type stkframe struct {
	// fn is the function being run in this frame. If there is
	// inlining, this is the outermost function.
	fn funcInfo

	// pc is the program counter within fn.
	//
	// The meaning of this is subtle:
	//
	// - Typically, this frame performed a regular function call
	//   and this is the return PC (just after the CALL
	//   instruction). In this case, pc-1 reflects the CALL
	//   instruction itself and is the correct source of symbolic
	//   information.
	//
	// - If this frame "called" sigpanic, then pc is the
	//   instruction that panicked, and pc is the correct address
	//   to use for symbolic information.
	//
	// - If this is the innermost frame, then PC is where
	//   execution will continue, but it may not be the
	//   instruction following a CALL. This may be from
	//   cooperative preemption, in which case this is the
	//   instruction after the call to morestack. Or this may be
	//   from a signal or an un-started goroutine, in which case
	//   PC could be any instruction, including the first
	//   instruction in a function. Conventionally, we use pc-1
	//   for symbolic information, unless pc == fn.entry(), in
	//   which case we use pc.
	pc uintptr

	// continpc is the PC where execution will continue in fn, or
	// 0 if execution will not continue in this frame.
	//
	// This is usually the same as pc, unless this frame "called"
	// sigpanic, in which case it's either the address of
	// deferreturn or 0 if this frame will never execute again.
	//
	// This is the PC to use to look up GC liveness for this frame.
	continpc uintptr

	lr   uintptr // program counter at caller aka link register
	sp   uintptr // stack pointer at pc
	fp   uintptr // stack pointer at caller aka frame pointer
	varp uintptr // top of local variables
	argp uintptr // pointer to function arguments
}

// reflectMethodValue is a partial duplicate of reflect.makeFuncImpl
// and reflect.methodValue.
type reflectMethodValue struct {
	fn     uintptr
	stack  *bitvector // ptrmap for both args and results
	argLen uintptr    // just args
}

// argBytes returns the argument frame size for a call to frame.fn.
func (frame *stkframe) argBytes() uintptr {
	if frame.fn.args != _ArgsSizeUnknown {
		return uintptr(frame.fn.args)
	}
	// This is an uncommon and complicated case. Fall back to fully
	// fetching the argument map to compute its size.
	argMap, _ := frame.argMapInternal()
	return uintptr(argMap.n) * goarch.PtrSize
}

// argMapInternal is used internally by stkframe to fetch special
// argument maps.
//
// argMap.n is always populated with the size of the argument map.
//
// argMap.bytedata is only populated for dynamic argument maps (used
// by reflect). If the caller requires the argument map, it should use
// this if non-nil, and otherwise fetch the argument map using the
// current PC.
//
// hasReflectStackObj indicates that this frame also has a reflect
// function stack object, which the caller must synthesize.
func (frame *stkframe) argMapInternal() (argMap bitvector, hasReflectStackObj bool) {
	f := frame.fn
	if f.args != _ArgsSizeUnknown {
		argMap.n = f.args / goarch.PtrSize
		return
	}
	// Extract argument bitmaps for reflect stubs from the calls they made to reflect.
	switch funcname(f) {
	case "reflect.makeFuncStub", "reflect.methodValueCall":
		// These take a *reflect.methodValue as their
		// context register and immediately save it to 0(SP).
		// Get the methodValue from 0(SP).
		arg0 := frame.sp + sys.MinFrameSize

		minSP := frame.fp
		if !usesLR {
			// The CALL itself pushes a word.
			// Undo that adjustment.
			minSP -= goarch.PtrSize
		}
		if arg0 >= minSP {
			// The function hasn't started yet.
			// This only happens if f was the
			// start function of a new goroutine
			// that hasn't run yet *and* f takes
			// no arguments and has no results
			// (otherwise it will get wrapped in a
			// closure). In this case, we can't
			// reach into its locals because it
			// doesn't have locals yet, but we
			// also know its argument map is
			// empty.
			if frame.pc != f.entry() {
				print("runtime: confused by ", funcname(f), ": no frame (sp=", hex(frame.sp), " fp=", hex(frame.fp), ") at entry+", hex(frame.pc-f.entry()), "\n")
				throw("reflect mismatch")
			}
			return bitvector{}, false // No locals, so also no stack objects
		}
		hasReflectStackObj = true
		mv := *(**reflectMethodValue)(unsafe.Pointer(arg0))
		// Figure out whether the return values are valid.
		// Reflect will update this value after it copies
		// in the return values.
		retValid := *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize))
		if mv.fn != f.entry() {
			print("runtime: confused by ", funcname(f), "\n")
			throw("reflect mismatch")
		}
		argMap = *mv.stack
		if !retValid {
			// argMap.n includes the results, but
			// those aren't valid, so drop them.
			n := int32((uintptr(mv.argLen) &^ (goarch.PtrSize - 1)) / goarch.PtrSize)
			if n < argMap.n {
				argMap.n = n
			}
		}
	}
	return
}

// getStackMap returns the locals and arguments live pointer maps, and
// stack object list for frame.
func (frame *stkframe) getStackMap(cache *pcvalueCache, debug bool) (locals, args bitvector, objs []stackObjectRecord) {
	targetpc := frame.continpc
	if targetpc == 0 {
		// Frame is dead. Return empty bitvectors.
		return
	}

	f := frame.fn
	pcdata := int32(-1)
	if targetpc != f.entry() {
		// Back up to the CALL. If we're at the function entry
		// point, we want to use the entry map (-1), even if
		// the first instruction of the function changes the
		// stack map.
		targetpc--
		pcdata = pcdatavalue(f, _PCDATA_StackMapIndex, targetpc, cache)
	}
	if pcdata == -1 {
		// We do not have a valid pcdata value but there might be a
		// stackmap for this function. It is likely that we are looking
		// at the function prologue, assume so and hope for the best.
		pcdata = 0
	}

	// Local variables.
	size := frame.varp - frame.sp
	var minsize uintptr
	switch goarch.ArchFamily {
	case goarch.ARM64:
		minsize = sys.StackAlign
	default:
		minsize = sys.MinFrameSize
	}
	if size > minsize {
		stackid := pcdata
		stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps))
		if stkmap == nil || stkmap.n <= 0 {
			print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n")
			throw("missing stackmap")
		}
		// If nbit == 0, there's no work to do.
		if stkmap.nbit > 0 {
			if stackid < 0 || stackid >= stkmap.n {
				// don't know where we are
				print("runtime: pcdata is ", stackid, " and ", stkmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n")
				throw("bad symbol table")
			}
			locals = stackmapdata(stkmap, stackid)
			if stackDebug >= 3 && debug {
				print("      locals ", stackid, "/", stkmap.n, " ", locals.n, " words ", locals.bytedata, "\n")
			}
		} else if stackDebug >= 3 && debug {
			print("      no locals to adjust\n")
		}
	}

	// Arguments. First fetch frame size and special-case argument maps.
	var isReflect bool
	args, isReflect = frame.argMapInternal()
	if args.n > 0 && args.bytedata == nil {
		// Non-empty argument frame, but not a special map.
		// Fetch the argument map at pcdata.
		stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
		if stackmap == nil || stackmap.n <= 0 {
			print("runtime: frame ", funcname(f), " untyped args ", hex(frame.argp), "+", hex(args.n*goarch.PtrSize), "\n")
			throw("missing stackmap")
		}
		if pcdata < 0 || pcdata >= stackmap.n {
			// don't know where we are
			print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n")
			throw("bad symbol table")
		}
		if stackmap.nbit == 0 {
			args.n = 0
		} else {
			args = stackmapdata(stackmap, pcdata)
		}
	}

	// stack objects.
	if (GOARCH == "amd64" || GOARCH == "arm64" || GOARCH == "ppc64" || GOARCH == "ppc64le" || GOARCH == "riscv64") &&
		unsafe.Sizeof(abi.RegArgs{}) > 0 && isReflect {
		// For reflect.makeFuncStub and reflect.methodValueCall,
		// we need to fake the stack object record.
		// These frames contain an internal/abi.RegArgs at a hard-coded offset.
		// This offset matches the assembly code on amd64 and arm64.
		objs = methodValueCallFrameObjs[:]
	} else {
		p := funcdata(f, _FUNCDATA_StackObjects)
		if p != nil {
			n := *(*uintptr)(p)
			p = add(p, goarch.PtrSize)
			r0 := (*stackObjectRecord)(noescape(p))
			objs = unsafe.Slice(r0, int(n))
			// Note: the noescape above is needed to keep
			// getStackMap from "leaking param content:
			// frame".  That leak propagates up to getgcmask, then
			// GCMask, then verifyGCInfo, which converts the stack
			// gcinfo tests into heap gcinfo tests :(
		}
	}

	return
}

var methodValueCallFrameObjs [1]stackObjectRecord // initialized in stackobjectinit

func stkobjinit() {
	var abiRegArgsEface any = abi.RegArgs{}
	abiRegArgsType := efaceOf(&abiRegArgsEface)._type
	if abiRegArgsType.kind&kindGCProg != 0 {
		throw("abiRegArgsType needs GC Prog, update methodValueCallFrameObjs")
	}
	// Set methodValueCallFrameObjs[0].gcdataoff so that
	// stackObjectRecord.gcdata() will work correctly with it.
	ptr := uintptr(unsafe.Pointer(&methodValueCallFrameObjs[0]))
	var mod *moduledata
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if datap.gofunc <= ptr && ptr < datap.end {
			mod = datap
			break
		}
	}
	if mod == nil {
		throw("methodValueCallFrameObjs is not in a module")
	}
	methodValueCallFrameObjs[0] = stackObjectRecord{
		off:       -int32(alignUp(abiRegArgsType.size, 8)), // It's always the highest address local.
		size:      int32(abiRegArgsType.size),
		_ptrdata:  int32(abiRegArgsType.ptrdata),
		gcdataoff: uint32(uintptr(unsafe.Pointer(abiRegArgsType.gcdata)) - mod.rodata),
	}
}
