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

Add support for function pointers #984

Draft
wants to merge 11 commits into
base: draft-v9
Choose a base branch
from

Conversation

RexJaeschke
Copy link
Contributor

@RexJaeschke RexJaeschke commented Nov 4, 2023

There are two parts of this feature that are not covered by this PR, but that need to be considered:

RESOLVED! ## Part 1: Organizing the unsafe-related text

The following sections either need detailed text (which I have written, but it is not in this PR) to be added that is unsafe-specific, or will need forward pointers to the unsafe chapter, which would have the text added there:

12.6.3.4 Input types
12.6.3.5 Output types
12.6.3.7 Output type inferences
12.6.3.10 Lower-bound inferences
12.6.3.11 Upper-bound inferences
12.6.4.3 Better function member
12.6.4.5 Better conversion from expression
12.8.9 Invocation expressions|12.8.9.1 General

See Issue #750 for a discussion on dealing with unsafe situations outside of the unsafe chapter. The following two sections show the alternatives for one of these sections.

Part 1 example - New unsafe text in Chapter 12

12.6.3.7 Output type inferences

An output type inference is made from an expression E to a type T in the following way:

  • If E is an anonymous function with inferred return type U (§12.6.3.13) and T is a delegate type or expression tree type with return type Tₓ, then a lower-bound inference (§12.6.3.10) is made from U to Tₓ.
  • Otherwise, if E is a method group and T is a delegate type or expression tree type with parameter types T₁...Tᵥ and return type Tₓ, and overload resolution of E with the types T₁...Tᵥ yields a single method with return type U, then a lower-bound inference is made from U to Tₓ.
  • [[IN UNSAFE MODE ONLY]] If E is an address-of method group and T is a function pointer type with parameter types T1..Tk and return type Tb, and overload resolution of E with the types T1..Tk yields a single method with return type U, then a lower-bound inference is made from U to Tb.
  • Otherwise, if E is an expression with type U, then a lower-bound inference is made from U to T.
  • Otherwise, no inferences are made.

We might also want to add a backward pointer to this section from the unsafe chapter.

Part 1 example - New unsafe text in Chapter 23

12.6.3.7 Output type inferences

This subclause is extended in unsafe code (§xx).

23.6.1.x Output type inferences

In §12.6.3.7, the following bullet is added between the second and third bullets:

  • If E is an address-of method group and T is a function pointer type with parameter types T1..Tk and return type Tb, and overload resolution of E with the types T1..Tk yields a single method with return type U, then a lower-bound inference is made from U to Tb.

[The problem with this approach is that the reader has to flip between two chapters to understand this, and over time, paras might be added, removed, or rearranged back in 12.6.3.7, rendering the relative para instructions used here, incorrect. And we don't want to replicate all the bullets from 12.6.3.7 here.]

Part 2: The library type System.Runtime.CompilerServices.UnmanagedCallersOnlyAttribute

Spec'ing of this attribute has not yet been resolved. However, Rex added this type to C.3, but TG2 might decider to remove it. Following is a message thread between Fred Silberberg and me in March 2022:

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?

2022-03-24 Fred Silberberg responded: No, this definitely cannot be removed. It affects your Comment 6 [method signature and calling convention combination], 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.

2022-03-25 Rex replied: 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.

2022-03-25 Fred replied: 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 RexJaeschke added the type: feature This issue describes a new feature label Nov 4, 2023
@RexJaeschke RexJaeschke added this to the C# 9.0 milestone Nov 4, 2023
@RexJaeschke RexJaeschke marked this pull request as draft November 4, 2023 16:27

In an unsafe context, several constructs are available for operating on pointers:
*unmanaged_calling_convention* supports a small number of predefined conventions (`Cdecl`, `Stdcall`, `Thiscall`, and `Fastcall`, all of which are contextual keywords), which may be used standalone or as an *identifier* in an *unmanaged_calling_convention* identifier list. Other implementation-defined conventions are permitted, and multiple conventions can be combined by using an *identifier* list, possibly containing one or more of these predefined conventions. Lookup and processing for identifiers in this list is done in an implementation-defined manner.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are @Stdcall and Stdc\u0061ll required to mean the same thing as Stdcall, or is that too implementation-defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding Stdc\u0061ll, §6.4.2, Unicode character escape sequences, states:

A Unicode escape sequence represents a Unicode code point. Unicode escape sequences are processed in identifiers (§6.4.3), character literals (§6.4.5.5), regular string literals (§6.4.5.6), and interpolated regular string expressions (§12.8.3). A Unicode escape sequence is not processed in any other location (for example, to form an operator, punctuator, or keyword).

This suggests that Stdc\u0061ll and Stdcall are equivalent. However, §6.4.4, Keywords, contains the following:

Just as with keywords, contextual keywords can be used as ordinary identifiers by prefixing them with the @ character.

Note: When used as contextual keywords, these identifiers cannot contain Unicode_Escape_Sequences. end note

If that Note is true, and that info can't be deduced from normative text, the Note should be made normative.

§6.4.3 Identifiers, contains the grammar for identifier. See the detailed Note following it. After several readings of that, I am not completely convinced of the situation w.r.t @.

@Nigel-Ecma, as you were heavily involved in the rewrite of this grammar, what is your opinion?

Copy link
Contributor

@KalleOlaviNiemitalo KalleOlaviNiemitalo Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The impression I get from the draft PR is that delegate* unmanaged[Stdcall]<void> uses a contextual keyword and shall be accepted by all implementations, but delegate* unmanaged[@Stdcall]<void> uses an identifier and does not need to be accepted; an implementation might even define that the identifier lookup depends on using directives. But I'm not sure the specification should give this freedom to implementations.

If that interpretation is intended, then this text in §6.4.4 Keywords

In certain cases the grammar is not enough to distinguish contextual keyword usage from identifiers. In all such cases it will be specified how to disambiguate between the two.

requires adding a disambiguation rule to §function-pointers, e.g.

If the unmanaged_calling_convention is Cdecl, Stdcall, Thiscall, or Fastcall, then it is a contextual keyword. Otherwise, it is an identifier.

But I feel it would be better to omit Cdecl etc. from the grammar and instead always parse unmanaged_calling_convention as a list of identifiers, and then specify that these four identifiers shall be accepted by all implementations.

@KalleOlaviNiemitalo
Copy link
Contributor

KalleOlaviNiemitalo commented Nov 5, 2023

Is there any definition of what the Cdecl, Fastcall, Stdcall, and Thiscall unmanaged calling conventions mean in C# — is it possible to write portable unmanaged code that uses these calling conventions and can interoperate with C#? AFAIK, calling conventions with similar names are defined in

  • Microsoft C++ compiler documentation
  • ECMA-335 6th edition:
    • II.14.5 Method pointers, which references CallConv in II.15.3 Calling convention. This has also vararg. Metadata encoding in II.23.2 Blobs and signatures, and II.23.2.3 StandAloneMethodSig.
    • II.15.5.2 Platform invoke, and II.23.1.8 Flags for ImplMap [PInvokeAttributes]. This one has also Platformapi.

If nothing about semantics is being standardised then perhaps it would be better to leave the names entirely implementation-defined (so no contextual keywords) and add a note recommending these names in implementations that target ECMA-335. Portable C# applications would then have to put #if around any use of delegate* unmanaged[Cdecl] as implementations would not be required to recognise the Cdecl identifier, even in code that won't be called at run time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature This issue describes a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants