Skip to content

Commit b344901

Browse files
authored
Implemented a customizable ModuleBuilderProvider (#122)
* Implemented a configurable ModuleBuilderProvider which enables overriding the default behavior. This allows using a collectable assembly builder which makes the expressions compatible with types from collectible assemblies * Simplified the ExpressionRuntimeOptions * Renamed the Provider property to ModuleBuilderProvider to make it more clear what it does * Removed backward compat comment
1 parent eca9294 commit b344901

12 files changed

Lines changed: 906 additions & 31 deletions

src/Hyperbee.Expressions/AsyncBlockExpression.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,22 @@ public class AsyncBlockExpression : Expression
1414

1515
public ReadOnlyCollection<Expression> Expressions { get; }
1616
public ReadOnlyCollection<ParameterExpression> Variables { get; }
17+
public ExpressionRuntimeOptions RuntimeOptions { get; }
1718

1819
internal LinkedDictionary<ParameterExpression, ParameterExpression> ScopedVariables { get; set; }
1920

2021
public Expression Result => Expressions[^1];
2122

22-
internal AsyncBlockExpression( ReadOnlyCollection<ParameterExpression> variables, ReadOnlyCollection<Expression> expressions )
23-
: this( variables, expressions, null )
23+
internal AsyncBlockExpression( ReadOnlyCollection<ParameterExpression> variables, ReadOnlyCollection<Expression> expressions, ExpressionRuntimeOptions options = null )
24+
: this( variables, expressions, null, options )
2425
{
2526
}
2627

2728
internal AsyncBlockExpression(
2829
ReadOnlyCollection<ParameterExpression> variables,
2930
ReadOnlyCollection<Expression> expressions,
30-
LinkedDictionary<ParameterExpression, ParameterExpression> scopedVariables
31+
LinkedDictionary<ParameterExpression, ParameterExpression> scopedVariables,
32+
ExpressionRuntimeOptions options = null
3133
)
3234
{
3335
if ( expressions == null || expressions.Count == 0 )
@@ -36,6 +38,7 @@ LinkedDictionary<ParameterExpression, ParameterExpression> scopedVariables
3638
Variables = variables;
3739
Expressions = expressions;
3840
ScopedVariables = scopedVariables;
41+
RuntimeOptions = options;
3942

4043
Type = GetTaskType( Result.Type );
4144
}
@@ -48,7 +51,7 @@ LinkedDictionary<ParameterExpression, ParameterExpression> scopedVariables
4851

4952
public override Expression Reduce()
5053
{
51-
return _stateMachine ??= AsyncStateMachineBuilder.Create( Result.Type, LoweringTransformer );
54+
return _stateMachine ??= AsyncStateMachineBuilder.Create( Result.Type, LoweringTransformer, RuntimeOptions );
5255
}
5356

5457
private AsyncLoweringInfo LoweringTransformer()
@@ -78,7 +81,7 @@ protected override Expression VisitChildren( ExpressionVisitor visitor )
7881
if ( Compare( newVariables, Variables ) && Compare( newExpressions, Expressions ) )
7982
return this;
8083

81-
return new AsyncBlockExpression( newVariables, newExpressions, ScopedVariables );
84+
return new AsyncBlockExpression( newVariables, newExpressions, ScopedVariables, RuntimeOptions );
8285
}
8386

8487
internal static bool Compare<T>( ICollection<T> compare, IReadOnlyList<T> current )
@@ -144,4 +147,24 @@ public static AsyncBlockExpression BlockAsync( ReadOnlyCollection<ParameterExpre
144147
{
145148
return new AsyncBlockExpression( variables, expressions );
146149
}
150+
151+
public static AsyncBlockExpression BlockAsync( Expression[] expressions, ExpressionRuntimeOptions options )
152+
{
153+
return new AsyncBlockExpression( ReadOnlyCollection<ParameterExpression>.Empty, new ReadOnlyCollection<Expression>( expressions ), options );
154+
}
155+
156+
public static AsyncBlockExpression BlockAsync( ParameterExpression[] variables, Expression[] expressions, ExpressionRuntimeOptions options )
157+
{
158+
return new AsyncBlockExpression( new ReadOnlyCollection<ParameterExpression>( variables ), new ReadOnlyCollection<Expression>( expressions ), options );
159+
}
160+
161+
public static AsyncBlockExpression BlockAsync( ReadOnlyCollection<Expression> expressions, ExpressionRuntimeOptions options )
162+
{
163+
return new AsyncBlockExpression( ReadOnlyCollection<ParameterExpression>.Empty, expressions, options );
164+
}
165+
166+
public static AsyncBlockExpression BlockAsync( ReadOnlyCollection<ParameterExpression> variables, ReadOnlyCollection<Expression> expressions, ExpressionRuntimeOptions options )
167+
{
168+
return new AsyncBlockExpression( variables, expressions, options );
169+
}
147170
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Reflection.Emit;
3+
4+
namespace Hyperbee.Expressions;
5+
6+
/// <summary>
7+
/// ModuleBuilder provider using AssemblyBuilderAccess.RunAndCollect.
8+
/// Use this when working with types from collectible assemblies.
9+
/// </summary>
10+
public sealed class CollectibleModuleBuilderProvider : IModuleBuilderProvider
11+
{
12+
public static readonly CollectibleModuleBuilderProvider Instance = new();
13+
14+
private static readonly Lazy<ModuleBuilder> AsyncModuleBuilder = new( () =>
15+
CreateModuleBuilder( "RuntimeStateMachineAssembly_Collectible", "RuntimeStateMachineModule" ) );
16+
17+
private static readonly Lazy<ModuleBuilder> EnumerableModuleBuilder = new( () =>
18+
CreateModuleBuilder( "RuntimeYieldStateMachineAssembly_Collectible", "RuntimeYieldStateMachineModule" ) );
19+
20+
public ModuleBuilder GetModuleBuilder( ModuleKind kind )
21+
{
22+
return kind switch
23+
{
24+
ModuleKind.Async => AsyncModuleBuilder.Value,
25+
ModuleKind.Enumerable => EnumerableModuleBuilder.Value,
26+
_ => throw new ArgumentOutOfRangeException( nameof( kind ), kind, "Unknown module kind" )
27+
};
28+
}
29+
30+
private static ModuleBuilder CreateModuleBuilder( string assemblyName, string moduleName )
31+
{
32+
var name = new AssemblyName( assemblyName );
33+
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( name, AssemblyBuilderAccess.RunAndCollect );
34+
return assemblyBuilder.DefineDynamicModule( moduleName );
35+
}
36+
}

src/Hyperbee.Expressions/CompilerServices/AsyncStateMachineBuilder.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -381,41 +381,38 @@ protected override Expression VisitParameter( ParameterExpression node )
381381
public static class AsyncStateMachineBuilder
382382
{
383383
private static readonly MethodInfo BuildStateMachineMethod;
384-
private static readonly ModuleBuilder ModuleBuilder;
385384
private static int __id;
386385

387-
const string RuntimeAssemblyName = "RuntimeStateMachineAssembly";
388-
const string RuntimeModuleName = "RuntimeStateMachineModule";
389386
const string StateMachineTypeName = "StateMachine";
390387

391388
static AsyncStateMachineBuilder()
392389
{
393390
BuildStateMachineMethod = typeof( AsyncStateMachineBuilder )
394391
.GetMethods( BindingFlags.NonPublic | BindingFlags.Static )
395392
.First( method => method.Name == nameof( Create ) && method.IsGenericMethod );
396-
397-
// Create the state machine module
398-
var assemblyName = new AssemblyName( RuntimeAssemblyName );
399-
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
400-
ModuleBuilder = assemblyBuilder.DefineDynamicModule( RuntimeModuleName );
401393
}
402394

403-
internal static Expression Create( Type resultType, AsyncLoweringTransformer loweringTransformer )
395+
internal static Expression Create( Type resultType, AsyncLoweringTransformer loweringTransformer, ExpressionRuntimeOptions options = null )
404396
{
405397
if ( resultType == typeof( void ) )
406398
resultType = typeof( IVoidResult );
407399

408400
var buildStateMachine = BuildStateMachineMethod.MakeGenericMethod( resultType );
409401

410-
return (Expression) buildStateMachine.Invoke( null, [loweringTransformer] );
402+
return (Expression) buildStateMachine.Invoke( null, [loweringTransformer, options] );
411403
}
412404

413-
internal static Expression Create<TResult>( AsyncLoweringTransformer loweringTransformer )
405+
internal static Expression Create<TResult>( AsyncLoweringTransformer loweringTransformer, ExpressionRuntimeOptions options = null )
414406
{
407+
options ??= new ExpressionRuntimeOptions();
408+
415409
var typeId = Interlocked.Increment( ref __id );
416410
var typeName = $"{StateMachineTypeName}{typeId}";
417411

418-
var stateMachineBuilder = new AsyncStateMachineBuilder<TResult>( ModuleBuilder, typeName );
412+
// Get ModuleBuilder from provider using ModuleKind.Async
413+
var moduleBuilder = options.ModuleBuilderProvider.GetModuleBuilder( ModuleKind.Async );
414+
415+
var stateMachineBuilder = new AsyncStateMachineBuilder<TResult>( moduleBuilder, typeName );
419416
var stateMachineExpression = stateMachineBuilder.CreateStateMachine( loweringTransformer, __id );
420417

421418
return stateMachineExpression; // the-best expression breakpoint ever

src/Hyperbee.Expressions/CompilerServices/EnumerableStateMachineBuilder.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -394,41 +394,38 @@ protected override Expression VisitParameter( ParameterExpression node )
394394
public static class YieldStateMachineBuilder
395395
{
396396
private static readonly MethodInfo BuildYieldStateMachineMethod;
397-
private static readonly ModuleBuilder YieldModuleBuilder;
398397
private static int __id;
399398

400-
const string RuntimeAssemblyName = "RuntimeYieldStateMachineAssembly";
401-
const string RuntimeModuleName = "RuntimeYieldStateMachineModule";
402399
const string StateMachineTypeName = "YieldStateMachine";
403400

404401
static YieldStateMachineBuilder()
405402
{
406403
BuildYieldStateMachineMethod = typeof( YieldStateMachineBuilder )
407404
.GetMethods( BindingFlags.NonPublic | BindingFlags.Static )
408405
.First( method => method.Name == nameof( Create ) && method.IsGenericMethod );
409-
410-
// Create the state machine module
411-
var assemblyName = new AssemblyName( RuntimeAssemblyName );
412-
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
413-
YieldModuleBuilder = assemblyBuilder.DefineDynamicModule( RuntimeModuleName );
414406
}
415407

416-
internal static Expression Create( Type resultType, YieldLoweringTransformer loweringTransformer )
408+
internal static Expression Create( Type resultType, YieldLoweringTransformer loweringTransformer, ExpressionRuntimeOptions options = null )
417409
{
418410
if ( resultType == typeof( void ) )
419411
throw new ArgumentException( "IEnumerable must have a valid result type", nameof( resultType ) );
420412

421413
var buildStateMachine = BuildYieldStateMachineMethod.MakeGenericMethod( resultType );
422414

423-
return (Expression) buildStateMachine.Invoke( null, [loweringTransformer] );
415+
return (Expression) buildStateMachine.Invoke( null, [loweringTransformer, options] );
424416
}
425417

426-
internal static Expression Create<TResult>( YieldLoweringTransformer loweringTransformer )
418+
internal static Expression Create<TResult>( YieldLoweringTransformer loweringTransformer, ExpressionRuntimeOptions options = null )
427419
{
420+
options ??= new ExpressionRuntimeOptions();
421+
428422
var typeId = Interlocked.Increment( ref __id );
429423
var typeName = $"{StateMachineTypeName}{typeId}";
430424

431-
var stateMachineBuilder = new EnumerableStateMachineBuilder<TResult>( YieldModuleBuilder, typeName );
425+
// Get ModuleBuilder from provider using ModuleKind.Enumerable
426+
var moduleBuilder = options.ModuleBuilderProvider.GetModuleBuilder( ModuleKind.Enumerable );
427+
428+
var stateMachineBuilder = new EnumerableStateMachineBuilder<TResult>( moduleBuilder, typeName );
432429
var stateMachineExpression = stateMachineBuilder.CreateStateMachine( loweringTransformer, __id );
433430

434431
return stateMachineExpression; // the-best expression breakpoint ever
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Reflection;
2+
using System.Reflection.Emit;
3+
4+
namespace Hyperbee.Expressions;
5+
6+
/// <summary>
7+
/// Default ModuleBuilder provider using AssemblyBuilderAccess.Run.
8+
/// </summary>
9+
public sealed class DefaultModuleBuilderProvider : IModuleBuilderProvider
10+
{
11+
public static readonly DefaultModuleBuilderProvider Instance = new();
12+
13+
private static readonly Lazy<ModuleBuilder> AsyncModuleBuilder = new( () =>
14+
CreateModuleBuilder( "RuntimeStateMachineAssembly", "RuntimeStateMachineModule" ) );
15+
16+
private static readonly Lazy<ModuleBuilder> EnumerableModuleBuilder = new( () =>
17+
CreateModuleBuilder( "RuntimeYieldStateMachineAssembly", "RuntimeYieldStateMachineModule" ) );
18+
19+
public ModuleBuilder GetModuleBuilder( ModuleKind kind )
20+
{
21+
return kind switch
22+
{
23+
ModuleKind.Async => AsyncModuleBuilder.Value,
24+
ModuleKind.Enumerable => EnumerableModuleBuilder.Value,
25+
_ => throw new ArgumentOutOfRangeException( nameof( kind ), kind, "Unknown module kind" )
26+
};
27+
}
28+
29+
private static ModuleBuilder CreateModuleBuilder( string assemblyName, string moduleName )
30+
{
31+
var name = new AssemblyName( assemblyName );
32+
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( name, AssemblyBuilderAccess.Run );
33+
return assemblyBuilder.DefineDynamicModule( moduleName );
34+
}
35+
}

src/Hyperbee.Expressions/EnumerableBlockExpression.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,23 @@ public class EnumerableBlockExpression : Expression
1111
private Type _enumerableType;
1212
public ReadOnlyCollection<Expression> Expressions { get; }
1313
public ReadOnlyCollection<ParameterExpression> Variables { get; }
14+
public ExpressionRuntimeOptions RuntimeOptions { get; }
1415

1516
internal LinkedDictionary<ParameterExpression, ParameterExpression> ScopedVariables { get; set; }
1617

1718
private static YieldTypeVisitor TypeVisitor = new();
1819

1920
public EnumerableBlockExpression(
2021
ReadOnlyCollection<ParameterExpression> variables,
21-
ReadOnlyCollection<Expression> expressions )
22+
ReadOnlyCollection<Expression> expressions,
23+
ExpressionRuntimeOptions options = null )
2224
{
2325
if ( expressions == null || expressions.Count == 0 )
2426
throw new ArgumentException( "YieldBlockExpression must contain at least one expression." );
2527

2628
Variables = variables;
2729
Expressions = expressions;
30+
RuntimeOptions = options;
2831
}
2932

3033
public override ExpressionType NodeType => ExpressionType.Extension;
@@ -34,7 +37,7 @@ public EnumerableBlockExpression(
3437

3538
public override Expression Reduce()
3639
{
37-
return YieldStateMachineBuilder.Create( EnumerableType, LoweringTransformer );
40+
return YieldStateMachineBuilder.Create( EnumerableType, LoweringTransformer, RuntimeOptions );
3841
}
3942

4043
private EnumerableLoweringInfo LoweringTransformer()
@@ -112,4 +115,24 @@ public static EnumerableBlockExpression BlockEnumerable( ReadOnlyCollection<Para
112115
{
113116
return new EnumerableBlockExpression( variables, expressions );
114117
}
118+
119+
public static EnumerableBlockExpression BlockEnumerable( Expression[] expressions, ExpressionRuntimeOptions options )
120+
{
121+
return new EnumerableBlockExpression( ReadOnlyCollection<ParameterExpression>.Empty, new ReadOnlyCollection<Expression>( expressions ), options );
122+
}
123+
124+
public static EnumerableBlockExpression BlockEnumerable( ParameterExpression[] variables, Expression[] expressions, ExpressionRuntimeOptions options )
125+
{
126+
return new EnumerableBlockExpression( new ReadOnlyCollection<ParameterExpression>( variables ), new ReadOnlyCollection<Expression>( expressions ), options );
127+
}
128+
129+
public static EnumerableBlockExpression BlockEnumerable( ReadOnlyCollection<Expression> expressions, ExpressionRuntimeOptions options )
130+
{
131+
return new EnumerableBlockExpression( ReadOnlyCollection<ParameterExpression>.Empty, expressions, options );
132+
}
133+
134+
public static EnumerableBlockExpression BlockEnumerable( ReadOnlyCollection<ParameterExpression> variables, ReadOnlyCollection<Expression> expressions, ExpressionRuntimeOptions options )
135+
{
136+
return new EnumerableBlockExpression( variables, expressions, options );
137+
}
115138
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Hyperbee.Expressions;
2+
3+
/// <summary>
4+
/// Configuration options for expression runtime behavior.
5+
/// </summary>
6+
public class ExpressionRuntimeOptions
7+
{
8+
/// <summary>
9+
/// Gets or sets the ModuleBuilder provider for this expression.
10+
/// Defaults to <see cref="DefaultModuleBuilderProvider"/>
11+
/// </summary>
12+
public IModuleBuilderProvider ModuleBuilderProvider { get; init; } = DefaultModuleBuilderProvider.Instance;
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Reflection.Emit;
2+
3+
namespace Hyperbee.Expressions;
4+
5+
/// <summary>
6+
/// Provides ModuleBuilder instances for dynamic type generation.
7+
/// Implement this interface to control AssemblyBuilderAccess or provide custom module configuration.
8+
/// </summary>
9+
public interface IModuleBuilderProvider
10+
{
11+
/// <summary>
12+
/// Gets a ModuleBuilder for the specified kind of state machine.
13+
/// </summary>
14+
/// <param name="kind">The kind of module builder needed.</param>
15+
/// <returns>A ModuleBuilder instance.</returns>
16+
ModuleBuilder GetModuleBuilder( ModuleKind kind );
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Hyperbee.Expressions;
2+
3+
/// <summary>
4+
/// Specifies the kind of module builder needed for state machine generation.
5+
/// </summary>
6+
public enum ModuleKind
7+
{
8+
/// <summary>
9+
/// Module for async/await state machines.
10+
/// </summary>
11+
Async,
12+
13+
/// <summary>
14+
/// Module for yield/enumerable state machines.
15+
/// </summary>
16+
Enumerable
17+
}

0 commit comments

Comments
 (0)