Reading execise

<x64 calling convention | Microsoft Docs;

To return a user-defined type by value in RAX, it must have a length
of 1, 2, 4, 8, 16, 32, or 64 bits. [...] Otherwise, the caller must
allocate memory for the return value and pass a pointer to it as the
first argument. The remaining arguments are then shifted one argument
to the right. The same pointer must be returned by the callee in RAX.

--- incompatible.c ---
__attribute__((ms_abi))
__uint128_t buggy(__uint128_t a, __uint128_t b, __uint128_t c) {
    return a + b * c;
}
--- EOF ---

clang -mno-sse -o- -O3 -S -target amd64-pc-windows incompatible.c

buggy: # @buggy
# %bb.0:
        movq (%rdx), %r9 # OUCH: before it was destroyed,
                                        # r9 held the address of c
        movq (%r8), %rax
        movq 8(%rdx), %r10
        imulq %rax, %r10
        mulq %r9 # OUCH: the source says b * c,
                                        # NOT a * <something>
        imulq 8(%r8), %r9 # OUCH: idem
        addq %r10, %rdx
        addq %r9, %rdx
        addq (%rcx), %rax # OUCH: rcx holds the address of
                                        # the return value!
                                        # OUCH: rax does NOT point to the
                                        # return value!
        adcq 8(%rcx), %rdx # OUCH: rcx holds the address of
                                        # the return value!
        retq

Stefan

<https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention#return-values>

To return a user-defined type by value in RAX, it must have a length
of 1, 2, 4, 8, 16, 32, or 64 bits. […] Otherwise, the caller must
allocate memory for the return value and pass a pointer to it as the
first argument. The remaining arguments are then shifted one argument
to the right. The same pointer must be returned by the callee in RAX.

Is there a particular reason for starting a new thread?

Statements have been made in https://lists.llvm.org/pipermail/cfe-dev/2020-September/066811.html to clarify the relationship between __[u]int128_t and the MS ABI.

— incompatible.c —
attribute((ms_abi))
__uint128_t buggy(__uint128_t a, __uint128_t b, __uint128_t c) {
return a + b * c;
}
— EOF —

clang -mno-sse -o- -O3 -S -target amd64-pc-windows incompatible.c

buggy: # @buggy

%bb.0:

movq (%rdx), %r9 # OUCH: before it was destroyed,

r9 held the address of c

It might help to provide the source code for a caller of this function and the associated assembly of the caller to demonstrate such a mismatch.

In fact, I did not. You simply aren't understanding what is going on. The problem here is either you don't understand what others have been telling you, or are being intentionally dense.

__int128_t and __uint128_t are both typedefs for __int128 and __uint128. That doesn't change anything. This isn't some big 'gotcha' moment here, its simply you not understanding what is going on in your example.

So I'll state this simply: The 128-bit integer types are integers. Integers are defined by the compiler, not by the user. Thus, the rule you quote about "user-defined types" is a hilariously misguided attempt to prove a non-existent-point.