# Copyright 2022-2026 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

# Single step through a simple (empty) function that was compiled
# without DWARF debug information.
#
# At each instruction check that the frame-id, and frame base address,
# are calculated correctly.
#
# Additionally, check we can correctly unwind to the previous frame,
# and that the previous stack-pointer value, and frame base address
# value, can be calculated correctly.

if {[prepare_for_testing_full "failed to prepare" \
	 [list ${testfile} $ldflags \
	      $srcfile $srcfile_flags $srcfile2 $srcfile2_flags]]} {
    return
}

if {![runto_main]} {
    return
}

# Return a two element list, the first element is the stack-pointer
# value (from the $sp register), and the second element is the frame
# base address (from the 'info frame' output).
proc get_sp_and_fba { testname } {
    with_test_prefix "get \$sp and frame base $testname" {
	set sp [get_hexadecimal_valueof "\$sp" "*UNKNOWN*"]

	set fba ""
	gdb_test_multiple "info frame" "" {
	    -re -wrap ".*Stack level ${::decimal}, frame at ($::hex):.*" {
		set fba $expect_out(1,string)
	    }
	}

	return [list $sp $fba]
    }
}

# Return the frame-id of the current frame, collected using the 'maint
# print frame-id' command.
proc get_fid { } {
    set fid ""
    gdb_test_multiple "maint print frame-id" "" {
	-re -wrap ".*frame-id for frame #${::decimal}: (.*)" {
	    set fid $expect_out(1,string)
	}
    }
    return $fid
}

# Record the current stack-pointer, and the frame base address.
lassign [get_sp_and_fba "in main"] main_sp main_fba
set main_fid [get_fid]

proc do_test { function step_cmd } {
    # Now enter the function.  Ideally, stop at the first insn, so set a
    # breakpoint at "*$function".  The "*$function" breakpoint may not trigger
    # for archs with gdbarch_skip_entrypoint_p, so set a backup breakpoint at
    # "$function".
    gdb_breakpoint "*$function"
    gdb_breakpoint "$function"
    gdb_continue_to_breakpoint "enter $function"
    # Cleanup breakpoints.
    delete_breakpoints

    # Record the current stack-pointer, and the frame base address.
    lassign [get_sp_and_fba "in $function"] fn_sp fn_fba
    set fn_fid [get_fid]

    for { set i_count 1 } { true } { incr i_count } {
	with_test_prefix "instruction ${i_count}" {

	    # The current stack-pointer value can legitimately change
	    # throughout the lifetime of a function, so we don't check the
	    # current stack-pointer value.  But the frame base address
	    # should not change, so we do check for that.
	    lassign [get_sp_and_fba "for fn"] sp_value fba_value
	    gdb_assert { $fba_value == $fn_fba }

	    # The frame-id should never change within a function, so check
	    # that now.
	    set fid [get_fid]
	    gdb_assert { [string equal $fid $fn_fid] } \
		"check frame-id matches"

	    # Check that the previous frame is 'main'.
	    gdb_test "bt 2" "\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"

	    # Move up the stack (to main).
	    gdb_test "up" \
		"\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"

	    # Check we can unwind the stack-pointer and the frame base
	    # address correctly.
	    lassign [get_sp_and_fba "for main"] sp_value fba_value
	    if { $i_count == 1 } {
		# The stack-pointer may have changed while running to *$function.
		set ::main_sp $sp_value
	    } else {
		gdb_assert { $sp_value == $::main_sp }
	    }
	    gdb_assert { $fba_value == $::main_fba }

	    # Check we have a consistent value for main's frame-id.
	    set fid [get_fid]
	    gdb_assert { [string equal $fid $::main_fid] }

	    # Move back to the inner most frame.
	    gdb_test "frame 0" ".*"

	    if { $i_count > 100 } {
		# We expect a handful of instructions, if we reach 100,
		# something is going wrong.  Avoid an infinite loop.
		fail "exceeded max number of instructions"
		break
	    }

	    gdb_test $step_cmd

	    set in_fn 0
	    gdb_test_multiple "info frame" "" {
		-re -wrap " = $::hex in ${function}( \\(.*\\))?;.*" {
		    set in_fn 1
		}
		-re -wrap "" {}
	    }

	    if { ! $in_fn } {
		break
	    }
	}
    }
}

foreach {
    function step_cmd
} {
    foo stepi
    bar nexti
} {
    with_test_prefix $function {
	do_test $function $step_cmd
    }
}
