aboutsummaryrefslogtreecommitdiff
path: root/lib/compilers/dotnet.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compilers/dotnet.ts')
-rw-r--r--lib/compilers/dotnet.ts219
1 files changed, 219 insertions, 0 deletions
diff --git a/lib/compilers/dotnet.ts b/lib/compilers/dotnet.ts
new file mode 100644
index 000000000..65d0e9a3a
--- /dev/null
+++ b/lib/compilers/dotnet.ts
@@ -0,0 +1,219 @@
+// Copyright (c) 2021, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+import path from 'path';
+
+import fs from 'fs-extra';
+
+/// <reference types="../base-compiler" />
+import { BaseCompiler } from '../base-compiler';
+
+class DotNetCompiler extends BaseCompiler {
+ private rID: string;
+ private targetFramework: string;
+ private buildConfig: string;
+ private nugetPackagesPath: string;
+ private clrBuildDir: string;
+ private additionalSources: string;
+ private langVersion: string;
+
+ constructor(compilerInfo, env) {
+ super(compilerInfo, env);
+
+ this.rID = this.compilerProps(`compiler.${this.compiler.id}.runtimeId`);
+ this.targetFramework = this.compilerProps(`compiler.${this.compiler.id}.targetFramework`);
+ this.buildConfig = this.compilerProps(`compiler.${this.compiler.id}.buildConfig`);
+ this.nugetPackagesPath = this.compilerProps(`compiler.${this.compiler.id}.nugetPackages`);
+ this.clrBuildDir = this.compilerProps(`compiler.${this.compiler.id}.clrDir`);
+ this.additionalSources = this.compilerProps(`compiler.${this.compiler.id}.additionalSources`);
+ this.langVersion = this.compilerProps(`compiler.${this.compiler.id}.langVersion`);
+ }
+
+ get compilerOptions() {
+ return ['publish', '-c', this.buildConfig, '--self-contained', '--runtime', this.rID, '-v', 'q', '--nologo'];
+ }
+
+ get configurableOptions() {
+ return ['--targetos', '--targetarch', '--instruction-set', '--singlemethodtypename', '--singlemethodname',
+ '--singlemethodindex', '--singlemethodgenericarg', '--codegenopt', '--codegen-options'];
+ }
+
+ get configurableSwitches() {
+ return ['-O', '--optimize', '--Od', '--optimize-disabled', '--Os', '--optimize-space', '--Ot',
+ '--optimize-time'];
+ }
+
+ async runCompiler(compiler, options, inputFileName, execOptions) {
+ if (!execOptions) {
+ execOptions = this.getDefaultExecOptions();
+ }
+
+ const programDir = path.dirname(inputFileName);
+ const sourceFile = path.basename(inputFileName);
+
+ const projectFilePath = path.join(programDir, `CompilerExplorer${this.lang.extensions[0]}proj`);
+ const crossgen2Path = path.join(this.clrBuildDir, 'crossgen2', 'crossgen2.dll');
+
+ const programPublishPath = path.join(
+ programDir,
+ 'bin',
+ this.buildConfig,
+ this.targetFramework,
+ this.rID,
+ 'publish',
+ );
+
+ const programDllPath = path.join(programPublishPath, 'CompilerExplorer.dll');
+ const projectFileContent =
+ `<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>${this.targetFramework}</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <Nullable>enable</Nullable>
+ <AssemblyName>CompilerExplorer</AssemblyName>
+ <LangVersion>${this.langVersion}</LangVersion>
+ <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
+ <EnablePreviewFeatures>${this.langVersion === 'preview' ? 'true' : 'false'}</EnablePreviewFeatures>
+ <RestoreAdditionalProjectSources>
+ https://api.nuget.org/v3/index.json;${this.additionalSources ? this.additionalSources : ''}
+ </RestoreAdditionalProjectSources>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="${sourceFile}" />
+ </ItemGroup>
+ </Project>
+ `;
+
+ execOptions.env.DOTNET_CLI_TELEMETRY_OPTOUT = 'true';
+ execOptions.env.DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 'true';
+ execOptions.env.NUGET_PACKAGES = this.nugetPackagesPath;
+ execOptions.env.DOTNET_NOLOGO='true';
+
+ execOptions.customCwd = programDir;
+ await fs.writeFile(projectFilePath, projectFileContent);
+
+ let crossgen2Options = [];
+ const configurableOptions = this.configurableOptions;
+
+ for (const configurableOption of configurableOptions) {
+ const optionIndex = options.indexOf(configurableOption);
+ if (optionIndex === -1 || optionIndex === options.length - 1) {
+ continue;
+ }
+ crossgen2Options = crossgen2Options.concat([options[optionIndex], options[optionIndex + 1]]);
+ }
+
+ const configurableSwitches = this.configurableSwitches;
+ for (const configurableSwitch of configurableSwitches) {
+ const switchIndex = options.indexOf(configurableSwitch);
+ if (switchIndex === -1) {
+ continue;
+ }
+ crossgen2Options.push(options[switchIndex]);
+ }
+
+ const compilerResult = await super.runCompiler(compiler, this.compilerOptions, inputFileName, execOptions);
+
+ if (compilerResult.code !== 0) {
+ return compilerResult;
+ }
+
+ const crossgen2Result = await this.runCrossgen2(
+ compiler,
+ execOptions,
+ crossgen2Path,
+ programPublishPath,
+ programDllPath,
+ crossgen2Options,
+ this.getOutputFilename(programDir, this.outputFilebase),
+ );
+
+ if (crossgen2Result.code !== 0) {
+ return crossgen2Result;
+ }
+
+ return compilerResult;
+ }
+
+ optionsForFilter() {
+ return this.compilerOptions;
+ }
+
+ cleanAsm(stdout) {
+ let cleanedAsm = '';
+
+ for (const line of stdout) {
+ if (line.text.startsWith('; Assembly listing for method')) {
+ // ; Assembly listing for method ConsoleApplication.Program:Main(System.String[])
+ // ^ This character is the 31st character in this string.
+ // `substring` removes the first 30 characters from it and uses the rest as a label.
+ cleanedAsm = cleanedAsm.concat(line.text.substring(30) + ':\n');
+ continue;
+ }
+
+ if (line.text.startsWith('Emitting R2R PE file')) {
+ continue;
+ }
+
+ if (line.text.startsWith(';') && !line.text.startsWith('; Emitting')) {
+ continue;
+ }
+
+ cleanedAsm = cleanedAsm.concat(line.text + '\n');
+ }
+
+ return cleanedAsm;
+ }
+
+ async runCrossgen2(compiler, execOptions, crossgen2Path, publishPath, dllPath, options, outputPath) {
+ const crossgen2Options = [
+ crossgen2Path, '-r', path.join(publishPath, '*'), dllPath, '-o', 'CompilerExplorer.r2r.dll',
+ '--codegenopt', 'NgenDisasm=*', '--codegenopt', 'JitDiffableDasm=1', '--parallelism', '1',
+ ].concat(options);
+
+ const result = await this.exec(compiler, crossgen2Options, execOptions);
+ result.inputFilename = dllPath;
+ const transformedInput = result.filenameTransform(dllPath);
+ this.parseCompilationOutput(result, transformedInput);
+
+ await fs.writeFile(
+ outputPath,
+ this.cleanAsm(result.stdout),
+ );
+
+ return result;
+ }
+}
+
+export class CSharpCompiler extends DotNetCompiler {
+ static get key() { return 'csharp'; }
+}
+
+export class FSharpCompiler extends DotNetCompiler {
+ static get key() { return 'fsharp'; }
+}
+
+export class VBCompiler extends DotNetCompiler {
+ static get key() { return 'vb'; }
+}