I want to use QBE (a simple compiler backend) which I have compiled for windows.
To try it out I wanted to compile the example file hello.ssa
:
function w $add(w %a, w %b) { # Define a function add
@start
%c =w add %a, %b # Adds the 2 arguments
ret %c # Return the result
}
export function w $main() { # Main function
@start
%r =w call $add(w 1, w 1) # Call add(1, 1)
call $printf(l $fmt, ..., w %r) # Show the result
ret 0
}
data $fmt = { b "One and one make %d!\n", b 0 }
I compiled it with QBE:
> qbe.exe -o out.s hello.ssa
Which gave me the following out.s
file:
.text
add:
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
addl %esi, %eax
leave
ret
.type add, @function
.size add, .-add
/* end function add */
.text
.globl main
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $1, %edi
callq add
movl %eax, %esi
leaq fmt(%rip), %rdi
movl $0, %eax
callq printf
movl $0, %eax
leave
ret
.type main, @function
.size main, .-main
/* end function main */
.data
.balign 8
fmt:
.ascii "One and one make %d!\n"
.byte 0
/* end data */
.section .note.GNU-stack,"",@progbits
Then I wanted to create an executable with MinGW:
> gcc -o hello.exe out.s
out.s: Assembler messages:
out.s:9: Warning: .type pseudo-op used outside of .def/.endef: ignored.
out.s:9: Error: junk at end of line, first unrecognized character is `a'
out.s:10: Warning: .size pseudo-op used outside of .def/.endef: ignored.
out.s:10: Error: junk at end of line, first unrecognized character is `a'
out.s:28: Warning: .type pseudo-op used outside of .def/.endef: ignored.
out.s:28: Error: junk at end of line, first unrecognized character is `m'
out.s:29: Warning: .size pseudo-op used outside of .def/.endef: ignored.
out.s:29: Error: junk at end of line, first unrecognized character is `m'
out.s:39: Error: junk at end of line, first unrecognized character is `-'
This is the output I get when running the example. It compiles fine with GCC on wsl:
wsl gcc -o hello out.s
But the MinGW gcc compiler it does not seem to understand the assembly.
I noticed that the default .s output for a simple C Hello, World is also different between the two compilers. E.g:
gcc -S hello.c
gives a different .s file depending on which platform is used.
So what compiler/compiler flag/assembler can I use to get machine code from my qbe-generated out.s file on windows?
Secondly, I think QBE only supports the system-V ABI at the moment, so is it correct that I need to link against system-V stub functions instead of the CRT library functions, or does the mingw CRT already use the system-V ABI?
I made it compile by patching QBE to not emit .size and .type in its assembly output. So when I compile:
function w $add(w %a, w %b) {
@start
%c =w add %a, %b
ret %c
}
export function w $main() {
@start
%r =w call $add(w 1, w 1)
call $sysv_printf(l $fmt, ..., w %r) # Calling my wrapper
ret 0
}
data $fmt = { b "One and one make %d!\n", b 0 }
with QBE I get:
.text
add:
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
addl %esi, %eax
leave
ret
/* end function add */
.text
.globl main
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $1, %edi
callq add
movl %eax, %esi
leaq fmt(%rip), %rdi
movl $0, %eax
callq sysv_printf
movl $0, %eax
leave
ret
/* end function main */
.data
.balign 8
fmt:
.ascii "One and one make %d!\n"
.byte 0
/* end data */
I have created a C wrapper:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
int __attribute__((sysv_abi)) sysv_printf( const char *restrict fmt, ... ) {
printf("Called with %s", fmt);
assert(strcmp(fmt, "One and one make %d!\n") == 0);
va_list ap;
va_start(ap, fmt);
int smth = va_arg(ap, int);
printf("%d\n", smth);
va_end(ap);
printf("Finish");
}
So if i run these steps in my batchfile
call qbe -o out.s hello.ssa
call gcc -c out.s
call gcc -c stub.c
call gcc -S -fno-asynchronous-unwind-tables stub.c
call gcc -o main.exe stub.o out.o
I get an executable main.exe.
However when I run it something does not work with the variadic arguments:
Called with One and one make %d!
The format string works, but the integer does not seem to be passed correctly. If I check the return value:
> echo %errorlevel%
-1073741819
Which is probably due to my program causing a segmentation fault.
Edit: Compiling with clang gives the following error message:
stub.c:17:5: error: 'va_start' used in System V ABI function
17 | va_start(ap, fmt);
| ^
C:\Program Files\LLVM\lib\clang\17\include\stdarg.h:33:29: note: expanded from macro 'va_start'
33 | #define va_start(ap, param) __builtin_va_start(ap, param)
| ^
1 error generated.
So I think it is not possible to use system-V ABI variadics on windows. But GCC could also have a nicer error message. Maybe I will trying to cross the ABI canyon with a bridge made from assembly instead of using a C compiler, but let's leave it at that.