~mpu/qbe

Add module-level inline assembly v1 PROPOSED

John Nunley: 1
 Add module-level inline assembly

 6 files changed, 90 insertions(+), 5 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~mpu/qbe/patches/56209/mbox | git am -3
Learn more about email & git

[PATCH] Add module-level inline assembly Export this patch

This patch adds module-level inline assembly. The main idea is to
introduce an "asm" keyword, which takes a string which is echoed
straight into the output file. The only post-processing done on the
string is escaping it.

This feature roughly corresponds to the top-level "asm" macro in most C
compilers or the "global_asm!" macro in Rust. I've added documentation
of this feature to "il.txt" as well.

I have not implemented instruction-level inline assembly, since it is
more controversial and will likely impact more of the codebase.

This is my first patch for this codebase. I may not be aware of some
code idioms.

Signed-off-by: John Nunley <dev@notgull.net>
---
 all.h               |  2 +-
 doc/il.txt          | 16 ++++++++++++++++
 main.c              |  8 +++++++-
 parse.c             | 37 +++++++++++++++++++++++++++++++++++--
 test/global_asm.ssa | 30 ++++++++++++++++++++++++++++++
 tools/lexh.c        |  2 +-
 6 files changed, 90 insertions(+), 5 deletions(-)
 create mode 100644 test/global_asm.ssa

diff --git a/all.h b/all.h
index 97cc41c..c4e176d 100644
--- a/all.h
+++ b/all.h
@@ -515,7 +515,7 @@ bshas(BSet *bs, uint elt)

/* parse.c */
extern Op optab[NOp];
void parse(FILE *, char *, void (char *), void (Dat *), void (Fn *));
void parse(FILE *, char *, void (char *), void (Dat *), void (Fn *), void (const char *));
void printfn(Fn *, FILE *);
void printref(Ref, Fn *, FILE *);
void err(char *, ...) __attribute__((noreturn));
diff --git a/doc/il.txt b/doc/il.txt
index 7ec5fd0..f37bb3e 100644
--- a/doc/il.txt
+++ b/doc/il.txt
@@ -510,6 +510,22 @@ necessary to compile a call is in the instruction itself.
The syntax and semantics for the body of functions
are described in the <@ Control > section.

~ Module-Level Inline Assembly
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    `bnf
    ASM := 'asm' '"' ... '"'

It is often necessary to bypass QBE in order to invoke
instructions or create assembly code that QBE cannot
emit. For instance, libc implementations will need to
invoke a "syscall" instruction, which QBE cannot emit.
One possible way to invoke these instructions is with
module-level inline assembly.

The contents of the string are echoed directly to the
output file that QBE is compiling.

- 6. Control
------------

diff --git a/main.c b/main.c
index 5ecb4d0..ebbf067 100644
--- a/main.c
+++ b/main.c
@@ -113,6 +113,12 @@ dbgfile(char *fn)
	emitdbgfile(fn, outf);
}

static void
glasm(const char *text)
{
	fprintf(outf, "%s", text);
}

int
main(int ac, char *av[])
{
@@ -187,7 +193,7 @@ main(int ac, char *av[])
				exit(1);
			}
		}
		parse(inf, f, dbgfile, data, func);
		parse(inf, f, dbgfile, data, func, glasm);
		fclose(inf);
	} while (++optind < ac);

diff --git a/parse.c b/parse.c
index e896679..9e93fd3 100644
--- a/parse.c
+++ b/parse.c
@@ -41,6 +41,7 @@ enum Token {
	Talloc1,
	Talloc2,

    Tasm,
	Tblit,
	Tcall,
	Tenv,
@@ -100,6 +101,7 @@ static char *kwmap[Ntok] = {
	[Tloadd] = "loadd",
	[Talloc1] = "alloc1",
	[Talloc2] = "alloc2",
	[Tasm] = "asm",
	[Tblit] = "blit",
	[Tcall] = "call",
	[Tenv] = "env",
@@ -137,7 +139,7 @@ enum {
	TMask = 16383, /* for temps hash */
	BMask = 8191, /* for blocks hash */

	K = 11183273, /* found using tools/lexh.c */
	K = 15286327, /* found using tools/lexh.c */
	M = 23,
};

@@ -1198,8 +1200,36 @@ parselnk(Lnk *lnk)
		}
}

static void
parseglasm(void glasm(const char *))
{
	const char *t;
	char *scont, *p;
	int len;

	expect(Tstr);
	t = tokval.str;
	assert(*t++ == '"');

	len = strlen(t);
	scont = malloc(len);
	if (!scont)
		die("unable to allocate memory");
	memcpy(scont, t, len);

	assert(scont[len-1] == '"');
	scont[len-1] = '\0';

	p = strtok(scont, "\\");
	do glasm(p);
	while ((p = strtok(NULL, "\\")));

	glasm("\n");
	free(scont);
}

void
parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn *))
parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn *), void glasm(const char *))
{
	Lnk lnk;
	uint n;
@@ -1220,6 +1250,9 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn
			expect(Tstr);
			dbgfile(tokval.str);
			break;
		case Tasm:
			parseglasm(glasm);
			break;
		case Tfunc:
			lnk.align = 16;
			func(parsefn(&lnk));
diff --git a/test/global_asm.ssa b/test/global_asm.ssa
new file mode 100644
index 0000000..4c25bed
--- /dev/null
+++ b/test/global_asm.ssa
@@ -0,0 +1,30 @@
# tests global assembly

asm "
.data
.balign 8
x:
    .ascii \"foobar\"

.text
.balign 16
.globl asm_function
asm_function:
    ret
.type asm_function, @function
.size asm_function, .-asm_function
"

export function $call_it() {
@start
    call $asm_function ()
    ret
}

# >>> driver
# extern void call_it();
# int main() {
#     call_it();
#     return 0;    
# }
# <<<
diff --git a/tools/lexh.c b/tools/lexh.c
index efc30fe..fea2b5e 100644
--- a/tools/lexh.c
+++ b/tools/lexh.c
@@ -25,7 +25,7 @@ char *tok[] = {
	"cgtd", "cged", "cned", "ceqd", "cod", "cuod",
	"vaarg", "vastart", "...", "env", "dbgloc",

	"call", "phi", "jmp", "jnz", "ret", "hlt", "export",
	"asm", "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",
-- 
2.34.1