[llvm-bugs] [Bug 43208] New: Struct return does not match Windows X64 stack convention.

via llvm-bugs llvm-bugs at lists.llvm.org
Tue Sep 3 09:23:43 PDT 2019


https://bugs.llvm.org/show_bug.cgi?id=43208

            Bug ID: 43208
           Summary: Struct return does not match Windows X64 stack
                    convention.
           Product: libraries
           Version: trunk
          Hardware: PC
                OS: Windows NT
            Status: NEW
          Severity: release blocker
          Priority: P
         Component: Backend: X86
          Assignee: unassignedbugs at nondot.org
          Reporter: LChrisman at Lumina.com
                CC: craig.topper at gmail.com, llvm-bugs at lists.llvm.org,
                    llvm-dev at redking.me.uk, spatel+llvm at rotateright.com

As I understand it, the Microsoft Win x64 calling convention returns a struct
that exceeds 64 bits as if it were the 1st parameter of the function. I.e.,
caller sets RCX to point to memory for the return struct (usually on caller's
stack), then callee sets RAX to same.

This is not the convention that I see being used when running LLVM on windows.
This function:

%MyStruct = type { i16, i64 }

define common %MyStruct @ret_struct() {
begin:
  ret %MyStruct { i16 13, i64 23 }
}

compiles to:

mov         ax,0Dh  
mov         edx,17h  
ret  

Then a call to the function from VC++ crashes.  I would expect to see something
more like:

mov word ptr [ecx],0Dh
mov dword ptr [rcx+8],17h
mov rax,rcx
ret

When I look at the code generated by VC++ for the function:

MyStruct G()
{
   return MyStruct{13,23};
}

I see it doing basically this.

Because of the calling convention mismatch, my call to the LLVM-compiled
function crashes, i.e.:

    auto F = (MyStruct(*)()) ExitOnErr(jit->lookup("ret_struct")).getAddress();
    MyStruct s = F();   // crash

I notice that llc does the same thing for this example. E.g.: given a file "try
call conv.ll" containing:

%MyStruct = type { i16, i64 }

define %MyStruct @ret_struct() {
begin:
  ret %MyStruct { i16 13, i64 23 }
}

Then the command:
> llc "try call conv.ll" -o -


        .def     ret_struct;
        .scl    2;
        .type   32;
        .endef
        .text
        .globl  ret_struct
        .align  16, 0x90
ret_struct:                             # @ret_struct
# BB#0:                                 # %begin
        movw    $13, %ax
        movl    $23, %edx
        ret


However, I also find an inconsistency between llc and the libraries. When I
change the example to

%MyStruct = type { i16, i8, i64 }

define %MyStruct @ret_struct() {
begin:
  ret %MyStruct { i16 13, i8 0, i64 23 }
}

(The i8 is in the padding area, so this doesn't change the struct size) In this
case, llc uses the window x64 return convention (good):


        .def     ret_struct;
        .scl    2;
        .type   32;
        .endef
        .text
        .globl  ret_struct
        .align  16, 0x90
ret_struct:                             # @ret_struct
# BB#0:                                 # %begin
        movq    $23, 8(%rcx)
        movb    $0, 2(%rcx)
        movw    $13, (%rcx)
        ret

But, when I make this same change in the C++ code, it still returns the struct
in registers, not using the Microsoft x64 convention.

In C++, setting the calling convention for the function to Win64 does not
change the code generated in either example.

---------------------------------
Here is the C++ code. Compiled on VC++ 2017. Same on both Win10 & Win7.

#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/Support//TargetSelect.h"
#include "llvm/IR/IRBuilder.h"
#include <iostream>

using namespace llvm;
using namespace llvm::orc;
using std::cout;
using std::endl;
using std::unique_ptr;
using std::make_unique;

ExitOnError ExitOnErr;

struct MyStruct {
        uint16_t n1_;
        uint64_t n2_;
};

ThreadSafeModule MakeM()
{
        auto pCtx = make_unique<LLVMContext>();
        auto& ctx = *pCtx;
        auto pM = make_unique<Module>("My_module",ctx);
        auto& M = *pM;

        auto myStruct = StructType::create(ctx,
                {       Type::getInt16Ty(ctx),
                        Type::getInt64Ty(ctx)
                },"MyStruct");
        FunctionType* proto = FunctionType::get(myStruct,false);
        Function* fn =
Function::Create(proto,GlobalValue::LinkageTypes::CommonLinkage,"ret_struct",M);
        //fn->setCallingConv(CallingConv::Win64);
        BasicBlock* BB = BasicBlock::Create(ctx,"begin",fn);
        IRBuilder<> builder(BB);
        //Value* ms = builder.CreateAlloca(myStruct,nullptr,"ms");
        auto ms = ConstantStruct::get(myStruct,
                {       ConstantInt::get(ctx,APInt(16,13,true)), 
                        ConstantInt::get(ctx,APInt(64,23uLL)) 
                } );
        builder.CreateRet(ms);
        cout << "The IR is:\n";
        outs() << M;
        cout << endl;

        return ThreadSafeModule(std::move(pM),std::move(pCtx));
}

static MyStruct G()
{
        return MyStruct{88,77};
}

void Test_Call_Convention()
{
        auto jit = ExitOnErr(LLJITBuilder().create());
        auto M = MakeM();
        ExitOnError ExitOnErr;
        ExitOnErr(jit->addIRModule(std::move(M)));
        auto F = (MyStruct(*)())
ExitOnErr(jit->lookup("ret_struct")).getAddress();

        auto g = G;
        MyStruct s1 = g();

        MyStruct s = F();
        std::cout << "MyStruct{" << s.n1_ << ',' << s.n2_ << "}\n";

}

int main()
{
        InitializeNativeTarget();
        InitializeNativeTargetAsmPrinter();

        Test_Call_Convention();
        return 0;
}


----------------------------------
For what it is worth, I traced down to
llvm\lib\CodeGen\SelectionDAG\FunctionLoweringInfo.cpp line 96:

  CanLowerReturn =
      TLI->CanLowerReturn(CC, *MF, Fn->isVarArg(), Outs, Fn->getContext());

When I put a breakpoint after this, then change the value to False in the
debugger, it produces the expected code. This routine calls into:

bool X86TargetLowering::CanLowerReturn(...)

in llvm\lib\Target\X86\X86ISelLowering.cpp

------------------------------------
I can't help but feel that I am making a fundamental mistake somewhere, since
this seems so blatant. Related bugs like 39251, 16168, 37760 and 42439 seem to
be far more subtle. If I'm missing something, accept my apologies and let me
know. If I am not wrong, seems like a release blocker, right?

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20190903/f64c4d32/attachment.html>


More information about the llvm-bugs mailing list