~mcf/qbe

d2f7b6913dcc2aad683dbdd05040a7623cbe736d — Michael Forney 1 year, 10 months ago 45bccda + cd778b4 master
Merge tag 'v1.0'
M .gitignore => .gitignore +2 -1
@@ 1,4 1,5 @@
obj
*.o
qbe
config.h
.comfile
*.out

M Makefile => Makefile +22 -30
@@ 1,7 1,8 @@
BIN = qbe
.POSIX:
.SUFFIXES: .o .c

V = @
OBJDIR = obj
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin

SRC      = main.c util.c parse.c cfg.c mem.c ssa.c alias.c load.c copy.c \
           fold.c live.c spill.c rega.c gas.c


@@ 10,33 11,24 @@ ARM64SRC = arm64/targ.c arm64/abi.c arm64/isel.c arm64/emit.c
RV64SRC  = rv64/targ.c rv64/abi.c rv64/isel.c rv64/emit.c
SRCALL   = $(SRC) $(AMD64SRC) $(ARM64SRC) $(RV64SRC)

AMD64OBJ = $(AMD64SRC:%.c=$(OBJDIR)/%.o)
ARM64OBJ = $(ARM64SRC:%.c=$(OBJDIR)/%.o)
RV64OBJ  = $(RV64SRC:%.c=$(OBJDIR)/%.o)
OBJ      = $(SRC:%.c=$(OBJDIR)/%.o) $(AMD64OBJ) $(ARM64OBJ) $(RV64OBJ)
AMD64OBJ = $(AMD64SRC:.c=.o)
ARM64OBJ = $(ARM64SRC:.c=.o)
RV64OBJ  = $(RV64SRC:.c=.o)
OBJ      = $(SRC:.c=.o) $(AMD64OBJ) $(ARM64OBJ) $(RV64OBJ)

CFLAGS += -Wall -Wextra -std=c99 -g -pedantic
CFLAGS = $(CPPFLAGS) -Wall -Wextra -std=c99 -g -Wpedantic

$(OBJDIR)/$(BIN): $(OBJ) $(OBJDIR)/timestamp
	@test -z "$(V)" || echo "ld $@"
	$(V)$(CC) $(LDFLAGS) $(OBJ) -o $@
qbe: $(OBJ)
	$(CC) $(LDFLAGS) $(OBJ) -o $@

$(OBJDIR)/%.o: %.c $(OBJDIR)/timestamp
	@test -z "$(V)" || echo "cc $<"
	$(V)$(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR)/timestamp:
	@mkdir -p $(OBJDIR)
	@mkdir -p $(OBJDIR)/amd64
	@mkdir -p $(OBJDIR)/arm64
	@mkdir -p $(OBJDIR)/rv64
	@touch $@
.c.o:
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJ): all.h ops.h
$(AMD64OBJ): amd64/all.h
$(ARM64OBJ): arm64/all.h
$(RV64OBJ): rv64/all.h
$(OBJDIR)/main.o: config.h
main.o: config.h

config.h:
	@case `uname` in                               \


@@ 60,26 52,26 @@ config.h:
		;;                                     \
	esac > $@

install: $(OBJDIR)/$(BIN)
	mkdir -p "$(DESTDIR)/$(PREFIX)/bin/"
	cp $< "$(DESTDIR)/$(PREFIX)/bin/"
install: qbe
	mkdir -p "$(DESTDIR)$(BINDIR)"
	install -m755 qbe "$(DESTDIR)$(BINDIR)/qbe"

uninstall:
	rm -f "$(DESTDIR)/$(PREFIX)/bin/$(BIN)"
	rm -f "$(DESTDIR)$(BINDIR)/qbe"

clean:
	rm -fr $(OBJDIR)
	rm -f *.o */*.o qbe

clean-gen: clean
	rm -f config.h

check: $(OBJDIR)/$(BIN)
check: qbe
	tools/test.sh all

check-arm64: $(OBJDIR)/$(BIN)
check-arm64: qbe
	TARGET=arm64 tools/test.sh all

check-rv64: $(OBJDIR)/$(BIN)
check-rv64: qbe
	TARGET=rv64 tools/test.sh all

src:

M README => README +5 -4
@@ 10,8 10,9 @@ rv64/   Architecture-specific code.

The LICENSE file applies to all files distributed.

- Compilation
- Compilation and Installation

Invoke GNU make in this directory to create the executable
file obj/qbe.  On some systems (BSD) you might have to use
'gmake' instead of 'make'.
Invoke make in this directory to create the executable
file qbe.  Install using 'make install', the standard
DESTDIR and PREFIX environment variables are supported.
Alternatively, you may simply copy the qbe binary manually.

M amd64/isel.c => amd64/isel.c +0 -1
@@ 538,7 538,6 @@ anumber(ANum *ai, Blk *b, Con *con)
	 *   0 * 2   -> 3    s * i (when constant is 1,2,4,8)
	 */
	static char add[10][10] = {
		[2] [2] = 2,              /* folding */
		[2] [4] = 4, [4] [2] = 4,
		[2] [6] = 6, [6] [2] = 6,
		[2] [7] = 7, [7] [2] = 7,

M arm64/emit.c => arm64/emit.c +4 -4
@@ 285,7 285,7 @@ loadcon(Con *c, int r, int k, FILE *f)
static void emitins(Ins *, E *);

static void
fixarg(Ref *pr, E *e)
fixarg(Ref *pr, int sz, E *e)
{
	Ins *i;
	Ref r;


@@ 294,7 294,7 @@ fixarg(Ref *pr, E *e)
	r = *pr;
	if (rtype(r) == RSlot) {
		s = slot(r.val, e);
		if (s > 32760) {
		if (s > sz * 4095u) {
			i = &(Ins){Oaddr, Kl, TMP(IP0), {r}};
			emitins(i, e);
			*pr = TMP(IP0);


@@ 313,9 313,9 @@ emitins(Ins *i, E *e)
	switch (i->op) {
	default:
		if (isload(i->op))
			fixarg(&i->arg[0], e);
			fixarg(&i->arg[0], loadsz(i), e);
		if (isstore(i->op))
			fixarg(&i->arg[1], e);
			fixarg(&i->arg[1], storesz(i), e);
	Table:
		/* most instructions are just pulled out of
		 * the table omap[], some special cases are

M doc/il.txt => doc/il.txt +34 -17
@@ 11,6 11,7 @@
      * <@ Input Files >
      * <@ BNF Notation >
      * <@ Sigils >
      * <@ Spacing >
  2. <@ Types >
      * <@ Simple Types >
      * <@ Subtyping >


@@ 82,8 83,9 @@ below using BNF syntax.  The different BNF constructs used
are listed below.

  * Keywords are enclosed between quotes;
  * `... | ...` expresses disjunctions;
  * `[ ... ]` marks some syntax as optional;
  * `... | ...` expresses alternatives;
  * `( ... )` groups syntax;
  * `[ ... ]` marks the nested syntax as optional;
  * `( ... ),` designates a comma-separated list of the
    enclosed syntax;
  * `...*` and `...+` are used for arbitrary and


@@ 105,6 107,19 @@ scope and nature of identifiers.
In this BNF syntax, we use `?IDENT` to designate an identifier
starting with the sigil `?`.

~ Spacing
~~~~~~~~~

    `bnf
    NL := '\n'+

Individual tokens in IL files must be separated by one or
more spacing characters.  Both spaces and tabs are recognized
as spacing characters.  In data and type definitions, newlines
may also be used as spaces to prevent overly long lines.  When
exactly one of two consecutive tokens is a symbol (for example
`,` or `=` or `{`), spacing may be omitted.

- 2. Types
----------



@@ 202,9 217,9 @@ constants by the linker.

    `bnf
    LINKAGE :=
        'export'
      | 'section' SECNAME
      | 'section' SECNAME SECFLAGS
        'export' [NL]
      | 'section' SECNAME [NL]
      | 'section' SECNAME SECFLAGS [NL]

    SECNAME  := '"' .... '"'
    SECFLAGS := '"' .... '"'


@@ 296,7 311,8 @@ their size between curly braces.

    `bnf
    DATADEF :=
        LINKAGE* 'data' $IDENT '=' ['align' NUMBER]
        LINKAGE*
	'data' $IDENT '=' ['align' NUMBER]
        '{'
            ( EXTTY DATAITEM+
            | 'z'   NUMBER ),


@@ 354,9 370,10 @@ Here are various examples of data definitions.

    `bnf
    FUNCDEF :=
        LINKAGE* 'function' [ABITY] $IDENT '(' (PARAM), ')'
        '{'
           BLOCK+
        LINKAGE*
	'function' [ABITY] $IDENT '(' (PARAM), ')' [NL]
        '{' NL
            BLOCK+
        '}'

    PARAM :=


@@ 440,25 457,25 @@ connected using jump instructions.

    `bnf
    BLOCK :=
        @IDENT    # Block label
        PHI*      # Phi instructions
        INST*     # Regular instructions
        JUMP      # Jump or return
        @IDENT NL     # Block label
        ( PHI NL )*   # Phi instructions
        ( INST NL )*  # Regular instructions
        JUMP NL       # Jump or return

All blocks have a name that is specified by a label at
their beginning.  Then follows a sequence of instructions
that have "fall-through" flow.  Finally one jump terminates
the block.  The jump can either transfer control to another
block of the same function or return; they are described
block of the same function or return; jumps are described
further below.

The first block in a function must not be the target of
any jump in the program.  If this is really needed,
the frontend could insert an empty prelude block
any jump in the program.  If a jump to the function start
is needed, the frontend must insert an empty prelude block
at the beginning of the function.

When one block jumps to the next block in the IL file,
it is not necessary to give the jump instruction, it
it is not necessary to write the jump instruction, it
will be automatically added by the parser.  For example
the start block in the example below jumps directly
to the loop block.

M fold.c => fold.c +15 -8
@@ 19,14 19,14 @@ static Use **usewrk;
static uint nuse;

static int
czero(Con *c, int w)
iscon(Con *c, int w, uint64_t k)
{
	if (c->type != CBits)
		return 0;
	if (w)
		return c->bits.i == 0;
		return (uint64_t)c->bits.i == k;
	else
		return (uint32_t)c->bits.i == 0;
		return (uint32_t)c->bits.i == (uint32_t)k;
}

static int


@@ 132,7 132,7 @@ visitjmp(Blk *b, int n, Fn *fn)
			edge[n][0].work = &edge[n][1];
			flowrk = &edge[n][0];
		}
		else if (czero(&fn->con[l], 0)) {
		else if (iscon(&fn->con[l], 0, 0)) {
			assert(edge[n][0].dead);
			edge[n][1].work = flowrk;
			flowrk = &edge[n][1];


@@ 297,7 297,7 @@ fold(Fn *fn)
					renref(&i->arg[n]);
		renref(&b->jmp.arg);
		if (b->jmp.type == Jjnz && rtype(b->jmp.arg) == RCon) {
				if (czero(&fn->con[b->jmp.arg.val], 0)) {
				if (iscon(&fn->con[b->jmp.arg.val], 0, 0)) {
					edgedel(b, &b->s1);
					b->s1 = b->s2;
					b->s2 = 0;


@@ 365,6 365,16 @@ foldint(Con *res, int op, int w, Con *cl, Con *cr)
	}
	else if (cl->type == CAddr || cr->type == CAddr)
		return 1;
	if (op == Odiv || op == Orem || op == Oudiv || op == Ourem) {
		if (iscon(cr, w, 0))
			return 1;
		if (op == Odiv || op == Orem) {
			x = w ? INT64_MIN : INT32_MIN;
			if (iscon(cr, w, -1))
			if (iscon(cl, w, x))
				return 1;
		}
	}
	switch (op) {
	case Oadd:  x = l.u + r.u; break;
	case Osub:  x = l.u - r.u; break;


@@ 508,9 518,6 @@ opfold(int op, int cls, Con *cl, Con *cr, Fn *fn)
	Ref r;
	Con c;

	if ((op == Odiv || op == Oudiv
	|| op == Orem || op == Ourem) && czero(cr, KWIDE(cls)))
		return Bot;
	if (cls == Kw || cls == Kl) {
		if (foldint(&c, op, cls == Kl, cl, cr))
			return Bot;

M gas.c => gas.c +2 -0
@@ 141,6 141,8 @@ gasemitfin(FILE *f)
	int sz, i;
	double d;

	if (gasasm == Gaself)
		fprintf(f, ".section .note.GNU-stack,\"\",@progbits\n\n");
	if (!stash)
		return;
	fprintf(f, "/* floating point constants */\n.data\n");

M live.c => live.c +1 -2
@@ 74,8 74,7 @@ Again:
			nlv[KBASE(f->tmp[t].cls)]++;
		if (rtype(b->jmp.arg) == RCall) {
			assert((int)bscount(b->in) == T.nrglob &&
				nlv[0] == T.nrglob &&
				nlv[1] == 0);
				b->in->t[0] == T.rglob);
			b->in->t[0] |= T.retregs(b->jmp.arg, nlv);
		} else
			bset(b->jmp.arg, b, nlv, f->tmp);

M main.c => main.c +2 -4
@@ 185,13 185,11 @@ main(int ac, char *av[])
			}
		}
		parse(inf, f, data, func);
		fclose(inf);
	} while (++optind < ac);

	if (!dbg) {
	if (!dbg)
		gasemitfin(outf);
		if (asmmode == Gaself)
			fprintf(outf, ".section .note.GNU-stack,\"\",@progbits\n");
	}

	exit(0);
}

M minic/mcc => minic/mcc +1 -1
@@ 1,7 1,7 @@
#!/bin/sh

DIR=`cd $(dirname $0); pwd`
QBE=$DIR/../obj/qbe
QBE=$DIR/../qbe

usage()
{

M parse.c => parse.c +4 -0
@@ 1092,6 1092,7 @@ void
parse(FILE *f, char *path, void data(Dat *), void func(Fn *))
{
	Lnk lnk;
	uint n;

	lexinit();
	inf = f;


@@ 1115,6 1116,9 @@ parse(FILE *f, char *path, void data(Dat *), void func(Fn *))
			parsetyp();
			break;
		case Teof:
			for (n=0; n<ntyp; n++)
				if (typ[n].nunion)
					vfree(typ[n].fields);
			vfree(typ);
			return;
		}

M rv64/all.h => rv64/all.h +7 -4
@@ 11,22 11,25 @@ enum Rv64Reg {
	S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11,

	/* globally live */
	FP, SP, GP, TP, RA, T6,
	FP, SP, GP, TP, RA,

	/* FP caller-save */
	FT0, FT1, FT2, FT3, FT4, FT5, FT6, FT7, FT8, FT9, FT10, FT11,
	FT0, FT1, FT2, FT3, FT4, FT5, FT6, FT7, FT8, FT9, FT10,
	FA0, FA1, FA2, FA3, FA4, FA5, FA6, FA7,

	/* FP callee-save */
	FS0, FS1, FS2, FS3, FS4, FS5, FS6, FS7, FS8, FS9, FS10, FS11,

	/* reserved (see rv64/emit.c) */
	T6, FT11,

	NFPR = FS11 - FT0 + 1,
	NGPR = T6 - T0 + 1,
	NGPR = RA - T0 + 1,
	NGPS = A7 - T0 + 1,
	NFPS = FA7 - FT0 + 1,
	NCLR = (S11 - S1 + 1) + (FS11 - FS0 + 1),
};
MAKESURE(reg_not_tmp, FS11 < (int)Tmp0);
MAKESURE(reg_not_tmp, FT11 < (int)Tmp0);

struct Rv64Op {
	char imm;

M rv64/emit.c => rv64/emit.c +5 -3
@@ 89,6 89,7 @@ static struct {
	{ Ocopy,   Ki, "mv %=, %0" },
	{ Ocopy,   Ka, "fmv.%k %=, %0" },
	{ Oswap,   Ki, "mv %?, %0\n\tmv %0, %1\n\tmv %1, %?" },
	{ Oswap,   Ka, "fmv.%k %?, %0\n\tfmv.%k %0, %1\n\tfmv.%k %1, %?" },
	{ Oreqz,   Ki, "seqz %=, %0" },
	{ Ornez,   Ki, "snez %=, %0" },
	{ Ocall,   Kw, "jalr %0" },


@@ 101,16 102,17 @@ static char *rname[] = {
	[GP] = "gp",
	[TP] = "tp",
	[RA] = "ra",
	[T6] = "t6",
	[T0] = "t0", "t1", "t2", "t3", "t4", "t5",
	[A0] = "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
	[S1] = "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8",
	       "s9", "s10", "s11",
	[FT0] = "ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7",
	        "ft8", "ft9", "ft10", "ft11",
	        "ft8", "ft9", "ft10",
	[FA0] = "fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7",
	[FS0] = "fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7",
	        "fs8", "fs9", "fs10", "fs11",
	[T6] = "t6",
	[FT11] = "ft11",
};

static int64_t


@@ 162,7 164,7 @@ emitf(char *s, Ins *i, Fn *fn, FILE *f)
			if (KBASE(k) == 0)
				fputs("t6", f);
			else
				abort();
				fputs("ft11", f);
			break;
		case 'k':
			if (i->cls != Kl)

M rv64/targ.c => rv64/targ.c +3 -4
@@ 11,7 11,7 @@ int rv64_rsave[] = {
	A0, A1, A2, A3, A4, A5, A6, A7,
	FA0, FA1, FA2,  FA3,  FA4, FA5, FA6, FA7,
	FT0, FT1, FT2,  FT3,  FT4, FT5, FT6, FT7,
	FT8, FT9, FT10, FT11,
	FT8, FT9, FT10,
	-1
};
int rv64_rclob[] = {


@@ 22,8 22,7 @@ int rv64_rclob[] = {
	-1
};

/* T6 used as swap register (TODO: is there a better choice?) */
#define RGLOB (BIT(FP) | BIT(SP) | BIT(GP) | BIT(TP) | BIT(RA) | BIT(T6))
#define RGLOB (BIT(FP) | BIT(SP) | BIT(GP) | BIT(TP) | BIT(RA))

static int
rv64_memargs(int op)


@@ 39,7 38,7 @@ Target T_rv64 = {
	.fpr0 = FT0,
	.nfpr = NFPR,
	.rglob = RGLOB,
	.nrglob = 6,
	.nrglob = 5,
	.rsave = rv64_rsave,
	.nrsave = {NGPS, NFPS},
	.retregs = rv64_retregs,

M tools/abifuzz.sh => tools/abifuzz.sh +1 -1
@@ 2,7 2,7 @@

OCAMLC=${OCAMLC:-/usr/bin/ocamlc}
DIR=`cd $(dirname "$0"); pwd`
QBE=$DIR/../obj/qbe
QBE=$DIR/../qbe

failure() {
	echo "Failure at stage:" $1 >&2

M tools/cra.sh => tools/cra.sh +1 -1
@@ 1,7 1,7 @@
#!/bin/sh

DIR=`cd $(dirname "$0"); pwd`
QBE=$DIR/../obj/qbe
QBE=$DIR/../qbe
BUGF=/tmp/bug.id
FIND=$1
FIND=${FIND:-afl-find}

M tools/test.sh => tools/test.sh +4 -4
@@ 1,8 1,8 @@
#!/bin/sh

dir=`cd $(dirname "$0"); pwd`
bin=$dir/../obj/qbe
binref=$dir/../obj/qbe.ref
bin=$dir/../qbe
binref=$dir/../qbe.ref

tmp=/tmp/qbe.zzzz



@@ 79,8 79,8 @@ init() {
			cc="cc"
			;;
		*)
			cc="cc -no-pie"
			testcc "$cc" || cc="cc"
			cc="${CC:-cc} -no-pie"
			testcc "$cc" || cc="${CC:-cc}"
			;;
		esac
		TARGET=`$bin -t?`