assemblygccmingwabi

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


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?


Solution

  • 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.