Why I got `attempt to multiply with overflow`?

When I run below code on Windows, I got arg = -1251143504 thread 'main' panicked at 'attempt to multiply with overflow', src\main.rs:8:9,
but if I move the foo outside call_foo, it works. how to fix it?

use std::arch::asm;

fn call_foo(arg: i32) -> i32 {
    extern "C" fn foo(arg: i32) -> i32 {
        println!("arg = {}", arg);
        arg * 2
    }

    unsafe {
        let result;
        asm!(
            "call {}",
            // Function pointer to call
            in(reg) foo,
            // 1st argument in rdi
            in("rdi") arg,
            // Return value in rax
            out("rax") result,
            // Mark all registers which are not preserved by the "C" calling
            // convention as clobbered.
            clobber_abi("C"),
        );
        result
    }

}

fn main(){
    call_foo(2);
}

I suspect the problem is that you appear to be writing assembly for the System V AMD64 calling convention, but the presence of "src\main.rs" suggests you're running on Windows, which uses the Microsoft x64 calling convention.

When it comes to writing assembly, the details really, really matter.

4 Likes

Yes, I run above code on Windows, but below code works:

use std::arch::asm;

extern "C" fn foo(arg: i32) -> i32 {
        println!("arg = {}", arg);
        arg * 2
}

fn call_foo(arg: i32) -> i32 {
    unsafe {
        let result;
        asm!(
            "call {}",
            // Function pointer to call
            in(reg) foo,
            // 1st argument in rdi
            in("rdi") arg,
            // Return value in rax
            out("rax") result,
            // Mark all registers which are not preserved by the "C" calling
            // convention as clobbered.
            clobber_abi("C"),
        );
        result
    }

}

fn main(){
    call_foo(2);
}

That’s weird!

I'm not sure what happens if you define an extern c function inside another. Does it have a stable address to call from assembly?

1 Like

I just tried it on Windows 10, and neither version works.

If the second appears to be working for youo, then I assume this is a coincidence, because I don't think you're using the correct calling convention. Looking at the assembly (something I'm inexperienced with), it looks like part of the calling convention modifies the rdi register.

I also couldn't find anything that looked like a significant change between the two versions, other than the exact placement and naming of the symbols.

2 Likes

The first integer parameter in Microsoft x64 calling convention is RCX, not RDI.

Additionally, RCX is a volatile register in the Microsoft x64 calling convention, so it's necessary to use inout("rcx") arg => _, to specify it's going to be clobbered to prevent the compiler from reusing its value.

Your code may happen to work if RCX happens to have the same value as RDI (call_foo in your code may get its arg as RCX), however it's not guaranteed in any way.

4 Likes

Got it, thanks!