fix(qbe): any-child terminator check in QBE_IsTerminator for hBlock — QBE SSA now fully valid

- QBE_IsTerminator(hBlock) checks ALL children, not just last
- Removed legacy c_backend.bux and nim_backend.bux from _selfhost/src/
- QBE self-hosting QBE phase: PASSES (SSA valid, assembled to .s)
- Remaining: linker errors from missing String_Eq/StringBuilder in runtime
This commit is contained in:
2026-06-02 13:56:12 +03:00
parent 6cb5528a4d
commit eb8179b6d0
142 changed files with 70706 additions and 1407 deletions
Vendored Executable
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/python3
# support script to create
# the abi8.ssa test
def ctype(arg):
if arg[0] == 'p': return ctype(arg[1:])
if arg[0] == ':': return 'S' + arg[1:]
return {'w':'int', 'l':'long',
's':'float', 'd':'double'}[arg]
def cparam(iarg):
return ctype(iarg[1]) + ' p' + str(iarg[0])
def gencfn(id, args):
out = '# extern void qfn' + id + '('
out += ', '.join(map(ctype, args)) + ');\n'
out += '# void cfn' + id + '('
out += ', '.join(map(cparam, enumerate(args)))
out += ') {\n'
out += '# \tprintf("qbe->c(%d)", ' + id + ');\n'
out += '# \t'
for (i, arg) in enumerate(args):
if arg[0] != 'p': continue
ty = arg[1:]
if ty[0] == ':':
out += 'p' + ty[1:] + '(&'
else:
out += 'p' + ty + '('
out += 'p' + str(i) + '); '
out += 'puts("");\n'
out += '# \tqfn' + id + '('
out += ', '.join('p'+str(i) for i in range(len(args)))
out += ');\n'
out += '# }\n'
return out
def qparam(iarg):
ty = iarg[1][1:] if iarg[1][0] == 'p' else iarg[1]
return ty + ' %p' + str(iarg[0])
def genqfn(id, args):
out = 'export\nfunction $qfn' + id + '('
out += ', '.join(map(qparam, enumerate(args)))
out += ') {\n'
out += '@start\n'
out += '\t%r0 =w call $printf(l $ctoqbestr, w ' + id + ')\n'
for (i, arg) in enumerate(args):
if arg[0] != 'p': continue
ty = arg[1:]
if ty[0] == ':':
out += '\tcall $p' + ty[1:]
out += '(l %p' + str(i) + ')\n'
else:
out += '\tcall $p' + ty
out += '(' + ty + ' %p' + str(i) + ')\n'
out += '\t%r1 =w call $puts(l $emptystr)\n'
out += '\tret\n'
out += '}\n'
return out
def carg(iarg):
i, arg = iarg
print = arg[0] == 'p'
ty = arg if not print else arg[1:]
if ty[0] == ':':
if print:
return ty + ' $' + ty[1:]
else:
return ty + ' $z' + ty[1:]
if not print:
return ty + ' 0'
if ty == 'w' or ty == 'l':
return ty + ' ' + str(i+1)
if ty == 's' or ty == 'd':
flt = str(i+1) + '.' + str(i+1)
return ty + ' ' + ty + '_' + flt
def genmaincall(id, args):
out = '\tcall $cfn' + id + '('
out += ', '.join(map(carg, enumerate(args)))
out += ')\n'
return out
def gen(tvec):
for i, t in enumerate(tvec):
print(genqfn(str(i), t), end='')
print('')
for i, t in enumerate(tvec):
print(genmaincall(str(i), t), end='')
print('')
for i, t in enumerate(tvec):
print(gencfn(str(i), t), end='')
TVEC = [
['s']*8 + ['ps'],
['pw', 'ps', 'p:fi1'],
['pw', 'p:fi2', 'ps'],
['pw', 'ps', 'p:fi3'],
['p:ss'],
['d']*7 + ['p:ss', 'ps', 'pl'],
['p:lb'],
['w']*7 + ['p:lb'],
['w']*8 + ['p:lb'],
[ 'p:big' ],
['w']*8 + ['p:big', 'ps', 'pl'],
]
if __name__ == '__main__':
gen(TVEC)
Vendored Executable
+107
View File
@@ -0,0 +1,107 @@
#!/bin/sh
OCAMLC=${OCAMLC:-/usr/bin/ocamlc}
DIR=`cd $(dirname "$0"); pwd`
QBE=$DIR/../qbe
failure() {
echo "Failure at stage:" $1 >&2
exit 1
}
cleanup() {
rm -fr $TMP
}
init() {
cp $DIR/callgen.ml $TMP
pushd $TMP > /dev/null
cat > Makefile << EOM
.PHONY: test
test: caller.o callee.o
c99 -o \$@ caller.o callee.o
%.o: %.c
c99 -c -o \$@ \$<
%.o: %.ssa
$QBE -o \$*.s \$<
c99 -c -o \$@ \$*.s
EOM
if ! $OCAMLC callgen.ml -o callgen
then
popd > /dev/null
cleanup
failure "abifuzz compilation"
fi
popd > /dev/null
}
once() {
if test -z "$3"
then
$TMP/callgen $TMP $1 $2
else
$TMP/callgen -s $3 $TMP $1 $2
fi
make -C $TMP test > /dev/null || failure "building"
$TMP/test || failure "runtime"
}
usage() {
echo "usage: abitest.sh [-callssa] [-callc] [-s SEED] [-n ITERATIONS]" >&2
exit 1
}
N=1
CALLER=c
CALLEE=ssa
while test -n "$1"
do
case "$1" in
"-callssa")
CALLER=c
CALLEE=ssa
;;
"-callc")
CALLER=ssa
CALLEE=c
;;
"-s")
test -n "$2" || usage
shift
SEED="$1"
;;
"-n")
test -n "$2" || usage
shift
N="$1"
;;
*)
usage
;;
esac
shift
done
TMP=`mktemp -d abifuzz.XXXXXX`
init
if test -n "$S"
then
once $CALLER $CALLEE $SEED
else
for n in `seq $N`
do
once $CALLER $CALLEE
echo "$n" | grep "00$"
done
fi
echo "All done."
cleanup
+535
View File
@@ -0,0 +1,535 @@
(* abi fuzzer, generates two modules one calling
* the other in two possibly different languages
*)
type _ bty =
| Char: int bty
| Short: int bty
| Int: int bty
| Long: int bty
| Float: float bty
| Double: float bty
type _ sty =
| Field: 'a bty * 'b sty -> ('a * 'b) sty
| Empty: unit sty
type _ aty =
| Base: 'a bty -> 'a aty
| Struct: 'a sty -> 'a aty
type anyb = AB: _ bty -> anyb (* kinda boring... *)
type anys = AS: _ sty -> anys
type anya = AA: _ aty -> anya
type testb = TB: 'a bty * 'a -> testb
type testa = TA: 'a aty * 'a -> testa
let align a x =
let m = x mod a in
if m <> 0 then x + (a-m) else x
let btysize: type a. a bty -> int = function
| Char -> 1
| Short -> 2
| Int -> 4
| Long -> 8
| Float -> 4
| Double -> 8
let btyalign = btysize
let styempty: type a. a sty -> bool = function
| Field _ -> false
| Empty -> true
let stysize s =
let rec f: type a. int -> a sty -> int =
fun sz -> function
| Field (b, s) ->
let a = btyalign b in
f (align a sz + btysize b) s
| Empty -> sz in
f 0 s
let rec styalign: type a. a sty -> int = function
| Field (b, s) -> max (btyalign b) (styalign s)
| Empty -> 1
(* Generate types and test vectors. *)
module Gen = struct
module R = Random
let init = function
| None ->
let f = open_in "/dev/urandom" in
let seed =
Char.code (input_char f) lsl 16 +
Char.code (input_char f) lsl 8 +
Char.code (input_char f) in
close_in f;
R.init seed;
seed
| Some seed ->
R.init seed;
seed
let int sz =
let bound = 1 lsl (8 * min sz 3 - 1) in
let i = R.int bound in
if R.bool () then - i else i
let float () =
let f = R.float 1000. in
if R.bool () then -. f else f
let testv: type a. a aty -> a =
let tb: type a. a bty -> a = function (* eh, dry... *)
| Float -> float ()
| Double -> float ()
| Char -> int (btysize Char)
| Short -> int (btysize Short)
| Int -> int (btysize Int)
| Long -> int (btysize Long) in
let rec ts: type a. a sty -> a = function
| Field (b, s) -> (tb b, ts s)
| Empty -> () in
function
| Base b -> tb b
| Struct s -> ts s
let b () = (* uniform *)
match R.int 6 with
| 0 -> AB Char
| 1 -> AB Short
| 2 -> AB Int
| 3 -> AB Long
| 4 -> AB Float
| _ -> AB Double
let smax = 5 (* max elements in structs *)
let structp = 0.3 (* odds of having a struct type *)
let amax = 8 (* max function arguments *)
let s () =
let rec f n =
if n = 0 then AS Empty else
let AB bt = b () in
let AS st = f (n-1) in
AS (Field (bt, st)) in
f (1 + R.int (smax-1))
let a () =
if R.float 1.0 > structp then
let AB bt = b () in
AA (Base bt)
else
let AB bt = b () in
let AS st = s () in
AA (Struct (Field (bt, st)))
let test () =
let AA ty = a () in
let t = testv ty in
TA (ty, t)
let tests () =
let rec f n =
if n = 0 then [] else
test () :: f (n-1) in
f (R.int amax)
end
(* Code generation for C *)
module OutC = struct
open Printf
let ctypelong oc name =
let cb: type a. a bty -> unit = function
| Char -> fprintf oc "char"
| Short -> fprintf oc "short"
| Int -> fprintf oc "int"
| Long -> fprintf oc "long"
| Float -> fprintf oc "float"
| Double -> fprintf oc "double" in
let rec cs: type a. int -> a sty -> unit =
fun i -> function
| Field (b, s) ->
cb b;
fprintf oc " f%d; " i;
cs (i+1) s;
| Empty -> () in
function
| Base b ->
cb b;
| Struct s ->
fprintf oc "struct %s { " name;
cs 1 s;
fprintf oc "}";
()
let ctype: type a. out_channel -> string -> a aty -> unit =
fun oc name -> function
| Struct _ -> fprintf oc "struct %s" name
| t -> ctypelong oc "" t
let base: type a. out_channel -> a bty * a -> unit =
fun oc -> function
| Char, i -> fprintf oc "%d" i
| Short, i -> fprintf oc "%d" i
| Int, i -> fprintf oc "%d" i
| Long, i -> fprintf oc "%d" i
| Float, f -> fprintf oc "%ff" f
| Double, f -> fprintf oc "%f" f
let init oc name (TA (ty, t)) =
let inits s =
let rec f: type a. a sty * a -> unit = function
| Field (b, s), (tb, ts) ->
base oc (b, tb);
fprintf oc ", ";
f (s, ts)
| Empty, () -> () in
fprintf oc "{ ";
f s;
fprintf oc "}"; in
ctype oc name ty;
fprintf oc " %s = " name;
begin match (ty, t) with
| Base b, tb -> base oc (b, tb)
| Struct s, ts -> inits (s, ts)
end;
fprintf oc ";\n";
()
let extension = ".c"
let comment oc s =
fprintf oc "/* %s */\n" s
let prelude oc = List.iter (fprintf oc "%s\n")
[ "#include <stdio.h>"
; "#include <stdlib.h>"
; ""
; "static void fail(char *chk)"
; "{"
; "\tfprintf(stderr, \"fail: checking %s\\n\", chk);"
; "\tabort();"
; "}"
; ""
]
let typedef oc name = function
| TA (Struct ts, _) ->
ctypelong oc name (Struct ts);
fprintf oc ";\n";
| _ -> ()
let check oc name =
let chkbase: type a. string -> a bty * a -> unit =
fun name t ->
fprintf oc "\tif (%s != " name;
base oc t;
fprintf oc ")\n\t\tfail(%S);\n" name; in
function
| TA (Base b, tb) -> chkbase name (b, tb)
| TA (Struct s, ts) ->
let rec f: type a. int -> a sty * a -> unit =
fun i -> function
| Field (b, s), (tb, ts) ->
chkbase (Printf.sprintf "%s.f%d" name i) (b, tb);
f (i+1) (s, ts);
| Empty, () -> () in
f 1 (s, ts)
let argname i = "arg" ^ string_of_int (i+1)
let proto oc (TA (tret, _)) args =
ctype oc "ret" tret;
fprintf oc " f(";
let narg = List.length args in
List.iteri (fun i (TA (targ, _)) ->
ctype oc (argname i) targ;
fprintf oc " %s" (argname i);
if i <> narg-1 then
fprintf oc ", ";
) args;
fprintf oc ")";
()
let caller oc ret args =
let narg = List.length args in
prelude oc;
typedef oc "ret" ret;
List.iteri (fun i arg ->
typedef oc (argname i) arg;
) args;
proto oc ret args;
fprintf oc ";\n\nint main()\n{\n";
List.iteri (fun i arg ->
fprintf oc "\t";
init oc (argname i) arg;
) args;
fprintf oc "\t";
let TA (tret, _) = ret in
ctype oc "ret" tret;
fprintf oc " ret;\n\n";
fprintf oc "\tret = f(";
List.iteri (fun i _ ->
fprintf oc "%s" (argname i);
if i <> narg-1 then
fprintf oc ", ";
) args;
fprintf oc ");\n";
check oc "ret" ret;
fprintf oc "\n\treturn 0;\n}\n";
()
let callee oc ret args =
prelude oc;
typedef oc "ret" ret;
List.iteri (fun i arg ->
typedef oc (argname i) arg;
) args;
fprintf oc "\n";
proto oc ret args;
fprintf oc "\n{\n\t";
init oc "ret" ret;
fprintf oc "\n";
List.iteri (fun i arg ->
check oc (argname i) arg;
) args;
fprintf oc "\n\treturn ret;\n}\n";
()
end
(* Code generation for QBE *)
module OutIL = struct
open Printf
let comment oc s =
fprintf oc "# %s\n" s
let tmp, lbl =
let next = ref 0 in
(fun () -> incr next; "%t" ^ (string_of_int !next)),
(fun () -> incr next; "@l" ^ (string_of_int !next))
let bvalue: type a. a bty * a -> string = function
| Char, i -> sprintf "%d" i
| Short, i -> sprintf "%d" i
| Int, i -> sprintf "%d" i
| Long, i -> sprintf "%d" i
| Float, f -> sprintf "s_%f" f
| Double, f -> sprintf "d_%f" f
let btype: type a. a bty -> string = function
| Char -> "w"
| Short -> "w"
| Int -> "w"
| Long -> "l"
| Float -> "s"
| Double -> "d"
let extension = ".ssa"
let argname i = "arg" ^ string_of_int (i+1)
let siter oc base s g =
let rec f: type a. int -> int -> a sty * a -> unit =
fun id off -> function
| Field (b, s), (tb, ts) ->
let off = align (btyalign b) off in
let addr = tmp () in
fprintf oc "\t%s =l add %d, %s\n" addr off base;
g id addr (TB (b, tb));
f (id + 1) (off + btysize b) (s, ts);
| Empty, () -> () in
f 0 0 s
let bmemtype b =
if AB b = AB Char then "b" else
if AB b = AB Short then "h" else
btype b
let init oc = function
| TA (Base b, tb) -> bvalue (b, tb)
| TA (Struct s, ts) ->
let base = tmp () in
fprintf oc "\t%s =l alloc%d %d\n"
base (styalign s) (stysize s);
siter oc base (s, ts)
begin fun _ addr (TB (b, tb)) ->
fprintf oc "\tstore%s %s, %s\n"
(bmemtype b) (bvalue (b, tb)) addr;
end;
base
let check oc id name =
let bcheck = fun id name (b, tb) ->
let tcmp = tmp () in
let nxtl = lbl () in
fprintf oc "\t%s =w ceq%s %s, %s\n"
tcmp (btype b) name (bvalue (b, tb));
fprintf oc "\tstorew %d, %%failcode\n" id;
fprintf oc "\tjnz %s, %s, @fail\n" tcmp nxtl;
fprintf oc "%s\n" nxtl; in
function
| TA (Base Char, i) ->
let tval = tmp () in
fprintf oc "\t%s =w extsb %s\n" tval name;
bcheck id tval (Int, i)
| TA (Base Short, i) ->
let tval = tmp () in
fprintf oc "\t%s =w extsh %s\n" tval name;
bcheck id tval (Int, i)
| TA (Base b, tb) ->
bcheck id name (b, tb)
| TA (Struct s, ts) ->
siter oc name (s, ts)
begin fun id' addr (TB (b, tb)) ->
let tval = tmp () in
let lsuffix =
if AB b = AB Char then "sb" else
if AB b = AB Short then "sh" else
"" in
fprintf oc "\t%s =%s load%s %s\n"
tval (btype b) lsuffix addr;
bcheck (100*id + id'+1) tval (b, tb);
end;
()
let ttype name = function
| TA (Base b, _) -> btype b
| TA (Struct _, _) -> ":" ^ name
let typedef oc name =
let rec f: type a. a sty -> unit = function
| Field (b, s) ->
fprintf oc "%s" (bmemtype b);
if not (styempty s) then
fprintf oc ", ";
f s;
| Empty -> () in
function
| TA (Struct ts, _) ->
fprintf oc "type :%s = { " name;
f ts;
fprintf oc " }\n";
| _ -> ()
let postlude oc = List.iter (fprintf oc "%s\n")
[ "@fail"
; "# failure code"
; "\t%fcode =w loadw %failcode"
; "\t%f0 =w call $printf(l $failstr, w %fcode)"
; "\t%f1 =w call $abort()"
; "\tret 0"
; "}"
; ""
; "data $failstr = { b \"fail on check %d\\n\", b 0 }"
]
let caller oc ret args =
let narg = List.length args in
List.iteri (fun i arg ->
typedef oc (argname i) arg;
) args;
typedef oc "ret" ret;
fprintf oc "\nexport function w $main() {\n";
fprintf oc "@start\n";
fprintf oc "\t%%failcode =l alloc4 4\n";
let targs = List.mapi (fun i arg ->
comment oc ("define argument " ^ (string_of_int (i+1)));
(ttype (argname i) arg, init oc arg)
) args in
comment oc "call test function";
fprintf oc "\t%%ret =%s call $f(" (ttype "ret" ret);
List.iteri (fun i (ty, tmp) ->
fprintf oc "%s %s" ty tmp;
if i <> narg-1 then
fprintf oc ", ";
) targs;
fprintf oc ")\n";
comment oc "check the return value";
check oc 0 "%ret" ret;
fprintf oc "\tret 0\n";
postlude oc;
()
let callee oc ret args =
let narg = List.length args in
List.iteri (fun i arg ->
typedef oc (argname i) arg;
) args;
typedef oc "ret" ret;
fprintf oc "\nexport function %s $f(" (ttype "ret" ret);
List.iteri (fun i arg ->
let a = argname i in
fprintf oc "%s %%%s" (ttype a arg) a;
if i <> narg-1 then
fprintf oc ", ";
) args;
fprintf oc ") {\n";
fprintf oc "@start\n";
fprintf oc "\t%%failcode =l alloc4 4\n";
List.iteri (fun i arg ->
comment oc ("checking argument " ^ (string_of_int (i+1)));
check oc (i+1) ("%" ^ argname i) arg;
) args;
comment oc "define the return value";
let rettmp = init oc ret in
fprintf oc "\tret %s\n" rettmp;
postlude oc;
()
end
module type OUT = sig
val extension: string
val comment: out_channel -> string -> unit
val caller: out_channel -> testa -> testa list -> unit
val callee: out_channel -> testa -> testa list -> unit
end
let _ =
let usage code =
Printf.eprintf "usage: abi.ml [-s SEED] DIR {c,ssa} {c,ssa}\n";
exit code in
let outmod = function
| "c" -> (module OutC : OUT)
| "ssa" -> (module OutIL: OUT)
| _ -> usage 1 in
let seed, dir, mcaller, mcallee =
match Sys.argv with
| [| _; "-s"; seed; dir; caller; callee |] ->
let seed =
try Some (int_of_string seed) with
Failure _ -> usage 1 in
seed, dir, outmod caller, outmod callee
| [| _; dir; caller; callee |] ->
None, dir, outmod caller, outmod callee
| [| _; "-h" |] ->
usage 0
| _ ->
usage 1 in
let seed = Gen.init seed in
let tret = Gen.test () in
let targs = Gen.tests () in
let module OCaller = (val mcaller : OUT) in
let module OCallee = (val mcallee : OUT) in
let ocaller = open_out (dir ^ "/caller" ^ OCaller.extension) in
let ocallee = open_out (dir ^ "/callee" ^ OCallee.extension) in
OCaller.comment ocaller (Printf.sprintf "seed %d" seed);
OCallee.comment ocallee (Printf.sprintf "seed %d" seed);
OCaller.caller ocaller tret targs;
OCallee.callee ocallee tret targs;
()
Vendored Executable
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
DIR=`cd $(dirname "$0"); pwd`
QBE=$DIR/../qbe
BUGF=/tmp/bug.id
FIND=$1
FIND=${FIND:-afl-find}
if ! test -f $BUGF
then
echo 1 > $BUGF
fi
while true
do
ID=`cat $BUGF`
if test `ls $FIND/crashes/id* | wc -l` -lt $ID
then
rm -f bug.ssa
echo "All done!"
exit 0
fi
BUG=`ls $FIND/crashes/id* | sed -ne "${ID}{p;q}"`
echo "*** Crash $ID"
cp $BUG bug.ssa
$QBE bug.ssa > /dev/null
RET=$?
if test \( $RET -ne 0 \) -a \( $RET -ne 1 \)
then
exit 1
fi
expr $ID + 1 > $BUGF
done
+94
View File
@@ -0,0 +1,94 @@
/*% c99 -O3 -Wall -o # %
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
char *tok[] = {
"add", "sub", "neg", "div", "rem", "udiv", "urem", "mul",
"and", "or", "xor", "sar", "shr", "shl", "stored",
"stores", "storel", "storew", "storeh", "storeb",
"load", "loadsw", "loaduw", "loadsh", "loaduh",
"loadsb", "loadub", "extsw", "extuw", "extsh",
"extuh", "extsb", "extub", "exts", "truncd",
"stosi", "dtosi", "stoui", "dtoui", "uwtof",
"ultof", "swtof", "sltof", "cast", "copy",
"alloc4", "alloc8", "alloc16", "culew", "cultw",
"cslew", "csltw", "csgtw", "csgew", "cugtw",
"cugew", "ceqw", "cnew", "culel", "cultl", "cslel",
"csltl", "csgtl", "csgel", "cugtl", "cugel",
"ceql", "cnel", "cles", "clts", "cgts", "cges",
"cnes", "ceqs", "cos", "cuos", "cled", "cltd",
"cgtd", "cged", "cned", "ceqd", "cod", "cuod",
"vaarg", "vastart", "...", "env", "dbgloc",
"call", "phi", "jmp", "jnz", "ret", "hlt", "export",
"function", "type", "data", "section", "align", "dbgfile",
"blit", "l", "w", "sh", "uh", "h", "sb", "ub", "b",
"d", "s", "z", "loadw", "loadl", "loads", "loadd",
"alloc1", "alloc2",
};
enum {
Ntok = sizeof tok / sizeof tok[0]
};
uint32_t th[Ntok];
uint32_t
hash(char *s)
{
uint32_t h;
h = 0;
for (; *s; ++s)
h = *s + 17*h;
return h;
}
int
main()
{
char *bmap;
uint32_t h, M, K;
int i, j;
bmap = malloc(1u << 31);
for (i=0; i<Ntok; ++i) {
h = hash(tok[i]);
for (j=0; j<i; ++j)
if (th[j] == h) {
printf("error: hash()\n");
printf("\t%s\n", tok[i]);
printf("\t%s\n", tok[j]);
exit(1);
}
th[i] = h;
}
for (i=9; 1<<i < Ntok; ++i);
M = 32 - i;
for (;; --M) {
printf("trying M=%d...\n", M);
K = 1;
do {
memset(bmap, 0, 1 << (32 - M));
for (i=0; i<Ntok; ++i) {
h = (th[i]*K) >> M;
if (bmap[h])
break;
bmap[h] = 1;
}
if (i==Ntok) {
printf("found K=%d for M=%d\n", K, M);
exit(0);
}
K += 2;
} while (K != 1);
}
}
+262
View File
@@ -0,0 +1,262 @@
/*% rm -f rega.o main.o && cc -g -std=c99 -Wall -DTEST_PMOV -o pmov % *.o
*
* This is a test framwork for the dopm() function
* in rega.c, use it when you want to modify it or
* all the parallel move functions.
*
* You might need to decrease NIReg to see it
* terminate, I used NIReg == 7 at most.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void assert_test(char *, int), fail(void), iexec(int *);
#include "../../rega.c"
static void bsinit_(BSet *, uint);
static RMap mbeg;
static Ins ins[NIReg], *ip;
static Blk dummyb = { .ins = ins };
int
main()
{
Ins *i1;
unsigned long long tm, rm, cnt;
RMap mend;
int reg[NIReg], val[NIReg+1];
int t, i, r, nr;
tmp = (Tmp[Tmp0+NIReg]){{{0}}};
for (t=0; t<Tmp0+NIReg; t++)
if (t >= Tmp0) {
tmp[t].cls = Kw;
tmp[t].hint.r = -1;
tmp[t].hint.m = 0;
tmp[t].slot = -1;
sprintf(tmp[t].name, "tmp%d", t-Tmp0+1);
}
bsinit_(mbeg.b, Tmp0+NIReg);
bsinit_(mend.b, Tmp0+NIReg);
cnt = 0;
for (tm = 0; tm < 1ull << (2*NIReg); tm++) {
mbeg.n = 0;
bszero(mbeg.b);
ip = ins;
/* find what temporaries are in copy and
* wether or not they are in register
*/
for (t=0; t<NIReg; t++)
switch ((tm >> (2*t)) & 3) {
case 0:
/* not in copy, not in reg */
break;
case 1:
/* not in copy, in reg */
radd(&mbeg, Tmp0+t, t+1);
break;
case 2:
/* in copy, not in reg */
*ip++ = (Ins){OCopy, Kw, TMP(Tmp0+t), {R, R}};
break;
case 3:
/* in copy, in reg */
*ip++ = (Ins){OCopy, Kw, TMP(Tmp0+t), {R, R}};
radd(&mbeg, Tmp0+t, t+1);
break;
}
if (ip == ins)
/* cancel if the parallel move
* is empty
*/
goto Nxt;
/* find registers for temporaries
* in mbeg
*/
nr = ip - ins;
rm = (1ull << (nr+1)) - 1;
for (i=0; i<nr; i++)
reg[i] = i+1;
for (;;) {
/* set registers on copies
*/
for (i=0, i1=ins; i1<ip; i1++, i++)
i1->arg[0] = TMP(reg[i]);
/* compile the parallel move
*/
rcopy(&mend, &mbeg);
dopm(&dummyb, ip-1, &mend);
cnt++;
/* check that mend contain mappings for
* source registers and does not map any
* assigned temporary, then check that
* all temporaries in mend are mapped in
* mbeg and not used in the copy
*/
for (i1=ins; i1<ip; i1++) {
r = i1->arg[0].val;
assert(rfree(&mend, r) == r);
t = i1->to.val;
assert(!bshas(mend.b, t));
}
for (i=0; i<mend.n; i++) {
t = mend.t[i];
assert(bshas(mbeg.b, t));
t -= Tmp0;
assert(((tm >> (2*t)) & 3) == 1);
}
/* execute the code generated and check
* that all assigned temporaries got their
* value, and that all live variables's
* content got preserved
*/
for (i=1; i<=NIReg; i++)
val[i] = i;
iexec(val);
for (i1=ins; i1<ip; i1++) {
t = i1->to.val;
r = rfind(&mbeg, t);
if (r != -1)
assert(val[r] == i1->arg[0].val);
}
for (i=0; i<mend.n; i++) {
t = mend.t[i];
r = mend.r[i];
assert(val[t-Tmp0+1] == r);
}
/* find the next register assignment */
i = nr - 1;
for (;;) {
r = reg[i];
rm &= ~(1ull<<r);
do
r++;
while (r <= NIReg && (rm & (1ull<<r)));
if (r == NIReg+1) {
if (i == 0)
goto Nxt;
i--;
} else {
rm |= (1ull<<r);
reg[i++] = r;
break;
}
}
for (; i<nr; i++)
for (r=1; r<=NIReg; r++)
if (!(rm & (1ull<<r))) {
rm |= (1ull<<r);
reg[i] = r;
break;
}
}
Nxt: freeall();
}
printf("%llu tests successful!\n", cnt);
exit(0);
}
/* execute what pmgen() wrote (swap, copy) */
#define validr(r) \
rtype(r) == RTmp && \
r.val > 0 && \
r.val <= NIReg
static void
iexec(int val[])
{
Ins *i;
int t;
for (i=insb; i<curi; i++)
switch (i->op) {
default:
assert(!"iexec: missing case\n");
exit(1);
case OSwap:
assert(validr(i->arg[0]));
assert(validr(i->arg[1]));
t = val[i->arg[0].val];
val[i->arg[0].val] = val[i->arg[1].val];
val[i->arg[1].val] = t;
break;
case OCopy:
assert(validr(i->to));
assert(validr(i->arg[0]));
val[i->to.val] = val[i->arg[0].val];
break;
}
}
/* failure diagnostics */
static int re;
static void
replay()
{
RMap mend;
re = 1;
bsinit_(mend.b, Tmp0+NIReg);
rcopy(&mend, &mbeg);
dopm(&dummyb, ip-1, &mend);
}
static void
fail()
{
Ins *i1;
int i;
printf("\nIn registers: ");
for (i=0; i<mbeg.n; i++)
printf("%s(r%d) ",
tmp[mbeg.t[i]].name,
mbeg.r[i]);
printf("\n");
printf("Parallel move:\n");
for (i1=ins; i1<ip; i1++)
printf("\t %s <- r%d\n",
tmp[i1->to.val].name,
i1->arg[0].val);
replay();
abort();
}
static void
assert_test(char *s, int x)
{
if (x)
return;
if (re)
abort();
printf("!assertion failure: %s\n", s);
fail();
}
static void
bsinit_(BSet *bs, uint n)
{
n = (n + NBit-1) / NBit;
bs->nt = n;
bs->t = emalloc(n * sizeof bs->t[0]);
}
/* symbols required by the linker */
char debug['Z'+1];
Vendored Executable
+220
View File
@@ -0,0 +1,220 @@
#!/bin/sh
dir=`dirname "$0"`
bin=$dir/../qbe
binref=$dir/../qbe.ref
tmp=/tmp/qbe.zzzz
drv=$tmp.c
asm=$tmp.s
asmref=$tmp.ref.s
exe=$tmp.exe
out=$tmp.out
init() {
case "$TARGET" in
arm64)
for p in aarch64-linux-musl aarch64-linux-gnu
do
cc="$p-gcc -no-pie -static"
qemu="qemu-aarch64"
if
$cc -v >/dev/null 2>&1 &&
$qemu -version >/dev/null 2>&1
then
if sysroot=$($cc -print-sysroot) && test -n "$sysroot"
then
qemu="$qemu -L $sysroot"
fi
break
fi
cc=
done
if test -z "$cc"
then
echo "Cannot find arm64 compiler or qemu."
exit 1
fi
bin="$bin -t arm64"
;;
rv64)
for p in riscv64-linux-musl riscv64-linux-gnu
do
cc="$p-gcc -no-pie -static"
qemu="qemu-riscv64"
if
$cc -v >/dev/null 2>&1 &&
$qemu -version >/dev/null 2>&1
then
if sysroot=$($cc -print-sysroot) && test -n "$sysroot"
then
qemu="$qemu -L $sysroot"
fi
break
fi
cc=
done
if test -z "$cc"
then
echo "Cannot find riscv64 compiler or qemu."
exit 1
fi
bin="$bin -t rv64"
;;
"")
case `uname` in
*Darwin*)
cc="cc"
;;
*OpenBSD*)
cc="cc -nopie -lpthread"
;;
*FreeBSD*)
cc="cc -lpthread"
;;
*)
cc="${CC:-cc}"
ccpost="-lpthread"
;;
esac
TARGET=`$bin -t?`
;;
*)
echo "Unknown target '$TARGET'."
exit 1
;;
esac
}
cleanup() {
rm -f $drv $asm $exe $out
}
extract() {
WHAT="$1"
FILE="$2"
awk "
/^# >>> $WHAT/ {
p = 1
next
}
/^# <<</ {
p = 0
}
p
" $FILE \
| sed -e 's/# //' \
| sed -e 's/#$//'
}
once() {
t="$1"
if ! test -f $t
then
echo "invalid test file $t" >&2
exit 1
fi
if
sed -e 1q $t |
grep "skip.* $TARGET\( .*\)\?$" \
>/dev/null
then
return 0
fi
printf "%-45s" "$(basename $t)..."
if ! $bin -o $asm $t
then
echo "[qbe fail]"
return 1
fi
if test -x $binref
then
$binref -o $asmref $t 2>/dev/null
fi
extract driver $t > $drv
extract output $t > $out
if test -s $drv
then
src="$drv $asm"
else
src="$asm"
fi
if ! $cc -g -o $exe $src $ccpost
then
echo "[cc fail]"
return 1
fi
if test -s $out
then
$qemu $exe a b c | diff -u - $out
ret=$?
reason="output"
else
$qemu $exe a b c
ret=$?
reason="returned $ret"
fi
if test $ret -ne 0
then
echo "[$reason fail]"
return 1
fi
echo "[ok]"
if test -f $asmref && ! cmp -s $asm $asmref
then
loc0=`wc -l $asm | cut -d' ' -f1`
loc1=`wc -l $asmref | cut -d' ' -f1`
printf " asm diff: %+d\n" $(($loc0 - $loc1))
return 0
fi
}
#trap cleanup TERM QUIT
init
if test -z "$1"
then
echo "usage: tools/test.sh {all, SSAFILE}" 2>&1
exit 1
fi
case "$1" in
"all")
fail=0
count=0
for t in $dir/../test/[!_]*.ssa
do
once $t
fail=`expr $fail + $?`
count=`expr $count + 1`
done
if test $fail -ge 1
then
echo
echo "$fail of $count tests failed!"
else
echo
echo "All is fine!"
fi
exit $fail
;;
*)
once $1
exit $?
;;
esac
+161
View File
@@ -0,0 +1,161 @@
# generate variadic calls to test the
# abi implementation
from random import seed, randint, uniform
from struct import unpack
I, D = 'd', 'g'
formats = [
# list of formats to test
[I],
[D],
[I,D],
[D,D],
[I,I,I,I],
[D,D,D,D],
[I,D,I,D],
[D,D,I,I],
[I,I,D,D],
[],
]
generate = [
# numbers of fixed integer and
# floating point arguments to
# test
(0, 0), (1, 0), (0, 1), (4, 0),
(0, 6), (5, 7), (10, 10), (9, 0),
]
def mkargs(nargs, type, name):
args = map(
lambda n: ''.join([type, name, str(n), ', ']),
range(nargs)
)
return ''.join(args)
def mkfstr(fmt):
fstr = map(
lambda x: {I: '%d ', D: '%g '}[x],
fmt
)
return '"' + ''.join(fstr) + '\\n"'
def randargs(fmt):
ra = {
I: lambda: '{}'.format(randint(-10, 10)),
D: lambda: '{0:.4g}'.format(uniform(-10, 10))
}
return list(map(lambda x: ra[x](), fmt))
def genssa(qbeprint, qbecall):
funcs = [('qbeprint', qbeprint), ('qbecall', qbecall)]
for fnum, (nia, nfa) in enumerate(generate):
params = "{}{}l %fmt, ...".format(
mkargs(nia, 'w ', '%argw'),
mkargs(nfa, 'd ', '%argd')
)
for name, code in funcs:
print('export function ${}{}({}) {}'
.format(name, fnum, params, code)
)
def gendriver():
print('# >>> driver')
print('# #include <stdarg.h>')
print('# #include <stdio.h>')
for fnum, (nia, nfa) in enumerate(generate):
params = "{}{}char *, ...".format(
mkargs(nia, 'int ', 'argw'),
mkargs(nfa, 'double ', 'argd')
)
for name in ['qbeprint', 'qbecall']:
print('# extern void {}{}({});'
.format(name, fnum, params)
)
output = ''
print('# int print(char *fmt, va_list *ap) {')
print('# return vprintf(fmt, *ap);');
print('# }')
print('# int main() {')
for fnum, (nia, nfa) in enumerate(generate):
info = '# ({} int, {} double)'.format(nia, nfa)
print('# puts("{}");'.format(info))
output += '# {}\n'.format(info)
for fmt in formats:
ra = randargs(fmt)
vaargs = ', '.join(ra)
expect = ' '.join(ra)
if fmt:
vaargs = ', ' + vaargs
expect = expect + ' '
args = ''.join(
['0, '] * (nia+nfa) +
[mkfstr(fmt), vaargs]
)
for name in ['qbeprint', 'qbecall']:
print('# {}{}({});'
.format(name, fnum, args)
)
output += '# {}\n'.format(expect)
print('# }')
print('# <<<')
print('\n# >>> output\n' + output + '# <<<')
qbeprint="""{{
@start
%fmtdbl =l alloc4 4
%fmtint =l alloc4 4
%emptys =l alloc4 4
storew {}, %fmtint
storew {}, %fmtdbl
storew 0, %emptys
%vp =l alloc8 32
%fmt1 =l add 1, %fmt
vastart %vp
@loop
%p =l phi @start %fmt1, @casef %p1, @cased %p1
%c =w loadsb %p
%p1 =l add 3, %p
jnz %c, @loop1, @end
@loop1
%isg =w ceqw %c, 103
jnz %isg, @casef, @cased
@casef
%dbl =d vaarg %vp
%r =w call $printf(l %fmtdbl, ..., d %dbl)
jmp @loop
@cased
%int =w vaarg %vp
%r =w call $printf(l %fmtint, ..., w %int)
jmp @loop
@end
%r =w call $puts(l %emptys)
ret
}}
""".format(
unpack("i", b'%d \x00')[0],
unpack("i", b'%g \x00')[0]
)
qbecall="""{
@start
%vp =l alloc8 32
vastart %vp
%r =w call $print(l %fmt, l %vp)
ret
}
"""
if __name__ == "__main__":
seed(42)
genssa(qbeprint, qbecall)
gendriver()