-
Notifications
You must be signed in to change notification settings - Fork 28
Conversation
This reduces the failing tests to 41 on Win64:
|
extern(D) doesn't seem to be too way off. This: import core.vararg;
import core.stdc.stdio;
extern(C) void logC(const(char)* str, ...)
{
va_list args;
va_start(args, str);
vprintf(str, args);
va_end(args);
}
void logD(const(char)* str, ...)
{
va_list args;
va_start(args, str);
assert(args == _argptr);
foreach (ti; _arguments)
printf(" %s [size %d]\n", ti.toString().ptr, ti.tsize);
vprintf(str, _argptr);
}
void main()
{
immutable fl = 3.0f;
logC ("logC: %d %f %f %lld %f %s\n", 1, 2.0, fl, 0x4_0000_0000L, fl + 2.0f, "lala".ptr);
logD ("logD: %d %f %f %lld %f %s\n", 1, 2.0, fl, 0x4_0000_0000L, fl + 2.0f, "lala".ptr);
printf("printf: %d %f %f %lld %f %s\n", 1, 2.0, fl, 0x4_0000_0000L, fl + 2.0f, "lala".ptr);
} yields:
So there's a problem with floats for extern(D). It looks as if floats are never passed directly for varargs and converted to double instead. E.g., there's no printf type specifier distinguishing between float and double (only a l prefix for longdouble). This explains the produced .ll output: define void @_D5hello4logDFPxaYv({ i64, %object.TypeInfo** } %._arguments, i8* noalias nocapture %._argptr, i8* %str_arg) #0 {
...
}
define i32 @_Dmain({ i64, { i64, i8* }* } %unnamed) #0 {
%fl = alloca float, align 4
%_argptr_storage = alloca { i64, double, i64, i64, i64, i8* }
store float 3.000000e+00, float* %fl
%1 = load float* %fl
call void (i8*, ...)* @logC(i8* getelementptr inbounds ([29 x i8]* @.str2, i32 0, i32 0), i32 1, double 2.000000e+00, double 3.000000e+00, i64 17179869184, double 5.000000e+00, i8* getelementptr inbounds ([5 x i8]* @.str3, i32 0, i32 0))
%2 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 0
%3 = bitcast i64* %2 to i32*
store i32 1, i32* %3
%4 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 1
store double 2.000000e+00, double* %4
%5 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 2
%6 = bitcast i64* %5 to float*
store float 3.000000e+00, float* %6
%7 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 3
store i64 17179869184, i64* %7
%8 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 4
%9 = bitcast i64* %8 to float*
store float 5.000000e+00, float* %9
%10 = getelementptr { i64, double, i64, i64, i64, i8* }* %_argptr_storage, i32 0, i32 5
store i8* getelementptr inbounds ([5 x i8]* @.str4, i32 0, i32 0), i8** %10
%11 = load { i64, %object.TypeInfo** }* @._arguments.array
%12 = bitcast { i64, double, i64, i64, i64, i8* }* %_argptr_storage to i8*
call void @_D5hello4logDFPxaYv({ i64, %object.TypeInfo** } %11, i8* noalias nocapture %12, i8* getelementptr inbounds ([28 x i8]* @.str5, i32 0, i32 0))
%tmp = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([29 x i8]* @.str6, i32 0, i32 0), i32 1, double 2.000000e+00, double 3.000000e+00, i64 17179869184, double 5.000000e+00, i8* getelementptr inbounds ([5 x i8]* @.str7, i32 0, i32 0))
ret i32 0
} i.e., the 2 floats are passed to logC() as double literals while they are stored in a 64-bit slot for the _argptr_storage struct without converting to double before. If we want C-ABI compliance in this case, we'd have to convert all floats to double, involving the The next thing to be tested are structs/static arrays > 64 bits passed to a vararg extern(D) function. The |
structs are currently stored directly inside the _argptr_storage struct, so the va_arg() functions fail horribly for bigger ones. This can be tested by inspecting the .ll output of: import core.vararg;
import core.stdc.stdio;
struct Small { long a; }
struct Big { long a, b; }
void variadic(T)(...)
{
foreach (ti; _arguments)
printf(" %s [size %d]\n", ti.toString().ptr, ti.tsize);
static if (!is(T == struct))
{
auto format = "%s " ~ (__traits(isFloating, T) ? "%f" : "%d") ~ "\n";
printf(format.ptr, typeid(T).toString().ptr, va_arg!T(_argptr));
}
}
void main()
{
variadic!int(1);
variadic!double(2.0);
variadic!float(3.0f);
Small small = { 4L };
variadic!Small(small);
Big big = { 5L, 6L };
variadic!Big(big);
} At least for x86_64, it seems it would be much easier and cleaner if we declared a variadic D function as a normal LLVM (C) variadic function, except for the injected TypeInfo[] parameter, and so got rid of the _argptr_storage struct. The _argptr pointer would be allocated and initialized to what the LLVM va_start intrinsic returns at the beginning of the variadic D function body. Argument transformations (byval struct passing, cast to int...) would need to be moved to the call sites (at least for the optional ones), fixing ldc-developers/ldc#172 at the same time. I don't know how compatible other platforms would be to such an approach, but iirc, @redstar mentioned that the PowerPC vararg ABI is similar. |
Yes, that's been my plan/suggestion all along. Sorry if I was ambiguous. |
For all platforms? Or are there any legacy considerations, e.g., for 32-bit x86? |
Edited title and description to reflect the LDC changes in ldc-developers/ldc#768. |
Added a generic experimental version of both files, currently lacking x86_64 support for non-Windows. |
// struct passed by reference. We define va_list as a raw pointer | ||
// (to the actual struct) for the byref semantics and allocate | ||
// the struct in LDC's va_start and va_copy intrinsics. | ||
alias char* va_list; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather actually make this __va_list*
or something so that code that assumes x86-style variadics just doesn't compile instead of crashing horribly at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I see the point, but there's a problem with the hidden argptr. It is currently defined as Type::tvalist
, i.e., char, see gen/target.cpp:81
. If we change it to a hardcoded core.vararg.__va_list_
for System V, I guess the user must import core.vararg when implementing a variadic extern(D) function, even when not accessing _argptr at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I see the problem. We should probably fix this at some point by moving __va_list
into object
and properly initializing Type::tvalist
, but that can wait for now.
System V AMD64 ABI still unsupported because I can't experiment with it.
LLVM's intrinsic apparently doesn't work for aggregate types.
Requires ldc-developers/ldc#768 and addresses ldc-developers/ldc#702 and ldc-developers/ldc#73 (for Win64 only!).