OP_LLVM_OUTARG_VT doesn't always create temporary vt needed by some ABI's.
commit2d06126dc97d09749dae7722fc1a8da44f60e129
authorlateralusX <lateralusx.github@gmail.com>
Wed, 14 Aug 2019 17:23:32 +0000 (14 19:23 +0200)
committerlateralusX <lateralusx.github@gmail.com>
Wed, 14 Aug 2019 17:23:32 +0000 (14 19:23 +0200)
tree4ed6eeb3d389adad0123d7029330cc8ac40abff0
parentff77b427f4f0c9ef7f926a302564ee239619e471
OP_LLVM_OUTARG_VT doesn't always create temporary vt needed by some ABI's.

LLVM lowering passes vt as reference to managed methods. On platforms where
the value type ABI is passing an address to the value type, either in register
or on stack (Windows x64) we must make sure we always use a temporary of
the value type or we will be breaking the value type semantics.

In JIT lowering of OP_OUTARG_VT we always make sure we use a temporary
in cases where argument storage is set to ArgValuetypeAddrInIReg/ArgValuetypeAddrOnStack.
On LLVM lowering this was currently not the case, but it still worked in many scenarios.
This was because IL load of arguments before doing a call did a
local copy and then we pass that byref. However, there are cases where this
copy could get optimized away and when that happens, we will change the
local value type in the caller’s frame, breaking the value type semantics.

Added test case in winx64structs hits such a scenario that will generate IL
breaking the value type semantics before the fix:

...
notail call monocc void @winx64_vector3Struct_Add_winx64_vector3Struct_winx64_vector3Struct
(i64 %49, %winx64_vector3Struct* %3, %winx64_vector3Struct* %16)
...
notail call monocc void @winx64_vector3Struct_Add_winx64_vector3Struct_winx64_vector3Struct
(i64 %64, %winx64_vector3Struct* %3, %winx64_vector3Struct* %15)
...

The two add calls will use the same reference (to a local value type in caller frame)
on both calls, this will break the value type semantics since they should both
be temporaries, like second argument is.

After the fix, the same code will be lowered into:

...
notail call monocc void @winx64_vector3Struct_Add_winx64_vector3Struct_winx64_vector3Struct
(i64 %55, %winx64_vector3Struct* %19, %winx64_vector3Struct* %18)
...
notail call monocc void @winx64_vector3Struct_Add_winx64_vector3Struct_winx64_vector3Struct
(i64 %72, %winx64_vector3Struct* %16, %winx64_vector3Struct* %15)
...

Each argument gets its own temporary that, according to the ABI, gets passed
by address down to function.

There will be situations where we could end up with two temporaries, one from
the load and one from the lowering of OP_LLVM_OUTARG_VT (same as we get on JIT),
this will however be detected by LLVM optimization pass, removing one of the copies.
mono/mini/mini-amd64.c
mono/mini/mini-llvm.c
mono/mini/mini.h
mono/tests/winx64structs.cs