128-bit integer support for new Target

I am currently working on porting Rust to an 32-bit out of tree target. My target doesn't support the i128 integer type. Now I am getting some error while compiling the Rust libraries for my target because some functions return i128.

Is it correct to assume a target must support i128 integer to support Rust? Is there no workaround for that?

Can you elaborate on what that means? Do you need to link something like GitHub - rust-lang/compiler-builtins: Porting `compiler-rt` intrinsics to Rust to get the implementations for the wider types?

Most target's don't have a native 128-bit integer type, so it's a matter of getting those operations lowered to operations on smaller types. Sometimes that's easy (like | or &) but for things like multiplication and division it often means lowering to a call to a function.

4 Likes

My target doesn't support i128 type in its calling convention. When I try to build rust using python x.py build --host x86_64-pc-windows-msvc --target opus3-octasic-opuskernel

At some point, im going to get: LLVM ERROR: unable to allocate function return #2 error: could not compile compiler_builtins

The allocate function return an i128, so TargetLowering::LowerCallTo will fail in my target for a return value of type i128.

I am not sure how to avoid this error. I am using an out of tree fork of llvm based on LLVM 14.

It would be acceptable for i128 to not work in extern "C", then, but you need some way for it to work in the Rust calling convention, even if just by splitting into a pair of 64-bit values.

Just to be clear I understand since I am new to Rust internal: each target define its own Rust calling convention (defined in rust\compiler\rustc_target\src\abi\call). So for my target I need to make sure i128 are splitted into pair of 64-bit values there?

Right. And that's not a stable ABI from version to version, so you can always change it if your target develops 64-bit support.

No, rustc_target::abi::call is for native calling conventions, I'm not sure we have any support for avoiding the use of the i128 LLVM type (though @nagisa might know something I don't).

Based on the #2 in your error, if I had to guess, the lowering of a LLVM i128 return type to 4 registers (each 32-bit) is failing on the third register.

That is, it seems to me that not only does LLVM already internally treat it like -> (i64, i64), it goes all the way to -> (i32, i32, i32, i32) which requires four return registers.

Now, many architectures likely only have two registers (if that) reserved for returning values, so you may be wondering, how does that even work on other targets?

Looking at a x64 example of -> (i128, i128), the LLVM IR simply returns the two values as if they would go into registers:

define { i128, i128 } @test() unnamed_addr #0 {
  ret { i128, i128 } { i128 1, i128 2 }
}

But you can also see that the assembly doesn't actually return in any registers:

test:
        mov     rax, rdi
        mov     qword ptr [rdi + 24], 0
        mov     qword ptr [rdi + 16], 2
        mov     qword ptr [rdi + 8], 0
        mov     qword ptr [rdi], 1
        ret

Instead, it appears that LLVM implicitly converted the function to use an indirect return pointer aka sret (which Rust would do explicitly for large enough types - my pair would qualify if not for the "scalar pair" optimization).

Now looking at some relevant LLVM source code, it seems that it offers this functionality independent of target, and most targets seem to call CheckReturn from their own CanLowerReturn. And in that case, SelectionDAGBuilder::visitRet will automatically switch to indirect returns (there's a few more places in that file where CanLowerReturn is checked, like in LowerArguments).

Given all the automation, my best guess is that the LLVM target you have there lacks a valid implementation of TargetLowering::CanLowerReturn - the default is return true; so if omitted, it will invalidly try to return everything in registers.

Not sure how to best test it, but note that i128 isn't necessary and -> (i64, i64) will also drain all of your return registers when CanLowerReturn is not implemented.

But anyway, this should be trivial to fix, just reuse any of the minimal impls, e.g. the MSP430 one - AFAICT all that's needed is to replace RetCC_MSP430 with RetCC_Opus3 (and RetCC_Opus3 should already exist, in order to be able to call AnalyzeReturn with it, since that's where you're getting the error right now).

6 Likes

Implementing TargetLowering::CanLowerReturn for my target (Opus3) fixed the problem.

Thank you very much.

1 Like