Differences in .s assembly between GCC and mingw: How to compile QBE output on windows?

118 Views Asked by At

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?

1

There are 1 best solutions below

9
OetkenPurveyorOfCode On BEST ANSWER

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.