aboutsummaryrefslogtreecommitdiff
path: root/lib/compilers/golang.ts
diff options
context:
space:
mode:
authorRubén Rincón Blanco <ruben@rinconblanco.es>2022-08-25 21:45:44 +0200
committerGitHub <noreply@github.com>2022-08-25 21:45:44 +0200
commit7c6a5291abdae74270c4462de29b086bdd67bb30 (patch)
tree760b710f49dcf4a66ff3d5ab62a4185b922aba11 /lib/compilers/golang.ts
parent44aa191cad2ca5571e870bb098a84b6d77ac2300 (diff)
downloadcompiler-explorer-7c6a5291abdae74270c4462de29b086bdd67bb30.tar.gz
compiler-explorer-7c6a5291abdae74270c4462de29b086bdd67bb30.zip
Move compilers/golang to Typescript (#3980)gh-4020
* Rename golang.js to golang.ts * Move golang to Typescript * Add types to base postProcess so golang knows about them * Remove CompilationResult type from signature * Remove asmSize, leave it for the other PR
Diffstat (limited to 'lib/compilers/golang.ts')
-rw-r--r--lib/compilers/golang.ts254
1 files changed, 254 insertions, 0 deletions
diff --git a/lib/compilers/golang.ts b/lib/compilers/golang.ts
new file mode 100644
index 000000000..b0abb1498
--- /dev/null
+++ b/lib/compilers/golang.ts
@@ -0,0 +1,254 @@
+// Copyright (c) 2022, 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 _ from 'underscore';
+
+import {ResultLine} from '../../types/resultline/resultline.interfaces';
+import {BaseCompiler} from '../base-compiler';
+import * as utils from '../utils';
+
+import {ClangParser} from './argument-parsers';
+
+// Each arch has a list of jump instructions in
+// Go source src/cmd/asm/internal/arch.
+// x86 -> j, b
+// arm -> cb, tb
+// s390x -> cmpb, cmpub
+const JUMP_RE = /^(j|b|cb|tb|cmpb|cmpub).*/i;
+const LINE_RE = /^\s+(0[Xx]?[\dA-Za-z]+)?\s?(\d+)\s*\(([^:]+):(\d+)\)\s*([A-Z]+)(.*)/;
+const UNKNOWN_RE = /^\s+(0[Xx]?[\dA-Za-z]+)?\s?(\d+)\s*\(<unknown line number>\)\s*([A-Z]+)(.*)/;
+const FUNC_RE = /TEXT\s+[".]*(\S+)\(SB\)/;
+const LOGGING_RE = /^[^:]+:\d+:(\d+:)?\s.*/;
+const DECIMAL_RE = /(\s+)(\d+)(\s?)$/;
+
+type GoEnv = {
+ GOROOT?: string;
+ GOARCH?: string;
+ GOOS?: string;
+};
+
+export class GolangCompiler extends BaseCompiler {
+ private readonly GOENV: GoEnv;
+
+ static get key() {
+ return 'golang';
+ }
+
+ constructor(compilerInfo, env) {
+ super(compilerInfo, env);
+ const goroot = this.compilerProps(`compiler.${this.compiler.id}.goroot`);
+ const goarch = this.compilerProps(`compiler.${this.compiler.id}.goarch`);
+ const goos = this.compilerProps(`compiler.${this.compiler.id}.goos`);
+
+ this.GOENV = {};
+ if (goroot) {
+ this.GOENV.GOROOT = goroot;
+ }
+ if (goarch) {
+ this.GOENV.GOARCH = goarch;
+ }
+ if (goos) {
+ this.GOENV.GOOS = goos;
+ }
+ }
+
+ convertNewGoL(code: ResultLine[]): string {
+ let prevLine: string | null = null;
+ let file: string | null = null;
+ let fileCount = 0;
+ let func: string | null = null;
+ const funcCollisions: Record<string, number> = {};
+ const labels: Record<string, boolean> = {};
+ const usedLabels: Record<string, boolean> = {};
+ const lines = code.map(obj => {
+ let pcMatch: string | null = null;
+ let fileMatch: string | null = null;
+ let lineMatch: string | null = null;
+ let ins: string | null = null;
+ let args: string | null = null;
+
+ const line = obj.text;
+ let match = line.match(LINE_RE);
+ if (match) {
+ pcMatch = match[2];
+ fileMatch = match[3];
+ lineMatch = match[4];
+ ins = match[5];
+ args = match[6];
+ } else {
+ match = line.match(UNKNOWN_RE);
+ if (match) {
+ pcMatch = match[2];
+ ins = match[3];
+ args = match[4];
+ } else {
+ return [];
+ }
+ }
+
+ match = line.match(FUNC_RE);
+ if (match) {
+ // Normalize function name.
+ func = match[1].replace(/[()*.]+/g, '_');
+
+ // It's possible for normalized function names to collide.
+ // Keep a count of collisions per function name. Labels get
+ // suffixed with _[collisions] when collisions > 0.
+ let collisions = funcCollisions[func];
+ if (collisions == null) {
+ collisions = 0;
+ } else {
+ collisions++;
+ }
+
+ funcCollisions[func] = collisions;
+ }
+
+ const res: string[] = [];
+ if (pcMatch && !labels[pcMatch]) {
+ // Create pseudo-label.
+ let label = pcMatch.replace(/^0{0,4}/, '');
+ let suffix = '';
+ if (func && funcCollisions[func] > 0) {
+ suffix = `_${funcCollisions[func]}`;
+ }
+
+ label = `${func}_pc${label}${suffix}:`;
+ if (!labels[label]) {
+ res.push(label);
+ labels[label] = true;
+ }
+ }
+
+ if (fileMatch && file !== fileMatch) {
+ fileCount++;
+ res.push(`\t.file ${fileCount} "${fileMatch}"`);
+ file = fileMatch;
+ }
+
+ if (lineMatch && prevLine !== lineMatch) {
+ res.push(`\t.loc ${fileCount} ${lineMatch} 0`);
+ prevLine = lineMatch;
+ }
+
+ if (func) {
+ args = this.replaceJump(func, funcCollisions[func], ins, args, usedLabels);
+ res.push(`\t${ins}${args}`);
+ }
+ return res;
+ });
+
+ // Find unused pseudo-labels so they can be filtered out.
+ const unusedLabels = _.mapObject(labels, (val, key) => !usedLabels[key]);
+
+ return lines
+ .flat()
+ .filter(line => !unusedLabels[line])
+ .join('\n');
+ }
+
+ replaceJump(
+ func: string,
+ collisions: number,
+ ins: string,
+ args: string,
+ usedLabels: Record<string, boolean>,
+ ): string {
+ // Check if last argument is a decimal number.
+ const match = args.match(DECIMAL_RE);
+ if (!match) {
+ return args;
+ }
+
+ // Check instruction has a jump prefix
+ if (JUMP_RE.test(ins)) {
+ let label = `${func}_pc${match[2]}`;
+ if (collisions > 0) {
+ label += `_${collisions}`;
+ }
+ usedLabels[label + ':'] = true; // record label use for later filtering
+ return `${match[1]}${label}${match[3]}`;
+ }
+
+ return args;
+ }
+
+ extractLogging(stdout: ResultLine[]): string {
+ const filepath = `./${this.compileFilename}`;
+
+ return stdout
+ .filter(obj => obj.text.match(LOGGING_RE))
+ .map(obj => obj.text.replace(filepath, '<source>'))
+ .join('\n');
+ }
+
+ override async postProcess(result) {
+ let out = result.stderr;
+ if (this.compiler.id === '6g141') {
+ out = result.stdout;
+ }
+ const logging = this.extractLogging(out);
+ result.asm = this.convertNewGoL(out);
+ result.stderr = null;
+ result.stdout = utils.parseOutput(logging, result.inputFilename);
+ return Promise.all([result, '']);
+ }
+
+ override getSharedLibraryPathsAsArguments() {
+ return [];
+ }
+
+ override optionsForFilter(filters, outputFilename, userOptions) {
+ // If we're dealing with an older version...
+ if (this.compiler.id === '6g141') {
+ return ['tool', '6g', '-g', '-o', outputFilename, '-S'];
+ }
+
+ if (filters.binary) {
+ return ['build', '-o', outputFilename, '-gcflags=' + userOptions.join(' ')];
+ } else {
+ // Add userOptions to -gcflags to preserve previous behavior.
+ return ['build', '-o', outputFilename, '-gcflags=-S ' + userOptions.join(' ')];
+ }
+ }
+
+ override filterUserOptions(userOptions) {
+ if (this.compiler.id === '6g141') {
+ return userOptions;
+ }
+ // userOptions are added to -gcflags in optionsForFilter
+ return [];
+ }
+
+ override getDefaultExecOptions() {
+ return {
+ ...super.getDefaultExecOptions(),
+ ...this.GOENV,
+ };
+ }
+
+ override getArgumentParser() {
+ return ClangParser;
+ }
+}