Navigation Menu

Skip to content
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

Crazy idea: Unsafe C# extensions #2011

Closed
wants to merge 1 commit into from
Closed

Crazy idea: Unsafe C# extensions #2011

wants to merge 1 commit into from

Conversation

alexrp
Copy link
Contributor

@alexrp alexrp commented Aug 31, 2015

This is a quite possibly insane idea that came to mind over the weekend and was fairly easy to hack into mcs. This patch adds a new unsafe language version (i.e. -langversion:unsafe) to mcs which implies experimental and enables the following extensions:

  • Pointers can be used in generic type arguments. (int i = 42; List<int*> list; list.Add (&i);)
  • Pointers to managed objects are allowed. (string s = "..."; string* p = &s;)
  • Managed types can be used in stackalloc declarations. (string* strings = stackalloc string [16];)
  • The size of managed references can be computed. (var sz = sizeof (string); /* 4 or 8 */)

All of these still require unsafe context. Also, there is no guarantee that the generated code will work with any other VM.

Pointers in generic type arguments

This one is interesting because it adds type safety that C# doesn't currently have when dealing with pointers in collections. Today, you have to maintain a List<IntPtr> which is not really any better than List<object> in terms of type safety.

Today, you do:

List<IntPtr> ptrs = new List<IntPtr> ();
int i = 42;
ptrs.Add ((IntPtr) &i);

If the type of i ever changes, you likely won't notice that you're now putting mistyped pointers into ptrs.

With the extension:

List<int*> ptrs = new List<int*> ();
int i = 42;
ptrs.Add (&i); // OK
short j = 21;
ptrs.Add (&j); // Not OK

Pointers to managed objects

This is a simple but surprisingly useful feature. It's as unsafe as any other kind of pointer, but when used with care can allow some patterns that are currently impossible in C# due to the restrictive nature of ref and out.

LLVM's instruction matching framework is one example of where this feature can be really useful. They have this code in lib/Transforms/InstCombine/InstCombineAddSub.cpp:

    Value *A = 0, *B = 0;
    if (match(RHS, m_Xor(m_Value(A), m_Value(B))) &&
        (match(LHS, m_And(m_Specific(A), m_Specific(B))) ||
         match(LHS, m_And(m_Specific(B), m_Specific(A)))))
      return BinaryOperator::CreateOr(A, B);

What happens here is that A and B are stored into temporary structures by reference via the m_... calls which set up match specifications. They are then set once the match call goes through the matching tree and calls match on each node. If the whole match passes, A and B have been set to the desired values from the instruction tree.

This can't really be done reasonably in C# with ref/out as these references can't be stored into fields. It can't be done with pointers currently, either, as pointers can only point to things that don't contain managed references, which is highly restrictive and impractical.

This change enables the above.

Managed types in stackalloc declarations

This quite simply allows the following:

struct ManagedData
{
    string SomeString;
}

object* objectArr = stackalloc object [16];
ManagedData* dataArr = stackalloc ManagedData [16];

That is, managed references can be stored in stackalloc'd memory, as can structures containing managed references. I suspect the latter case will be the most useful one in practice.

Note that this relies entirely on Mono doing stack scanning conservatively for managed stack frames. If that ever changes, this won't work, and for that reason I'm not really convinced this is as worthwhile an idea as the other features. On the other hand, even a stack-precise Mono could scan stackalloc'd memory conservatively to enable this feature. Who knows.

Size of managed references

This is a small feature that makes the above features really come together. Marshal.SizeOf has all sorts of special cases and only sometimes does what the user wants. sizeof will now simply do what you'd expect for any type: Return the size of storing that type in memory. For structures, this means the size of all its fields as the runtime sees it, while for references, this means the size of a managed reference. No special cases where certain types are not allowed.

This means that unsafe code that computes sizes of types will be clearer and more maintainable.

TL;DR

This off-by-default extension enables more practical and 'safe' unsafe programming in C# but generates code that won't necessarily work in other VMs. Useful enough to merge? I don't know. But I could see myself using these features in some tools that aren't intended to run anywhere but on Mono.

@Therzok
Copy link
Contributor

Therzok commented Aug 31, 2015

These look awesome to me. 👍

@txdv
Copy link
Contributor

txdv commented Aug 31, 2015

Looks very interesting, what is the purpose though of getting a pointer to an object on the heap?

@dori4n
Copy link

dori4n commented Aug 31, 2015

Is this comparable to or the same as this?
https://msdn.microsoft.com/en-us/library/aa288474(v=vs.71).aspx

@txdv
Copy link
Contributor

txdv commented Aug 31, 2015

@dorianmuthig not really, the functionality you show is the default unsafe functionality. This is an addition on top, usually in C# you can get pointers only to blittable types.

@redknightlois
Copy link

Loved it. Specially "Pointers can be used in generic type arguments"

@tritao
Copy link
Contributor

tritao commented Sep 1, 2015

Me and @ddobrev have been discussing the usefulness of these for CppSharp. We could use these features to improve the generated bindings in some places but unfortunately we won't be able to because of our compatibility to .NET.

Still nice work on this, I'd like to see these getting in to allow for further experimentation.

@alexrp
Copy link
Contributor Author

alexrp commented Sep 23, 2015

@txdv

Looks very interesting, what is the purpose though of getting a pointer to an object on the heap?

The LLVM example is one use case. What it's doing can't be expressed in C# today.

@tritao

We could use these features to improve the generated bindings in some places but unfortunately we won't be able to because of our compatibility to .NET.

The thing is, if this is added as an extension and it turns out people find this useful in practice, we could probably push for standardization with Microsoft. Ecma 335 even states that pointers not being allowed in generics is merely an arbitrary limitation.

@ddobrev
Copy link

ddobrev commented Sep 23, 2015

If this is pushed to ECMA, I am all for. I am afraid though that up until then we cannot use it even just on Mono because there would be a difference in the API. We do need to provide different binding assemblies per OS anyway but our API can and must be the same.

@tritao
Copy link
Contributor

tritao commented Oct 11, 2015

@lewurm
Copy link
Contributor

lewurm commented Mar 28, 2016

is there still interest to merge this or should it be closed?

@alexrp
Copy link
Contributor Author

alexrp commented Mar 29, 2016

I think that's a @marek-safar question. I believe he was looking into how an extension like this could be integrated neatly?

@dnfclas
Copy link

dnfclas commented Apr 6, 2016

@alexrp, Thanks for signing the contribution license agreement so quickly! Actual humans will now validate the agreement and then evaluate the PR.

Thanks, DNFBOT;

@dnfclas
Copy link

dnfclas commented Apr 11, 2016

@alexrp, Thanks for signing the contribution license agreement so quickly! Actual humans will now validate the agreement and then evaluate the PR.

Thanks, DNFBOT;

@alexrp
Copy link
Contributor Author

alexrp commented Jun 6, 2016

Since we're probably switching to Roslyn in the near future, I'll close this.

@alexrp alexrp closed this Jun 6, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants