llvm-mos / llvm-mos-sdk

SDK for developing with the llvm-mos compiler
https://www.llvm-mos.org
Other
255 stars 52 forks source link

Bad .prg generation (code stripped by the optimizer?). #328

Closed AGPX closed 3 months ago

AGPX commented 3 months ago

Hi, I've tried the following very simple code (.cpp):

int main()
{
    while (true) {
        *((unsigned char *)0xd020) = 7;
    }

    return 0;
}

The output (.s):

    .text
    .zeropage   __rc0
    .zeropage   __rc1
    .zeropage   __rc2
    .zeropage   __rc3
    .zeropage   __rc4
    .zeropage   __rc5
    .zeropage   __rc6
    .zeropage   __rc7
    .zeropage   __rc8
    .zeropage   __rc9
    .zeropage   __rc10
    .zeropage   __rc11
    .zeropage   __rc12
    .zeropage   __rc13
    .zeropage   __rc14
    .zeropage   __rc15
    .zeropage   __rc16
    .zeropage   __rc17
    .zeropage   __rc18
    .zeropage   __rc19
    .zeropage   __rc20
    .zeropage   __rc21
    .zeropage   __rc22
    .zeropage   __rc23
    .zeropage   __rc24
    .zeropage   __rc25
    .zeropage   __rc26
    .zeropage   __rc27
    .zeropage   __rc28
    .zeropage   __rc29
    .zeropage   __rc30
    .zeropage   __rc31
    .file   "ld-temp.o"
                                        ; Start of file scope inline assembly
    .section    .init.250,"axR",@progbits
shift:
    lda #14
    jsr __CHROUT

                                        ; End of file scope inline assembly
    .section    .text.main,"ax",@progbits
    .globl  main                            ; -- Begin function main
    .type   main,@function
main:                                   ; @main
; %bb.0:
.Lfunc_end0:
    .size   main, .Lfunc_end0-main
                                        ; -- End function
    .section    .text.memcpy,"ax",@progbits
    .weak   memcpy                          ; -- Begin function memcpy
    .type   memcpy,@function
memcpy:                                 ; @memcpy
; %bb.0:
    sta __rc8
    txa
    bne .LBB95_2
        ...

The code of the main function is totally missing and the program crash. Tried with the latest version. The compiler command is the following:

call mos-c64-clang++ -Os -o c:\tmp\test.prg c:\tmp\test.cpp

It only works with -O0, so it seems a problem of the optimizer.

mysterymath commented 3 months ago

The only constraints that the C standard places on a compilers output are:

Writing to an arbitrary memory location is neither, so the compiler freely removes it. Accordingly, such accesses must be marked volatile to be preserved for their own sake; otherwise the compiler will remove them if they can be proven never to affect IO or volatile accesses.

mysterymath commented 3 months ago

Oh, and since this is C++, infinite loops are undefined behavior. So the compiler can assume that main is unreachable, that is, that the program can never be run ;)

AGPX commented 3 months ago

Well, but at least the return 0 must be emitted, otherwise the program crash. If you try a program like that in any x86 compiler, it doesn't crash. That's for sure a compiler bug.

mysterymath commented 3 months ago

The program could never reach a return 0, why should one be emitted. In fact, it's legal to assume the program can never reach the infinite loop.

lgblgblgb commented 3 months ago

Well, but at least the return 0 must be emitted, otherwise the program crash. If you try a program like that in any x86 compiler, it doesn't crash. That's for sure a compiler bug.

That's called "dead-code elimination" (DCE, for short) optimization. The compiler here does not need to emit "return 0" as that code is "dead", since it cannot be ever reached.

SupernaviX commented 3 months ago

This happens upstream as well, and it's a "known issue". I think it's unlikely that llvm will treat this output as a bug unless/until the language semantics change.

lgblgblgb commented 3 months ago

This happens upstream as well, and it's a "known issue". I think it's unlikely that llvm will treat this output as a bug unless/until the language semantics change.

But if you change line

*((unsigned char *)0xd020) = 7;

to

*((volatile unsigned char *)0xd020) = 7;

the situation is different. I guess, the problem here, that the compiler does not know it's an I/O register, and since the pointed value is never used otherwise in the code, it looks like a no-op: storing a value which will be never used. Thus the volatile can help in such a cases. Well, at least, as far as I can see ...

With that volatile the asm output changes to:

main:                                   # @main
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    mov     byte ptr [53280], 7
    jmp     .LBB0_1

Surely, the return 0; still hasn't been emitted, but that's normal, since it's dead-code: because of the infinite loop before, that won't be ever reached, so no point to emit it.