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