blob: 5c2cc0bcfdfa582e20196108cfbab63a18efd740 [file] [log] [blame] [raw]
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -07001// Copyright (c) 2018, Microsoft Corporation
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// * Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// * Redistributions in binary form must reproduce the above copyright
10// notice, this list of conditions and the following disclaimer in the
11// documentation and/or other materials provided with the distribution.
12//
13// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23// POSSIBILITY OF SUCH DAMAGE.
24
25const AsmParserBase = require('./asm-parser'),
26 logger = require('./logger').logger,
Matt Godbolteabe33c2018-10-22 23:00:28 -050027 utils = require('./utils'),
28 AsmRegex = require('./asmregex').AsmRegex;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -070029
30class AsmParser extends AsmParserBase {
31 constructor(compilerProps) {
32 super(compilerProps);
Partoufb691b3f2018-09-14 03:19:08 +020033 this.asmBinaryParser = new AsmParserBase(compilerProps);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -070034 this.miscDirective = /^\s*(include|INCLUDELIB|TITLE|\.|THUMB|ARM64|TTL|END$)/;
35 this.localLabelDef = /^([a-zA-Z$_]+) =/;
36 this.commentOnly = /^;/;
37 this.filenameComment = /^; File (.+)/;
38 this.lineNumberComment = /^; Line ([0-9]+)/;
39 this.beginSegment = /^(CONST|_BSS|\.?[prx]?data(\$[a-zA-Z]+)?|CRT(\$[a-zA-Z]+)?|_TEXT|\.?text(\$[a-zA-Z]+)?)\s+SEGMENT|\s*AREA/;
40 this.endSegment = /^(CONST|_BSS|[prx]?data(\$[a-zA-Z]+)?|CRT(\$[a-zA-Z]+)?|_TEXT|text(\$[a-zA-Z]+)?)\s+ENDS/;
41 this.beginFunction = /^; Function compile flags: /;
42 this.endProc = /^([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)?\s+ENDP/;
43 // on x86, we use the end of the segment to end a function
44 // on arm, we use ENDP
45 this.endFunction = /^(_TEXT\s+ENDS|\s+ENDP)/;
46
47 this.labelDef = /^\|?([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)\|?\s+(PROC|=|D[BWDQ])/;
48 this.definesGlobal = /^\s*(PUBLIC|EXTRN|EXPORT)\s+/;
49 this.definesFunction = /^\|?([a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*)\|?\s+PROC/;
50 this.labelFind = /[a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*/g;
Partouf654689f2018-10-27 14:40:05 +020051 this.dataDefn = /^(\|?[a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*\|?)\sDC?[BWDQ]\s|\s+DC?[BWDQ]\s|\s+ORG/;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -070052
53 // these are set to an impossible regex, because VC doesn't have inline assembly
54 this.startAppBlock = this.startAsmNesting = /a^/;
55 this.endAppBLock = this.endAsmNesting = /a^/;
56 // same, but for CUDA
57 this.cudaBeginDef = /a^/;
58 }
59
60 hasOpcode(line) {
61 // note: cl doesn't output leading labels
62 // strip comments
63 line = line.split(';', 1)[0];
64 // check for empty lines
65 if (line.length === 0) return false;
66 // check for a local label definition
67 if (line.match(this.localLabelDef)) return false;
68 // check for global label definitions
69 if (line.match(this.definesGlobal)) return false;
70 // check for data definitions
71 if (line.match(this.dataDefn)) return false;
72 // check for segment begin and end
73 if (line.match(this.beginSegment) || line.match(this.endSegment)) return false;
74 // check for function begin and end
75 // note: functionBegin is used for the function compile flags comment
76 if (line.match(this.definesFunction) || line.match(this.endProc)) return false;
77 // check for miscellaneous directives
78 if (line.match(this.miscDirective)) return false;
79
80 return !!line.match(this.hasOpcodeRe);
81 }
82
83 labelFindFor() {
84 return this.labelFind;
85 }
86
87 processAsm(asm, filters) {
Partoufb691b3f2018-09-14 03:19:08 +020088 if (filters.binary) {
89 return this.asmBinaryParser.processAsm(asm, filters);
90 }
91
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -070092 const getFilenameFromComment = line => {
93 const matches = line.match(this.filenameComment);
94 if (!matches) {
95 return null;
96 } else {
97 return matches[1];
98 }
99 };
100 const getLineNumberFromComment = line => {
101 const matches = line.match(this.lineNumberComment);
102 if (!matches) {
103 return null;
104 } else {
105 return parseInt(matches[1]);
106 }
107 };
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700108
109 const asmLines = utils.splitLines(asm);
110 // note: VC doesn't output unused labels, afaict
111
112 const stdInLooking = /<stdin>|^-$|example\.[^/]+$|<source>/;
113
114 // type source = {file: string option; line: int}
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700115 // type line = {line: string; source: source option}
116 // type func =
117 // { lines: line array
118 // ; name: string | undefined
119 // ; initialLine: int
120 // ; file: string option | undefined }
121 let resultObject = {
122 prefix: [], // line array
123 functions: [], // func array
124 postfix: null // line?
125 };
126
127 let currentFunction = null; // func option
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700128 let currentFile = undefined;
129 let currentLine = undefined;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700130
131 let seenEnd = false;
132
Partouf32481d22018-11-01 14:12:54 +0100133 const datadefLabels = [];
134 const datadefLabelsUsed = [];
Partouf654689f2018-10-27 14:40:05 +0200135
Partouf94abf882018-10-27 15:56:14 +0200136 const createSourceFor = (hasopc, currentFile, currentLine) => {
137 if (hasopc && (currentFile || currentLine)) {
138 return {
139 file: (currentFile ? currentFile : null),
140 line: (currentLine ? currentLine : null)
141 };
142 }
143
144 return null;
145 };
146
Partouf32481d22018-11-01 14:12:54 +0100147 const checkUsedDatadefLabels = (line) => {
148 const labels = line.match(this.labelFind);
Partouf94abf882018-10-27 15:56:14 +0200149 labels.splice(0, 1);
150 labels.forEach((item) => {
151 if (datadefLabels.find(l => item === l)) {
Partouf32481d22018-11-01 14:12:54 +0100152 datadefLabelsUsed.push(item);
Partouf94abf882018-10-27 15:56:14 +0200153 }
154 });
155 };
156
157 const checkBeginFunction = (line) => {
158 if (line.match(this.beginFunction)) {
159 currentFunction = {
160 lines: [],
161 initialLine: undefined,
162 name: undefined,
163 file: undefined
164 };
165 resultObject.functions.push(currentFunction);
166 }
167 };
168
169 const checkForDdefLabel = (line) => {
170 const ddef = line.match(this.dataDefn);
171 if (ddef && ddef[1]) {
172 datadefLabels.push(ddef[1]);
173 }
174 };
175
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700176 asmLines.forEach(line => {
177 if (line.trim() === "END") {
178 seenEnd = true;
179 if (!filters.directives) {
Nicole Mazzuca61ffd872018-07-30 14:18:46 -0700180 resultObject.postfix = {text: line, source: null};
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700181 }
182 return;
183 }
184 if (line.trim() === "") {
185 if (seenEnd) return;
186
187 const emptyLine = {text: "", source: null};
188 if (currentFunction === null) {
189 resultObject.prefix.push(emptyLine);
190 } else {
191 currentFunction.lines.push(emptyLine);
192 }
193 return;
194 }
195 if (seenEnd) {
196 // this should never happen
197 throw Error("Visual C++: text after the end statement");
198 }
199
200 let tmp = null;
201 tmp = getFilenameFromComment(line);
202 if (tmp !== null) {
203 if (currentFunction === null) {
Matt Godbolt1f6e4c82019-05-16 17:30:15 -0500204 logger.error("We have a file comment outside of a function: %s",
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700205 line);
206 }
207 // if the file is the "main file", give it the file `null`
208 if (tmp.match(stdInLooking)) {
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700209 currentFile = null;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700210 } else {
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700211 currentFile = tmp;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700212 }
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700213 if (currentFunction.file === undefined) {
214 currentFunction.file = currentFile;
215 }
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700216 } else {
217 tmp = getLineNumberFromComment(line);
218 if (tmp !== null) {
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700219 if (currentFile === undefined) {
Matt Godbolt1f6e4c82019-05-16 17:30:15 -0500220 logger.error("Somehow, we have a line number comment without a file comment: %s",
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700221 line);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700222 }
Nicole Mazzucadb325b22018-08-06 14:58:10 -0700223 if (currentFunction.initialLine === undefined) {
224 currentFunction.initialLine = tmp;
225 }
226 currentLine = tmp;
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700227 }
228 }
229
Partouf94abf882018-10-27 15:56:14 +0200230 checkBeginFunction(line);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700231
232 const functionName = line.match(this.definesFunction);
233 if (functionName) {
Matt Godbolteabe33c2018-10-22 23:00:28 -0500234 if (asmLines.length === 0) {
235 return;
236 }
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700237 currentFunction.name = functionName[1];
238 }
239
240 if (filters.commentOnly && line.match(this.commentOnly)) return;
241
242 const shouldSkip = filters.directives && (
243 line.match(this.endSegment) ||
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700244 line.match(this.definesGlobal) ||
245 line.match(this.miscDirective) ||
246 line.match(this.beginSegment));
247
248 if (shouldSkip) {
249 return;
250 }
251
Partouf94abf882018-10-27 15:56:14 +0200252 checkForDdefLabel(line);
Partouf654689f2018-10-27 14:40:05 +0200253
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700254 line = utils.expandTabs(line);
Partouf654689f2018-10-27 14:40:05 +0200255 const hasopc = this.hasOpcode(line);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700256 const textAndSource = {
Matt Godbolteabe33c2018-10-22 23:00:28 -0500257 text: AsmRegex.filterAsmLine(line, filters),
Partouf94abf882018-10-27 15:56:14 +0200258 source: createSourceFor(hasopc, currentFile, currentLine)
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700259 };
260 if (currentFunction === null) {
261 resultObject.prefix.push(textAndSource);
262 } else if (!shouldSkip) {
263 currentFunction.lines.push(textAndSource);
264 }
Partouf654689f2018-10-27 14:40:05 +0200265
Partouf32481d22018-11-01 14:12:54 +0100266 checkUsedDatadefLabels(line);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700267 });
268
Partouf32481d22018-11-01 14:12:54 +0100269 return this.resultObjectIntoArray(resultObject, filters, datadefLabelsUsed);
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700270 }
271
Partouf654689f2018-10-27 14:40:05 +0200272 resultObjectIntoArray(obj, filters, ddefLabelsUsed) {
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700273 obj.functions.sort((f1, f2) => {
274 // order the main file above all others
275 if (f1.file === null && f2.file !== null) {
276 return -1;
277 }
278 if (f1.file !== null && f2.file === null) {
279 return 1;
280 }
281 // order no-file below all others
282 if (f1.file === undefined && f2.file !== undefined) {
283 return 1;
284 }
285 if (f1.file !== undefined && f2.file === undefined) {
286 return -1;
287 }
288
289 // if the files are the same, use line number ordering
290 if (f1.file === f2.file) {
291 // if the lines are the same as well, it's either:
292 // - two template instantiations, or
293 // - two compiler generated functions
294 // order by name
295 if (f1.initialLine === f2.initialLine) {
296 return f1.name.localeCompare(f2.name);
297 } else {
298 return f1.initialLine - f2.initialLine;
299 }
300 }
301
302 // else, order by file
303 return f1.file.localeCompare(f2.file);
304 });
305
306 let result = [];
307 let lastLineWasWhitespace = true;
308 let pushLine = line => {
309 if (line.text.trim() === '') {
310 if (!lastLineWasWhitespace) {
311 result.push({text: "", source: null});
312 lastLineWasWhitespace = true;
313 }
314 } else {
315 result.push(line);
316 lastLineWasWhitespace = false;
317 }
318 };
319
Partouf654689f2018-10-27 14:40:05 +0200320 if (filters.labels) {
321 let currentDdef = false;
322 let isUsed = false;
323 for (const line of obj.prefix) {
324 const matches = line.text.match(this.dataDefn);
325 if (matches) {
326 if (matches[1]) {
327 currentDdef = matches[1];
328 isUsed = ddefLabelsUsed.find(label => currentDdef === label);
329 }
330
331 if (isUsed) {
332 pushLine(line);
333 }
334 } else {
335 currentDdef = false;
336 pushLine(line);
337 }
338 }
339 } else {
340 for (const line of obj.prefix) {
341 pushLine(line);
342 }
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700343 }
344
345 for (const func of obj.functions) {
Nicole Mazzuca44d39302018-07-31 11:38:46 -0700346 if (!filters.libraryCode || func.file === null) {
347 pushLine({text: "", source: null});
348 for (const line of func.lines) {
349 pushLine(line);
350 }
Nicole Mazzuca9ddf22f2018-07-28 15:18:43 -0700351 }
352 }
353
354 if (obj.postfix !== null) {
355 pushLine(obj.postfix);
356 }
357
358 return result;
359 }
360}
361
362module.exports = AsmParser;