1 /** 2 * Inline assembler for the GCC D compiler. 3 * 4 * Copyright (C) 2018-2020 by The D Language Foundation, All Rights Reserved 5 * Authors: Iain Buclaw 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/iasmgcc.d, _iasmgcc.d) 8 * Documentation: https://dlang.org/phobos/dmd_iasmgcc.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/iasmgcc.d 10 */ 11 12 module dmd.iasmgcc; 13 14 import core.stdc.string; 15 16 import dmd.arraytypes; 17 import dmd.astcodegen; 18 import dmd.dscope; 19 import dmd.errors; 20 import dmd.expression; 21 import dmd.expressionsem; 22 import dmd.identifier; 23 import dmd.globals; 24 import dmd.parse; 25 import dmd.tokens; 26 import dmd.statement; 27 import dmd.statementsem; 28 29 private: 30 31 /*********************************** 32 * Parse list of extended asm input or output operands. 33 * Grammar: 34 * | Operands: 35 * | SymbolicName(opt) StringLiteral ( AssignExpression ) 36 * | SymbolicName(opt) StringLiteral ( AssignExpression ), Operands 37 * | 38 * | SymbolicName: 39 * | [ Identifier ] 40 * Params: 41 * p = parser state 42 * s = asm statement to parse 43 * Returns: 44 * number of operands added to the gcc asm statement 45 */ 46 int parseExtAsmOperands(Parser)(Parser p, GccAsmStatement s) 47 { 48 int numargs = 0; 49 50 while (1) 51 { 52 Expression arg; 53 Identifier name; 54 Expression constraint; 55 56 switch (p.token.value) 57 { 58 case TOK.semicolon: 59 case TOK.colon: 60 case TOK.endOfFile: 61 return numargs; 62 63 case TOK.leftBracket: 64 if (p.peekNext() == TOK.identifier) 65 { 66 // Skip over opening `[` 67 p.nextToken(); 68 // Store the symbolic name 69 name = p.token.ident; 70 p.nextToken(); 71 } 72 else 73 { 74 p.error(s.loc, "expected identifier after `[`"); 75 goto Lerror; 76 } 77 // Look for closing `]` 78 p.check(TOK.rightBracket); 79 // Look for the string literal and fall through 80 if (p.token.value == TOK.string_) 81 goto case; 82 else 83 goto default; 84 85 case TOK.string_: 86 constraint = p.parsePrimaryExp(); 87 // @@@DEPRECATED@@@ 88 // Old parser allowed omitting parentheses around the expression. 89 // Deprecated in 2.091. Can be made permanent error after 2.100 90 if (p.token.value != TOK.leftParentheses) 91 { 92 arg = p.parseAssignExp(); 93 deprecation(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars()); 94 } 95 else 96 { 97 // Look for the opening `(` 98 p.check(TOK.leftParentheses); 99 // Parse the assign expression 100 arg = p.parseAssignExp(); 101 // Look for the closing `)` 102 p.check(TOK.rightParentheses); 103 } 104 105 if (!s.args) 106 { 107 s.names = new Identifiers(); 108 s.constraints = new Expressions(); 109 s.args = new Expressions(); 110 } 111 s.names.push(name); 112 s.args.push(arg); 113 s.constraints.push(constraint); 114 numargs++; 115 116 if (p.token.value == TOK.comma) 117 p.nextToken(); 118 break; 119 120 default: 121 p.error("expected constant string constraint for operand, not `%s`", 122 p.token.toChars()); 123 goto Lerror; 124 } 125 } 126 Lerror: 127 while (p.token.value != TOK.rightCurly && 128 p.token.value != TOK.semicolon && 129 p.token.value != TOK.endOfFile) 130 p.nextToken(); 131 132 return numargs; 133 } 134 135 /*********************************** 136 * Parse list of extended asm clobbers. 137 * Grammar: 138 * | Clobbers: 139 * | StringLiteral 140 * | StringLiteral , Clobbers 141 * Params: 142 * p = parser state 143 * Returns: 144 * array of parsed clobber expressions 145 */ 146 Expressions *parseExtAsmClobbers(Parser)(Parser p) 147 { 148 Expressions *clobbers; 149 150 while (1) 151 { 152 Expression clobber; 153 154 switch (p.token.value) 155 { 156 case TOK.semicolon: 157 case TOK.colon: 158 case TOK.endOfFile: 159 return clobbers; 160 161 case TOK.string_: 162 clobber = p.parsePrimaryExp(); 163 if (!clobbers) 164 clobbers = new Expressions(); 165 clobbers.push(clobber); 166 167 if (p.token.value == TOK.comma) 168 p.nextToken(); 169 break; 170 171 default: 172 p.error("expected constant string constraint for clobber name, not `%s`", 173 p.token.toChars()); 174 goto Lerror; 175 } 176 } 177 Lerror: 178 while (p.token.value != TOK.rightCurly && 179 p.token.value != TOK.semicolon && 180 p.token.value != TOK.endOfFile) 181 p.nextToken(); 182 183 return clobbers; 184 } 185 186 /*********************************** 187 * Parse list of extended asm goto labels. 188 * Grammar: 189 * | GotoLabels: 190 * | Identifier 191 * | Identifier , GotoLabels 192 * Params: 193 * p = parser state 194 * Returns: 195 * array of parsed goto labels 196 */ 197 Identifiers *parseExtAsmGotoLabels(Parser)(Parser p) 198 { 199 Identifiers *labels; 200 201 while (1) 202 { 203 switch (p.token.value) 204 { 205 case TOK.semicolon: 206 case TOK.endOfFile: 207 return labels; 208 209 case TOK.identifier: 210 if (!labels) 211 labels = new Identifiers(); 212 labels.push(p.token.ident); 213 214 if (p.nextToken() == TOK.comma) 215 p.nextToken(); 216 break; 217 218 default: 219 p.error("expected identifier for goto label name, not `%s`", 220 p.token.toChars()); 221 goto Lerror; 222 } 223 } 224 Lerror: 225 while (p.token.value != TOK.rightCurly && 226 p.token.value != TOK.semicolon && 227 p.token.value != TOK.endOfFile) 228 p.nextToken(); 229 230 return labels; 231 } 232 233 /*********************************** 234 * Parse a gcc asm statement. 235 * There are three forms of inline asm statements, basic, extended, and goto. 236 * Grammar: 237 * | AsmInstruction: 238 * | BasicAsmInstruction 239 * | ExtAsmInstruction 240 * | GotoAsmInstruction 241 * | 242 * | BasicAsmInstruction: 243 * | Expression 244 * | 245 * | ExtAsmInstruction: 246 * | Expression : Operands(opt) : Operands(opt) : Clobbers(opt) 247 * | 248 * | GotoAsmInstruction: 249 * | Expression : : Operands(opt) : Clobbers(opt) : GotoLabels(opt) 250 * Params: 251 * p = parser state 252 * s = asm statement to parse 253 * Returns: 254 * the parsed gcc asm statement 255 */ 256 GccAsmStatement parseGccAsm(Parser)(Parser p, GccAsmStatement s) 257 { 258 s.insn = p.parseExpression(); 259 if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) 260 goto Ldone; 261 262 // No semicolon followed after instruction template, treat as extended asm. 263 foreach (section; 0 .. 4) 264 { 265 p.check(TOK.colon); 266 267 final switch (section) 268 { 269 case 0: 270 s.outputargs = p.parseExtAsmOperands(s); 271 break; 272 273 case 1: 274 p.parseExtAsmOperands(s); 275 break; 276 277 case 2: 278 s.clobbers = p.parseExtAsmClobbers(); 279 break; 280 281 case 3: 282 s.labels = p.parseExtAsmGotoLabels(); 283 break; 284 } 285 286 if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) 287 goto Ldone; 288 } 289 Ldone: 290 p.check(TOK.semicolon); 291 292 return s; 293 } 294 295 /*********************************** 296 * Parse and run semantic analysis on a GccAsmStatement. 297 * Params: 298 * s = gcc asm statement being parsed 299 * sc = the scope where the asm statement is located 300 * Returns: 301 * the completed gcc asm statement, or null if errors occurred 302 */ 303 public Statement gccAsmSemantic(GccAsmStatement s, Scope *sc) 304 { 305 //printf("GccAsmStatement.semantic()\n"); 306 scope p = new Parser!ASTCodegen(sc._module, ";", false); 307 308 // Make a safe copy of the token list before parsing. 309 Token *toklist = null; 310 Token **ptoklist = &toklist; 311 312 for (Token *token = s.tokens; token; token = token.next) 313 { 314 *ptoklist = p.allocateToken(); 315 memcpy(*ptoklist, token, Token.sizeof); 316 ptoklist = &(*ptoklist).next; 317 *ptoklist = null; 318 } 319 p.token = *toklist; 320 p.scanloc = s.loc; 321 322 // Parse the gcc asm statement. 323 const errors = global.errors; 324 s = p.parseGccAsm(s); 325 p.reportDiagnostics(); 326 if (errors != global.errors) 327 return null; 328 s.stc = sc.stc; 329 330 // Fold the instruction template string. 331 s.insn = semanticString(sc, s.insn, "asm instruction template"); 332 333 if (s.labels && s.outputargs) 334 s.error("extended asm statements with labels cannot have output constraints"); 335 336 // Analyse all input and output operands. 337 if (s.args) 338 { 339 foreach (i; 0 .. s.args.dim) 340 { 341 Expression e = (*s.args)[i]; 342 e = e.expressionSemantic(sc); 343 // Check argument is a valid lvalue/rvalue. 344 if (i < s.outputargs) 345 e = e.modifiableLvalue(sc, null); 346 else if (e.checkValue()) 347 e = new ErrorExp(); 348 (*s.args)[i] = e; 349 350 e = (*s.constraints)[i]; 351 e = e.expressionSemantic(sc); 352 assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1); 353 (*s.constraints)[i] = e; 354 } 355 } 356 357 // Analyse all clobbers. 358 if (s.clobbers) 359 { 360 foreach (i; 0 .. s.clobbers.dim) 361 { 362 Expression e = (*s.clobbers)[i]; 363 e = e.expressionSemantic(sc); 364 assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1); 365 (*s.clobbers)[i] = e; 366 } 367 } 368 369 // Analyse all goto labels. 370 if (s.labels) 371 { 372 foreach (i; 0 .. s.labels.dim) 373 { 374 Identifier ident = (*s.labels)[i]; 375 GotoStatement gs = new GotoStatement(s.loc, ident); 376 if (!s.gotos) 377 s.gotos = new GotoStatements(); 378 s.gotos.push(gs); 379 gs.statementSemantic(sc); 380 } 381 } 382 383 return s; 384 } 385 386 unittest 387 { 388 import dmd.mtype : TypeBasic; 389 390 uint errors = global.startGagging(); 391 scope(exit) global.endGagging(errors); 392 393 // If this check fails, then Type._init() was called before reaching here, 394 // and the entire chunk of code that follows can be removed. 395 assert(ASTCodegen.Type.tint32 is null); 396 // Minimally initialize the cached types in ASTCodegen.Type, as they are 397 // dependencies for some fail asm tests to succeed. 398 ASTCodegen.Type.stringtable._init(); 399 scope(exit) 400 { 401 ASTCodegen.Type.deinitialize(); 402 ASTCodegen.Type.tint32 = null; 403 } 404 scope tint32 = new TypeBasic(ASTCodegen.Tint32); 405 ASTCodegen.Type.tint32 = tint32; 406 407 // Imitates asmSemantic if version = IN_GCC. 408 static int semanticAsm(Token* tokens) 409 { 410 const errors = global.errors; 411 scope gas = new GccAsmStatement(Loc.initial, tokens); 412 scope p = new Parser!ASTCodegen(null, ";", false); 413 p.token = *tokens; 414 p.parseGccAsm(gas); 415 p.reportDiagnostics(); 416 return global.errors - errors; 417 } 418 419 // Imitates parseStatement for asm statements. 420 static void parseAsm(string input, bool expectError) 421 { 422 // Generate tokens from input test. 423 scope p = new Parser!ASTCodegen(null, input, false); 424 p.nextToken(); 425 426 Token* toklist = null; 427 Token** ptoklist = &toklist; 428 p.check(TOK.asm_); 429 p.check(TOK.leftCurly); 430 while (1) 431 { 432 if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile) 433 break; 434 *ptoklist = p.allocateToken(); 435 memcpy(*ptoklist, &p.token, Token.sizeof); 436 ptoklist = &(*ptoklist).next; 437 *ptoklist = null; 438 p.nextToken(); 439 } 440 p.check(TOK.rightCurly); 441 442 auto res = semanticAsm(toklist); 443 // Checks for both unexpected passes and failures. 444 assert((res == 0) != expectError); 445 } 446 447 /// Assembly Tests, all should pass. 448 /// Note: Frontend is not initialized, use only strings and identifiers. 449 immutable string[] passAsmTests = [ 450 // Basic asm statement 451 q{ asm { "nop"; 452 } }, 453 454 // Extended asm statement 455 q{ asm { "cpuid" 456 : "=a" (a), "=b" (b), "=c" (c), "=d" (d) 457 : "a" (input); 458 } }, 459 460 // Assembly with symbolic names 461 q{ asm { "bts %[base], %[offset]" 462 : [base] "+rm" (*ptr), 463 : [offset] "Ir" (bitnum); 464 } }, 465 466 // Assembly with clobbers 467 q{ asm { "cpuid" 468 : "=a" (a) 469 : "a" (input) 470 : "ebx", "ecx", "edx"; 471 } }, 472 473 // Goto asm statement 474 q{ asm { "jmp %l0" 475 : 476 : 477 : 478 : Ljmplabel; 479 } }, 480 481 // Any CTFE-able string allowed as instruction template. 482 q{ asm { generateAsm(); 483 } }, 484 485 // Likewise mixins, permissible so long as the result is a string. 486 q{ asm { mixin(`"repne"`, `~ "scasb"`); 487 } }, 488 ]; 489 490 immutable string[] failAsmTests = [ 491 // Found 'h' when expecting ';' 492 q{ asm { ""h; 493 } }, 494 495 // https://issues.dlang.org/show_bug.cgi?id=20592 496 q{ asm { "nop" : [name] string (expr); } }, 497 498 // Expression expected, not ';' 499 q{ asm { ""[; 500 } }, 501 502 // Expression expected, not ':' 503 q{ asm { "" 504 : 505 : "g" (a ? b : : c); 506 } }, 507 ]; 508 509 foreach (test; passAsmTests) 510 parseAsm(test, false); 511 512 foreach (test; failAsmTests) 513 parseAsm(test, true); 514 }