Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions Compilation/ILCompiler.AsyncGenerators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,19 +222,21 @@ private void EmitAsyncGeneratorMoveNextAsyncBody(AsyncGeneratorStateMachineBuild
/// Emits the body of an instance async generator method using a state machine.
/// Called for class methods marked with both IsAsync and IsGenerator = true.
/// </summary>
private void EmitAsyncGeneratorMethodBody(MethodBuilder methodBuilder, Stmt.Function method, FieldInfo fieldsField,
string? currentClassName = null)
private void EmitAsyncGeneratorMethodBody(MethodBuilder methodBuilder, Stmt.Function method, FieldInfo? fieldsField,
bool isInstanceMethod = true, string? currentClassName = null)
{
// Analyze async generator function to determine yield/await points and hoisted variables
var analysis = _asyncGenerators.Analyzer.Analyze(method);

// Build state machine type for instance method. Use the MethodBuilder's (mangled) name rather
// than method.Name.Lexeme so a private async generator's `#p` lexeme doesn't put a `#` in the name.
// Build state machine type. A static async generator method (#778) has no `this`/instance fields,
// so it is set up like a free function (isInstanceMethod: false, static stub). Use the
// MethodBuilder's (mangled) name rather than method.Name.Lexeme so a private async generator's
// `#p` lexeme doesn't put a `#` in the name.
var smBuilder = new AsyncGeneratorStateMachineBuilder(_moduleBuilder, _types, _asyncGenerators.StateMachineCounter++);
smBuilder.DefineStateMachine(
$"{methodBuilder.DeclaringType!.Name}_{methodBuilder.Name}",
analysis,
isInstanceMethod: true, // This is an instance method
isInstanceMethod: isInstanceMethod,
runtime: _runtime
);

Expand All @@ -246,15 +248,21 @@ private void EmitAsyncGeneratorMethodBody(MethodBuilder methodBuilder, Stmt.Func
if (methodDCKey != null && _closures.FunctionDisplayClasses.TryGetValue(methodDCKey, out var methodFuncDC))
smBuilder.DefineFunctionDisplayClassField(methodFuncDC);

// Emit stub method body (creates state machine and returns it)
EmitAsyncGeneratorInstanceStubMethod(methodBuilder, smBuilder, method, methodDCKey);
// Emit stub method body (creates state machine and returns it). A static async generator method
// (#778) has no `this` and no function-DC write-capture support (it is not registered in
// RegisterGeneratorMethodFunctionDisplayClasses, so methodDCKey is null), mirroring the sync
// static generator (#692).
if (isInstanceMethod)
EmitAsyncGeneratorInstanceStubMethod(methodBuilder, smBuilder, method, methodDCKey);
else
EmitAsyncGeneratorStaticStubMethod(methodBuilder, smBuilder, method.Parameters);

// Create context for MoveNextAsync emission
var il = smBuilder.MoveNextAsyncMethod.GetILGenerator();
var ctx = new CompilationContext(il, _typeMapper, _functions.Builders, _classes.Builders, _namespaceFields, _namespaceVarFields, _types)
{
FieldsField = fieldsField,
IsInstanceMethod = true,
IsInstanceMethod = isInstanceMethod,
ClosureAnalyzer = _closures.Analyzer,
ArrowMethods = _closures.ArrowMethods,
ConstArrowBindings = _closures.ConstArrowBindings,
Expand Down Expand Up @@ -378,4 +386,48 @@ private void EmitAsyncGeneratorInstanceStubMethod(
// Return the state machine (which implements IAsyncEnumerable<object>)
il.Emit(OpCodes.Ret);
}

/// <summary>
/// Emits the stub that creates the async generator state machine for a STATIC method (#778): like
/// the instance stub but with no <c>this</c> and parameters starting at arg 0 (no receiver slot).
/// Value-type parameters are boxed into the object-typed state-machine fields. Mirrors
/// <see cref="EmitGeneratorStaticStubMethod"/> (the sync analogue, #692).
/// </summary>
private void EmitAsyncGeneratorStaticStubMethod(
MethodBuilder methodBuilder,
AsyncGeneratorStateMachineBuilder smBuilder,
List<Stmt.Parameter> parameters)
{
var il = methodBuilder.GetILGenerator();

// Create new instance of the state machine
il.Emit(OpCodes.Newobj, smBuilder.Constructor);

// Box value types since state machine fields are object-typed. Decide from the method's ACTUAL
// IL signature (methodBuilder.GetParameters()), not the AST-resolved types: a private static
// method's parameters are all `object` slots, so boxing the AST-resolved value type would
// mismatch the `object` argument actually loaded (StackUnexpected). Mirrors EmitAsyncStubMethod.
var paramTypes = methodBuilder.GetParameters();

// Copy parameters to state machine fields (static methods start params at index 0).
for (int i = 0; i < parameters.Count; i++)
{
var field = smBuilder.GetVariableField(parameters[i].Name.Lexeme);
if (field != null)
{
il.Emit(OpCodes.Dup); // Keep state machine reference on stack
il.Emit(OpCodes.Ldarg, i);

if (i < paramTypes.Length && paramTypes[i].ParameterType.IsValueType)
{
il.Emit(OpCodes.Box, paramTypes[i].ParameterType);
}

il.Emit(OpCodes.Stfld, field);
}
}

// Return the state machine (which implements IAsyncEnumerable<object>)
il.Emit(OpCodes.Ret);
}
}
152 changes: 137 additions & 15 deletions Compilation/ILCompiler.Classes.ClassExpressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,17 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
_classExprs.Constructors[classExpr] = ctorBuilder;
_classes.Constructors[className] = ctorBuilder;

// Define static methods
foreach (var method in classExpr.Methods.Where(m => m.Body != null && m.IsStatic && m.Name.Lexeme != "constructor"))
// Define static methods (computed symbol-keyed methods are handled by
// DefineClassExpressionSymbolMethods below, like the class-declaration path).
foreach (var method in classExpr.Methods.Where(m => m.Body != null && m.IsStatic && m.Name.Lexeme != "constructor" && m.ComputedKey == null))
{
var paramTypes = method.Parameters.Select(_ => typeof(object)).ToArray();
Type returnType = method.IsAsync ? _types.TaskOfObject : typeof(object);
// Match the method kind to its state machine's return type (#765), mirroring
// DefineClassMethodsOnly. Async generator FIRST since it has both flags set.
Type returnType = (method.IsAsync && method.IsGenerator) ? _types.IAsyncEnumerableOfObject :
method.IsAsync ? _types.TaskOfObject :
method.IsGenerator ? _types.IEnumerableOfObject :
typeof(object);

var methodBuilder = typeBuilder.DefineMethod(
method.Name.Lexeme,
Expand All @@ -334,16 +340,22 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
_classExprs.StaticMethods[classExpr][method.Name.Lexeme] = methodBuilder;
}

// Define instance methods
foreach (var method in classExpr.Methods.Where(m => m.Body != null && !m.IsStatic && m.Name.Lexeme != "constructor"))
// Define instance methods (computed symbol-keyed methods are handled by
// DefineClassExpressionSymbolMethods below, like the class-declaration path).
foreach (var method in classExpr.Methods.Where(m => m.Body != null && !m.IsStatic && m.Name.Lexeme != "constructor" && m.ComputedKey == null))
{
var paramTypes = method.Parameters.Select(_ => typeof(object)).ToArray();

MethodAttributes methodAttrs = MethodAttributes.Public | MethodAttributes.Virtual;
if (method.IsAbstract)
methodAttrs |= MethodAttributes.Abstract;

Type returnType = method.IsAsync ? typeof(Task<object>) : typeof(object);
// Match the method kind to its state machine's return type (#765), mirroring
// DefineClassMethodsOnly. Async generator FIRST since it has both flags set.
Type returnType = (method.IsAsync && method.IsGenerator) ? _types.IAsyncEnumerableOfObject :
method.IsAsync ? typeof(Task<object>) :
method.IsGenerator ? _types.IEnumerableOfObject :
typeof(object);

var methodBuilder = typeBuilder.DefineMethod(
method.Name.Lexeme,
Expand All @@ -354,6 +366,10 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
_classExprs.InstanceMethods[classExpr][method.Name.Lexeme] = methodBuilder;
}

// Computed symbol-keyed methods (`*[Symbol.iterator]()` etc.) get synthetic uniquely-named
// builders plus runtime symbol-method registration, mirroring the class-declaration path (#755).
DefineClassExpressionSymbolMethods(classExpr, typeBuilder);

// Define user-defined accessors (overrides property accessors)
if (classExpr.Accessors != null)
{
Expand Down Expand Up @@ -428,18 +444,22 @@ private void EmitClassExpressionBody(Expr.ClassExpr classExpr)
// Emit instance constructor
EmitClassExpressionConstructor(classExpr, typeBuilder, fieldsField);

// Emit instance method bodies
foreach (var method in classExpr.Methods.Where(m => m.Body != null && !m.IsStatic && m.Name.Lexeme != "constructor"))
// Emit instance method bodies (computed symbol-keyed methods are emitted below).
foreach (var method in classExpr.Methods.Where(m => m.Body != null && !m.IsStatic && m.Name.Lexeme != "constructor" && m.ComputedKey == null))
{
EmitClassExpressionMethod(classExpr, typeBuilder, method, fieldsField);
}

// Emit static method bodies
foreach (var method in classExpr.Methods.Where(m => m.Body != null && m.IsStatic))
// Emit static method bodies (computed symbol-keyed methods are emitted below).
foreach (var method in classExpr.Methods.Where(m => m.Body != null && m.IsStatic && m.ComputedKey == null))
{
EmitClassExpressionStaticMethodBody(classExpr, method);
}

// Emit computed symbol-keyed method bodies (#755), then their runtime registration runs in
// the class-expression .cctor (EmitClassExpressionStaticConstructor → EmitSymbolMethodRegistrations).
EmitClassExpressionSymbolMethods(classExpr, typeBuilder, fieldsField);

// Emit user-defined accessor bodies
if (classExpr.Accessors != null)
{
Expand Down Expand Up @@ -547,11 +567,12 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
{
bool hasStaticFields = classExpr.Fields.Any(f => f.IsStatic && f.Initializer != null);
bool hasStaticInitializers = classExpr.StaticInitializers?.Count > 0;
// Symbol-keyed computed accessors (#281) register in the .cctor, keyed by
// this class's generated name (mirrors the class-declaration path #266).
// Symbol-keyed computed accessors (#281) and methods (#755) register in the .cctor, keyed by
// this class's generated name (mirrors the class-declaration path #266/#647).
bool hasSymbolAccessors = _classes.SymbolAccessors.ContainsKey(typeBuilder.Name);
bool hasSymbolMethods = _classes.SymbolMethods.ContainsKey(typeBuilder.Name);

if (!hasStaticFields && !hasStaticInitializers && !hasSymbolAccessors) return;
if (!hasStaticFields && !hasStaticInitializers && !hasSymbolAccessors && !hasSymbolMethods) return;

var cctor = typeBuilder.DefineConstructor(
MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
Expand Down Expand Up @@ -599,9 +620,10 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
}
}

// Register symbol-keyed computed accessors (#281) in the runtime registry,
// keyed by this class's Type so dynamic bracket get/set can dispatch them.
// Register symbol-keyed computed accessors (#281) and methods (#755) in the runtime registry,
// keyed by this class's Type so dynamic bracket get / for...of dispatch can find them.
EmitSymbolAccessorRegistrations(emitter, il, typeBuilder);
EmitSymbolMethodRegistrations(emitter, il, typeBuilder);

il.Emit(OpCodes.Ret);
}
Expand Down Expand Up @@ -806,13 +828,28 @@ private void EmitClassExpressionMethod(
// args with the `undefined` sentinel on the value-call path (covers sync + async).
MarkPadsUndefined(methodBuilder);

// Generator methods route through the same state-machine emitters as class declarations (#765).
// Async generator FIRST since `async *m()` has both IsAsync and IsGenerator set.
if (method.IsAsync && method.IsGenerator)
{
EmitAsyncGeneratorMethodBody(methodBuilder, method, fieldsField);
return;
}

// Handle async methods via state machine
if (method.IsAsync)
{
EmitClassExpressionAsyncMethod(classExpr, typeBuilder, method, methodBuilder, fieldsField);
return;
}

// Generator methods use the generator state machine (#765).
if (method.IsGenerator)
{
EmitGeneratorMethodBody(methodBuilder, method, fieldsField);
return;
}

var il = methodBuilder.GetILGenerator();
var ctx = CreateClassExpressionContext(il, classExpr, typeBuilder, fieldsField);
ctx.IsInstanceMethod = true;
Expand Down Expand Up @@ -864,12 +901,26 @@ private void EmitClassExpressionStaticMethodBody(Expr.ClassExpr classExpr, Stmt.

var typeBuilder = _classExprs.Builders[classExpr];

// Static generator methods route like a free function (no `this`), mirroring the class-
// declaration static path (#765/#778). Async generator FIRST since it has both flags set.
if (method.IsAsync && method.IsGenerator)
{
EmitAsyncGeneratorMethodBody(methodBuilder, method, fieldsField: null, isInstanceMethod: false);
return;
}

if (method.IsAsync)
{
EmitClassExpressionStaticAsyncMethod(classExpr, typeBuilder, method, methodBuilder);
return;
}

if (method.IsGenerator)
{
EmitGeneratorMethodBody(methodBuilder, method, fieldsField: null, isInstanceMethod: false);
return;
}

var il = methodBuilder.GetILGenerator();
var ctx = CreateClassExpressionContext(il, classExpr, typeBuilder, null);
ctx.IsInstanceMethod = false;
Expand Down Expand Up @@ -955,6 +1006,77 @@ private void EmitClassExpressionSymbolAccessors(
}
}

/// <summary>
/// Pre-defines a uniquely-named .NET method for each computed symbol-keyed method of a class
/// EXPRESSION (<c>*[Symbol.iterator]() {…}</c> and the async/generator forms), mirroring the
/// class-declaration <see cref="DefineSymbolMethods"/> (#755). Recorded in the shared
/// <see cref="ClassState.SymbolMethods"/> registry (keyed by the generated type name) so the
/// bodies emit through the normal class-expression per-method emitters and the .cctor registers
/// them in the runtime symbol-method registry.
/// </summary>
private void DefineClassExpressionSymbolMethods(Expr.ClassExpr classExpr, TypeBuilder typeBuilder)
{
string className = typeBuilder.Name;
if (_classes.SymbolMethods.ContainsKey(className))
return; // already defined (idempotent across multi-module pre-define/emit passes)

var computed = classExpr.Methods.Where(m => m.ComputedKey != null && m.Body != null).ToList();
if (computed.Count == 0)
return;

var list = new List<(Stmt.Function, Expr, MethodBuilder)>();
for (int i = 0; i < computed.Count; i++)
{
var method = computed[i];
// Unique, deterministic name so multiple computed methods don't collide and the synthetic
// `<computed>` lexeme (not a dispatchable name) is replaced by a real IL name.
string uniqueName = $"$symmethod_{i}";
var renamed = method with { Name = new Token(TokenType.IDENTIFIER, uniqueName, null, method.Name.Line) };

// Class-expression methods use all-object parameter slots (computed iterator methods are
// typically parameterless anyway). Async generator FIRST since it sets both flags.
var paramTypes = method.Parameters.Select(_ => typeof(object)).ToArray();
Type returnType = (method.IsAsync && method.IsGenerator) ? _types.IAsyncEnumerableOfObject :
method.IsAsync ? _types.TaskOfObject :
method.IsGenerator ? _types.IEnumerableOfObject :
typeof(object);

// Non-virtual (like symbol accessors): the registry holds the exact MethodInfo.
MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.HideBySig;
if (method.IsStatic)
attrs |= MethodAttributes.Static;

var mb = typeBuilder.DefineMethod(uniqueName, attrs, returnType, paramTypes);

// Register under the unique name so EmitClassExpression(Static)MethodBody resolves the builder.
if (method.IsStatic)
_classExprs.StaticMethods[classExpr][uniqueName] = mb;
else
_classExprs.InstanceMethods[classExpr][uniqueName] = mb;

list.Add((renamed, method.ComputedKey!, mb));
}
_classes.SymbolMethods[className] = list;
}

/// <summary>
/// Emits the bodies of the computed symbol-keyed methods recorded by
/// <see cref="DefineClassExpressionSymbolMethods"/>, reusing the class-expression per-method
/// emitters so the generator/async state machines compose (#755).
/// </summary>
private void EmitClassExpressionSymbolMethods(Expr.ClassExpr classExpr, TypeBuilder typeBuilder, FieldInfo fieldsField)
{
if (!_classes.SymbolMethods.TryGetValue(typeBuilder.Name, out var list))
return;
foreach (var (method, _key, _builder) in list)
{
if (method.IsStatic)
EmitClassExpressionStaticMethodBody(classExpr, method);
else
EmitClassExpressionMethod(classExpr, typeBuilder, method, fieldsField);
}
}

private void EmitClassExpressionAccessor(
Expr.ClassExpr classExpr,
TypeBuilder typeBuilder,
Expand Down
Loading
Loading