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