Skip to content

SIGBUS on AArch64 when Proc returns large extern struct by value #14322

@HertzDevil

Description

@HertzDevil

This example from spec/compiler/codegen/extern_spec.cr, which currently runs only on x86-64 targets, crashes on AArch64 (both macOS and Alpine):

lib LibMylib
  struct Struct
    x : Int32
    y : Int32
    z : Int32
    w : Int32
    a : Int32
  end
end

f = ->{
  s = LibMylib::Struct.new
  s.x = 1
  s.y = 2
  s.z = 3
  s.w = 4
  s.a = 5
  s
}

s = f.call
s.x &+ s.y &+ s.z &+ s.w &+ s.a

LibMylib::Struct must be passed indirectly according to the AArch64 ABI. The generated LLVM IR shows how this is done:

define i32 @__crystal_main(i32 %argc, ptr %argv) {
  ; ...
  %6 = load %"->", ptr %f, align 8
  %7 = extractvalue %"->" %6, 0
  %8 = extractvalue %"->" %6, 1
  %9 = icmp eq ptr %8, null
  br i1 %9, label %ctx_is_null, label %ctx_is_not_null

ctx_is_null:                                      ; preds = %entry
  call void %7(ptr %1)
  br label %exit

ctx_is_not_null:                                  ; preds = %entry
  %10 = call %"struct.LibMylib::Struct" %7(ptr %8)
  store %"struct.LibMylib::Struct" %10, ptr %2, align 4
  br label %exit

exit:                                             ; preds = %ctx_is_not_null, %ctx_is_null
  %11 = phi ptr [ %1, %ctx_is_null ], [ %2, %ctx_is_not_null ]
  %12 = load %"struct.LibMylib::Struct", ptr %11, align 4
  store %"struct.LibMylib::Struct" %12, ptr %s, align 4
  ; ...
}

define void @"~procProc(LibMylib::Struct)@test.cr:11"(ptr sret(%"struct.LibMylib::Struct") %0) #0 {
alloca:
  %s = alloca %"struct.LibMylib::Struct", align 8
  %1 = alloca %"struct.LibMylib::Struct", align 8
  br label %entry

entry:                                            ; preds = %alloca
  %2 = call %"struct.LibMylib::Struct" @"*struct.LibMylib::Struct::new:struct.LibMylib::Struct"()
  store %"struct.LibMylib::Struct" %2, ptr %1, align 4
  %3 = load %"struct.LibMylib::Struct", ptr %1, align 4
  store %"struct.LibMylib::Struct" %3, ptr %s, align 4
  %4 = getelementptr inbounds %"struct.LibMylib::Struct", ptr %s, i32 0, i32 0
  store i32 1, ptr %4, align 4
  %5 = getelementptr inbounds %"struct.LibMylib::Struct", ptr %s, i32 0, i32 1
  store i32 2, ptr %5, align 4
  %6 = getelementptr inbounds %"struct.LibMylib::Struct", ptr %s, i32 0, i32 2
  store i32 3, ptr %6, align 4
  %7 = getelementptr inbounds %"struct.LibMylib::Struct", ptr %s, i32 0, i32 3
  store i32 4, ptr %7, align 4
  %8 = getelementptr inbounds %"struct.LibMylib::Struct", ptr %s, i32 0, i32 4
  store i32 5, ptr %8, align 4
  %9 = load %"struct.LibMylib::Struct", ptr %s, align 4
  store %"struct.LibMylib::Struct" %9, ptr %0, align 4
  ret void
}

Apparently, the store %"struct.LibMylib::Struct" %9, ptr %0 instruction is reading %0 from the wrong register, but everything works if the call is adjusted like this:

ctx_is_null:                                      ; preds = %entry
  call void %7(ptr sret(%"struct.LibMylib::Struct") %1)
  br label %exit

That is, the sret at the call argument should match the sret at the function parameter.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions