# Test code for libgccjit.so
#
# We will compile each of jit.dg/test-*.c into an executable
# dynamically linked against libgccjit.so, and then run each
# such executable.
#
# These executables call into the libgccjit.so API to create
# code, compile it, and run it, verifying that the results
# are as expected.  See harness.h for shared code used by all
# such executables.
#
# The executables call into DejaGnu's unit testing C API to
# report PASS/FAIL results, which this script gathers back
# up into the Tcl world, reporting a summary of all results
# across all of the executables.

# Kludge alert:
# We need g++_init so that it can find the stdlib include path.
#
# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
# which normally comes from the definition of
# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
#
# However, for us, ${tool} is "jit".
# Hence we load wrapper.exp with tool == "g++", so that
# g++_maybe_build_wrapper is defined.
set tool g++
load_lib wrapper.exp
set tool jit

load_lib dg.exp
load_lib prune.exp
load_lib target-supports.exp
load_lib gcc-defs.exp
load_lib timeout.exp
load_lib target-libpath.exp
load_lib gcc.exp
load_lib g++.exp
load_lib dejagnu.exp
load_lib target-supports-dg.exp
load_lib valgrind.exp

# Skip these tests for targets that don't support -lgccjit
if { ![check_effective_target_lgccjit] } {
    return
}

# The default do-what keyword.
set dg-do-what-default compile

# Given WRES, the result from "wait", issue a PASS
# if the spawnee exited cleanly, or a FAIL for various kinds of
# unexpected exits.

proc verify_exit_status { executable wres } {
    set extra [lassign $wres pid spawnid os_error_flag value]
    verbose "pid: $pid" 3
    verbose "spawnid: $spawnid" 3
    verbose "os_error_flag: $os_error_flag" 3
    verbose "value: $value" 3

    # Detect segfaults etc:
    set len [llength $extra]
    if { $len >= 1 } {
	if { [lindex $extra 0] == "CHILDKILLED" } {
	    set reason "Unknown Reason"
	    set detail "No Details"
	    if { $len >= 2 } {
		set reason [lindex $extra 1]
		if { $len >= 3 } {
		    set detail [lindex $extra 2]
		}
	    }
	    fail "$executable killed: $reason $detail"
	    return
	}
    }
    if { $os_error_flag != 0 } {
	fail "$executable: OS error: $os_error_flag $extra"
	return
    }
    if { $value != 0 } {
	fail "$executable: non-zero exit code: $value $extra"
	return
    }
    pass "$executable exited cleanly"
}

# This is host_execute from dejagnu.exp commit
#   126a089777158a7891ff975473939f08c0e31a1c
# with the following patch applied, and renaming to "fixed_host_execute".
# See the discussion at
#  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
#
#  --- /usr/share/dejagnu/dejagnu.exp.old  2014-10-08 13:38:57.274068541 -0400
#  +++ /usr/share/dejagnu/dejagnu.exp      2014-10-10 12:27:51.113813659 -0400
#  @@ -113,8 +113,6 @@ proc host_execute {args} {
#       set timetol 0
#       set arguments ""
#   
#  -    expect_before buffer_full { perror "Buffer full" }
#  -
#       if { [llength $args] == 0} {
#          set executable $args
#       } else {


# Execute the executable file, and anaylyse the output for the
# test state keywords.
#    Returns:
#	A "" (empty) string if everything worked, or an error message
#	if there was a problem.
#
proc fixed_host_execute {args} {
    global env
    global text
    global spawn_id

    verbose "fixed_host_execute: $args"

    set timeoutmsg "Timed out: Never got started, "
    set timeout 100
    set file all
    set timetol 0
    set arguments ""

    if { [llength $args] == 0} {
	set executable $args
    } else {
	set executable [lindex $args 0]
	set params [lindex $args 1]
    }

    verbose "The executable is $executable" 2
    if {![file exists ${executable}]} {
	perror "The executable, \"$executable\" is missing" 0
	return "No source file found"
    } elseif {![file executable ${executable}]} {
	perror "The executable, \"$executable\" is not usable" 0
	return "Bad executable found"
    }

    verbose "params: $params" 2

    # spawn the executable and look for the DejaGnu output messages from the
    # test case.
    # spawn -noecho -open [open "|./${executable}" "r"]

    # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
    # Note that it's best to configure gcc with --enable-valgrind-annotations
    # when testing under valgrind.
    set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
    if $run_under_valgrind {
	set valgrind_logfile "${executable}.valgrind.txt"
	set valgrind_params {"valgrind"}
	lappend valgrind_params "--leak-check=full"
	lappend valgrind_params "--log-file=${valgrind_logfile}"
    } else {
	set valgrind_params {}
    }
    verbose "valgrind_params: $valgrind_params" 2

    set args ${valgrind_params}
    lappend args "./${executable}"
    set args [concat $args ${params}]
    verbose "args: $args" 2

    # We checked that the executable exists above, and can be executed, but
    # that does not cover other reasons that the launch could fail (e.g.
    # missing or malformed params); catch such cases here and report them.
    set err [catch "spawn -noecho $args" pid]
    set sub_proc_id $spawn_id
    if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } {
        warning "failed to spawn : $args : err = $err"
    }

    # Increase the buffer size, if needed to avoid spurious buffer-full
    # events; GCC uses 10000; chose a power of two here.
    set current_max_match [match_max -i $sub_proc_id]
    if { $current_max_match < 8192 } {
        match_max -i $sub_proc_id 8192
        set used [match_max -i $sub_proc_id]
    }

    # If we get a buffer-full error, that seems to be unrecoverable so try to
    # exit in a reasonable manner to avoid wedged processes.
    expect_after full_buffer {
        verbose -log "fixed_host_execute: $args FULL BUFFER"
        # FIXME: this all assumes that closing the connection will cause the
        # sub-process to terminate (but that is not going to be the case if,
        # for example, there is something started with -nohup somewhere).
        # We should explicitly kill it here.
        # Set the process to be a nowait exit.
        wait -nowait -i $sub_proc_id
        catch close
        perror "${executable} got full_buffer"
        return "${executable} got full_buffer"
    }

    set prefix "\[^\r\n\]*"
    # Work around a Darwin tcl or termios bug that sometimes inserts extra
    # CR characters into the cooked tty stream
    set endline "\r\n"
    if { [istarget *-*-darwin*] } {
        set endline "\r(\r)*\n"
    }
    
    # Note that the logic here assumes that we cannot (validly) get single
    # carriage return or line feed characters in the stream.  If those occur,
    # it will stop any further matching.  We arange for the matching to be
    # at the start of the buffer - so that if there is any spurious output
    # to be discarded, it must be done explicitly - not by matching part-way
    # through the buffer.
    expect {
	-re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" {
	    regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output
	    verbose "$output" 3
	    set timetol 0
	    exp_continue
	}
	-re "^\tNOTE: (\[^\r\n\]+)$endline" {
	    # discard notes.
	    verbose "Ignored note: $expect_out(1,string)" 2
	    set timetol 0
	    exp_continue
	}
	-re "^\tPASSED: (\[^\r\n\]+)$endline" {
	    pass "$expect_out(1,string)"
	    set timetol 0
	    exp_continue
	}
	-re "^\tFAILED: (\[^\r\n\]+)$endline" {
	    fail "$expect_out(1,string)"
	    set timetol 0
	    exp_continue
	}
	-re "^\tUNTESTED: (\[^\r\n\]+)$endline" {
	    untested "$expect_out(1,string)"
	    set timetol 0
	    exp_continue
	}
	-re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" {
	    unresolved "$expect_out(1,string)"
	    set timetol 0
	    exp_continue
	}
	-re "^$prefix$endline" {
            # This matches and discards any other lines (including blank ones).
            if { [string length $expect_out(buffer)] <= 2 } {
                set output "blank line"
            } else {
	        set output [string range $expect_out(buffer) 0 end-2]
	    }
	    verbose -log "DISCARDED $expect_out(spawn_id) : $output"
	    exp_continue
	}
	eof {
	    # This seems to be the only way that we can reliably know that the
	    # output is finished since there are cases where further output
	    # follows the dejagnu test harness totals.
	    verbose "saw eof" 2
	}
	timeout {
	    if { $timetol <= 2 } {
	        verbose -log "Timed out with retry (timeout = $timeout)"
		incr timetol
		exp_continue
	    } else {
	        warning "Timed out executing testcase (timeout = $timeout)"
		catch close
		return "Timed out executing test case"
	    }
	}
    }

    # Use "wait" to pick up the sub-process exit state.  If the sub-process is
    # writing to a file (perhaps under valgrind) then that also needs to be
    # complete; only attempt this on a valid spawn.
    if { $sub_proc_id > 0 } {
        verbose "waiting for $sub_proc_id" 1
        # Be explicit about what we are waiting for.
        catch "wait -i $sub_proc_id" wres
        verbose "wres: $wres" 2
        verify_exit_status $executable $wres
    }
 
    if $run_under_valgrind {
	upvar 2 name name
	# Use xfail to report leaks, as libgccjit isn't yet clean of
	# memory leaks (PR jit/63854)
	parse_valgrind_logfile $name $valgrind_logfile xfail
    }

    # force a close of the executable to be safe.
    catch close

    return ""
}

# (end of code from dejagnu.exp)

# GCC_UNDER_TEST is needed by gcc_target_compile
global GCC_UNDER_TEST
if ![info exists GCC_UNDER_TEST] {
    set GCC_UNDER_TEST "[find_gcc]"
}

g++_init

# Initialize dg.
dg-init

# Gather a list of all tests.

# C tests within the testsuite: gcc/testsuite/jit.dg/test-*.c
set tests [find $srcdir/$subdir test-*.c]

# C++ tests within the testsuite: gcc/testsuite/jit.dg/test-*.cc
set tests [concat $tests [find $srcdir/$subdir test-*.cc]]

# We also test the examples within the documentation, to ensure that
# they compile:
set tests [concat $tests [find $srcdir/../jit/docs/examples *.c]]
set tests [concat $tests [find $srcdir/../jit/docs/examples *.cc]]

set tests [lsort $tests]

verbose "tests: $tests"

# Is testcase NAME meant to generate a reproducer?
proc is_testcase_meant_to_generate_a_reproducer {name} {
    # We expect most testcases to generate a reproducer.
    # The exceptions are the tutorials (which don't have a "test-"
    # prefix), and test-threads.c and test-benchmark.c (which are each
    # unique).
    verbose "is_testcase_meant_to_generate_a_reproducer: $name"
    if { [string match "*test-*" $name] } {
	if { [string match "*test-threads.c" $name] } {
	    return 0
	}
	if { [string match "*test-benchmark.c" $name] } {
	    return 0
	}
	return 1
    }
    return 0
}

# libgloss has found the driver (as "xgcc" or "gcc) and stored
# its full path as GCC_UNDER_TEST.
proc get_path_of_driver {} {
    global GCC_UNDER_TEST

    verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST"
    set binary [lindex $GCC_UNDER_TEST 0]
    verbose "binary: $binary"

    return [file dirname $binary]
}

# Expand "SRCDIR" within ARG to the location of the top-level
# src directory

proc jit-expand-vars {arg} {
    verbose "jit-expand-vars: $arg"
    global srcdir
    verbose " srcdir: $srcdir"
    # "srcdir" is that of the gcc/testsuite directory, so
    # we need to go up two levels.
    set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
    verbose " new arg: $arg"
    return $arg
}

# Parameters used when invoking the executables built from the test cases.

global jit-exe-params
set jit-exe-params {}

# Set "jit-exe-params", expanding "SRCDIR" in each arg to the location of
# the top-level srcdir.

proc dg-jit-set-exe-params { args } {
    verbose "dg-jit-set-exe-params: $args"

    global jit-exe-params
    set jit-exe-params {}
    # Skip initial arg (line number)
    foreach arg [lrange $args 1 [llength $args] ] {
	lappend jit-exe-params [jit-expand-vars $arg]
    }
}

# For test-debuginfo.c. Starts gdb, does cmds and checks the output against match
proc jit-check-debug-info { obj_file cmds match } {
    verbose "Checking debug info for $obj_file with match: $match"

    if { [catch {exec gdb -v} fid] } {
        verbose "No gdb seems to be in path. Can't check debug info. Reporting 'unsupported'."
        unsupported "No gdb seems to be in path. Can't check debug info"
        return
    }

    spawn gdb $obj_file

    verbose "Disable color styling in GDB newer then 8.3 (errors on older)"
    send "set style enabled off\n"

    foreach cmd $cmds {
        send $cmd
    }
    expect {
	-re "Dwarf Error: wrong version in compilation unit header" {
	    set testcase [testname-for-summary]
	    unsupported "$testcase: gdb does not support dwarf version"
	}
        -re $match { pass OK }
        default { fail FAIL }
    }

    # Quit gdb
    send "set confirm off\n"
    send "q\n"
}

proc jit-dg-test { prog do_what extra_tool_flags } {
    verbose "within jit-dg-test..."
    verbose "  prog: $prog"
    verbose "  do_what: $do_what"
    verbose "  extra_tool_flags: $extra_tool_flags"

    global dg-do-what-default
    set dg-do-what [list ${dg-do-what-default} "" P]

    set tmp [dg-get-options $prog]
    foreach op $tmp {
	verbose "Processing option: $op" 3
	set status [catch "$op" errmsg]
	if { $status != 0 } {
	    if { 0 && [info exists errorInfo] } {
		# This also prints a backtrace which will just confuse
		# testcase writers, so it's disabled.
		perror "$name: $errorInfo\n"
	    } else {
		perror "$name: $errmsg for \"$op\"\n"
	    }
	    perror "$name: $errmsg for \"$op\"" 0
	    return
	}
    }

    # If we're not supposed to try this test on this target, we're done.
    if { [lindex ${dg-do-what} 1] == "N" } {
	unsupported "$name"
	verbose "$name not supported on this target, skipping it" 3
	return
    }

    # test-threads.c needs to be linked against pthreads
    if {[string match "*test-threads.c" $prog]} {
	append extra_tool_flags " -lpthread"
    }

    # test-add-driver-options.c needs a shared library built from
    # add-driver-options-testlib.c
    if {[string match "*test-add-driver-options.c" $prog]} {
	global srcdir
	global subdir

	set comp_output [gcc_target_compile \
			     $srcdir/$subdir/add-driver-options-testlib.c \
			     "libadd-driver-options-testlib.so" \
			     "executable" \
			     "additional_flags=-fPIC additional_flags=-shared"]
    }

    # Any test case that uses jit-verify-output-file-was-created
    # needs to call jit-setup-compile-to-file here.
    # (is there a better way to handle setup/finish pairs in dg?)
    set tmp [grep $prog "jit-verify-output-file-was-created"]
    if {![string match "" $tmp]} {
	jit-setup-compile-to-file $prog
    }

    # Determine what to name the built executable.
    #
    # We simply append .exe to the filename, e.g.
    #  "test-foo.c.exe"
    # since some testcases exist in both
    #  "test-foo.c" and
    #  "test-foo.cc"
    # variants, and we don't want them to clobber each other's
    # executables.
    #
    # This also ensures that the source name makes it into the
    # pass/fail output, so that we can distinguish e.g. which test-foo
    # is failing.
    set output_file "[file tail $prog].exe"
    verbose "output_file: $output_file"

    # Create the test executable:
    set extension [file extension $prog]
    if {$extension == ".cc"} {
	set compilation_function "g++_target_compile"
	set options "{additional_flags=$extra_tool_flags}"
    } else {
	set compilation_function "gcc_target_compile"
	# Until recently, <dejagnu.h> assumed -fgnu89-inline
	# Ideally we should fixincludes it (PR other/63613), but
	# for now add -fgnu89-inline when compiling C JIT testcases.
	# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613
	# and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html
	set options "{additional_flags=$extra_tool_flags -fgnu89-inline}"
    }
    verbose "compilation_function=$compilation_function"
    verbose "options=$options"

    set comp_output [$compilation_function $prog $output_file \
			 "executable" $options]
    upvar 1 name name
    if ![jit_check_compile "$name" "initial compilation" \
	    $output_file $comp_output] then {
      return
    }

    # Most of the test cases use gcc_jit_context_dump_reproducer_to_file
    # as they run to write out a .c file that reproduces their behavior,
    # exercising that API.
    set generated_reproducer "${output_file}.reproducer.c"

    # Delete any such generated .c file from a previous run.
    catch "exec rm -f $generated_reproducer"

    # Run the test executable, capturing the PASS/FAIL textual output
    # from the C API, converting it into the Tcl API.

    # We need to set LD_LIBRARY_PATH so that the test files can find
    # libgccjit.so
    # Do this using set_ld_library_path_env_vars from target-libpath.exp
    # We will restore the old value later using
    # restore_ld_library_path_env_vars.

    # Unfortunately this API only supports a single saved value, rather
    # than a stack, and g++_init has already called into this API,
    # injecting the appropriate value for LD_LIBRARY_PATH for finding
    # the built copy of libstdc++.
    # Hence the call to restore_ld_library_path_env_vars would restore
    # the *initial* value of LD_LIBRARY_PATH, and attempts to run
    # a C++ testcase after running any prior testcases would thus look
    # in the wrong place for libstdc++.  This led to failures at startup
    # of the form:
    #   ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
    # when the built libstdc++ is more recent that the system libstdc++.
    #
    # As a workaround, reset the variable "orig_environment_saved" within
    # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
    # API saves/restores the current value of LD_LIBRARY_PATH (as set up
    # by g++_init).
    global orig_environment_saved
    set orig_environment_saved 0

    global ld_library_path
    global base_dir
    set ld_library_path "$base_dir/../../"
    set_ld_library_path_env_vars

    # libgccjit uses the driver to convert .s files to .so libraries
    # via its *installed* name, FULL_DRIVER_NAME
    #   ${target_noncanonical}-gcc-${gcc_BASEVER}${exeext}
    # e.g. "x86_64-unknown-linux-gnu-gcc-5.0.0"
    # looking for it on PATH.  Hence we need to prepend the location of
    # that executable to PATH when running the tests
    set dir_containing_driver [get_path_of_driver ]
    verbose "dir_containing_driver: $dir_containing_driver"
    global env
    set old_path $env(PATH)
    setenv "PATH" $dir_containing_driver:$old_path
    verbose -log "PATH=[getenv PATH]"

    # We have:
    #   test-executables
    #     linked to -> libgccjit.so
    #                    -> invokes driver:
    #                         -> invokes the assembler
    #                         -> invokes the linker
    # We want to be able to run this from the builddir without installing
    # but the linker needs to be able to locate various libraries, or we
    # get:
    #   ld: cannot find crtbeginS.o: No such file or directory
    #   ld: cannot find -lgcc
    #   ld: cannot find -lgcc_s
    # These can be found in the "gcc" subdir of the build.
    # Hence to be able to run the testsuite without installing, we need
    # to set or prepend the "gcc" subdir of the build to LIBRARY_PATH:
    if { [info exists env(LIBRARY_PATH) ] } {
	set old_library_path $env(LIBRARY_PATH)
	setenv "LIBRARY_PATH" $dir_containing_driver:$old_library_path
    } else {
	setenv "LIBRARY_PATH" $dir_containing_driver
    }
    verbose -log "LIBRARY_PATH=[getenv LIBRARY_PATH]"

    # dejagnu.exp's host_execute has code to scrape out test results
    # from the DejaGnu C API and bring back into the tcl world, so we
    # use that to invoke the built code.
    # However, it appears to be buggy; see:
    #  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
    # We instead call a patched local copy, "fixed_host_execute", defined
    # above.

    global jit-exe-params
    set args ${jit-exe-params}
    set jit-exe-params {}

    set result [fixed_host_execute $output_file $args ]
    verbose "result: $result"

    # Restore PATH
    setenv "PATH" $old_path

    # Restore LIBRARY_PATH
    if { [info exists old_library_path] } {
	setenv "LIBRARY_PATH" $old_library_path
    } else {
	unsetenv "LIBRARY_PATH"
    }

    restore_ld_library_path_env_vars

    # Most of the test cases use gcc_jit_context_dump_reproducer_to_file
    # as they run to write out a .c file that reproduces their behavior,
    # exercising that API.

    if { [is_testcase_meant_to_generate_a_reproducer $name] } {
	verbose "$name is meant to generate a reproducer"
	# Verify that a reproducer was generated
	if { [file exists $generated_reproducer] == 1} {
	    pass "found generated reproducer: $generated_reproducer"
	    set output_file "${generated_reproducer}.exe"
	    # (this overwrites output_file)

	    # Try to compile the generated reproducer
	    verbose "compilation_function=$compilation_function"

	    # The .c file written by gcc_jit_context_dump_reproducer_to_file
	    # assigns the result of each API call to a unique variable, and not
	    # all are necessarily used, so we need -Wno-unused-variable.
	    set options \
		"{additional_flags=$extra_tool_flags -Wno-unused-variable}"
	    verbose "options=$options"

	    set comp_output2 [$compilation_function $generated_reproducer \
				  $output_file "executable" $options]
	    if ![jit_check_compile "generated reproducer from $name" "initial compilation" \
		     $output_file $comp_output2] then {
			 return
		     }

	    # The caller, dg-test, will verify comp_output, which contains
	    # the output from compiling the testcase and will issue a fail
	    # if it's non-empty (e.g. containing warnings, the
	    # "test for excess errors").
	    #
	    # Append the output from compiling the reproducer, so that this is also
	    # verified:
	    append comp_output $comp_output2

	    # TODO: we should try to run the built executable
	    # It's not quite a quine, since it embeds ptrs which could change
	    # from run to run.
	} else {
	    fail "did not find a generated reproducer: $generated_reproducer"
	}
    } else {
	verbose "$name is not meant to generate a reproducer"
    }

    # Normally we would return $comp_output and $output_file to the
    # caller, which would delete $output_file, the generated executable.
    # If we need to debug, it's handy to be able to suppress this behavior,
    # keeping the executable around.
    set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
    if $preserve_executables {
	set output_file ""
    }
    
    return [list $comp_output $output_file]
}

# Given source file PROG, scrape out the value of
#   #define OUTPUT_FILENAME
# failing if it's not found.

proc jit-get-output-filename {prog} {
    set tmp [grep $prog "#define OUTPUT_FILENAME (.*)"]
    if {![string match "" $tmp]} {
	foreach i $tmp {
	    verbose "i: $i"
	    if {[regexp "^\#define OUTPUT_FILENAME\[ \t\]\+\"(.*)\"$" $i i group] } {
		verbose "group: '$group'"
		return $group
	    } else {
		fail "Unable to parse line: $i"
	    }
	}
    }
    fail "Unable to locate OUTPUT_FILENAME"
    return ""
}

# For testcases that use jit-verify-output-file-was-created
# delete OUTPUT_FILENAME beforehand, to ensure that the
# testcase is indeed creating it.

proc jit-setup-compile-to-file { prog } {
    verbose "jit-setup-compile-to-file: $prog"
    set output_filename [jit-get-output-filename $prog]
    verbose "  output_filename: $output_filename"
    if {![string match "" $output_filename]} {
	verbose "  deleting any $output_filename"
	catch "exec rm -f $output_filename"
    }
}

proc jit-verify-output-file-was-created { args } {
    verbose "jit-verify-output-file-was-created: $args"

    upvar 2 prog prog
    verbose "prog: $prog"
    set output_filename [jit-get-output-filename $prog]
    verbose "  output_filename: $output_filename"

    # Verify that the expected file was written out
    if { [file exists $output_filename] == 1} {
	pass "$output_filename exists"
    } else {
	fail "$output_filename does not exist"
    }
}

# Verify that the given file exists, and is executable.
# Attempt to execute it, and verify that its stdout matches
# the given regex.

proc jit-run-executable { args } {
    verbose "jit-run-executable: $args"

    set executable-name [lindex $args 0]
    verbose "executable-name: ${executable-name}"

    set dg-output-text [lindex $args 1]
    verbose "dg-output-text: ${dg-output-text}"

    if { [file executable ${executable-name}] } {
	pass "${executable-name} has executable bit set"
    } else {
	fail "${executable-name} does not have executable bit set"
    }

    # Attempt to run the executable; adapted from dg.exp's dg-test
    set status -1
    set result [jit_load ./${executable-name}]
    set status [lindex $result 0]
    set output [lindex $result 1]
    verbose "  status: $status"
    verbose "  output: $output"
    # send_user "After exec, status: $status\n"
    if { "$status" == "pass" } {
	pass "${executable-name} execution test"
	verbose "Exec succeeded." 3
	set texttmp ${dg-output-text}
	if { ![regexp $texttmp ${output}] } {
	    fail "${executable-name} output pattern test, is ${output}, should match $texttmp"
	    verbose "Failed test for output pattern $texttmp" 3
	} else {
	    pass "${executable-name} output pattern test, $texttmp"
	    verbose "Passed test for output pattern $texttmp" 3
	}
	unset texttmp
    } elseif { "$status" == "fail" } {
	# It would be nice to get some info out of errorCode.
	if {[info exists errorCode]} {
	    verbose "Exec failed, errorCode: $errorCode" 3
	} else {
	    verbose "Exec failed, errorCode not defined!" 3
	}
	fail "${executable-name} execution test"
    } else {
	$status "${executable-name} execution test"
    }
}

# Assuming that a .s file has been written out named
# OUTPUT_FILENAME, invoke the driver to try to turn it into
# an executable, and try to run the result.
# For use by the test-compile-to-assembler.c testcase.
proc jit-verify-assembler { args } {
    verbose "jit-verify-assembler: $args"

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set asm_filename [jit-get-output-filename $prog]
    verbose "  asm_filename: ${asm_filename}"

    # Name the built executable as OUTPUT_FILENAME with
    # ".exe" appended.
    set executable_from_asm ${asm_filename}.exe
    verbose "  executable_from_asm: ${executable_from_asm}"

    # Invoke the driver to assemble/link the .s file to the .exe
    set comp_output [gcc_target_compile \
			 ${asm_filename} \
			 ${executable_from_asm} \
			 "executable" \
			 "{}"]
    if ![jit_check_compile \
	     "$name" \
	     "assemble/link of ${asm_filename}" \
	     ${executable_from_asm} \
	     $comp_output] then {
      return
    }

    # Verify that the executable was created.
    if { [file exists $executable_from_asm] == 1} {
	pass "$executable_from_asm exists"
    } else {
	fail "$executable_from_asm does not exist"
    }

    # Run it and verify that the output matches the regex.
    jit-run-executable ${executable_from_asm} ${dg-output-text}
}

# Assuming that a .s file has been written out named
# OUTPUT_FILENAME, check that the argument matches the
# output file.
# For use by the test-link-section-assembler.c testcase.
proc jit-verify-assembler-output { args } {
    verbose "jit-verify-assembler: $args"

    if { [llength $args] > 3 } {
	error "jit-verify-assembler-output: too many arguments"
	return
    }

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    if { [llength $args] >= 2 } {
	switch [dg-process-target [lindex $args 1]] {
	    "S" { }
	    "N" { return }
	    "F" { setup_xfail "*-*-*" }
	    "P" { }
	}
    }

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set asm_filename [jit-get-output-filename $prog]
    verbose "  asm_filename: ${asm_filename}"

    # Read the assembly file.
    set f [open $asm_filename r]
    set content [read $f]
    close $f

    # Verify that the assembly matches the regex.
    if { ![regexp ${dg-output-text} $content] } {
	fail "${asm_filename} output pattern test, is ${content}, should match ${dg-output-text}"
	verbose "Failed test for output pattern ${dg-output-text}" 3
    } else {
	pass "${asm_filename} output pattern test, ${dg-output-text}"
	verbose "Passed test for output pattern ${dg-output-text}" 3
    }
}

# Assuming that a .s file has been written out named
# OUTPUT_FILENAME, check that the argument doesn't match
# the output file.
proc jit-verify-assembler-output-not { args } {
    verbose "jit-verify-assembler: $args"

    if { [llength $args] > 3 } {
	error "jit-verify-assembler-output-not: too many arguments"
	return
    }

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    if { [llength $args] >= 2 } {
	switch [dg-process-target [lindex $args 1]] {
	    "S" { }
	    "N" { return }
	    "F" { setup_xfail "*-*-*" }
	    "P" { }
	}
    }

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set asm_filename [jit-get-output-filename $prog]
    verbose "  asm_filename: ${asm_filename}"

    # Read the assembly file.
    set f [open $asm_filename r]
    set content [read $f]
    close $f

    # Verify that the assembly matches the regex.
    if { [regexp ${dg-output-text} $content] } {
	fail "${asm_filename} output pattern test, is ${content}, should not match ${dg-output-text}"
	verbose "Failed test for output pattern ${dg-output-text}" 3
    } else {
	pass "${asm_filename} output pattern test, ${dg-output-text}"
	verbose "Passed test for output pattern ${dg-output-text}" 3
    }
}


# Assuming that a .o file has been written out named
# OUTPUT_FILENAME, invoke the driver to try to turn it into
# an executable, and try to run the result.
# For use by the test-compile-to-object.c testcase.
proc jit-verify-object { args } {
    verbose "jit-verify-object: $args"

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set obj_filename [jit-get-output-filename $prog]
    verbose "  obj_filename: ${obj_filename}"

    # Name the linked executable as OUTPUT_FILENAME with
    # ".exe" appended.
    set executable_from_obj ${obj_filename}.exe
    verbose "  executable_from_obj: ${executable_from_obj}"

    # Invoke the driver to link the .o file to the .exe
    set comp_output [gcc_target_compile \
			 ${obj_filename} \
			 ${executable_from_obj} \
			 "executable" \
			 "{}"]
    if ![jit_check_compile \
	     "$name" \
	     "link of ${obj_filename}" \
	     ${executable_from_obj} \
	     $comp_output] then {
      return
    }

    # Verify that the executable was created.
    if { [file exists $executable_from_obj] == 1} {
	pass "$executable_from_obj exists"
    } else {
	fail "$executable_from_obj does not exist"
    }

    # Run it and verify that the output matches the regex.
    jit-run-executable ${executable_from_obj} ${dg-output-text}
}

# Assuming that a .so file has been written out named
# OUTPUT_FILENAME, build a test executable to use it,
# and try to run the result.
# For use by the test-compile-to-dynamic-library.c testcase.
proc jit-verify-dynamic-library { args } {
    verbose "jit-verify-object: $args"

    global srcdir
    global subdir

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set obj_filename [jit-get-output-filename $prog]
    verbose "  obj_filename: ${obj_filename}"

    # Build a test executable from
    # verify-dynamic-library.c
    set test_src "verify-dynamic-library.c"
    set test_executable ${test_src}.exe
    verbose "  test_executable: ${test_executable}"

    # Invoke the driver to build the test executable
    set comp_output [gcc_target_compile \
			 $srcdir/$subdir/${test_src} \
			 ${test_executable} \
			 "executable" \
			 "{additional_flags=-ldl}"]
    if ![jit_check_compile \
	     "$name" \
	     "build of ${test_executable}" \
	     ${test_executable} \
	     $comp_output] then {
      return
    }

    # Verify that the test executable was created.
    if { [file exists $test_executable] == 1} {
	pass "$test_executable exists"
    } else {
	fail "$test_executable does not exist"
    }

    # Run it and verify that the output matches the regex.
    jit-run-executable ${test_executable} ${dg-output-text}
}

# A way to invoke "jit-run-executable" with the given regex,
# using OUTPUT_FILENAME within the testcase to determine
# the name of the executable to run.
# For use by the test-compile-to-executable.c testcase.

proc jit-verify-executable { args } {
    verbose "jit-verify-executable: $args"

    set dg-output-text [lindex $args 0]
    verbose "dg-output-text: ${dg-output-text}"

    upvar 2 name name
    verbose "name: $name"

    upvar 2 prog prog
    verbose "prog: $prog"
    set output_filename [jit-get-output-filename $prog]
    verbose "  output_filename: $output_filename"

    jit-run-executable $output_filename ${dg-output-text}
}

set DEFAULT_CFLAGS "-I$srcdir/../jit -lgccjit -g -Wall -Werror"

# We need to link with --export-dynamic for test-calling-external-function.c
# so that the JIT-built code can call into functions from the main program.

if { [check_effective_target_rdynamic] } {
  set DEFAULT_CFLAGS "$DEFAULT_CFLAGS -rdynamic"
}

# Main loop.  This will invoke jig-dg-test on each test-*.c file.
dg-runtest $tests "" $DEFAULT_CFLAGS

# All done.
dg-finish
