static void Main(string[] args) { using StreamWriter twOutputCommon = new StreamWriter(Path.Combine(args[0], @$ "{CommonAndImplAssemblyName}.il")); using StreamWriter twOutputTest = new StreamWriter(Path.Combine(args[0], @$ "{TestAssemblyName}.il")); StringWriter swMainMethodBody = new StringWriter(); StringWriter swTestClassMethods = new StringWriter(); EmitTestGlobalHeader(twOutputCommon); EmitAssemblyRecord(twOutputCommon, CommonAndImplAssemblyName); EmitCodeForCommonLibrary(twOutputCommon); GenerateImplementations(twOutputCommon); EmitTestGlobalHeader(twOutputTest); EmitAssemblyExternRecord(twOutputTest, CommonAndImplAssemblyName); EmitAssemblyRecord(twOutputTest, TestAssemblyName); foreach (var scenario in TestScenario.GetScenarios()) { string scenarioName = scenario.ToString(); string constrainedType = AppendSuffixToConstrainedType(scenario, GetConstrainedTypeName(scenario.ConstrainedTypeDefinition), out bool skipScenario); if (skipScenario) { continue; } string interfaceTypeSansImplPrefix; string interfaceMethod; string constrainedTypePrefix; if (constrainedType.Contains("Valuetype")) { constrainedTypePrefix = $"valuetype "; } else { constrainedTypePrefix = $"class "; } switch (scenario.InterfaceType) { case InterfaceType.NonGeneric: interfaceTypeSansImplPrefix = "IFaceNonGeneric"; break; case InterfaceType.GenericOverString: if (scenario.CallerScenario == CallerMethodScenario.GenericOverConstrainedType) { interfaceTypeSansImplPrefix = "IFaceGeneric`1<!!1>"; } else { interfaceTypeSansImplPrefix = "IFaceGeneric`1<string>"; } break; case InterfaceType.GenericOverObject: if (scenario.CallerScenario == CallerMethodScenario.GenericOverConstrainedType) { interfaceTypeSansImplPrefix = "IFaceGeneric`1<!!1>"; } else { interfaceTypeSansImplPrefix = "IFaceGeneric`1<object>"; } break; case InterfaceType.CuriouslyRecurringGeneric: interfaceTypeSansImplPrefix = $"IFaceCuriouslyRecurringGeneric`1<{constrainedTypePrefix}{constrainedType}>"; break; default: throw new Exception("Unexpected value"); } string interfaceMethodRoot; string interfaceMethodInstantiation = ""; switch (scenario.MethodType) { case MethodType.NormalMethod: interfaceMethodRoot = "NormalMethod"; break; case MethodType.GenericMethodOverInt: interfaceMethodRoot = "GenericMethod"; interfaceMethodInstantiation = "<int32>"; break; case MethodType.GenericMethodOverString: interfaceMethodRoot = "GenericMethod"; interfaceMethodInstantiation = "<string>"; break; case MethodType.GenericMethodOverTypeParameter: interfaceMethodRoot = "GenericMethod"; if (scenario.CallerScenario == CallerMethodScenario.NonGeneric) { continue; } interfaceMethodInstantiation = "<!!0>"; break; default: throw new Exception("Unexpected"); } interfaceMethod = interfaceMethodRoot + interfaceMethodInstantiation; TextWriter twIL; MethodDesc mdIndividualTestMethod = new MethodDesc(); string basicTestMethodName = $"Test_{scenarioName}"; mdIndividualTestMethod.Name = basicTestMethodName; mdIndividualTestMethod.HasBody = true; mdIndividualTestMethod.MethodFlags = "public static"; mdIndividualTestMethod.MethodImpls = null; mdIndividualTestMethod.ReturnType = "void"; mdIndividualTestMethod.Arguments = ""; string expectedString = constrainedTypePrefix + AppendSuffixToConstrainedType(scenario, GetConstrainedTypeName(scenario.ConstrainedTypeDefinition, withImplPrefix: false), out _) + "'" + interfaceTypeSansImplPrefix + "." + interfaceMethodRoot + "'" + interfaceMethodInstantiation; expectedString = expectedString.Replace(ImplPrefix, ""); if (scenario.CallerScenario == CallerMethodScenario.NonGeneric) { EmitTestMethod(); CallTestEntrypoint($" call void TestEntrypoint::{mdIndividualTestMethod.Name}()"); } else { string methodInstantiation; switch (scenario.CallerScenario) { case CallerMethodScenario.GenericOverInt32: case CallerMethodScenario.GenericOverString: mdIndividualTestMethod.Name = mdIndividualTestMethod.Name + "<T>"; methodInstantiation = "string"; if (scenario.CallerScenario == CallerMethodScenario.GenericOverInt32) { methodInstantiation = "int32"; } expectedString = expectedString.Replace("!!0", $"{methodInstantiation}"); expectedString = expectedString.Replace(ImplPrefix, ""); EmitTestMethod(); CallTestEntrypoint($" call void TestEntrypoint::{basicTestMethodName}<{methodInstantiation}>()"); break; case CallerMethodScenario.GenericOverConstrainedType: mdIndividualTestMethod.Name = $"{mdIndividualTestMethod.Name}<({(interfaceTypeSansImplPrefix.Contains("`") ? "class " : "")}{CommonPrefix}{interfaceTypeSansImplPrefix}) T,U>"; expectedString = expectedString.Replace("!!0", $"{constrainedTypePrefix}{constrainedType}"); expectedString = expectedString.Replace(ImplPrefix, ""); EmitTestMethod(); string callCommand = GetSetBangBang1IfNeeded("string") + $" call void TestEntrypoint::{basicTestMethodName}<{constrainedTypePrefix}{constrainedType},string>()"; if (scenario.InterfaceType == InterfaceType.GenericOverObject) { callCommand = callCommand + Environment.NewLine + GetSetBangBang1IfNeeded("object") + $" call void TestEntrypoint::{basicTestMethodName}<{constrainedTypePrefix}{constrainedType},object>()"; } CallTestEntrypoint(callCommand); string GetSetBangBang1IfNeeded(string bangBang1Expected) { if (expectedString.Contains("!!1")) { return($" ldstr \"{bangBang1Expected}\"" + Environment.NewLine + " stsfld string [GenericContextCommonCs]Statics::BangBang1Param" + Environment.NewLine); } else { return(""); } } break; default: throw new Exception("AACLL"); } } void CallTestEntrypoint(string callCommand) { swMainMethodBody.WriteLine(" .try {"); swMainMethodBody.WriteLine(callCommand); swMainMethodBody.WriteLine($" leave.s {scenarioName}Done"); swMainMethodBody.WriteLine(" } catch [System.Runtime]System.Exception {"); swMainMethodBody.WriteLine($" stloc.0"); swMainMethodBody.WriteLine($" ldstr \"{scenarioName}\""); swMainMethodBody.WriteLine($" ldstr \"{expectedString}\""); swMainMethodBody.WriteLine($" ldloc.0"); swMainMethodBody.WriteLine($" callvirt instance string [System.Runtime]System.Object::ToString()"); swMainMethodBody.WriteLine($" call void [GenericContextCommonCs]Statics::CheckForFailure(string,string,string)"); swMainMethodBody.WriteLine($" leave.s {scenarioName}Done"); swMainMethodBody.WriteLine(" }"); swMainMethodBody.WriteLine($"{scenarioName}Done: nop"); } // If test scenario requires generic class caller, Create Caller class and make a global method method which calls it // If test scenario requires generic method caller, create global generic method as required and non-generic test method // If test scenario requires non-generic method caller, just make global method as caller // Call callee // // Create Callee class // With callee method that implements scenario // fill expected value static with string computed based on scenario + exact type of calle class/generic args of callee method // compute expected result string void EmitTestMethod() { EmitMethod(swTestClassMethods, mdIndividualTestMethod); EmitILToCallActualMethod(swTestClassMethods); swTestClassMethods.WriteLine($" ldstr \"{scenario.ToString()}\""); swTestClassMethods.WriteLine($" ldstr \"{expectedString}\""); if (expectedString.Contains("!!1")) { swTestClassMethods.WriteLine(" ldstr \"!!1\""); swTestClassMethods.WriteLine(" ldsfld string [GenericContextCommonCs]Statics::BangBang1Param"); swTestClassMethods.WriteLine(" call instance string [System.Runtime]System.String::Replace(string, string)"); } swTestClassMethods.WriteLine($" call void {CommonCsPrefix}Statics::CheckForFailure(string,string)"); swTestClassMethods.WriteLine($" ret"); twIL = swTestClassMethods; EmitEndMethod(swTestClassMethods, mdIndividualTestMethod); } void EmitILToCallActualMethod(TextWriter twActualIL) { // Emit the IL to call the actual method switch (scenario.Operation) { case OperationTested.Call: EmitConstrainedPrefix(); twActualIL.WriteLine($" call void class {ImplPrefix}{interfaceTypeSansImplPrefix}::{interfaceMethod}()"); break; case OperationTested.Ldftn: EmitConstrainedPrefix(); twActualIL.WriteLine($" ldftn void class {ImplPrefix}{interfaceTypeSansImplPrefix}::{interfaceMethod}()"); twActualIL.WriteLine($" volatile."); twActualIL.WriteLine($" stsfld native int modreq([System.Runtime]System.Runtime.CompilerServices.IsVolatile) {CommonCsPrefix}Statics::FtnHolder"); twActualIL.WriteLine($" volatile."); twActualIL.WriteLine($" ldsfld native int modreq([System.Runtime]System.Runtime.CompilerServices.IsVolatile) {CommonCsPrefix}Statics::FtnHolder"); twActualIL.WriteLine($" calli void()"); break; case OperationTested.CreateDelegate: twActualIL.WriteLine(" ldnull"); EmitConstrainedPrefix(); twActualIL.WriteLine($" ldftn void class {ImplPrefix}{interfaceTypeSansImplPrefix}::{interfaceMethod}()"); twActualIL.WriteLine($" newobj instance void [System.Runtime]System.Action::.ctor(object,"); twActualIL.WriteLine($" native int)"); twActualIL.WriteLine($" volatile."); twActualIL.WriteLine($" stsfld class [System.Runtime] System.Action modreq([System.Runtime] System.Runtime.CompilerServices.IsVolatile) {CommonCsPrefix}Statics::ActionHolder"); twActualIL.WriteLine($" volatile."); twActualIL.WriteLine($" ldsfld class [System.Runtime] System.Action modreq([System.Runtime] System.Runtime.CompilerServices.IsVolatile) {CommonCsPrefix}Statics::ActionHolder"); twActualIL.WriteLine($" callvirt instance void[System.Runtime] System.Action::Invoke()"); break; default: throw new Exception(); } void EmitConstrainedPrefix() { if (scenario.CallerScenario == CallerMethodScenario.GenericOverConstrainedType) { twActualIL.WriteLine($" constrained. !!0"); } else { twActualIL.WriteLine($" constrained. {constrainedTypePrefix}{constrainedType}"); } } } } ClassDesc mainClass = new ClassDesc(); mainClass.BaseType = "[System.Runtime]System.Object"; mainClass.ClassFlags = "public auto ansi"; mainClass.Name = "TestEntrypoint"; EmitClass(twOutputTest, mainClass); twOutputTest.Write(swTestClassMethods.ToString()); MethodDesc mainMethod = new MethodDesc(); mainMethod.Name = "Main"; mainMethod.Arguments = ""; mainMethod.ReturnType = "int32"; mainMethod.MethodImpls = null; mainMethod.HasBody = true; mainMethod.MethodFlags = "public static"; EmitMethod(twOutputTest, mainMethod); twOutputTest.WriteLine(" .entrypoint"); twOutputTest.WriteLine(" .locals init (class [System.Runtime]System.Exception V_0)"); twOutputTest.Write(swMainMethodBody.ToString()); twOutputTest.WriteLine($" call int32 { CommonCsPrefix}Statics::ReportResults()"); twOutputTest.WriteLine(" ret"); EmitEndMethod(twOutputTest, mainMethod); EmitEndClass(twOutputTest, mainClass); }
static void Main(string[] args) { int maxCases = Int32.MaxValue; string rootPath = Path.GetDirectoryName(typeof(Program).Assembly.Location); string scenarioSuffix = ""; if (args.Length > 0) { rootPath = args[0]; } if (args.Length > 2) { maxCases = Int32.Parse(args[1]); scenarioSuffix = args[2]; } using StreamWriter twOutputTest = new StreamWriter(Path.Combine(rootPath, @$ "{TestAssemblyName}{scenarioSuffix}.il")); StringWriter swMainMethodBody = new StringWriter(); StringWriter swTestClassMethods = new StringWriter(); EmitTestGlobalHeader(twOutputTest); EmitAssemblyRecord(twOutputTest, TestAssemblyName); int currentCase = 0; foreach (var scenario in TestScenario.GetScenarios()) { if ((++currentCase) > maxCases) { break; } string scenarioName = scenario.ToString(); // Emit interface ClassDesc iface = new ClassDesc(); iface.ClassFlags = "interface public abstract auto ansi"; iface.GenericParams = scenario.InterfaceTypeGenericParams; iface.Name = "Interface" + scenarioName + GenericTypeSuffix(scenario.InterfaceTypeGenericParams);; EmitClass(twOutputTest, iface); MethodDesc ifaceMethod = new MethodDesc(); ifaceMethod.HasBody = false; ifaceMethod.MethodFlags = "public newslot virtual abstract static"; ifaceMethod.ReturnType = scenario.InterfaceReturnType; ifaceMethod.Name = "Method"; EmitMethod(twOutputTest, ifaceMethod); EmitEndMethod(twOutputTest, ifaceMethod); EmitEndClass(twOutputTest, iface); // Emit base class which implements static method to implement interface. Mark it abstract if we don't put the methodimpl there ClassDesc baseType = new ClassDesc(); baseType.BaseType = "[System.Runtime]System.Object"; switch (scenario.InterfaceImplementationApproach) { case InterfaceImplementationApproach.OnBaseType: case InterfaceImplementationApproach.OnBothBaseAndDerived: baseType.ClassFlags = "public auto ansi"; break; case InterfaceImplementationApproach.OnBothBaseAndDerivedBaseIsAbstract: baseType.ClassFlags = "public abstract auto ansi"; break; default: throw new Exception("Unknown interface approach"); } baseType.GenericParams = scenario.BaseTypeGenericParams; baseType.Name = "Base" + scenarioName + GenericTypeSuffix(scenario.BaseTypeGenericParams); if (scenario.InterfaceImplementationApproach.ToString().Contains("Base")) { baseType.InterfacesImplemented = new string[] { ToILDasmTypeName(iface.Name, scenario.InterfaceTypeInstantiationOnBaseType) }; } EmitClass(twOutputTest, baseType); switch (scenario.InterfaceImplementationApproach) { case InterfaceImplementationApproach.OnBaseType: case InterfaceImplementationApproach.OnBothBaseAndDerived: MethodDesc ifaceImplMethod = new MethodDesc(); ifaceImplMethod.HasBody = true; ifaceImplMethod.MethodFlags = "public static"; ifaceImplMethod.ReturnType = scenario.BaseTypeReturnType; ifaceImplMethod.Name = "Method"; EmitMethod(twOutputTest, ifaceImplMethod); twOutputTest.WriteLine($" .override method {scenario.InterfaceReturnType} {ToILDasmTypeName(iface.Name, scenario.InterfaceTypeInstantiationOnBaseType)}::Method()"); twOutputTest.WriteLine($" .locals init ({scenario.BaseTypeReturnType} V_O)"); twOutputTest.WriteLine($" ldloca.s 0"); twOutputTest.WriteLine($" initobj {scenario.BaseTypeReturnType}"); twOutputTest.WriteLine($" ldloc.0"); twOutputTest.WriteLine($" ret"); EmitEndMethod(twOutputTest, ifaceImplMethod); break; case InterfaceImplementationApproach.OnBothBaseAndDerivedBaseIsAbstract: break; default: throw new Exception("Unknown interface approach"); } EmitEndClass(twOutputTest, baseType); // Emit derived class. ClassDesc derivedType = new ClassDesc(); derivedType.BaseType = ToILDasmTypeName(baseType.Name, scenario.BaseTypeInstantiationOnDerivedType); switch (scenario.InterfaceImplementationApproach) { case InterfaceImplementationApproach.OnBaseType: case InterfaceImplementationApproach.OnBothBaseAndDerived: case InterfaceImplementationApproach.OnBothBaseAndDerivedBaseIsAbstract: derivedType.ClassFlags = "public auto ansi"; break; default: throw new Exception("Unknown interface approach"); } derivedType.Name = "Derived" + scenarioName + GenericTypeSuffix(scenario.DerivedTypeGenericParams); derivedType.GenericParams = scenario.DerivedTypeGenericParams; if (scenario.InterfaceImplementationApproach.ToString().Contains("Derived")) { derivedType.InterfacesImplemented = new string[] { ToILDasmTypeName(iface.Name, scenario.InterfaceTypeInstantiationOnDerivedType) }; } EmitClass(twOutputTest, derivedType); switch (scenario.InterfaceImplementationApproach) { case InterfaceImplementationApproach.OnBaseType: case InterfaceImplementationApproach.OnBothBaseAndDerived: break; case InterfaceImplementationApproach.OnBothBaseAndDerivedBaseIsAbstract: MethodDesc ifaceImplMethod = new MethodDesc(); ifaceImplMethod.HasBody = true; ifaceImplMethod.MethodFlags = "public static"; ifaceImplMethod.ReturnType = scenario.DerivedTypeReturnType; ifaceImplMethod.Name = "MethodImplOnDerived"; EmitMethod(twOutputTest, ifaceImplMethod); twOutputTest.WriteLine($" .override method {scenario.InterfaceReturnType} {ToILDasmTypeName(iface.Name, scenario.InterfaceTypeInstantiationOnDerivedType)}::Method()"); twOutputTest.WriteLine($" .locals init ({scenario.DerivedTypeReturnType} V_O)"); twOutputTest.WriteLine($" ldloca.s 0"); twOutputTest.WriteLine($" initobj {scenario.DerivedTypeReturnType}"); twOutputTest.WriteLine($" ldloc.0"); twOutputTest.WriteLine($" ret"); EmitEndMethod(twOutputTest, ifaceImplMethod); break; default: throw new Exception("Unknown interface approach"); } EmitEndClass(twOutputTest, derivedType); // Emit test method which performs constrained call to hit the method MethodDesc mdIndividualTestMethod = new MethodDesc(); string basicTestMethodName = $"Test_{scenarioName}"; mdIndividualTestMethod.Name = basicTestMethodName; mdIndividualTestMethod.HasBody = true; mdIndividualTestMethod.MethodFlags = "public static"; mdIndividualTestMethod.MethodImpls = null; mdIndividualTestMethod.ReturnType = "void"; mdIndividualTestMethod.Arguments = ""; EmitMethod(swTestClassMethods, mdIndividualTestMethod); swTestClassMethods.WriteLine($" constrained. {ToILDasmTypeName(derivedType.Name, scenario.DerivedTypeInstantiation)}"); swTestClassMethods.WriteLine($" call {scenario.CallReturnType} {ToILDasmTypeName(iface.Name, scenario.CallInterfaceTypeInstantiation)}::Method()"); if (scenario.CallReturnType != "void") { // TODO: should we rather convert the value to string and stsfld Statics.String? swTestClassMethods.WriteLine($" pop"); } swTestClassMethods.WriteLine($" ldstr \"{scenarioName}\""); swTestClassMethods.WriteLine($" ldnull"); swTestClassMethods.WriteLine($" call void {CommonCsPrefix}Statics::CheckForFailure(string,string)"); swTestClassMethods.WriteLine($" ret"); EmitEndMethod(swTestClassMethods, mdIndividualTestMethod); // Call test method from main method swMainMethodBody.WriteLine(" .try {"); swMainMethodBody.WriteLine($" call void TestEntrypoint::{mdIndividualTestMethod.Name}()"); swMainMethodBody.WriteLine($" leave.s {scenarioName}Done"); swMainMethodBody.WriteLine(" } catch [System.Runtime]System.Exception {"); swMainMethodBody.WriteLine($" stloc.0"); swMainMethodBody.WriteLine($" ldstr \"{scenarioName}\""); swMainMethodBody.WriteLine($" ldnull"); swMainMethodBody.WriteLine($" ldloc.0"); swMainMethodBody.WriteLine($" callvirt instance string [System.Runtime]System.Object::ToString()"); swMainMethodBody.WriteLine($" call void [TypeHierarchyCommonCs]Statics::CheckForFailure(string,string,string)"); swMainMethodBody.WriteLine($" leave.s {scenarioName}Done"); swMainMethodBody.WriteLine(" }"); swMainMethodBody.WriteLine($"{scenarioName}Done: nop"); string GenericTypeSuffix(string genericParams) { if (String.IsNullOrEmpty(genericParams)) { return(""); } return($"`{genericParams.Split(',').Length}"); } } ClassDesc mainClass = new ClassDesc(); mainClass.BaseType = "[System.Runtime]System.Object"; mainClass.ClassFlags = "public auto ansi"; mainClass.Name = "TestEntrypoint"; EmitClass(twOutputTest, mainClass); twOutputTest.Write(swTestClassMethods.ToString()); MethodDesc mainMethod = new MethodDesc(); mainMethod.Name = "Main"; mainMethod.Arguments = ""; mainMethod.ReturnType = "int32"; mainMethod.MethodImpls = null; mainMethod.HasBody = true; mainMethod.MethodFlags = "public static"; EmitMethod(twOutputTest, mainMethod); twOutputTest.WriteLine(" .entrypoint"); twOutputTest.WriteLine(" .locals init (class [System.Runtime]System.Exception V_0)"); twOutputTest.Write(swMainMethodBody.ToString()); twOutputTest.WriteLine($" call int32 { CommonCsPrefix}Statics::ReportResults()"); twOutputTest.WriteLine(" ret"); EmitEndMethod(twOutputTest, mainMethod); EmitEndClass(twOutputTest, mainClass); }