Skip to content

Commit

Permalink
C# 7.x: Add initializer list to stackalloc (#238)
Browse files Browse the repository at this point in the history
* Update unsafe-code.md

* Update unsafe-code.md

* Move most of stackalloc spec from unsafe to here

* Impact of moving most of stackalloc spec from unsafe to expressions

* Moved most of stackalloc spec to expressions

* Fix links to new stackalloc spec location

* Add Span & ReadOnlySpan types

* fix build issues

* address feedback

* Stack initializers are only allowed as local variable initializers

This clarifies and simplifies some of the language for this PR.

* fix markdown lint issue

* respond to feedback.

* fix build issues

* one more round of build issues

* respond to feedback.

* decisions from 5/17 meeting.

* add safe context rules.

---------

Co-authored-by: Bill Wagner <wiwagn@microsoft.com>
  • Loading branch information
RexJaeschke and BillWagner authored May 19, 2023
1 parent 50ff244 commit 3f4f7f6
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 41 deletions.
84 changes: 79 additions & 5 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ The precedence of an operator is established by the definition of its associated
>
> | **Subclause** | **Category** | **Operators** |
> | ----------------- | ------------------------------- | -------------------------------------------------------|
> | [§12.8](expressions.md#128-primary-expressions) | Primary | `x.y` `x?.y` `f(x)` `a[x]` `a?[x]` `x++` `x--` `new` `typeof` `default` `checked` `unchecked` `delegate` |
> | [§12.8](expressions.md#128-primary-expressions) | Primary | `x.y` `x?.y` `f(x)` `a[x]` `a?[x]` `x++` `x--` `new` `typeof` `default` `checked` `unchecked` `delegate` `stackalloc` |
> | [§12.9](expressions.md#129-unary-operators) | Unary | `+` `-` `!` `~` `++x` `--x` `(T)x` `await x` |
> | [§12.10](expressions.md#1210-arithmetic-operators) | Multiplicative | `*` `/` `%` |
> | [§12.10](expressions.md#1210-arithmetic-operators) | Additive | `+` `-` |
Expand Down Expand Up @@ -1280,6 +1280,7 @@ primary_no_array_creation_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
```
Expand Down Expand Up @@ -1431,22 +1432,22 @@ Six of the lexical rules defined above are *context sensitive* as follows:
| *Interpolated_Regular_String_End* | Only recognised after an *Interpolated_Regular_String_Start* and only if any intervening tokens are either *Interpolated_Regular_String_Mid*s or tokens that can be part of *regular_interpolation*s, including tokens for any *interpolated_regular_string_expression*s contained within such interpolations. |
| *Interpolated_Verbatim_String_Mid* *Verbatim_Interpolation_Format* *Interpolated_Verbatim_String_End* | Recognition of these three rules follows that of the corresponding rules above with each mentioned *regular* grammar rule replaced by the corresponding *verbatim* one. |

> *Note:* The above rules are context sensitive as their definitions overlap with those of
> *Note*: The above rules are context sensitive as their definitions overlap with those of
other tokens in the language. *end note*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note:* The above grammar is not ANTLR-ready due to the context sensitive lexical rules. As with
> *Note*: The above grammar is not ANTLR-ready due to the context sensitive lexical rules. As with
other lexer generators ANTLR supports context sensitive lexical rules, for example using its *lexical modes*,
but this is an implementation detail and therefore not part of this Standard. *end note*

An *interpolated_string_expression* is classified as a value. If it is immediately converted to `System.IFormattable` or `System.FormattableString` with an implicit interpolated string conversion ([§10.2.5](conversions.md#1025-implicit-interpolated-string-conversions)), the interpolated string expression has that type. Otherwise, it has the type `string`.

> *Note:* The differences between the possible types an *interpolated_string_expression* may be determined from the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). *end note*
> *Note*: The differences between the possible types an *interpolated_string_expression* may be determined from the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). *end note*
The meaning of an interpolation, both *regular_interpolation* and *verbatim_interpolation*, is to format the value of the *expression* as a `string` either according to the format specified by the *Regular_Interpolation_Format* or *Verbatim_Interpolation_Format*, or according to a default format for the type of *expression*. The formatted string is then modified by the *interpolation_minimum_width*, if any, to produce the final `string` to be interpolated into the *interpolated_string_expression*.

> *Note:* How the default format for a type is determined is detailed in the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). Descriptions of standard formats, which are identical for *Regular_Interpolation_Format* and *Verbatim_Interpolation_Format*, may be found in the documentation for `System.IFormattable` ([§C.4](standard-library.md#c4-format-specifications)) and in other types in the standard library ([§C](standard-library.md#annex-c-standard-library)). *end note*
> *Note*: How the default format for a type is determined is detailed in the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). Descriptions of standard formats, which are identical for *Regular_Interpolation_Format* and *Verbatim_Interpolation_Format*, may be found in the documentation for `System.IFormattable` ([§C.4](standard-library.md#c4-format-specifications)) and in other types in the standard library ([§C](standard-library.md#annex-c-standard-library)). *end note*
In an *interpolation_minimum_width* the *constant_expression* shall have an implicit conversion to `int`. Let the *field width* be the absolute value of this *constant_expression* and the *alignment* be the sign (positive or negative) of the value of this *constant_expression*:

Expand Down Expand Up @@ -3053,6 +3054,79 @@ A *default_value_expression* is a constant expression ([§12.23](expressions.md#
- one of the following value types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,`; or
- any enumeration type.

### §stack-allocation Stack allocation

A stack allocation expression allocates a block of memory from the execution stack. The ***execution stack*** is an area of memory where local variables are stored. The execution stack is not part of the managed heap. The memory used for local variable storage is automatically recovered when the current function returns.

The result of a stack allocation may not be copied out of its safe-context (§safe-context-rules). The safe context rules for a stack allocation expression are described in §safe-context-rules-stackalloc.

```ANTLR
stackalloc_expression
: 'stackalloc' unmanaged_type '[' expression ']'
| 'stackalloc' unmanaged_type? '[' expression? ']' stackalloc_initializer
;
stackalloc_initializer
: '{' stackalloc_initializer_element_list '}'
;
stackalloc_initializer_element_list
: stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
;
stackalloc_element_initializer
: expression
;
```

The *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) indicates the type of the items that will be stored in the newly allocated location, and the *expression* indicates the number of these items. Taken together, these specify the required allocation size. As the size of a stack allocation cannot be negative, it is a compile-time error to specify the number of items as a *constant_expression* that evaluates to a negative value.

If *unmanaged_type* is omitted, it is inferred from the corresponding *stackalloc_initializer*. If *expression* is omitted from *stackalloc_expression*, it is inferred to be the number of *stackalloc_element_initializer*s in the corresponding *stackalloc_initializer*.

When a *stackalloc_expression* includes both *expression* and *stackalloc_initializer*, the *expression* shall be a *constant_expression* and the number of elements in that *stackalloc_initializer* shall match the value of *expression*.

A stack allocation initializer of the form `stackalloc T[E]` requires `T` to be an *unmanaged_type* and `E` to be an expression implicitly convertible to type `int`. The operator allocates `E * sizeof(T)` bytes from the call stack. The result is a pointer, of type `T*`, to the newly allocated block. For use in safe contexts, a *stackalloc_expression* has an implicit conversion from `T*` to `Span<T>`. As pointer contexts require unsafe code, see §stack-allocation for more information.

If `E` is a negative value, then the behavior is undefined. If `E` is zero, then no allocation is made, and the value returned is implementation-defined. If there is not enough memory available to allocate a block of the given size, a `System.StackOverflowException` is thrown.

When *stackalloc_initializer* is present, the *stackalloc_initializer_element_list* shall consist of a sequence of expressions, each having an implicit conversion to *unmanaged_type* ([§10.2](conversions.md#102-implicit-conversions)). The expressions initialize elements in the allocated memory in increasing order, starting with the element at index zero. In the absence of a *stackalloc_initializer*, the content of the newly allocated memory is undefined.

Access via an instance of `System.Span<T>` to the elements of an allocated block is range checked.

Stack allocation initializers are not permitted in `catch` or `finally` blocks ([§13.11](statements.md#1311-the-try-statement)).

> *Note*: Stack allocation initializers are allowed in `async` methods, but their return value can't be assigned. Neither pointers nor `ref struct` types, like `Span<T>` are allowed in `async` methods. *end note*
<!-- markdownlint-disable MD028 -->
<!-- markdownlint-enable MD028 -->
> *Note*: There is no way to explicitly free memory allocated using `stackalloc`. *end note*
All stack-allocated memory blocks created during the execution of a function member are automatically discarded when that function member returns.

Except for the `stackalloc` operator, C# provides no predefined constructs for managing non-garbage collected memory. Such services are typically provided by supporting class libraries or imported directly from the underlying operating system.

> *Example*:
>
> ```csharp
> Span<int> span1 = stackalloc int[3]; // memory uninitialized
> Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // memory initialized
> Span<int> span3 = stackalloc[] { 11, 12, 13 }; // type int is inferred
> var spn4 = stackalloc[] { 11, 12, 13 }; // error; result is int*, not allowed in a safe context
> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // error; no conversion from Span<int> to Span<long>
> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // converts 11 and 13, and returns Span<long>
> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // converts all and returns Span<long>
> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // implicit conversion of Span<T>
> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; // implicit conversion of Span<T>
>
> public class Widget<T>
> {
> public static implicit operator Widget<T>(Span<double> sp) { return null; }
> }
> ```
>
> In the case of `span8`, `stackalloc` results in a `Span<int>`, which is converted by an implicit operator to `ReadOnlySpan<int>`. Similarly, for `span9`, the resulting `Span<double>` is converted to the user-defined type `Widget<double> using the conversion, as shown.
> *end example*
### 12.8.21 Nameof expressions
A *nameof_expression* is used to obtain the name of a program entity as a constant string.
Expand Down
17 changes: 8 additions & 9 deletions standard/portability-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,34 @@ 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 awaiters 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. 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. 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 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. 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` ([§23.9](unsafe-code.md#239-stack-allocation)).
1. Attempting to allocate a negative number of items using `stackalloc` ([§23.9](unsafe-code.md#239-stack-allocation)).
1. The content of memory newly allocated by `stackalloc` (§stack-allocation).
1. Attempting to allocate a negative number of items using `stackalloc`stack-allocation).

## B.3 Implementation-defined behavior

A conforming implementation is required to document its choice of behavior in each of the areas listed in this subclause. The following are implementation-defined:

1. The behavior when an identifier not in Normalization Form C is encountered ([§6.4.3](lexical-structure.md#643-identifiers)).
1. The maximum value allowed for `Decimal_Digit+` in `PP_Line_Indicator` ([§6.5.8](lexical-structure.md#658-line-directives)).
1. The interpretation of the *input_characters* in the *pp_pragma-text* of a `#pragma` directive ([§6.5.9](lexical-structure.md#659-pragma-directives)).
1. The interpretation of the *input_characters* in the *pp_pragma-text* of a #pragma directive ([§6.5.9](lexical-structure.md#659-pragma-directives)).
1. The values of any application parameters passed to `Main` by the host environment prior to application startup ([§7.1](basic-concepts.md#71-application-startup)).
1. The precise structure of the expression tree, as well as the exact process for creating it, when an anonymous function is converted to an expression tree ([§10.7.3](conversions.md#1073-evaluation-of-lambda-expression-conversions-to-expression-tree-types)).
1. The precise structure of the expression tree, as well as the exact process for creating it, when an anonymous function is converted to an expression-tree ([§10.7.3](conversions.md#1073-evaluation-of-lambda-expression-conversions-to-expression-tree-types)).
1. Whether a `System.ArithmeticException` (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand, when in an `unchecked` context and the left operand of an integer division is the maximum negative `int` or `long` value and the right operand is `–1` ([§12.10.3](expressions.md#12103-division-operator)).
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 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 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 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)).
1. The behavior of the `fixed` statement if the string expression is `null` ([§23.7](unsafe-code.md#237-the-fixed-statement)).
1. The value returned when a stack allocation of size zero is made ([§23.9](unsafe-code.md#239-stack-allocation)).
1. The value returned when a stack allocation of size zero is made (§stack-allocation).

## B.4 Unspecified behavior

Expand Down
16 changes: 16 additions & 0 deletions standard/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,22 @@ namespace System.Threading.Tasks
}
```

```csharp
namespace System
{
public ref struct ReadOnlySpan<T>
{
}
}
namespace System
{
public ref struct Span<T>
{
public static implicit operator ReadOnlySpan<T>(Span<T> span);
}
}
```

## C.4 Format Specifications

The meaning of the formats, as used in interpolated string expressions ([§12.8.3](expressions.md#1283-interpolated-string-expressions)), are defined in ISO/IEC 23271:2012. For convenience the following text is copied from the description of `System.IFormatable`.
Expand Down
3 changes: 0 additions & 3 deletions standard/statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,9 @@ local_variable_declarator
local_variable_initializer
: 'ref'? expression
| array_initializer
| stackalloc_initializer // unsafe code support
;
```

*stackalloc_initializer* ([§23.9](unsafe-code.md#239-stack-allocation)) is only available in unsafe code ([§23](unsafe-code.md#23-unsafe-code)).

The *local_variable_type* of a *local_variable_declaration* either directly specifies the type of the variables introduced by the declaration, or indicates with the identifier `var` that the type should be inferred based on an initializer. The type is followed by a list of *local_variable_declarator*s, each of which introduces a new variable. A *local_variable_declarator* consists of an *identifier* that names the variable, optionally followed by an “`=`” token and a *local_variable_initializer* that gives the initial value of the variable. However, it is a compile-time error to omit *local_variable_initializer* from a *local_variable_declarator* for a variable declared `ref` or `ref readonly`.

The *expression* in a *local_variable_initializer* for a variable declared `ref` or `ref readonly` shall be a variable. It is a compile time error if the scope of the local variable is wider than the *ref_safe_scope* of the *local_variable_initializer* expression (§ref-safe-contexts).
Expand Down
Loading

0 comments on commit 3f4f7f6

Please sign in to comment.