Skip to content

[PowerPC] power9 f128 ABI issue: bitcasts can reverse halves #92246

Closed
@tgross35

Description

@tgross35

Doing a roundtrip i128 -> f128 -> i128 should work but it appears to be broken when the calls are external, possibly depending on some optimizations. It seems like the f128/i128 is getting split into two 64-bit values, which are getting reversed at some point. The problem does not seem to happen without enabling the pwr9 target feature.

Originally identified with rust in rust-lang/rust#125102. Source files:

// transmute_lib.rs

#![feature(f128)]
#![allow(improper_ctypes_definitions)] // ok since llvm 18

use std::mem::transmute;

#[no_mangle]
pub extern "C" fn entry(a: u128) -> f128 {
    unsafe { transmute::<u128, f128>(a) }
}
// transmute_call_lib.rs

#![feature(f128)]
#![allow(improper_ctypes)] // ok since llvm 18

extern crate transmute_lib;

use std::mem::transmute;
use std::process::exit;

extern "C" {
    fn entry(a: u128) -> f128;
}

fn main() {
    let a = unsafe { entry(0x1) }; // bitcast in an external function
    let res = unsafe { transmute::<f128, u128>(a) }; // bitcast back

    if res == 0x1 {
        exit(0); // correct
    } else if res == 0x1 << 64 {
        exit(1); // incorrect value with endianness flipped
    } else {
        exit(2); // unexpected value
    }
}

Versions:

$ rustc -Vv
rustc 1.80.0-nightly (9c9b56879 2024-05-05)
binary: rustc
commit-hash: 9c9b568792ef20d8459c745345dd3e79b7c7fa8c
commit-date: 2024-05-05
host: x86_64-unknown-linux-gnu
release: 1.80.0-nightly
LLVM version: 18.1.

Compilation:

rustc transmute_lib.rs --target powerpc64-unknown-linux-gnu -C linker=powerpc64-linux-gnu-gcc -Ctarget-cpu=pwr9 --crate-type=lib
rustc transmute_call_lib.rs --target powerpc64-unknown-linux-gnu -C linker=powerpc64-linux-gnu-gcc -Ctarget-cpu=pwr9 -o transmute_call_lib.rs.ppc64.pwr9 --extern transmute_lib=libtransmute_lib.rlib

Execution:

$ qemu-ppc64 -L /usr/powerpc64-linux-gnu/ transmute_call_lib.rs.ppc64.pwr9; echo $?
1

The above should output 0. Full IR:

Library function
; ModuleID = 'transmute_lib.1e179323af95c5c5-cgu.0'
source_filename = "transmute_lib.1e179323af95c5c5-cgu.0"
target datalayout = "E-m:e-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512"
target triple = "powerpc64-unknown-linux-gnu"

; Function Attrs: uwtable
define fp128 @entry(i128 %a) unnamed_addr #0 {
start:
  %_0 = bitcast i128 %a to fp128
  ret fp128 %_0
}

attributes #0 = { uwtable "probe-stack"="inline-asm" "target-cpu"="pwr9" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{!"rustc version 1.80.0-nightly (9c9b56879 2024-05-05)"}
Executable

The only relevant part is in transmute_call_lib::main

; ModuleID = 'transmute_call_lib.1791869a4d73965-cgu.0'
source_filename = "transmute_call_lib.1791869a4d73965-cgu.0"
target datalayout = "E-m:e-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512"
target triple = "powerpc64-unknown-linux-gnu"

@vtable.0 = private unnamed_addr constant <{ ptr, [16 x i8], ptr, ptr, ptr }> <{ ptr @"_ZN4core3ptr85drop_in_place$LT$std..rt..lan

; std::sys_common::backtrace::__rust_begin_short_backtrace
; Function Attrs: noinline uwtable
define internal void @_ZN3std10sys_common9backtrace28__rust_begin_short_backtrace17hf94855fdbb5829c5E(ptr %f) unnamed_addr #0 {
start:
; call core::ops::function::FnOnce::call_once
  call void @_ZN4core3ops8function6FnOnce9call_once17h3056e9bd87d66482E(ptr %f)
  call void asm sideeffect "", "~{memory}"(), !srcloc !3
  ret void
}

; std::rt::lang_start
; Function Attrs: uwtable
define hidden i64 @_ZN3std2rt10lang_start17hdc6989dbc5702db1E(ptr %main, i64 %argc, ptr %argv, i8 %sigpipe) unnamed_addr #1 {
start:
  %_8 = alloca [8 x i8], align 8
  %_5 = alloca [8 x i8], align 8
  store ptr %main, ptr %_8, align 8
; call std::rt::lang_start_internal
  %0 = call i64 @_ZN3std2rt19lang_start_internal17he440c14c55fd4760E(ptr align 1 %_8, ptr align 8 @vtable.0, i64 %argc, ptr %argv,
  store i64 %0, ptr %_5, align 8
  %v = load i64, ptr %_5, align 8
  ret i64 %v
}

; std::rt::lang_start::{{closure}}
; Function Attrs: inlinehint uwtable
define internal i32 @"_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hdb386b686ec8565dE"(ptr align 8 %_1) unnamed_addr #2 {
start:
  %self = alloca [1 x i8], align 1
  %_4 = load ptr, ptr %_1, align 8
; call std::sys_common::backtrace::__rust_begin_short_backtrace
  call void @_ZN3std10sys_common9backtrace28__rust_begin_short_backtrace17hf94855fdbb5829c5E(ptr %_4)
; call <() as std::process::Termination>::report
  %0 = call i8 @"_ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h3606faab680ab85fE"()
  store i8 %0, ptr %self, align 1
  %_6 = load i8, ptr %self, align 1
  %_0 = zext i8 %_6 to i32
  ret i32 %_0
}

; core::ops::function::FnOnce::call_once{{vtable.shim}}
; Function Attrs: inlinehint uwtable
define internal i32 @"_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h956497c27ef6ef7fE"(ptr %_1) unnamed
start:
  %_2 = alloca [0 x i8], align 1
  %0 = load ptr, ptr %_1, align 8
; call core::ops::function::FnOnce::call_once
  %_0 = call i32 @_ZN4core3ops8function6FnOnce9call_once17h9737a54d47c05e22E(ptr %0)
  ret i32 %_0
}

; core::ops::function::FnOnce::call_once
; Function Attrs: inlinehint uwtable
define internal void @_ZN4core3ops8function6FnOnce9call_once17h3056e9bd87d66482E(ptr %_1) unnamed_addr #2 {
start:
  %_2 = alloca [0 x i8], align 1
  call void %_1()
  ret void
}

; core::ops::function::FnOnce::call_once
; Function Attrs: inlinehint uwtable
define internal i32 @_ZN4core3ops8function6FnOnce9call_once17h9737a54d47c05e22E(ptr %0) unnamed_addr #2 personality ptr @rust_eh_p
start:
  %1 = alloca [16 x i8], align 8
  %_2 = alloca [0 x i8], align 1
  %_1 = alloca [8 x i8], align 8
  store ptr %0, ptr %_1, align 8
; invoke std::rt::lang_start::{{closure}}
  %_0 = invoke i32 @"_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hdb386b686ec8565dE"(ptr align 8 %_1)
          to label %bb1 unwind label %cleanup

bb3:                                              ; preds = %cleanup
  %2 = load ptr, ptr %1, align 8
  %3 = getelementptr inbounds i8, ptr %1, i64 8
  %4 = load i32, ptr %3, align 8
  %5 = insertvalue { ptr, i32 } poison, ptr %2, 0
  %6 = insertvalue { ptr, i32 } %5, i32 %4, 1
  resume { ptr, i32 } %6

cleanup:                                          ; preds = %start
  %7 = landingpad { ptr, i32 }
          cleanup
  %8 = extractvalue { ptr, i32 } %7, 0
  %9 = extractvalue { ptr, i32 } %7, 1
  store ptr %8, ptr %1, align 8
  %10 = getelementptr inbounds i8, ptr %1, i64 8
  store i32 %9, ptr %10, align 8
  br label %bb3

bb1:                                              ; preds = %start
  ret i32 %_0
}

; core::ptr::drop_in_place<std::rt::lang_start<()>::{{closure}}>
; Function Attrs: inlinehint uwtable
define internal void @"_ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h64db6
start:
  ret void
}

; <() as std::process::Termination>::report
; Function Attrs: inlinehint uwtable
define internal i8 @"_ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h3606faab680ab85fE"() unnamed_addr #2 {
start:
  ret i8 0
}

; transmute_call_lib::main
; Function Attrs: uwtable
define internal void @_ZN18transmute_call_lib4main17hfa19d9d60e61b95dE() unnamed_addr #1 {
start:
  %a = call fp128 @entry(i128 1)
  %res = bitcast fp128 %a to i128
  %0 = icmp eq i128 %res, 1
  br i1 %0, label %bb2, label %bb3

bb2:                                              ; preds = %start
; call std::process::exit
  call void @_ZN3std7process4exit17hb2f96155fda111fdE(i32 0) #5
  unreachable

bb3:                                              ; preds = %start
  %_4 = icmp eq i128 %res, 18446744073709551616
  br i1 %_4, label %bb5, label %bb6

bb6:                                              ; preds = %bb3
; call std::process::exit
  call void @_ZN3std7process4exit17hb2f96155fda111fdE(i32 2) #5
  unreachable

bb5:                                              ; preds = %bb3
; call std::process::exit
  call void @_ZN3std7process4exit17hb2f96155fda111fdE(i32 1) #5
  unreachable
}

; std::rt::lang_start_internal
; Function Attrs: uwtable
declare i64 @_ZN3std2rt19lang_start_internal17he440c14c55fd4760E(ptr align 1, ptr align 8, i64, ptr, i8) unnamed_addr #1

; Function Attrs: uwtable
declare zeroext i32 @rust_eh_personality(i32 signext, i32 zeroext, i64, ptr, ptr) unnamed_addr #1

; Function Attrs: uwtable
declare fp128 @entry(i128) unnamed_addr #1

; std::process::exit
; Function Attrs: noreturn uwtable
declare void @_ZN3std7process4exit17hb2f96155fda111fdE(i32) unnamed_addr #3

define i32 @main(i32 %0, ptr %1) unnamed_addr #4 {
top:
  %2 = sext i32 %0 to i64
; call std::rt::lang_start
  %3 = call i64 @_ZN3std2rt10lang_start17hdc6989dbc5702db1E(ptr @_ZN18transmute_call_lib4main17hfa19d9d60e61b95dE, i64 %2, ptr %1, i8 0)
  %4 = trunc i64 %3 to i32
  ret i32 %4
}

attributes #0 = { noinline uwtable "probe-stack"="inline-asm" "target-cpu"="pwr9" }
attributes #1 = { uwtable "probe-stack"="inline-asm" "target-cpu"="pwr9" }
attributes #2 = { inlinehint uwtable "probe-stack"="inline-asm" "target-cpu"="pwr9" }
attributes #3 = { noreturn uwtable "probe-stack"="inline-asm" "target-cpu"="pwr9" }
attributes #4 = { "target-cpu"="pwr9" }
attributes #5 = { noreturn }

!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 7, !"PIE Level", i32 2}
!2 = !{!"rustc version 1.80.0-nightly (9c9b56879 2024-05-05)"}
!3 = !{i32 1628919}

This could be indirectly related to #92233 (comment) since that needs use of f128 to cause the crash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backend:PowerPCgood first issuehttps://github.com/llvm/llvm-project/contribute

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions