Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 1 | // 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 | |
| 25 | const AsmParserBase = require('./asm-parser'), |
| 26 | logger = require('./logger').logger, |
Matt Godbolt | eabe33c | 2018-10-22 23:00:28 -0500 | [diff] [blame] | 27 | utils = require('./utils'), |
| 28 | AsmRegex = require('./asmregex').AsmRegex; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 29 | |
| 30 | class AsmParser extends AsmParserBase { |
| 31 | constructor(compilerProps) { |
| 32 | super(compilerProps); |
Partouf | b691b3f | 2018-09-14 03:19:08 +0200 | [diff] [blame] | 33 | this.asmBinaryParser = new AsmParserBase(compilerProps); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 34 | 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; |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 51 | this.dataDefn = /^(\|?[a-zA-Z@$?_][a-zA-Z0-9@$?_<>]*\|?)\sDC?[BWDQ]\s|\s+DC?[BWDQ]\s|\s+ORG/; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 52 | |
| 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) { |
Partouf | b691b3f | 2018-09-14 03:19:08 +0200 | [diff] [blame] | 88 | if (filters.binary) { |
| 89 | return this.asmBinaryParser.processAsm(asm, filters); |
| 90 | } |
| 91 | |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 92 | 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 Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 108 | |
| 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 Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 115 | // 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 Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 128 | let currentFile = undefined; |
| 129 | let currentLine = undefined; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 130 | |
| 131 | let seenEnd = false; |
| 132 | |
Partouf | 32481d2 | 2018-11-01 14:12:54 +0100 | [diff] [blame] | 133 | const datadefLabels = []; |
| 134 | const datadefLabelsUsed = []; |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 135 | |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 136 | 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 | |
Partouf | 32481d2 | 2018-11-01 14:12:54 +0100 | [diff] [blame] | 147 | const checkUsedDatadefLabels = (line) => { |
| 148 | const labels = line.match(this.labelFind); |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 149 | labels.splice(0, 1); |
| 150 | labels.forEach((item) => { |
| 151 | if (datadefLabels.find(l => item === l)) { |
Partouf | 32481d2 | 2018-11-01 14:12:54 +0100 | [diff] [blame] | 152 | datadefLabelsUsed.push(item); |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 153 | } |
| 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 Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 176 | asmLines.forEach(line => { |
| 177 | if (line.trim() === "END") { |
| 178 | seenEnd = true; |
| 179 | if (!filters.directives) { |
Nicole Mazzuca | 61ffd87 | 2018-07-30 14:18:46 -0700 | [diff] [blame] | 180 | resultObject.postfix = {text: line, source: null}; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 181 | } |
| 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 Godbolt | 1f6e4c8 | 2019-05-16 17:30:15 -0500 | [diff] [blame] | 204 | logger.error("We have a file comment outside of a function: %s", |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 205 | line); |
| 206 | } |
| 207 | // if the file is the "main file", give it the file `null` |
| 208 | if (tmp.match(stdInLooking)) { |
Nicole Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 209 | currentFile = null; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 210 | } else { |
Nicole Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 211 | currentFile = tmp; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 212 | } |
Nicole Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 213 | if (currentFunction.file === undefined) { |
| 214 | currentFunction.file = currentFile; |
| 215 | } |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 216 | } else { |
| 217 | tmp = getLineNumberFromComment(line); |
| 218 | if (tmp !== null) { |
Nicole Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 219 | if (currentFile === undefined) { |
Matt Godbolt | 1f6e4c8 | 2019-05-16 17:30:15 -0500 | [diff] [blame] | 220 | logger.error("Somehow, we have a line number comment without a file comment: %s", |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 221 | line); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 222 | } |
Nicole Mazzuca | db325b2 | 2018-08-06 14:58:10 -0700 | [diff] [blame] | 223 | if (currentFunction.initialLine === undefined) { |
| 224 | currentFunction.initialLine = tmp; |
| 225 | } |
| 226 | currentLine = tmp; |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 227 | } |
| 228 | } |
| 229 | |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 230 | checkBeginFunction(line); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 231 | |
| 232 | const functionName = line.match(this.definesFunction); |
| 233 | if (functionName) { |
Matt Godbolt | eabe33c | 2018-10-22 23:00:28 -0500 | [diff] [blame] | 234 | if (asmLines.length === 0) { |
| 235 | return; |
| 236 | } |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 237 | 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 Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 244 | line.match(this.definesGlobal) || |
| 245 | line.match(this.miscDirective) || |
| 246 | line.match(this.beginSegment)); |
| 247 | |
| 248 | if (shouldSkip) { |
| 249 | return; |
| 250 | } |
| 251 | |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 252 | checkForDdefLabel(line); |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 253 | |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 254 | line = utils.expandTabs(line); |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 255 | const hasopc = this.hasOpcode(line); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 256 | const textAndSource = { |
Matt Godbolt | eabe33c | 2018-10-22 23:00:28 -0500 | [diff] [blame] | 257 | text: AsmRegex.filterAsmLine(line, filters), |
Partouf | 94abf88 | 2018-10-27 15:56:14 +0200 | [diff] [blame] | 258 | source: createSourceFor(hasopc, currentFile, currentLine) |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 259 | }; |
| 260 | if (currentFunction === null) { |
| 261 | resultObject.prefix.push(textAndSource); |
| 262 | } else if (!shouldSkip) { |
| 263 | currentFunction.lines.push(textAndSource); |
| 264 | } |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 265 | |
Partouf | 32481d2 | 2018-11-01 14:12:54 +0100 | [diff] [blame] | 266 | checkUsedDatadefLabels(line); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 267 | }); |
| 268 | |
Partouf | 32481d2 | 2018-11-01 14:12:54 +0100 | [diff] [blame] | 269 | return this.resultObjectIntoArray(resultObject, filters, datadefLabelsUsed); |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 270 | } |
| 271 | |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 272 | resultObjectIntoArray(obj, filters, ddefLabelsUsed) { |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 273 | 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 | |
Partouf | 654689f | 2018-10-27 14:40:05 +0200 | [diff] [blame] | 320 | 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 Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 343 | } |
| 344 | |
| 345 | for (const func of obj.functions) { |
Nicole Mazzuca | 44d3930 | 2018-07-31 11:38:46 -0700 | [diff] [blame] | 346 | if (!filters.libraryCode || func.file === null) { |
| 347 | pushLine({text: "", source: null}); |
| 348 | for (const line of func.lines) { |
| 349 | pushLine(line); |
| 350 | } |
Nicole Mazzuca | 9ddf22f | 2018-07-28 15:18:43 -0700 | [diff] [blame] | 351 | } |
| 352 | } |
| 353 | |
| 354 | if (obj.postfix !== null) { |
| 355 | pushLine(obj.postfix); |
| 356 | } |
| 357 | |
| 358 | return result; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | module.exports = AsmParser; |