示例#1
0
文件: Program.cs 项目: z77ma/runtime
        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);
        }
示例#2
0
        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);
        }