Skip to content

Commit

Permalink
Codegen for module initializers feature (#43281)
Browse files Browse the repository at this point in the history
  • Loading branch information
jnm2 authored Apr 28, 2020
1 parent 176ad60 commit 6fe4010
Show file tree
Hide file tree
Showing 23 changed files with 441 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6070,4 +6070,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_GeneratorFailedDuringInitialization_Title" xml:space="preserve">
<value>Generator failed to initialize.</value>
</data>
<data name="IDS_FeatureModuleInitializers" xml:space="preserve">
<value>module initializers</value>
</data>
</root>
36 changes: 36 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Reflection.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
Expand Down Expand Up @@ -2015,6 +2016,14 @@ private protected override bool IsSymbolAccessibleWithinCore(
throw new NotImplementedException();
}

private ConcurrentSet<MethodSymbol>? _moduleInitializerMethods;

internal void AddModuleInitializerMethod(MethodSymbol method)
{
Debug.Assert(!_declarationDiagnosticsFrozen);
LazyInitializer.EnsureInitialized(ref _moduleInitializerMethods).Add(method);
}

#endregion

#region Binding
Expand Down Expand Up @@ -2802,6 +2811,8 @@ internal override bool CompileMethods(
filterOpt: filterOpt,
cancellationToken: cancellationToken);

GenerateModuleInitializer(moduleBeingBuilt, methodBodyDiagnosticBag);

bool hasMethodBodyError = !FilterAndAppendAndFreeDiagnostics(diagnostics, ref methodBodyDiagnosticBag);

if (hasDeclarationErrors || hasMethodBodyError)
Expand All @@ -2813,6 +2824,31 @@ internal override bool CompileMethods(
return true;
}

private void GenerateModuleInitializer(PEModuleBuilder moduleBeingBuilt, DiagnosticBag methodBodyDiagnosticBag)
{
Debug.Assert(_declarationDiagnosticsFrozen);

if (_moduleInitializerMethods is object)
{
var ilBuilder = new ILBuilder(moduleBeingBuilt, new LocalSlotManager(slotAllocator: null), OptimizationLevel.Release, areLocalsZeroed: false);

// PROTOTYPE(module-initializers): require deterministic order
foreach (var method in _moduleInitializerMethods)
{
ilBuilder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);

ilBuilder.EmitToken(
moduleBeingBuilt.Translate(method, methodBodyDiagnosticBag, needDeclaration: true),
CSharpSyntaxTree.Dummy.GetRoot(),
methodBodyDiagnosticBag);
}

ilBuilder.EmitRet(isVoid: true);
ilBuilder.Realize();
moduleBeingBuilt.RootModuleType.SetStaticConstructorBody(ilBuilder.RealizedIL);
}
}

internal override bool GenerateResourcesAndDocumentationComments(
CommonPEModuleBuilder moduleBuilder,
Stream? xmlDocStream,
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ internal enum MessageID
IDS_FeatureMemberNotNull = MessageBase + 12768,
IDS_FeatureNativeInt = MessageBase + 12769,
IDS_FeatureTargetTypedObjectCreation = MessageBase + 12770,
IDS_FeatureModuleInitializers = MessageBase + 12771,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -305,6 +306,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureTargetTypedObjectCreation: // syntax check
case MessageID.IDS_FeatureMemberNotNull:
case MessageID.IDS_FeatureNativeInt:
case MessageID.IDS_FeatureModuleInitializers: // semantic check on method attribute
return LanguageVersion.Preview;

// C# 8.0 features.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,12 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut
MessageID.IDS_FeatureMemberNotNull.CheckFeatureAvailability(arguments.Diagnostics, arguments.AttributeSyntaxOpt);
CSharpAttributeData.DecodeMemberNotNullWhenAttribute<MethodWellKnownAttributeData>(ContainingType, ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.ModuleInitializerAttribute))
{
MessageID.IDS_FeatureModuleInitializers.CheckFeatureAvailability(arguments.Diagnostics, arguments.AttributeSyntaxOpt);
// PROTOTYPE(module-initializers): diagnostics
DeclaringCompilation.AddModuleInitializerMethod(this);
}
else
{
var compilation = this.DeclaringCompilation;
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">skrývání názvů ve vnořených funkcích</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">Namensshadowing in geschachtelten Funktionen</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">sombreado de nombres en funciones anidadas</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">ombrage des noms dans les fonctions imbriquées</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">shadowing dei nomi nelle funzioni annidate</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">入れ子になった関数での名前シャドウイング</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">중첩된 함수의 이름 섀도잉</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">zasłanianie nazw w funkcjach zagnieżdżonych</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">sombreamento de nome em funções aninhadas</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">скрытие имен во вложенных функциях</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">iç içe işlevlerde ad gölgeleme</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">在嵌套函数中的名称映射</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,11 @@
<target state="new">MemberNotNull attribute</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureModuleInitializers">
<source>module initializers</source>
<target state="new">module initializers</target>
<note />
</trans-unit>
<trans-unit id="IDS_FeatureNameShadowingInNestedFunctions">
<source>name shadowing in nested functions</source>
<target state="translated">巢狀函式中的名稱鏡像處理</target>
Expand Down
141 changes: 141 additions & 0 deletions src/Compilers/CSharp/Test/Symbol/Symbols/ModuleInitializersTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Reflection;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols
{
[CompilerTrait(CompilerFeature.ModuleInitializers)]
public sealed class ModuleInitializersTests : CSharpTestBase
{
private static readonly CSharpParseOptions s_parseOptions = TestOptions.RegularPreview;

[Fact]
public static void LastLanguageVersionNotSupportingModuleInitializersIs8()
{
var source =
@"using System.Runtime.CompilerServices;

class C
{
[ModuleInitializer]
internal static void M() { }
}

namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } }
";
var compilation = CreateCompilation(source, parseOptions: TestOptions.Regular8);

compilation.VerifyDiagnostics(
// (5,6): error CS8652: The feature 'module initializers' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
// [ModuleInitializer]
Diagnostic(ErrorCode.ERR_FeatureInPreview, "ModuleInitializer").WithArguments("module initializers").WithLocation(5, 6)
);
}

[Fact]
public static void FirstLanguageVersionSupportingModuleInitializersIs9()
{
var source =
@"using System.Runtime.CompilerServices;

class C
{
[ModuleInitializer]
internal static void M() { }
}

namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } }
";
var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);

compilation.VerifyDiagnostics();
}

[Fact]
public void ModuleTypeStaticConstructorIsNotEmittedWhenNoMethodIsMarkedWithModuleInitializerAttribute()
{
string source = @"
using System;
using System.Runtime.CompilerServices;

class C
{
internal static void M() => Console.WriteLine(""C.M"");
}

class Program
{
static void Main() => Console.WriteLine(""Program.Main"");
}

namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } }
";

CompileAndVerify(
source,
parseOptions: s_parseOptions,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, metadataImportOptions: MetadataImportOptions.All),
symbolValidator: module =>
{
var rootModuleType = (TypeSymbol)module.GlobalNamespace.GetMember("<Module>");
Assert.Null(rootModuleType.GetMember(".cctor"));
},
expectedOutput: @"
Program.Main");
}

[Fact]
public void ModuleTypeStaticConstructorCallsMethodMarkedWithModuleInitializerAttribute()
{
string source = @"
using System;
using System.Runtime.CompilerServices;

class C
{
[ModuleInitializer]
internal static void M() => Console.WriteLine(""C.M"");
}

class Program
{
static void Main() => Console.WriteLine(""Program.Main"");
}

namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } }
";

CompileAndVerify(
source,
parseOptions: s_parseOptions,
options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All),
symbolValidator: module =>
{
var rootModuleType = (TypeSymbol)module.GlobalNamespace.GetMember("<Module>");
var staticConstructor = (PEMethodSymbol)rootModuleType.GetMember(".cctor");

Assert.NotNull(staticConstructor);
Assert.Equal(MethodKind.StaticConstructor, staticConstructor.MethodKind);

var expectedFlags =
MethodAttributes.Private
| MethodAttributes.Static
| MethodAttributes.SpecialName
| MethodAttributes.RTSpecialName
| MethodAttributes.HideBySig;

Assert.Equal(expectedFlags, staticConstructor.Flags);
},
expectedOutput: @"
C.M
Program.Main");
}
}
}
Loading

0 comments on commit 6fe4010

Please sign in to comment.