-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support the vararg
calling convention in C#
#37
Comments
Isn't varargs already available via the |
Why have this as a separate issue? Performance and usability are related discussions. Having them as two separate issues is going to make the conversation harder, not easier. |
@mburbea Two things
|
Yeah, it like |
The problem with [DllImport("user32.dll")]
static extern int wsprintf([Out] StringBuilder lpOut, string lpFmt, __arglist); See also: System.ArgIterator |
Is it |
Access to an |
@mikedn Suppose for example you have the following method: int Sum(params int[] values)
{
int sum = 0;
for (int i = 0; i < values.Length; i++)
{
sum += values[i];
}
return sum;
} Now suppose you want to implement the same method without requiring the creation of an array. I believe you could use this: int Sum(params ArgIterator values)
{
int sum = 0;
int count = values.GetRemainingCount();
for (int i = 0; i < count; i++)
{
sum += __refvalue(values.GetNextArg(), int);
}
return sum;
} I have not checked to determine at which point the second method is slower than the first. |
I believe the working version for the varargs looks like the following, at least that's what I tested: static int Sum(__arglist) {
var value = new ArgIterator(__arglist);
var count = value.GetRemainingCount();
var sum = 0;
for (int i = 0; i < count; i++)
sum += __refvalue(value.GetNextArg(), int);
return sum;
} 1,000,000 Can you guess how much time the It takes 10ms. No comment 😄 |
Yes, __arglist is legacy, not-optimized and shall not be used. It's also not portable. |
I just tested and the slow part seems to be the public delegate IntPtr GetPtrType(RuntimeArgumentHandle a);
public GetPtrType GetPtr = new Func<GetPtrType>(()=>{
var dynamicMethod = new DynamicMethod("name", typeof(IntPtr),
new[]{typeof(RuntimeArgumentHandle)}, true);
var ilgen = dynamicMethod.GetILGenerator();
ilgen.Emit(OpCodes.Ldarga_S, 0);
ilgen.Emit(OpCodes.Call,
typeof(RuntimeArgumentHandle).GetMethod("get_Value", BindingFlags.NonPublic | BindingFlags.Instance));
ilgen.Emit(OpCodes.Ret);
var func = dynamicMethod.CreateDelegate(typeof(GetPtrType)) as GetPtrType;
return func;
}.Invoke(); I'm not sure what you can do with it but maybe somebody can write some unsafe code to parse out values. Usage static IntPtr GetThePtr(__arglist){
GetPtr(__arglist);
} |
Yes, the call itself is fast, the arguments are simply pushed on the stack like any other call arguments. The problem is extracting the arguments from the list, that requires parsing the signature used at the callsite and that's done via calls to runtime helper functions. Getting the pointer won't help, you need to know the format of that signature. In general, any attempt at extracting the arguments from the pointer is a recipe for disaster. |
Is there anyway to get that data via stackcrawls? Or is that what is so horribly slow? |
Stackcrawls as in System.Diagnostics.StackTrace? Even if it would be possible how would that help? You'll end up allocating at least one object, if you're going to do that then you may as well use the |
Probably too much to change/implement, but maybe that could work. Just wondering:
However, since array size is constant, it would probably be fine to allow it and use the stack (even if dynamic size _alloca() could work on small values; maybe a if (arraySize < 16) _alloca else GC_alloc()). Then, any small fixed size array that is not shared (as the ones used with Stack allocated objects would then need to be mapped to C# somehow (maybe using same syntax, MyType%?). It could also open the door to many other optimizations. |
An array is a reference type, reference types can never be allocated on the stack because you may end up with references to objects that have been deallocated. Stack allocation for reference types is possible as a JIT compiler optimization, it could stack allocate if it finds that the array reference is never stored in the heap or another place that would allow the use of the object after the function returns. Unfortunately this requires some rather expensive analysis and it's more likely to see such an optimization in a AOT compiler like .NET Native rather than in the traditional JIT compiler. |
I agree that this not safe, but it is definitely possible (as an example, C++/CLI allows you to do so, so it can be expressed in terms of MSIL). Of course, it should only be allowed inside an unsafe block. Or better, have something similar to https://wiki.gnome.org/Projects/Vala/ReferenceHandling : have "owned" pointer to make it safe (easy to know if somebody borrow a reference). And as you said, it can also be an automatic optimization when it can proven to be safe. |
I'm not sure what in C++/CLI makes you think that you can stack allocate arrays. You seem to be drawing the wrong conclusion, that "reference on the stack (using %)" avoids GC allocations. And anyway, if this requires the callers to be marked unsafe it's kind of useless. unsafe has its good uses but C# is primarily a type safe language and that's not going to change. As for reference counting & co. - it's probably easier to improve __arglist than go that way. |
What does stackalloc has to do with this? You cannot use stackalloc to store reference types. |
It does allocate on the stack (MS named it the "stack semantic operator") and you don't have to use gcnew but directly the value type ctor like a struct, without even a "new": https://msdn.microsoft.com/en-us/library/ms177191.aspx It doesn't work on arrays/string in C++/CLI (because they have variable size), but it could be possible for MS to technically support it (using _alloca()). I definitely agree it is not safe (except if you can guarantee reference are "owned" with additional keyword, similar to Vala, or MS research http://research.microsoft.com/pubs/170528/msr-tr-2012-79.pdf ). Just wanted to mention this would avoid most drawbacks of params. |
Oh, the "stack semantics" thing. That terminology is misleading, it should be called "scoped semantics" as the feature has nothing to do with the stack. Reference types can never be allocated on the stack and C++/CLI doesn't do that. It just claims "stack semantics" because it calls Dispose on the object at the end of the scope in which the object variable was declared. |
Right, my bad, sorry for the confusion. ref class A
{
public:
B A1; // Edit: Looks like inside the class, but it's not
B^ A2;
}; Edit: Thought first A1 was inside the class, but it actually is NOT inside (despite a modreq(IsByValue) in IL, and misleading C++ syntax). int main(array<System::String ^> ^args)
{
Object% a1 = Object(); // This is actually allocated with an indirection
Object^ a2 = gcnew Object();
} However, I was wondering, is anything really preventing such ref to be allocated directly on the stack? (as long as GC knows about it, and ref to stack is not transferred outside of scope) |
You can do that but it's the same trick, A1 will end up on the GC heap and A will only store a reference to it. You need to separate language syntax and semantics from implementation details. I suppose it's technically possible to allocate reference types on the stack but neither the runtime nor the language currently allow this. Basically the reference types would have to be treated as value types and passed around by using C#'s |
Concerning ref stack alloc, I meant allocating like a value type on the stack but still include vtable (like A1) and pass this stack pointer around like a normal reference. Basically it would just replace GCalloc by stack alloc.
This could be quite useful for LINQ, params and many custom scenario that were usually avoided in game engine dev due to unecessary GC pressure (sometimes it is hard to avoid allocating, esp. BCL internals). I might try to experiment this feature later in https://github.com/xen2/SharpLang |
Of course the implementation I proposed is part of the "unsafe" realm, but could be made safe if ownership concept was added to the language (same as Vala or MS research http://research.microsoft.com/pubs/170528/msr-tr-2012-79.pdf ). Note that http://joeduffyblog.com/2013/12/27/csharp-for-systems-programming/ mentions it in first point: "We’ve stolen a page from C++ — in areas like rvalue references, move semantics, destruction, references / borrowing — and yet retained the necessary elements of safety, and merged them with ideas from functional languages. This allows us to aggressively stack allocate objects, deterministically destruct, and more.". I guess it is probably something similar. |
I don't know what you have checked in memory but what you're describing is not possible. Maybe the B class from your example is a traditional C++ class instead of a C++/CLI As for the rest of the stuff, we'll see. It's one thing to do that in a research project and it's another thing to do that in an existing language with millions of lines of library and application code already written. I'm not sure if you noticed but one of the authors of that paper, Jared Parsons, is now a member of the Roslyn team and even posted in this discussion. And Joe Duffy is now the director of the language group at Microsoft. |
Sorry, after careful checking, seems it is in fact not laid out in memory inside the class (despite the modreq(IsByVal) being emitted in MSIL -- syntax and IL can be deceiving!). Must have checked too quickly the memory and since both instances were close in memory, I didn't see there was more in between. Concerning stack alloc of ref types, of course I didn't say it would be good to add in roslyn and commercial project (lot of implications and corner case to deal with when adding such a language feature -- note: unsafe feature is probably much easier to add than a safe one though). Note: updated previous post to strike out the wrong assumptions. Thanks for pointing it out, I really thought it was laid out differently in memory. |
Reading that In my case there is a |
modreq doesn't do anything, it's purpose is solely to be recognized and handled by compilers. Probably the best thing is to allow it on delegates. |
Why couldn't we just |
We have no expectation of ever doing this. |
Add build action to ensure server builds on PRs
The Virtual Execution System defined in ECMA-335 defines a
vararg
calling convention for methods which is not accessible from C#. As mentioned by @jaredpar in issue #36, both of the following signatures incur memory allocations for the purpose of passing parameters, even if those parameters are value types:For applications which are sensitive to memory allocations but still wish to provide API methods which take a variable number of arguments, I propose including the following feature in addition to the more "friendly" methods taking
object[]
orIEnumerable<object>
.A method containing a final
params ArgIterator
argument would be emitted using thevararg
calling convention. Access to thearglist
IL instruction is implicit via C# code which accesses this argument.The text was updated successfully, but these errors were encountered: