From d2097f6c1b0b8e374d1a3f947e8475f4df870887 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 4 Nov 2023 11:19:08 -0400 Subject: [PATCH 01/11] Add support for function pointers --- standard/lexical-structure.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 6f718ff1f..9f93fb998 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -591,10 +591,12 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has ```ANTLR contextual_keyword : 'add' | 'alias' | 'ascending' | 'async' | 'await' - | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' + | 'by' | 'Cdecl' | 'descending' | 'dynamic' | 'equals' + | 'Fastcall' | 'from' | 'get' | 'global' | 'group' | 'into' | 'join' - | 'let' | 'nameof' | 'on' | 'orderby' | 'partial' - | 'remove' | 'select' | 'set' | 'unmanaged' | 'value' + | 'let' | 'managed' | 'nameof' | 'on' | 'orderby' + | 'partial' | 'remove' | 'select' | 'set' | 'Stdcall' + | 'Thiscall' | 'unmanaged' | 'value' | 'var' | 'when' | 'where' | 'yield' ; ``` From 0eb925139f553a739971ae55c06bb52c72770116 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 4 Nov 2023 11:28:55 -0400 Subject: [PATCH 02/11] Add support for function pointers --- standard/types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/types.md b/standard/types.md index 917bab6fa..05ffdf844 100644 --- a/standard/types.md +++ b/standard/types.md @@ -13,7 +13,7 @@ type ; ``` -*pointer_type* ([§23.3](unsafe-code.md#233-pointer-types)) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)). +*pointer_type* (§pointer-types-general) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)). Value types differ from reference types in that variables of the value types directly contain their data, whereas variables of the reference types store ***references*** to their data, the latter being known as ***objects***. With reference types, it is possible for two variables to reference the same object, and thus possible for operations on one variable to affect the object referenced by the other variable. With value types, the variables each have their own copy of the data, and it is not possible for operations on one to affect the other. @@ -71,7 +71,7 @@ delegate_type ; ``` -*pointer_type* is available only in unsafe code ([§23.3](unsafe-code.md#233-pointer-types)). +*pointer_type* (§pointer-types-general) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)). A reference type value is a reference to an ***instance*** of the type, the latter known as an object. The special value `null` is compatible with all reference types and indicates the absence of an instance. From fafa583e697debaac30220141e1cfa752873963f Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 4 Nov 2023 12:05:30 -0400 Subject: [PATCH 03/11] Add support for function pointers --- standard/unsafe-code.md | 329 ++++++++++++++++++++++++++++++---------- 1 file changed, 252 insertions(+), 77 deletions(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 15a5406eb..22471cc43 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -10,7 +10,7 @@ An implementation that does not support unsafe code is required to diagnose any > > While practically every pointer type construct in C or C++ has a reference type counterpart in C#, nonetheless, there are situations where access to pointer types becomes a necessity. For example, interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm might not be possible or practical without access to pointers. To address this need, C# provides the ability to write ***unsafe code***. > -> In unsafe code, it is possible to declare and operate on pointers, to perform conversions between pointers and integral types, to take the address of variables, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program. +> In unsafe code, it is possible to declare and operate on pointers, to perform conversions between data pointers and integral types, to take the address of variables and methods, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program. > > Unsafe code is in fact a “safe” feature from the perspective of both developers and users. Unsafe code shall be clearly marked with the modifier `unsafe`, so developers can’t possibly use unsafe features accidentally, and the execution engine works to ensure that unsafe code cannot be executed in an untrusted environment. > @@ -115,39 +115,92 @@ When the `unsafe` modifier is used on a partial type declaration ([§15.2.7](cla ## 23.3 Pointer types -In an unsafe context, a *type* ([§8.1](types.md#81-general)) can be a *pointer_type* as well as a *value_type*, a *reference_type*, or a *type_parameter*. In an unsafe context a *pointer_type* may also be the element type of an array ([§17](arrays.md#17-arrays)). A *pointer_type* may also be used in a typeof expression ([§12.8.17](expressions.md#12817-the-typeof-operator)) outside of an unsafe context (as such usage is not unsafe). +### §pointer-types-general General + +A ***pointer*** is a variable that is capable of containing the address of a variable or static method. A pointer with value `null` is a ***null pointer***, and does not currently point to a variable or static method. The act of attempting to access the target of a pointer is called ***dereferencing***. -A *pointer_type* is written as an *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) or the keyword `void`, followed by a `*` token: +In an unsafe context, a *type* ([§8.1](types.md#81-general)) can be a *pointer_type* as well as a *value_type*, a *reference_type*, or a *type_parameter*. In an unsafe context a *pointer_type* may also be the element type of an array ([§17](arrays.md#17-arrays)). A *pointer_type* may also be used in a typeof expression ([§12.8.17](expressions.md#12817-the-typeof-operator)) outside of an unsafe context (as such usage is not unsafe). ```ANTLR pointer_type - : value_type ('*')+ - | 'void' ('*')+ + : dataptr_type + | funcptr_type + | voidptr_type ; ``` -The type specified before the `*` in a pointer type is called the ***referent type*** of the pointer type. It represents the type of the variable to which a value of the pointer type points. +The type of the target of a pointer type is called the ***referent type*** of the pointer type. It represents the type of the variable to which a value of the pointer type points. A *pointer_type* may only be used in an *array_type* in an unsafe context ([§23.2](unsafe-code.md#232-unsafe-contexts)). A *non_array_type* is any type that is not itself an *array_type*. -Unlike references (values of reference types), pointers are not tracked by the garbage collector—the garbage collector has no knowledge of pointers and the data to which they point. For this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer shall be an *unmanaged_type*. Pointer types themselves are unmanaged types, so a pointer type may be used as the referent type for another pointer type. +Unlike references (values of reference types), pointers are not tracked by the garbage collector—the garbage collector has no knowledge of pointers and the data or static methods to which they point. For this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer shall be an *unmanaged_type*. Pointer types themselves are unmanaged types, so a pointer type may be used as the referent type for another pointer type. + +The intuitive rule for mixing pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references. + +For a given implementation, all pointer types shall have the same size and representation. A null pointer value shall be represented by all-bits-zero. + +Pointer types are a separate category of types. Unlike reference types and value types, pointer types do not inherit from `object` and no conversions exist between pointer types and `object`. In particular, boxing and unboxing ([§8.3.13](types.md#8313-boxing-and-unboxing)) are not supported for pointers. However, conversions are permitted between different pointer types and between pointer types and the integral types. This is described in [§23.5](unsafe-code.md#235-pointer-conversions). + +A *pointer_type* cannot be used as a type argument ([§8.4](types.md#84-constructed-types)), and type inference ([§12.6.3](expressions.md#1263-type-inference)) fails on generic method calls that would have inferred a type argument to be a pointer type. + +A *pointer_type* cannot be used as a type of a subexpression of a dynamically bound operation ([§12.3.3](expressions.md#1233-dynamic-binding)). + +A *pointer_type* cannot be used as the type of the first parameter in an extension method ([§15.6.10](classes.md#15610-extension-methods)). + +A *pointer_type* may be used as the type of a volatile field ([§15.5.4](classes.md#1554-volatile-fields)). + +The *dynamic erasure* of a type `E*` is the pointer type with referent type of the dynamic erasure of `E`. + +An expression with a pointer type cannot be used to provide the value in a *member_declarator* within an *anonymous_object_creation_expression* ([§12.8.16.7](expressions.md#128167-anonymous-object-creation-expressions)). + +The default value ([§9.3](variables.md#93-default-values)) for any pointer type is `null`. -The intuitive rule for mixing of pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references. +A method can return a value of some type, and that type can be a pointer. -> *Example*: Some examples of pointer types are given in the table below: +> *Example*: When given a pointer to a contiguous sequence of `int`s, that sequence’s element count, and some other `int` value, the following method returns the address of that value in that sequence, if a match occurs; otherwise it returns `null`: +> +> +> ```csharp +> unsafe static int* Find(int* pi, int size, int value) +> { +> for (int i = 0; i < size; ++i) +> { +> if (*pi == value) +> { +> return pi; +> } +> ++pi; +> } +> return null; +> } +> ``` > +> *end example* + +### §data-pointers Data pointers + +A ***data pointer*** is a pointer capable of containing the address of a *value_type* ([§8.3.1](types.md#831-general) variable. + +```ANTLR +dataptr_type + : value_type ('*')+ + ; +``` + +A *dataptr_type* is written as an *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) followed by one or more `*` tokens. + +> *Example*: Some examples of data pointer types are given in the table below: +> > **Example** | **Description** > --------- | ----------- > `byte*` | Pointer to `byte` > `char*` | Pointer to `char` > `int**` | Pointer to pointer to `int` > `int*[]` | Single-dimensional array of pointers to `int` -> `void*` | Pointer to unknown type +> `delegate**` | Pointer to a pointer to a static method having no parameters and a `void` return type > > *end example* -For a given implementation, all pointer types shall have the same size and representation. - > *Note*: Unlike C and C++, when multiple pointers are declared in the same declaration, in C# the `*` is written along with the underlying type only, not as a prefix punctuator on each pointer name. For example: > > ```csharp @@ -156,35 +209,18 @@ For a given implementation, all pointer types shall have the same size and repre > > *end note* -The value of a pointer having type `T*` represents the address of a variable of type `T`. The pointer indirection operator `*` ([§23.6.2](unsafe-code.md#2362-pointer-indirection)) can be used to access this variable. +The value of a data pointer having type `T*` represents the address of a variable of type `T`. The pointer indirection operator `*` ([§22.6.2](unsafe-code.md#2262-pointer-indirection)) can be used to access this variable. Applying the indirection operator to a null pointer results in implementation-defined behavior ([§22.6.2](unsafe-code.md#2262-pointer-indirection)). > *Example*: Given a variable `P` of type `int*`, the expression `*P` denotes the `int` variable found at the address contained in `P`. *end example* -Like an object reference, a pointer may be `null`. Applying the indirection operator to a `null`-valued pointer results in implementation-defined behavior ([§23.6.2](unsafe-code.md#2362-pointer-indirection)). A pointer with value `null` is represented by all-bits-zero. - -The `void*` type represents a pointer to an unknown type. Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type `void*`, nor can any arithmetic be performed on such a pointer. However, a pointer of type `void*` can be cast to any other pointer type (and vice versa) and compared to values of other pointer types ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). - -Pointer types are a separate category of types. Unlike reference types and value types, pointer types do not inherit from `object` and no conversions exist between pointer types and `object`. In particular, boxing and unboxing ([§8.3.13](types.md#8313-boxing-and-unboxing)) are not supported for pointers. However, conversions are permitted between different pointer types and between pointer types and the integral types. This is described in [§23.5](unsafe-code.md#235-pointer-conversions). - -A *pointer_type* cannot be used as a type argument ([§8.4](types.md#84-constructed-types)), and type inference ([§12.6.3](expressions.md#1263-type-inference)) fails on generic method calls that would have inferred a type argument to be a pointer type. - -A *pointer_type* cannot be used as a type of a subexpression of a dynamically bound operation ([§12.3.3](expressions.md#1233-dynamic-binding)). - -A *pointer_type* cannot be used as the type of the first parameter in an extension method ([§15.6.10](classes.md#15610-extension-methods)). - -A *pointer_type* may be used as the type of a volatile field ([§15.5.4](classes.md#1554-volatile-fields)). - -The *dynamic erasure* of a type `E*` is the pointer type with referent type of the dynamic erasure of `E`. - -An expression with a pointer type cannot be used to provide the value in a *member_declarator* within an *anonymous_object_creation_expression* ([§12.8.16.7](expressions.md#128167-anonymous-object-creation-expressions)). - -The default value ([§9.3](variables.md#93-default-values)) for any pointer type is `null`. - -> *Note*: Although pointers can be passed as `in`, `ref` or `out` parameters, doing so can cause undefined behavior, since the pointer might well be set to point to a local variable that no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example: +> *Note*: Although data pointers can be passed as `ref` or `out` parameters, doing so can cause undefined behavior, since the pointer might well be set to point to a local variable that no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example: > > > +> > ```csharp +> using System; +> > class Test > { > static int value = 20; @@ -192,24 +228,24 @@ The default value ([§9.3](variables.md#93-default-values)) for any pointer type > unsafe static void F(out int* pi1, ref int* pi2) > { > int i = 10; -> pi1 = &i; // return address of local variable +> pi1 = &i; > fixed (int* pj = &value) > { > // ... -> pi2 = pj; // return address that will soon not be fixed +> pi2 = pj; > } > } > > static void Main() > { -> int i = 15; +> int i = 10; > unsafe > { > int* px1; > int* px2 = &i; > F(out px1, ref px2); -> int v1 = *px1; // undefined -> int v2 = *px2; // undefined +> // Undefined behavior +> Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}", > } > } > } @@ -217,45 +253,151 @@ The default value ([§9.3](variables.md#93-default-values)) for any pointer type > > *end note* -A method can return a value of some type, and that type can be a pointer. +In an unsafe context, several constructs are available for operating on data pointers: -> *Example*: When given a pointer to a contiguous sequence of `int`s, that sequence’s element count, and some other `int` value, the following method returns the address of that value in that sequence, if a match occurs; otherwise it returns `null`: +- The unary `*` operator may be used to perform pointer indirection ([§23.6.2](unsafe-code.md#2362-pointer-indirection)). +- The `->` operator may be used to access a member of a struct through a pointer ([§23.6.3](unsafe-code.md#2363-pointer-member-access)). +- The `[]` operator may be used to index a pointer ([§23.6.4](unsafe-code.md#2364-pointer-element-access)). +- The unary `&` operator may be used to obtain the address of a variable ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)). +- The `++` and `--` operators may be used to increment and decrement pointers ([§23.6.6](unsafe-code.md#2366-pointer-increment-and-decrement)). +- The binary `+` and `-` operators may be used to perform pointer arithmetic ([§23.6.7](unsafe-code.md#2367-pointer-arithmetic)). +- The `==`, `!=`, `<`, `>`, `<=`, and `>=` operators may be used to compare pointers ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). +- The `stackalloc` operator may be used to allocate memory from the call stack ([§23.9](unsafe-code.md#239-stack-allocation)). +- The `fixed` statement may be used to temporarily fix a variable so its address can be obtained ([§23.7](unsafe-code.md#237-the-fixed-statement)). + +### §function-pointers Function pointers + +A ***function pointer*** is a pointer capable of containing the address of a static method. + +```ANTLR +funcptr_type + : 'delegate' '*' calling_convention_specifier? + '<' funcptr_parameter_list funcptr_return_type '>' + ; + +calling_convention_specifier + : 'managed' + | 'unmanaged' ('[' unmanaged_calling_convention ']')? + ; + +unmanaged_calling_convention + : 'Cdecl' + | 'Stdcall' + | 'Thiscall' + | 'Fastcall' + | identifier (',' identifier)* + ; + +funcptr_parameter_list + : (funcptr_parameter ',')* + ; + +funcptr_parameter + : parameter_mode_modifier? type + ; + +funcptr_return_type + : ref_kind? return_type + ; +``` + +Just as a method has a signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)), a function pointer type has a signature for the method type to which it may point. That signature includes the calling convention. + +The non-null value of a function pointer having type `T` represents the address of a method having a signature compatible with type `T`. + +If no *calling_convention_specifier* is provided, the default is `managed`, which results in the execution environment’s default mechanism being used. Specific unmanaged conventions can be specified using *unmanaged_calling_convention* whose tokens are mapped to implementation-defined names having implementation-defined semantics. The set of valid combinations of these tokens is implementation-defined. + +> *Note*: The *calling_convention_specifier* allows a potentially more efficient calling mechanism to be chosen, or for methods written in languages other than C# to be called. *end note*. + +> *Example*: Some examples of function pointer types are given in the table below: +> +> **Example** | **Description** +> --------- | ----------- +> `delegate*` | Pointer to a managed method having no parameters and a `void` return type +> `delegate*` | Pointer to a managed method having two `string` parameters and a `bool` return type +> `delegate*` | Pointer to a managed method having no parameters and returning a `ref readonly int` +> `delegate*, void>` | Pointer to a managed method having one parameter that is a pointer to a method having no parameters and an `int` return type, and a `void` return type +> `delegate* unmanaged[Stdcall] ` | Pointer to an unmanaged method having no parameters and a `void` return type, using the `Stdcall` calling convention > -> +> Consider the following: +> +> > ```csharp -> unsafe static int* Find(int* pi, int size, int value) +> unsafe class Util > { -> for (int i = 0; i < size; ++i) +> static void Log() { ... } +> static void Log(string p1) { ... } +> +> static void User() > { -> if (*pi == value) +> delegate*[] ary1 = new delegate*[] { &Log, null }; +> foreach (var element in ary1) > { -> return pi; +> if (element != null) +> { +> element(); // call the method being pointed to +> } > } -> ++pi; > } -> return null; > } > ``` > -> *end example* +> Given that the function pointers in the array point to methods with no parameters, `&Log` takes the address of the `Log` method having no parameters. *end example* -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. -- The unary `*` operator may be used to perform pointer indirection ([§23.6.2](unsafe-code.md#2362-pointer-indirection)). -- The `->` operator may be used to access a member of a struct through a pointer ([§23.6.3](unsafe-code.md#2363-pointer-member-access)). -- The `[]` operator may be used to index a pointer ([§23.6.4](unsafe-code.md#2364-pointer-element-access)). -- The unary `&` operator may be used to obtain the address of a variable ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)). -- The `++` and `--` operators may be used to increment and decrement pointers ([§23.6.6](unsafe-code.md#2366-pointer-increment-and-decrement)). -- The binary `+` and `-` operators may be used to perform pointer arithmetic ([§23.6.7](unsafe-code.md#2367-pointer-arithmetic)). -- The `==`, `!=`, `<`, `>`, `<=`, and `>=` operators may be used to compare pointers ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). -- The `stackalloc` operator may be used to allocate memory from the call stack ([§23.9](unsafe-code.md#239-stack-allocation)). -- The `fixed` statement may be used to temporarily fix a variable so its address can be obtained ([§23.7](unsafe-code.md#237-the-fixed-statement)). +> *Example*: Given an implementation-defined calling convention `SuppressGCTransition`, +> +> +> ```csharp +> unsafe class C +> { +> delegate* unmanaged[SuppressGCTransition] fpx; +> delegate* unmanaged[Stdcall, SuppressGCTransition] fpy; +> } +> ``` +> +> both cases use the identifier-list grammar rule. *end example* + +Custom attributes cannot be applied to a *funcptr_type* or to any of its elements. + +A parameter of type *funcptr_type* shall not be marked as `params` ([§15.6.2.1](classes.md#15621-general)). + +In an unsafe context, the following constructs are available for operating on function pointers: + +- The `&` operator may be used to obtain the address of a static method ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)) +- The `==`, `!=`, `<`, `>`, `<=`, and `=>` operators may be used to compare pointers ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). +- The invocation_expression operator, `()`, may be used to call the method being pointed to ([§13.8.9.1](expressions.md#12891-general)). + +### §void-pointers Void pointers + +A ***void pointer*** is a pointer capable of containing the value of a data pointer or a function pointer. + +```ANTLR +voidptr_type + : 'void' ('*')+ + ; +``` + +A *voidptr_type* is written as the keyword `void` followed by one or more `*` tokens. + +> *Example*: Some examples of void-pointer types are given in the table below: +> +> **Example** | **Description** +> --------- | ----------- +> `void*` | Pointer to unknown type +> `void**` | Pointer to pointer to unknown type +> `void*[,,]` | Three-dimensional array of pointers to unknown type +> +> *end example* + +A *voidptr_type* type represents a pointer to an unknown type. Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type `void*`, nor can any arithmetic be performed on such a pointer. However, a pointer of type `void*` can be cast to any other pointer type (and vice versa) and compared to values of other pointer types ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). ## 23.4 Fixed and moveable variables The address-of operator ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)) and the `fixed` statement ([§23.7](unsafe-code.md#237-the-fixed-statement)) divide variables into two categories: ***Fixed variables*** and ***moveable variables***. -Fixed variables reside in storage locations that are unaffected by operation of the garbage collector. (Examples of fixed variables include local variables, value parameters, and variables created by dereferencing pointers.) On the other hand, moveable variables reside in storage locations that are subject to relocation or disposal by the garbage collector. (Examples of moveable variables include fields in objects and elements of arrays.) +Fixed variables reside in storage locations that are unaffected by operation of the garbage collector. (Examples of fixed variables include local variables, value parameters, and variables created by dereferencing data pointers.) On the other hand, moveable variables reside in storage locations that are subject to relocation or disposal by the garbage collector. (Examples of moveable variables include fields in objects and elements of arrays.) The `&` operator ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using a `fixed statement` ([§23.7](unsafe-code.md#237-the-fixed-statement)), and that address remains valid only for the duration of that `fixed` statement. @@ -276,7 +418,14 @@ A static field is classified as a moveable variable. Also, an `in`, `out`, or `r In an unsafe context, the set of available implicit conversions ([§10.2](conversions.md#102-implicit-conversions)) is extended to include the following implicit pointer conversions: - From any *pointer_type* to the type `void*`. -- From the `null` literal ([§6.4.5.7](lexical-structure.md#6457-the-null-literal)) to any *pointer_type*. +- From *null_literal* ([§6.4.5.7](lexical-structure.md#6457-the-null-literal)) to any *pointer_type*. +- From _*funcptr_type* `F0` to *funcptr_type* `F1`, provided all of the following are true: + - `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter `D1n` in `F1`. + - For each value parameter, an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `F0` to the corresponding parameter type in `F1`. + - For each `ref`, `out`, or `in` parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`. + - If the return type is by value, an identity, implicit reference, or implicit pointer conversion exists from the return type of `F1` to the return type of `F0`. + - If the return type is by reference, the return type and `ref` modifiers of `F1` are the same as the return type and `ref` modifiers of `F0`. + - The calling convention of `F0` is the same as the calling convention of `F1`. Additionally, in an unsafe context, the set of available explicit conversions ([§10.3](conversions.md#103-explicit-conversions)) is extended to include the following explicit pointer conversions: @@ -287,7 +436,7 @@ Additionally, in an unsafe context, the set of available explicit conversions ([ Finally, in an unsafe context, the set of standard implicit conversions ([§10.4.2](conversions.md#1042-standard-implicit-conversions)) includes the following pointer conversions: - From any *pointer_type* to the type `void*`. -- From the `null` literal to any *pointer_type*. +- From *null_literal* to any *pointer_type*. Conversions between two pointer types never change the actual pointer value. In other words, a conversion from one pointer type to another has no effect on the underlying address given by the pointer. @@ -400,13 +549,13 @@ pointer_indirection_expression ; ``` -The unary `*` operator denotes pointer indirection and is used to obtain the variable to which a pointer points. The result of evaluating `*P`, where `P` is an expression of a pointer type `T*`, is a variable of type `T`. It is a compile-time error to apply the unary `*` operator to an expression of type `void*` or to an expression that isn’t of a pointer type. +The unary `*` operator denotes pointer indirection and is used to obtain the variable to which a data pointer points. The result of evaluating `*P`, where `P` is an expression of a pointer type `T*`, is a variable of type `T`. It is a compile-time error to apply the unary `*` operator to an operand having type *funcptr_type* or *voidptr_type*. -The effect of applying the unary `*` operator to a `null`-valued pointer is implementation-defined. In particular, there is no guarantee that this operation throws a `System.NullReferenceException`. +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`. -If an invalid value has been assigned to the pointer, the behavior of the unary `*` operator is undefined. +If an invalid value has been assigned to the data pointer, the behavior of the unary `*` operator is undefined. -> *Note*: Among the invalid values for dereferencing a pointer by the unary `*` operator are an address inappropriately aligned for the type pointed to (see example in [§23.5](unsafe-code.md#235-pointer-conversions)), and the address of a variable after the end of its lifetime. +> *Note*: Among the invalid values for dereferencing a data pointer by the unary `*` operator are an address inappropriately aligned for the type pointed to (see example in [§23.5](unsafe-code.md#235-pointer-conversions)), and the address of a variable after the end of its lifetime. For purposes of definite assignment analysis, a variable produced by evaluating an expression of the form `*P` is considered initially assigned ([§9.4.2](variables.md#942-initially-assigned-variables)). @@ -420,7 +569,7 @@ pointer_member_access ; ``` -In a pointer member access of the form `P->I`, `P` shall be an expression of a pointer type, and `I` shall denote an accessible member of the type to which `P` points. +In a pointer member access of the form `P->I`, `P` shall be an expression of a data pointer type, and `I` shall denote an accessible member of the type to which `P` points. It is a compile-time error for `P` to have type *funcptr_type* or *voidptr_type*. A pointer member access of the form `P->I` is evaluated exactly as `(*P).I`. For a description of the pointer indirection operator (`*`), see [§23.6.2](unsafe-code.md#2362-pointer-indirection). For a description of the member access operator (`.`), see [§12.8.7](expressions.md#1287-member-access). @@ -484,7 +633,7 @@ pointer_element_access ; ``` -In a pointer element access of the form `P[E]`, `P` shall be an expression of a pointer type other than `void*`, and `E` shall be an expression that can be implicitly converted to `int`, `uint`, `long`, or `ulong`. +In a pointer element access of the form `P[E]`, `P` shall be an expression of a data pointer type, and `E` shall be an expression that can be implicitly converted to `int`, `uint`, `long`, or `ulong`. It is a compile-time error for `P` to have type *funcptr_type* or *voidptr_type*. A pointer element access of the form `P[E]` is evaluated exactly as `*(P + E)`. For a description of the pointer indirection operator (`*`), see [§23.6.2](unsafe-code.md#2362-pointer-indirection). For a description of the pointer addition operator (`+`), see [§23.6.7](unsafe-code.md#2367-pointer-arithmetic). @@ -544,6 +693,8 @@ addressof_expression ; ``` +*unary_expression* shall designate either a variable or a method group. The variable case is described immediately below. + Given an expression `E` which is of a type `T` and is classified as a fixed variable ([§23.4](unsafe-code.md#234-fixed-and-moveable-variables)), the construct `&E` computes the address of the variable given by `E`. The type of the result is `T*` and is classified as a value. A compile-time error occurs if `E` is not classified as a variable, if `E` is classified as a read-only local variable, or if `E` denotes a moveable variable. In the last case, a fixed statement ([§23.7](unsafe-code.md#237-the-fixed-statement)) can be used to temporarily “fix” the variable before obtaining its address. > *Note*: As stated in [§12.8.7](expressions.md#1287-member-access), outside an instance constructor or static constructor for a struct or class that defines a `readonly` field, that field is considered a value, not a variable. As such, its address cannot be taken. Similarly, the address of a constant cannot be taken. @@ -581,22 +732,44 @@ The `&` operator does not require its argument to be definitely assigned, but fo > *Note*: When a local variable, value parameter, or parameter array is captured by an anonymous function ([§12.8.23](expressions.md#12823-anonymous-method-expressions)), that local variable, parameter, or parameter array is no longer considered to be a fixed variable ([§23.7](unsafe-code.md#237-the-fixed-statement)), but is instead considered to be a moveable variable. Thus it is an error for any unsafe code to take the address of a local variable, value parameter, or parameter array that has been captured by an anonymous function. *end note* +The case of *unary_expression* designating a method group is described immediately below. + +In an unsafe context, a method `M` is compatible with a *funcptr_type* `F` if all of the following are true: + +- `M` and `F` have the same number of parameters, and each parameter in `M` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter in `F`. +- For each value parameter, an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `M` to the corresponding parameter type in `F`. +- For each `ref`, `out`, or `in` parameter, the parameter type in `M` is the same as the corresponding parameter type in `F`. +- If the return type is by value, an identity, implicit reference, or implicit pointer conversion exists from the return type of `F` to the return type of `M`. +- If the return type is by reference, the return type and `ref` modifiers of `F` are the same as the return type and `ref` modifiers of `M`. +- The calling convention of `M` is the same as the calling convention of `F`. +- `M` is a static method. + +An implicit conversion exists from a *unary_expression* whose target is a method group `E`, to a compatible function pointer type `F` if `E` contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of `F`, as described in the following: + +- A single method `M` is selected corresponding to a method invocation of the form `E(A)` with the following modifications: + - The arguments list `A` is a list of expressions, each classified as a variable and with the type and modifier of the corresponding *funcptr_parameter_list* of `F`. + - The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form. + - The candidate methods are only those methods that are static. +- If the algorithm of overload resolution produces an error, then a compile-time error occurs. Otherwise, the algorithm produces a single best method `M` having the same number of parameters as `F` and the conversion is considered to exist. +- The selected method `M` must be compatible (as defined above) with the function pointer type `F`. Otherwise, a compile-time error occurs. +- The result of the conversion is a function pointer of type `F`. + ### 23.6.6 Pointer increment and decrement -In an unsafe context, the `++` and `--` operators ([§12.8.15](expressions.md#12815-postfix-increment-and-decrement-operators) and [§12.9.6](expressions.md#1296-prefix-increment-and-decrement-operators)) can be applied to pointer variables of all types except `void*`. Thus, for every pointer type `T*`, the following operators are implicitly defined: +In an unsafe context, the `++` and `--` operators ([§12.8.15](expressions.md#12815-postfix-increment-and-decrement-operators) and [§12.9.6](expressions.md#1296-prefix-increment-and-decrement-operators)) can be applied to data pointer variables of all types. It is a compile-time error for these operators to be applied to variables of type *funcptr_type* or *voidptr_type*. Thus, for every data pointer type `T*`, the following operators are implicitly defined: ```csharp T* operator ++(T* x); T* operator --(T* x); ``` -The operators produce the same results as `x+1` and `x-1`, respectively ([§23.6.7](unsafe-code.md#2367-pointer-arithmetic)). In other words, for a pointer variable of type `T*`, the `++` operator adds `sizeof(T)` to the address contained in the variable, and the `--` operator subtracts `sizeof(T)` from the address contained in the variable. +The operators produce the same results as `x+1` and `x-1`, respectively ([§23.6.7](unsafe-code.md#2367-pointer-arithmetic)). In other words, for a data pointer variable of type `T*`, the `++` operator adds `sizeof(T)` to the address contained in the variable, and the `--` operator subtracts `sizeof(T)` from the address contained in the variable. If a pointer increment or decrement operation overflows the domain of the pointer type, the result is implementation-defined, but no exceptions are produced. ### 23.6.7 Pointer arithmetic -In an unsafe context, the `+` operator ([§12.10.5](expressions.md#12105-addition-operator)) and `–` operator ([§12.10.6](expressions.md#12106-subtraction-operator)) can be applied to values of all pointer types except `void*`. Thus, for every pointer type `T*`, the following operators are implicitly defined: +In an unsafe context, the `+` operator ([§12.10.5](expressions.md#12105-addition-operator)) and `–` operator ([§12.10.6](expressions.md#12106-subtraction-operator)) can be applied to values of all data pointer types. It is a compile-time error for these operators to be applied to a value of type *funcptr_type* or *voidptr_type*. Thus, for every data pointer type `T*`, the following operators are implicitly defined: ```csharp T* operator +(T* x, int y); @@ -614,9 +787,9 @@ T* operator –(T* x, ulong y); long operator –(T* x, T* y); ``` -Given an expression `P` of a pointer type `T*` and an expression `N` of type `int`, `uint`, `long`, or `ulong`, the expressions `P + N` and `N + P` compute the pointer value of type `T*` that results from adding `N * sizeof(T)` to the address given by `P`. Likewise, the expression `P – N` computes the pointer value of type `T*` that results from subtracting `N * sizeof(T)` from the address given by `P`. +Given an expression `P` of a data pointer type `T*` and an expression `N` of type `int`, `uint`, `long`, or `ulong`, the expressions `P + N` and `N + P` compute the pointer value of type `T*` that results from adding `N * sizeof(T)` to the address given by `P`. Likewise, the expression `P – N` computes the pointer value of type `T*` that results from subtracting `N * sizeof(T)` from the address given by `P`. -Given two expressions, `P` and `Q`, of a pointer type `T*`, the expression `P – Q` computes the difference between the addresses given by `P` and `Q` and then divides that difference by `sizeof(T)`. The type of the result is always `long`. In effect, `P - Q` is computed as `((long)(P) - (long)(Q)) / sizeof(T)`. +Given two expressions, `P` and `Q`, of a data pointer type `T*`, the expression `P – Q` computes the difference between the addresses given by `P` and `Q` and then divides that difference by `sizeof(T)`. The type of the result is always `long`. In effect, `P - Q` is computed as `((long)(P) - (long)(Q)) / sizeof(T)`. > *Example*: > @@ -651,7 +824,7 @@ If a pointer arithmetic operation overflows the domain of the pointer type, the ### 23.6.8 Pointer comparison -In an unsafe context, the `==`, `!=`, `<`, `>`, `<=`, and `>=` operators ([§12.12](expressions.md#1212-relational-and-type-testing-operators)) can be applied to values of all pointer types. The pointer comparison operators are: +In an unsafe context, the `==`, `!=`, `<`, `>`, `<=`, and `>=` operators ([§12.12](expressions.md#1212-relational-and-type-testing-operators)) can safely be applied to values of all *dataptr_type*s and to values of all *voidptr_types* that are copies of *dataptr_type* values. The pointer comparison operators are: ```csharp bool operator ==(void* x, void* y); @@ -662,7 +835,9 @@ bool operator <=(void* x, void* y); bool operator >=(void* x, void* y); ``` -Because an implicit conversion exists from any pointer type to the `void*` type, operands of any pointer type can be compared using these operators. The comparison operators compare the addresses given by the two operands as if they were unsigned integers. +Because an implicit conversion exists from any pointer type to the `void*` type, operands of any pointer type can be compared using these operators. The comparison operators compare the addresses given by the two operands as if they were unsigned integers. However, the behavior when comparing values of *funcptr_type*s, or `void*` copies thereof, is undefined. + +*Note*: On some platforms, it is possible that when the address of a given method is taken multiple times, the results differ, making comparisons against them unreliable. *end note* ### 23.6.9 The sizeof operator @@ -697,7 +872,7 @@ fixed_pointer_initializer ; ``` -Each *fixed_pointer_declarator* declares a local variable of the given *pointer_type* and initializes that local variable with the address computed by the corresponding *fixed_pointer_initializer*. A local variable declared in a fixed statement is accessible in any *fixed_pointer_initializer*s occurring to the right of that variable’s declaration, and in the *embedded_statement* of the fixed statement. A local variable declared by a fixed statement is considered read-only. A compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the `++` and `--` operators) or pass it as a `ref` or `out` parameter. +Each *fixed_pointer_declarator* declares a local variable of the given *pointer_type* and initializes that local variable with the address computed by the corresponding *fixed_pointer_initializer*. *pointer_type* shall not be *funcptr_type*. A local variable declared in a fixed statement is accessible in any *fixed_pointer_initializer*s occurring to the right of that variable’s declaration, and in the *embedded_statement* of the fixed statement. A local variable declared by a fixed statement is considered read-only. A compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the `++` and `--` operators) or pass it as a `ref` or `out` parameter. It is an error to use a captured local variable ([§12.19.6.2](expressions.md#121962-captured-outer-variables)), value parameter, or parameter array in a *fixed_pointer_initializer*. A *fixed_pointer_initializer* can be one of the following: @@ -1049,7 +1224,7 @@ When the outermost containing struct variable of a fixed-size buffer member is a See [§12.8.21](expressions.md#12821-stack-allocation) for general information about the operator `stackalloc`. Here, the ability of that operator to result in a pointer is discussed. -In an unsafe context if a *stackalloc_expression* ([§12.8.21](expressions.md#12821-stack-allocation)) occurs as the initializing expression of a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)), where the *local_variable_type* is either a pointer type ([§23.3](unsafe-code.md#233-pointer-types)) or inferred (`var`), then the result of the *stackalloc_expression* is a pointer of type `T *` to be beginning of the allocated block, where `T` is the *unmanaged_type* of the *stackalloc_expression*. +In an unsafe context if a *stackalloc_expression* ([§12.8.21](expressions.md#12821-stack-allocation)) occurs as the initializing expression of a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)), where the *local_variable_type* is either a pointer type ([§23.3](unsafe-code.md#233-pointer-types)) or inferred (`var`), then the result of the *stackalloc_expression* is a pointer of type `T*` to the beginning of the allocated block, where `T` is the *unmanaged_type* of the *stackalloc_expression*. In all other respects the semantics of *local_variable_declaration*s ([§13.6.2](statements.md#1362-local-variable-declarations)) and *stackalloc_expression*s ([§12.8.21](expressions.md#12821-stack-allocation)) in unsafe contexts follow those defined for safe contexts. From 5405e748944777a01240d2abb86b952526296dd5 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 4 Nov 2023 12:14:00 -0400 Subject: [PATCH 04/11] Add support for function pointers --- standard/portability-issues.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/standard/portability-issues.md b/standard/portability-issues.md index 7e846a428..cdd3594c7 100644 --- a/standard/portability-issues.md +++ b/standard/portability-issues.md @@ -11,10 +11,11 @@ This annex collects some information about portability that appears in this spec The behavior is undefined in the following circumstances: 1. The behavior of the enclosing async function when an awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` does not cause the resumption delegate to be invoked at most once ([§12.9.8.4](expressions.md#12984-run-time-evaluation-of-await-expressions)). -1. Passing pointers as `ref` or `out` parameters ([§23.3](unsafe-code.md#233-pointer-types)). +1. Passing pointers as `ref` or `out` parameters (§data-pointers). 1. When dereferencing the result of converting one pointer type to another and the resulting pointer is not correctly aligned for the pointed-to type. ([§23.5.1](unsafe-code.md#2351-general)). 1. When the unary `*` operator is applied to a pointer containing an invalid value ([§23.6.2](unsafe-code.md#2362-pointer-indirection)). 1. When a pointer is subscripted to access an out-of-bounds element ([§23.6.4](unsafe-code.md#2364-pointer-element-access)). +1. When comparing values of *funcptr_type*s, or `void*` copies thereof ([§23.6.8](unsafe-code.md#2368-pointer-comparison). 1. Modifying objects of managed type through fixed pointers ([§23.7](unsafe-code.md#237-the-fixed-statement)). 1. The content of memory newly allocated by `stackalloc` ([§12.8.21](expressions.md#12821-stack-allocation)). 1. Attempting to allocate a negative number of items using `stackalloc`([§12.8.21](expressions.md#12821-stack-allocation)). @@ -33,8 +34,10 @@ A conforming implementation is required to document its choice of behavior in ea 1. When a `System.ArithmeticException` (or a subclass thereof) is thrown when performing a decimal remainder operation ([§12.10.4](expressions.md#12104-remainder-operator)). 1. The impact of thread termination when a thread has no handler for an exception, and the thread is itself terminated ([§13.10.6](statements.md#13106-the-throw-statement)). 1. The impact of thread termination when no matching `catch` clause is found for an exception and the code that initially started that thread is reached. ([§21.4](exceptions.md#214-how-exceptions-are-handled)). +1. The token name mapping and semantics of unmanaged calling conventions beyond those required by this specification, and the set of valid combinations of those tokens (§function-pointers). 1. The mappings between pointers and integers ([§23.5.1](unsafe-code.md#2351-general)). 1. The effect of applying the unary `*` operator to a `null` pointer ([§23.6.2](unsafe-code.md#2362-pointer-indirection)). +1. The type of exception thrown when the *primary_expression* of an *invocation_expression* is a function pointer with value `null`, and an attempt is made to invoke the (non-existent) pointed-to method (§invocation-expressions). 1. The behavior when pointer arithmetic overflows the domain of the pointer type ([§23.6.6](unsafe-code.md#2366-pointer-increment-and-decrement), [§23.6.7](unsafe-code.md#2367-pointer-arithmetic)). 1. The result of the `sizeof` operator for non-pre-defined value types ([§23.6.9](unsafe-code.md#2369-the-sizeof-operator)). 1. The behavior of the `fixed` statement if the array expression is `null` or if the array has zero elements ([§23.7](unsafe-code.md#237-the-fixed-statement)). From 0b6d66ba300d3768459aeff4db710715fff4b532 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 6 Nov 2023 07:12:48 -0500 Subject: [PATCH 05/11] fix md and links --- standard/unsafe-code.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 22471cc43..313ea15ab 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -190,7 +190,7 @@ dataptr_type A *dataptr_type* is written as an *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) followed by one or more `*` tokens. > *Example*: Some examples of data pointer types are given in the table below: -> +> > **Example** | **Description** > --------- | ----------- > `byte*` | Pointer to `byte` @@ -200,7 +200,9 @@ A *dataptr_type* is written as an *unmanaged_type* ([§8.8](types.md#88-unmanage > `delegate**` | Pointer to a pointer to a static method having no parameters and a `void` return type > > *end example* + + > *Note*: Unlike C and C++, when multiple pointers are declared in the same declaration, in C# the `*` is written along with the underlying type only, not as a prefix punctuator on each pointer name. For example: > > ```csharp @@ -209,10 +211,12 @@ A *dataptr_type* is written as an *unmanaged_type* ([§8.8](types.md#88-unmanage > > *end note* -The value of a data pointer having type `T*` represents the address of a variable of type `T`. The pointer indirection operator `*` ([§22.6.2](unsafe-code.md#2262-pointer-indirection)) can be used to access this variable. Applying the indirection operator to a null pointer results in implementation-defined behavior ([§22.6.2](unsafe-code.md#2262-pointer-indirection)). +The value of a data pointer having type `T*` represents the address of a variable of type `T`. The pointer indirection operator `*` ([§23.6.2](unsafe-code.md#2362-pointer-indirection)) can be used to access this variable. Applying the indirection operator to a null pointer results in implementation-defined behavior. > *Example*: Given a variable `P` of type `int*`, the expression `*P` denotes the `int` variable found at the address contained in `P`. *end example* + + > *Note*: Although data pointers can be passed as `ref` or `out` parameters, doing so can cause undefined behavior, since the pointer might well be set to point to a local variable that no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example: > > @@ -245,7 +249,7 @@ The value of a data pointer having type `T*` represents the address of a variabl > int* px2 = &i; > F(out px1, ref px2); > // Undefined behavior -> Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}", +> Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}"); > } > } > } @@ -308,16 +312,18 @@ The non-null value of a function pointer having type `T` represents the address If no *calling_convention_specifier* is provided, the default is `managed`, which results in the execution environment’s default mechanism being used. Specific unmanaged conventions can be specified using *unmanaged_calling_convention* whose tokens are mapped to implementation-defined names having implementation-defined semantics. The set of valid combinations of these tokens is implementation-defined. > *Note*: The *calling_convention_specifier* allows a potentially more efficient calling mechanism to be chosen, or for methods written in languages other than C# to be called. *end note*. + + > *Example*: Some examples of function pointer types are given in the table below: -> +> > **Example** | **Description** > --------- | ----------- > `delegate*` | Pointer to a managed method having no parameters and a `void` return type > `delegate*` | Pointer to a managed method having two `string` parameters and a `bool` return type > `delegate*` | Pointer to a managed method having no parameters and returning a `ref readonly int` > `delegate*, void>` | Pointer to a managed method having one parameter that is a pointer to a method having no parameters and an `int` return type, and a `void` return type -> `delegate* unmanaged[Stdcall] ` | Pointer to an unmanaged method having no parameters and a `void` return type, using the `Stdcall` calling convention +> `delegate* unmanaged[Stdcall]` | Pointer to an unmanaged method having no parameters and a `void` return type, using the `Stdcall` calling convention > > Consider the following: > @@ -367,7 +373,7 @@ In an unsafe context, the following constructs are available for operating on fu - The `&` operator may be used to obtain the address of a static method ([§23.6.5](unsafe-code.md#2365-the-address-of-operator)) - The `==`, `!=`, `<`, `>`, `<=`, and `=>` operators may be used to compare pointers ([§23.6.8](unsafe-code.md#2368-pointer-comparison)). -- The invocation_expression operator, `()`, may be used to call the method being pointed to ([§13.8.9.1](expressions.md#12891-general)). +- The invocation_expression operator, `()`, may be used to call the method being pointed to ([§12.8.9.1](expressions.md#12891-general)). ### §void-pointers Void pointers @@ -382,7 +388,7 @@ voidptr_type A *voidptr_type* is written as the keyword `void` followed by one or more `*` tokens. > *Example*: Some examples of void-pointer types are given in the table below: -> +> > **Example** | **Description** > --------- | ----------- > `void*` | Pointer to unknown type @@ -747,9 +753,9 @@ In an unsafe context, a method `M` is compatible with a *funcptr_type* `F` if al An implicit conversion exists from a *unary_expression* whose target is a method group `E`, to a compatible function pointer type `F` if `E` contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of `F`, as described in the following: - A single method `M` is selected corresponding to a method invocation of the form `E(A)` with the following modifications: - - The arguments list `A` is a list of expressions, each classified as a variable and with the type and modifier of the corresponding *funcptr_parameter_list* of `F`. - - The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form. - - The candidate methods are only those methods that are static. + - The arguments list `A` is a list of expressions, each classified as a variable and with the type and modifier of the corresponding *funcptr_parameter_list* of `F`. + - The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form. + - The candidate methods are only those methods that are static. - If the algorithm of overload resolution produces an error, then a compile-time error occurs. Otherwise, the algorithm produces a single best method `M` having the same number of parameters as `F` and the conversion is considered to exist. - The selected method `M` must be compatible (as defined above) with the function pointer type `F`. Otherwise, a compile-time error occurs. - The result of the conversion is a function pointer of type `F`. From 1ab565d73d1287e5d00cfc0e97b7b32b5988c0e0 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 6 Nov 2023 07:25:51 -0500 Subject: [PATCH 06/11] fix md --- standard/unsafe-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 313ea15ab..89b8999bf 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -425,7 +425,7 @@ In an unsafe context, the set of available implicit conversions ([§10.2](conver - From any *pointer_type* to the type `void*`. - From *null_literal* ([§6.4.5.7](lexical-structure.md#6457-the-null-literal)) to any *pointer_type*. -- From _*funcptr_type* `F0` to *funcptr_type* `F1`, provided all of the following are true: +- From *funcptr_type* `F0` to *funcptr_type* `F1`, provided all of the following are true: - `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter `D1n` in `F1`. - For each value parameter, an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `F0` to the corresponding parameter type in `F1`. - For each `ref`, `out`, or `in` parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`. From 482973942aba8e361360c1fa90978e568052b9ad Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 16 Jun 2024 17:43:18 -0400 Subject: [PATCH 07/11] Add support for function pointers --- standard/unsafe-code.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 89b8999bf..34a952088 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -545,6 +545,8 @@ In an unsafe context, the *primary_no_array_creation_expression* ([§12.8](expre > *Note*: The precedence and associativity of the unsafe operators is implied by the grammar. *end note* +All aspects of type inferencing with regard to function pointers are described in the corresponding subclauses of [§12.6](expressions.md#126-function-members) and [§12.8](expressions.md#128-primary-expressions). + ### 23.6.2 Pointer indirection A *pointer_indirection_expression* consists of an asterisk (`*`) followed by a *unary_expression*. From 6fb2281785d86b102356347903d7adf9db429a5f Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 16 Jun 2024 17:46:37 -0400 Subject: [PATCH 08/11] Add support for function pointers --- standard/standard-library.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/standard/standard-library.md b/standard/standard-library.md index 79a6d3c3c..ed18ee164 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -526,6 +526,14 @@ namespace System.Runtime.CompilerServices public TResult GetResult(); } + [System.AttributeUsage(System.AttributeTargets.Method, Inherited=false)] + public sealed class UnmanagedCallersOnlyAttribute : Attribute + { + public UnmanagedCallersOnlyAttribute (); + public Type[]? CallConvs; + public string? EntryPoint; + } + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion { From 295c5178e94c8cf446e0da746934d5528ad07ded Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 16 Jun 2024 17:58:17 -0400 Subject: [PATCH 09/11] Add support for function pointers --- standard/expressions.md | 61 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index d2c0df118..0383184f8 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -782,10 +782,18 @@ The second phase proceeds as follows: If `E` is a method group or implicitly typed anonymous function and `T` is a delegate type or expression tree type then all the parameter types of `T` are *input types of* `E` *with type* `T`. +If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then all the parameter types of `T` are input types of `E` with type `T`. + +> *Note*: This is only applicable in unsafe code. *end note* + #### 12.6.3.5 Output types If `E` is a method group or an anonymous function and `T` is a delegate type or expression tree type then the return type of `T` is an *output type of* `E` *with type* `T`. +If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then the return type of `T` is an output type of `E` with type `T`. + +> *Note*: This is only applicable in unsafe code. *end note* + #### 12.6.3.6 Dependence An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type variable `Xₑ` if for some argument `Eᵥ` with type `Tᵥ` `Xₑ` occurs in an *input type* of `Eᵥ` with type `Tᵥ` and `Xᵢ` occurs in an *output type* of `Eᵥ` with type `Tᵥ`. @@ -798,6 +806,8 @@ An *output type inference* is made *from* an expression `E` *to* a type T in t - If `E` is an anonymous function with inferred return type `U` ([§12.6.3.13](expressions.md#126313-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) 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ₓ`. +- If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then 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`. + > *Note*: This is only applicable in unsafe code. *end note* - 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. @@ -829,14 +839,25 @@ A *lower-bound inference from* a type `U` *to* a type `V` is made as follows: - `V` is an array type `V₁[...]`and `U` is an array type `U₁[...]`of the same rank - `V` is one of `IEnumerable`, `ICollection`, `IReadOnlyList>`, `IReadOnlyCollection` or `IList` and `U` is a single-dimensional array type `U₁[]` - `V` is a constructed `class`, `struct`, `interface` or `delegate` type `C` and there is a unique type `C` such that `U` (or, if `U` is a type `parameter`, its effective base class or any member of its effective interface set) is identical to, `inherits` from (directly or indirectly), or implements (directly or indirectly) `C`. + - `V` is a function pointer type (§function-pointers) `delegate*` and there is a function pointer type `delegate*` such that `U` is identical to `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. + > *Note*: This is only applicable in unsafe code. *end note* - (The “uniqueness” restriction means that in the case interface `C{} class U: C, C{}`, then no inference is made when inferring from `U` to `C` because `U₁` could be `X` or `Y`.) If any of these cases apply then an inference is made from each `Uᵢ` to the corresponding `Vᵢ` as follows: - - If `Uᵢ` is not known to be a reference type then an *exact inference* is made + - If `Uᵢ` is not known to be a reference type then an *exact inference* is made; or alternatively, if `U` is not a function pointer type and `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` is not known to be a function pointer type or a reference type, then an exact inference is made + > *Note*: This is only applicable in unsafe code. *end note* - Otherwise, if `U` is an array type then a *lower-bound inference* is made - Otherwise, if `V` is `C` then inference depends on the `i-th` type parameter of `C`: - If it is covariant then a *lower-bound inference* is made. - If it is contravariant then an *upper-bound inference* is made. - If it is invariant then an *exact inference* is made. + - Otherwise, if `V` is `delegate*` then inference depends on the i-th parameter of `delegate*`: + - If V1: + - If the return is by value, then a lower-bound inference is made. + - If the return is by reference, then an exact inference is made. + - If V2..Vk: + - If the parameter is by value, then an upper-bound inference is made. + - If the parameter is by reference, then an exact inference is made. + > *Note*: This is only applicable in unsafe code. *end note* - Otherwise, no inferences are made. #### 12.6.3.11 Upper-bound inferences @@ -849,14 +870,25 @@ An *upper-bound inference from* a type `U` *to* a type `V` is made as follows: - `U` is one of `IEnumerable`, `ICollection`, `IReadOnlyList`, `IReadOnlyCollection` or `IList` and `V` is a single-dimensional array type `Vₑ[]` - `U` is the type `U1?` and `V` is the type `V1?` - `U` is constructed class, struct, interface or delegate type `C` and `V` is a `class, struct, interface` or `delegate` type which is `identical` to, `inherits` from (directly or indirectly), or implements (directly or indirectly) a unique type `C` + - `U` is a function pointer type (§function-pointers) then `delegate*` and `V` is a function pointer type which is identical to `delegate*`, and the calling convention of `U` is identical to `V`, and the refness of `Ui` is identical to `Vi`. + > *Note*: This is only applicable in unsafe code. *end note* - (The “uniqueness” restriction means that given an interface `C{} class V: C>, C>{}`, then no inference is made when inferring from `C` to `V`. Inferences are not made from `U₁` to either `X` or `Y`.) If any of these cases apply then an inference is made from each `Uᵢ` to the corresponding `Vᵢ` as follows: - - If `Uᵢ` is not known to be a reference type then an *exact inference* is made + - If `U` is not a function pointer type and `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` is not known to be a function pointer type or a reference type, then an _exact inference_ is made + > *Note*: Function-pointer type-related text is only applicable in unsafe code. *end note* - Otherwise, if `V` is an array type then an *upper-bound inference* is made - Otherwise, if `U` is `C` then inference depends on the `i-th` type parameter of `C`: - If it is covariant then an *upper-bound inference* is made. - If it is contravariant then a *lower-bound inference* is made. - If it is invariant then an *exact inference* is made. +- Otherwise, if `U` is `delegate*` then inference depends on the i-th parameter of `delegate*`: + - If `U1`: + - If the return is by value, then an upper-bound inference is made. + - If the return is by reference, then an exact inference is made. + - If `U2`..`Uk`: + - If the parameter is by value, then a lower-bound inference is made. + - If the parameter is by reference, then an exact inference is made. + > *Note*: This is only applicable in unsafe code. *end note* - Otherwise, no inferences are made. #### 12.6.3.12 Fixing @@ -1082,6 +1114,10 @@ In case the parameter type sequences `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂ - If for at least one parameter `Mᵥ` uses the ***better parameter-passing choice*** ([§12.6.4.4](expressions.md#12644-better-parameter-passing-mode)) than the corresponding parameter in `Mₓ` and none of the parameters in `Mₓ` use the better parameter-passing choice than `Mᵥ`, `Mᵥ` is better than `Mₓ`. - Otherwise, no function member is better. +A `delegate*` is more specific than `void*`. + +> *Note*: This is only applicable in unsafe code, and permits overloading on `void*` and a `delegate*` allowing the `&` operator to distinguish between the two. *end note* + #### 12.6.4.4 Better parameter-passing mode It is permitted to have corresponding parameters in two overloaded methods differ only by parameter-passing mode provided one of the two parameters has value-passing mode, as follows: @@ -1102,6 +1138,8 @@ Given an implicit conversion `C₁` that converts from an expression `E` to a ty - `E` exactly matches `T₁` and `E` does not exactly match `T₂` ([§12.6.4.6](expressions.md#12646-exactly-matching-expression)) - `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a better conversion target than `T₂` ([§12.6.4.7](expressions.md#12647-better-conversion-target)) + - `V` is a function pointer type `delegate*` and `U` is a function pointer type `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. + > *Note*: This is only applicable in unsafe code. *end note* - `E` is a method group ([§12.2](expressions.md#122-expression-classifications)), `T₁` is compatible ([§20.4](delegates.md#204-delegate-compatibility)) with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂` #### 12.6.4.6 Exactly matching expression @@ -1845,6 +1883,25 @@ The result of evaluating an *invocation_expression* is classified as follows: - Otherwise, if the *invocation_expression* invokes a returns-by-ref method ([§15.6.1](classes.md#1561-general)) or a returns-by-ref delegate, the result is a variable with an associated type of the return type of the method or delegate. If the invocation is of an instance method, and the receiver is of a class type `T`, the associated type is picked from the first declaration or override of the method found when starting with `T` and searching through its base classes. - Otherwise, the *invocation_expression* invokes a returns-by-value method ([§15.6.1](classes.md#1561-general)) or returns-by-value delegate, and the result is a value, with an associated type of the return type of the method or delegate. If the invocation is of an instance method, and the receiver is of a class type `T`, the associated type is picked from the first declaration or override of the method found when starting with `T` and searching through its base classes. +> *Note*: The following is only applicable in unsafe code. *end note* + +The *method_declaration* [§15.6.1](classes.md#1561-general) for an unmanaged method shall have the attribute `System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute`. The use of this attribute on a method results in the following constraints: + +- It is an error to directly call that method from C#. Instead, one can obtain a function pointer (§function-pointers) to that method and then invoke the method via that pointer. +- It is an error for that method to have a parameter or return type that is not an `unmanaged_type` ([§8.8](types.md#88-unmanaged-types)). +- It is an error for that method to have type parameters, even if those type parameters are constrained to `unmanaged`. +- It is an error for that method to be in a generic type. +- It is an error to convert that method to a delegate type. + +For a function pointer invocation, the *primary_expression* of the *invocation_expression* shall be a value of a *funcptr_type*. Furthermore, considering the method being pointed to to be a function member with the same parameter list as the *funcptr_type*, the *funcptr_type* shall be applicable ([§12.6.4.2](expressions.md#12642-applicable-function-member)) with respect to the *argument_list* of the *invocation_expression*. + +The run-time processing of a function pointer invocation of the form `F(A)`, where `F` is a *primary_expression* of a *funcptr_type* and `A` is an optional *argument_list*, consists of the following steps: + +- `F` is evaluated. If this evaluation causes an exception, no further steps are executed. +- The argument list `A` is evaluated. If this evaluation causes an exception, no further steps are executed. +- The value of `F` is checked to be valid. If that value is `null`, an implementation-defined exception is thrown, and no further steps are executed. +- Otherwise, `F` points to a method. Function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed on the method to which `F` points. + #### 12.8.9.2 Method invocations For a method invocation, the *primary_expression* of the *invocation_expression* shall be a method group. The method group identifies the one method to invoke or the set of overloaded methods from which to choose a specific method to invoke. In the latter case, determination of the specific method to invoke is based on the context provided by the types of the arguments in the *argument_list*. From 9d56298dc138c31b33ab40fed422f5ade2144136 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 16 Jun 2024 18:10:58 -0400 Subject: [PATCH 10/11] disable undefined output --- standard/unsafe-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 34a952088..c5eadf898 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -249,7 +249,7 @@ The value of a data pointer having type `T*` represents the address of a variabl > int* px2 = &i; > F(out px1, ref px2); > // Undefined behavior -> Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}"); +> // Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}"); > } > } > } From fc84a7e1fb8a1f5c7a2cc1ef8ac598b43d5a74ec Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Thu, 12 Sep 2024 05:30:45 -0400 Subject: [PATCH 11/11] Update unsafe-code.md --- standard/unsafe-code.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index c5eadf898..cda7803a9 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -217,7 +217,7 @@ The value of a data pointer having type `T*` represents the address of a variabl -> *Note*: Although data pointers can be passed as `ref` or `out` parameters, doing so can cause undefined behavior, since the pointer might well be set to point to a local variable that no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example: +> *Note*: Although data pointers can be passed as reference or output parameters, doing so can cause undefined behavior, since the pointer might well be set to point to a local variable that no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example: > > > @@ -415,7 +415,7 @@ In precise terms, a fixed variable is one of the following: All other variables are classified as moveable variables. -A static field is classified as a moveable variable. Also, an `in`, `out`, or `ref` parameter is classified as a moveable variable, even if the argument given for the parameter is a fixed variable. Finally, a variable produced by dereferencing a pointer is always classified as a fixed variable. +A static field is classified as a moveable variable. Also, a by-reference parameter is classified as a moveable variable, even if the argument given for the parameter is a fixed variable. Finally, a variable produced by dereferencing a pointer is always classified as a fixed variable. ## 23.5 Pointer conversions @@ -426,9 +426,9 @@ In an unsafe context, the set of available implicit conversions ([§10.2](conver - From any *pointer_type* to the type `void*`. - From *null_literal* ([§6.4.5.7](lexical-structure.md#6457-the-null-literal)) to any *pointer_type*. - From *funcptr_type* `F0` to *funcptr_type* `F1`, provided all of the following are true: - - `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter `D1n` in `F1`. + - `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same by-reference parameter modifiers as the corresponding parameter `D1n` in `F1`. - For each value parameter, an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `F0` to the corresponding parameter type in `F1`. - - For each `ref`, `out`, or `in` parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`. + - For each by-reference parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`. - If the return type is by value, an identity, implicit reference, or implicit pointer conversion exists from the return type of `F1` to the return type of `F0`. - If the return type is by reference, the return type and `ref` modifiers of `F1` are the same as the return type and `ref` modifiers of `F0`. - The calling convention of `F0` is the same as the calling convention of `F1`. @@ -746,7 +746,7 @@ In an unsafe context, a method `M` is compatible with a *funcptr_type* `F` if al - `M` and `F` have the same number of parameters, and each parameter in `M` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter in `F`. - For each value parameter, an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `M` to the corresponding parameter type in `F`. -- For each `ref`, `out`, or `in` parameter, the parameter type in `M` is the same as the corresponding parameter type in `F`. +- For each by-reference parameter, the parameter type in `M` is the same as the corresponding parameter type in `F`. - If the return type is by value, an identity, implicit reference, or implicit pointer conversion exists from the return type of `F` to the return type of `M`. - If the return type is by reference, the return type and `ref` modifiers of `F` are the same as the return type and `ref` modifiers of `M`. - The calling convention of `M` is the same as the calling convention of `F`. @@ -880,7 +880,7 @@ fixed_pointer_initializer ; ``` -Each *fixed_pointer_declarator* declares a local variable of the given *pointer_type* and initializes that local variable with the address computed by the corresponding *fixed_pointer_initializer*. *pointer_type* shall not be *funcptr_type*. A local variable declared in a fixed statement is accessible in any *fixed_pointer_initializer*s occurring to the right of that variable’s declaration, and in the *embedded_statement* of the fixed statement. A local variable declared by a fixed statement is considered read-only. A compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the `++` and `--` operators) or pass it as a `ref` or `out` parameter. +Each *fixed_pointer_declarator* declares a local variable of the given *pointer_type* and initializes that local variable with the address computed by the corresponding *fixed_pointer_initializer*. *pointer_type* shall not be *funcptr_type*. A local variable declared in a fixed statement is accessible in any *fixed_pointer_initializer*s occurring to the right of that variable’s declaration, and in the *embedded_statement* of the fixed statement. A local variable declared by a fixed statement is considered read-only. A compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the `++` and `--` operators) or pass it as a reference or output parameter. It is an error to use a captured local variable ([§12.19.6.2](expressions.md#121962-captured-outer-variables)), value parameter, or parameter array in a *fixed_pointer_initializer*. A *fixed_pointer_initializer* can be one of the following: