mikepaul-LuaJIT/tests/jit_tester.lua

399 lines
8.8 KiB
Lua

local jit = require("jit")
local jutil = require("jit.util")
local vmdef = require("jit.vmdef")
local tracker = require("tracetracker")
local funcinfo, funcbc, traceinfo = jutil.funcinfo, jutil.funcbc, jutil.traceinfo
local traceinfo, traceir, tracek = jutil.traceinfo, jutil.traceir, jutil.tracek
local band = bit.band
local unpack = unpack
--set a high count for now to work around hot counter backoff
local testloopcount = 80
local sub = string.sub
local function fmtfunc(func, pc)
local fi = funcinfo(func, pc)
if fi.loc then
return fi.loc
elseif fi.ffid then
return vmdef.ffnames[fi.ffid]
elseif fi.addr then
return string.format("C:%x", fi.addr)
else
return "(?)"
end
end
local bcnames = {}
for i=1,#vmdef.bcnames/6 do
bcnames[i] = string.sub(vmdef.bcnames, i+1, i+6)
end
local irlookup = {}
for i=0,(#vmdef.irnames)/6 do
local ir = sub(vmdef.irnames, (i*6)+1, (i+1) * 6)
ir = ir:match("^%s*(.-)%s*$")
irlookup[ir] = i
end
local function getiroptype(tr, ref)
local m, ot = traceir(tr, ref)
return shr(ot, 8)
end
function checkir(tr, ir, exop1, exop2, start)
local info = traceinfo(tr)
assert(info)
local findop = irlookup[ir]
assert(findop, "No IR opcode named "..tostring(ir))
local op1kind = (exop1 and irlookup[exop1]) or -1
assert(op1kind == -1 or exop1 == nil, "op1 no IR opcode named "..tostring(exop1))
local op2kind = (exop2 and irlookup[exop2]) or -1
assert(op2kind == -1 or exop2 == nil, "op2 no IR opcode named "..tostring(exop2))
start = start or 1
local err = false
for ins=start,info.nins do
local m, ot, op1, op2, ridsp = traceir(tr, ins)
local op, t = shr(ot, 8), band(ot, 31)
if op == findop then
local match = true
if op1kind ~= -1 and op1 ~= op1kind then
match = false
end
return ins
end
end
trerror("Trace contained no %s IR opcodes", ir)
end
local expectedlnk = "return"
local function trerror(s, a1, ...)
tracker.print_savedevevents()
if(a1) then
error(string.format(s, a1, ...), 4)
else
error(s, 4)
end
end
local function asserteq(result, expected, info)
if result ~= expected then
error(string.format("expected %q but got %q - %s", tostring(expected), tostring(result), info or "", 3))
end
end
local function checktrace(func, tr, mode)
local traces = tracker.traces()
if tr == nil then
if #traces == 0 then
trerror("no traces were started for test")
else
--TODO: Filter out trace that are from checker code
tr = traces[1]
end
end
if tr.abort then
trerror("trace aborted with error %s at %s", abort, fmtfunc(tr.stopfunc, tr.stoppc))
end
local info = traceinfo(tr.traceno)
if info.linktype == "stitch" and expectedlnk ~= "stitch" then
trerror("trace did not cover full function stitched at %s", fmtfunc(tr.stopfunc, tr.stoppc))
end
if tr.startfunc ~= func then
trerror("trace did not start in tested function. started in %s", fmtfunc(tr.startfunc, tr.startpc))
end
if tr.stopfunc ~= func then
trerror("trace did not stop in tested function. stoped in %s", fmtfunc(tr.stopfunc, tr.stoppc))
end
if info.linktype ~= expectedlnk then
trerror("expect trace link '%s but got %s", expectedlnk, info.linktype)
end
if mode == "root" then
if tracker.hasexits() then
trerror("unexpect traces exits ")
end
if #traces > 1 then
trerror("unexpect extra traces were started for test ")
end
end
end
local started_tracker = false
local function begintest(func)
if not started_tracker then
tracker.start()
started_tracker = true
end
jit.flush()
jit.on(func, true) --clear any interpreter only function/loop headers that may have been caused by other tests
tracker.clear()
end
local function trerror2(s, a1, ...)
tracker.print_savedevevents()
if(a1) then
error(string.format(s, a1, ...), 3)
else
error(s, 3)
end
end
function testsingle(expected, func, ...)
begintest(func)
local expectedval = expected
for i=1, testloopcount do
local result
if type(expected) == "function" then
expectedval, result = expected(i, func(...))
else
result = func(...)
end
if (result ~= expectedval) then
local jitted, anyjited = tracker.isjited(func)
tracker.print_savedevevents()
trerror2("expected %q but got %q - %s", tostring(expected), tostring(result), (jitted and "JITed") or "Interpreted")
end
end
checktrace(func, tr, "root")
return true
end
function testwithchecker(checker, func, ...)
begintest(func)
jit.off(checker)
for i=1, testloopcount do
local expected, result = checker(i, func(i, ...))
if (result ~= expected) then
local jitted, anyjited = tracker.isjited(func)
tracker.print_savedevevents()
trerror2("expected %q but got %q - %s", tostring(expected), tostring(result), (jitted and "JITed") or "Interpreted")
end
end
checktrace(func, nil, "root")
return true
end
local state = {
WaitFirstTrace = 1,
CheckNoExits = 2,
RunNextConfig = 3,
CheckCompiledSideTrace = 4,
}
--FIXME: the side traces that happen for config 2 will always abort because they trace out into this function which has jit turned off
local function testexits(func, config1, config2)
begintest(func)
local jitted = false
local trcount = 0
local config, expected, shoulderror = config1, config1.expected, config1.shoulderror
local state = 1
local sidestart = 0
for i=1, testloopcount do
local status, result
if not shoulderror then
result = func(unpack(config.args))
else
status, result = pcall(func, unpack(config.args))
if(status) then
tracker.print_savedevevents()
trerror2("expected call to trigger error but didn't "..tostring(i))
end
end
if state == 2 then
if tracker.hasexits() then
trerror2("trace exited on first run after being compiled "..expected)
end
state = 3
end
local newtraces = tracker.traceattemps() ~= trcount
if newtraces then
trcount = tracker.traceattemps()
jitted, anyjited = tracker.isjited(func)
if state == 1 then
--let the trace be executed once before we switch to the next arguments
state = 2
elseif state == 4 and not tracker.traces()[trcount].abort then
state = 5
sidestart = tracker.exitcount()
print("side trace compiled ".. tostring(expected))
end
end
if not shoulderror and result ~= expected then
tracker.print_savedevevents()
error(string.format("expected %q but got %q - %s", tostring(expected), tostring(result), (jitted and "JITed") or "Interpreted"), 2)
end
if state == 3 then
config = config2
expected = config2.expected
shoulderror = config2.shoulderror
state = 4
end
end
local traces = tracker.traces()
if #traces == 0 then
trerror2("no traces were started for test "..expected)
end
local tr = traces[1]
checktrace(func, tr)
if not tracker.hasexits() then
trerror2("Expect trace to exit to interpreter")
end
if sidestart ~= 0 and tracker.exitcount() > sidestart then
trerror2("Unexpected exits from side trace")
end
assert(state >= 4)
return true
end
local function texiterror(msg)
tracker.printexits()
error(msg, 4)
end
local function testexit(expected, func, ...)
tracker.clearexits()
local result = func(...)
if not tracker.hasexits() then
texiterror("Expected trace to exit but didn't")
end
asserteq(result, expected)
return true
end
local function testnoexit(expected, func, ...)
tracker.clearexits()
local result = func(...)
if tracker.hasexits() then
texiterror("Unexpected trace exits")
end
asserteq(result, expected)
return true
end
local function testexiterr(func, ...)
tracker.clearexits()
local status, result = pcall(func, ...)
if not tracker.hasexits() then
texiterror("Expected trace to exit but didn't")
end
if(status) then
texiterror("Expected call to trigger error but didn't ")
end
return true
end
jit.off(true, true)
local function setasserteq(func)
jit.off(func)
--force interpreter only func header bc
for i=1, 30 do
func(1, 1)
end
asserteq = func
end
require("jit.opt").start("hotloop=2", "minstitch=60000")
--force the loop and function header in testjit to abort and be patched
local dummyfunc = function() return "" end
for i=1,30 do
pcall(testsingle, "", dummyfunc, "")
end
require("jit.opt").start("hotloop=6", "minstitch=0")
return {
testsingle = testsingle,
testwithchecker = testwithchecker,
testexit = testexit,
testnoexit = testnoexit,
testexiterr = testexiterr,
testexits = testexits,
testloopcount = testloopcount,
setasserteq = setasserteq,
}