dotnet / docs

This repository contains .NET Documentation.
https://learn.microsoft.com/dotnet
Creative Commons Attribution 4.0 International
4.23k stars 5.87k forks source link

Issues/Questions on the V9 proposal for function pointers #28782

Open RexJaeschke opened 2 years ago

RexJaeschke commented 2 years ago

Using this proposal, I’ve been writing a formal spec for this feature to add to the C# standard, and have the following comments:

Comment 1: “Syntactically incorrect code examples”

In “Function pointers,” we have the following code fragments:

delegate* managed<int, int>;
delegate* unmanaged<int, int>;
delegate* unmanaged[Cdecl] <int, int>;
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;

As written, each is a type followed by a semicolon. Either remove the semicolons or add a distinct identifier before each to make them valid parameter or variable declarations.

The same thing occurs in “Function pointer syntax”, in the following:

delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;

Comment 2: “Add extra restriction”

In “Function pointers,” there is a list of Restrictions. Add to that,

W.r.t the unary * operator, I plan to add the following Note to the C# standard:

Note: In C and C++, calling a function via a function pointer fp can be done using either (*fp)() or fp(). C# supports the latter only, as a function pointer cannot be dereferenced explicitly. end note

BTW, attempting to dereference a properly initialized function pointer fp using (*fp)() results in VS 16.11.2 telling me, “CS0193: the or -> operator must be applied to a pointer.” That was [almost] correct before function pointers were added, but it should now say “data pointer” instead of “pointer,” or “ and -> operator cannot be applied to a function pointer.” [Re the current message, I see that attempting to dereference a void pointer results in CS0242, a different message, even though a void pointer is* a pointer.]

Note that my use of “data pointer” excludes void*. As I’m adding function pointer support to the standard, I’m breaking the pointer_type grammar rule into three subrules, as follows, so I can distinguish between data and void pointers:

pointer_type
    : dataptr_type
    | funcptr_type
    | voidptr_type
    ;

Comment 3: “Grammar rule name typo”

In “Function pointer syntax,” the name of the grammar rule funptr_parameter_list is misspelled. The reference to that rule (and all other function-pointer rule names) use a “fun[c]” prefix instead, so this should be funcptr_parameter_list.

Comment 4: “Unnecessary new grammar rule”

In “Function pointer syntax,” rule funcptr_parameter_modifier is defined, as follows, and is referenced:

funcptr_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
;

The existing rule parameter_mode_modifier has that exact definition, and can be used here instead.

Comment 5: “Function pointer comparisons”

When I wrote code to compare two function pointers, I go the warning “CS8909 Comparison of function pointers might yield an unexpected result, since [multiple] pointers to the same function may be distinct.” A Google search led me to an issue on stackoverflow, which, in turn, led me to Roslyn Issue #48919, “Warn on pointer comparison between function pointers.”

What should I say in the C# standard w.r.t the reliability of the six kinds of comparisons of two function pointers? Is it always unreliable? Or is it only so with pointers to managed methods? To unmanaged methods?

Comment 6: “Question regarding method signature and calling convention combination”

In “Allow address-of to target methods,” the proposal states, “Method groups will now be allowed as arguments to an address-of expression. The type of such an expression will be a delegate* which has the equivalent signature of the target method and a managed calling convention:.”

I’m confused by the presence of managed. How does one initialize an unmanaged function pointer to point to an unmanaged method? I’m thinking specifically of calling a function written in C using the Cdecl convention.

Unless I’m missing something, my guess is that managed should be removed.

Comment 7: “Spurious ; in example”

In “Allow address-of to target methods,” we have

unsafe class Util {
    public static void Log() { }
    public static void Log(string p1) { }
    public static void Log(int i) { };

The one trailing ; should be removed.

Comment 8: “Function pointer invocations”

§11.7.8, “Invocation expressions” of the C# standard covers calls to methods by name or via a delegate. I expected this proposal to contain text augmenting that to cover calls to methods via a function pointer. And while there are some suggestions about that, mostly in the context of overloaded method selection and such during conversion with the & operator, I don’t see anything I can use to create a new section “Function pointer invocations” for the Unsafe chapter.

My current thinking is that in this new section I’d say something like the following: “A function pointer invocation is handled like a method invocation (§11.7.8.2), but with primary_expression being a function pointer rather than a method.” Is that completely true, or do I need to add/remove steps as well? If so, depending on how much modification of those steps is necessary, I might have to clone text from the method-invocation section rather than punt to it.

At the end of the proposal section, “Operators on Function Pointer Types,” the following should be added to the list of operations permitted on a function type:

  • The invocation-expression operator, (), may be used to call the method being pointed to (§11.7.8.1).

The one thing applicable to function pointer invocation that does not apply to method invocation, is the possibility of primary_expression being null. My test shows that attempting to call through a null-valued function pointer results in an exception of type System.AccessViolationException. My guess is the standard should say that “the nature of the failure is implementation-defined” rather than guarantee the actual type thrown. (We already say, “The effect of applying the unary * operator to a null [data] pointer is implementation-defined. In particular, there is no guarantee that this operation throws a System.NullReferenceException.)

Any light you can shed on this would be much appreciated.

Comment 9: “UnmanagedCallersOnlyAttribute”

This part of the proposal looks like implementation detail. As such, I don’t mention it in my C# standard proposal. Is that reasonable?

The MS proposal doesn’t show any examples of initializing an unmanaged function pointer using &. I’m guessing it might go something like this:

[UnmanagedCallersOnlyAttribute(CallConvs = new[] { typeof(CallConvCdecl) })]
static extern void Cfunction(int value);

delegate* unmanaged[Cdecl]<int, void> fp = &Cfunction;

Am I on the right track?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

BillWagner commented 2 years ago

ping @333fred

Can you check these comments from Rex and propose the correct fixes?

333fred commented 2 years ago
  1. Sure.
  2. Filed https://github.com/dotnet/roslyn/issues/60362 to improve the diagnostic clarity here.
  3. :+1:
  4. I don't think that existed at the time. If it does now, that's fine.
  5. The results are undefined. We don't guarantee anything about whether comparisons will be valid, for any type of function pointer comparison, with any type of function pointer.
  6. This is related to Q9. By default, it is managed. If UnmanagedCallersOnly is applied, the calling convention changes. This is covered by https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/function-pointers.md#systemruntimeinteropservicesunmanagedcallersonlyattribute: The compiler then looks at this effective CallKind and modopt collection and uses normal metadata rules to determine the final calling convention of the function pointer type.
  7. :+1:
  8. I would expect a function pointer invocation section to be nearly identical to the Delegate invocations section of the spec, with function pointer swapped for delegate.
  9. No, this definitely cannot be removed. It affects 6, and also affects the ability for methods to be called without taking a function pointer to them. It is very explicitly not an implementation detail.
RexJaeschke commented 2 years ago

Thanks, @333fred for your quick response.

Re my Comment #9, I still haven't fully digested your response and the attribute in question, but I wanted to bring to your attention a statement from the C# Standard's Introduction:

Ecma Technical Committee 39 (TC39) [later renamed to TC49] Task Group 2 (TG2) was formed in September 2000, to produce a standard for C#. Another Task Group, TG3, was also formed at that time to produce a standard for a library and execution environment called Common Language Infrastructure (CLI). (CLI is based on a subset of the .NET Framework.) Although Microsoft’s implementation of C# relies on CLI for library and run-time support, other implementations of C# need not, provided they support an alternate way of getting at the minimum CLI features required by this C# standard (see Annex C).

My concern is, "How much of the machinery should be exposed in a standard that does not require .NET/CLI? That is, is this attribute part of the support needed by the minimum CLI features required by this C# standard?" My guess is that you will answer, "Yes, it's needed" and I'm OK with that.

333fred commented 2 years ago

I will indeed. Since the presence of the attribute determines whether a method group can be converted to an unmanaged function pointer, it needs to be present in the spec. If a minimum cli didn't have the attribute, it would probably be fine technically: users of that cli would just never be able to directly convert a method group to an unmanaged function pointer.

RexJaeschke commented 2 years ago

All my concerns have been answered; thanks!