| /** |
| * Inline assembler for the GCC D compiler. |
| * |
| * Copyright (C) 2018-2025 by The D Language Foundation, All Rights Reserved |
| * Authors: Iain Buclaw |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/iasmgcc.d, _iasmgcc.d) |
| * Documentation: https://dlang.org/phobos/dmd_iasmgcc.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/iasmgcc.d |
| */ |
| |
| module dmd.iasm.gcc; |
| |
| import core.stdc.string; |
| |
| import dmd.arraytypes; |
| import dmd.astcodegen; |
| import dmd.dscope; |
| import dmd.dsymbol; |
| import dmd.errors; |
| import dmd.errorsink; |
| import dmd.expression; |
| import dmd.expressionsem; |
| import dmd.identifier; |
| import dmd.globals; |
| import dmd.location; |
| import dmd.cparse; |
| import dmd.parse; |
| import dmd.target; |
| import dmd.tokens; |
| import dmd.statement; |
| import dmd.statementsem; |
| import dmd.typesem; |
| |
| /*********************************** |
| * Parse and run semantic analysis on a GccAsmStatement. |
| * Params: |
| * s = gcc asm statement being parsed |
| * sc = the scope where the asm statement is located |
| * Returns: |
| * the completed gcc asm statement, or null if errors occurred |
| */ |
| public Statement gccAsmSemantic(GccAsmStatement s, Scope* sc) |
| { |
| //printf("GccAsmStatement.semantic()\n"); |
| const bool doUnittests = global.params.parsingUnittestsRequired(); |
| scope p = (sc && sc.inCfile) |
| ? new CParser!ASTCodegen(sc._module, "", false, global.errorSink, target.c, null, &global.compileEnv) |
| : new Parser!ASTCodegen(sc._module, "", false, global.errorSink, &global.compileEnv, doUnittests); |
| |
| // Make a safe copy of the token list before parsing. |
| Token* toklist = null; |
| Token **ptoklist = &toklist; |
| |
| for (Token* token = s.tokens; token; token = token.next) |
| { |
| *ptoklist = p.allocateToken(); |
| memcpy(*ptoklist, token, Token.sizeof); |
| ptoklist = &(*ptoklist).next; |
| *ptoklist = null; |
| } |
| // Append closing `;` location. |
| *ptoklist = p.allocateToken(); |
| (*ptoklist).value = TOK.semicolon; |
| (*ptoklist).loc = s.loc; |
| ptoklist = &(*ptoklist).next; |
| *ptoklist = null; |
| |
| // Adjust starting line number of the parser. |
| p.token = *toklist; |
| p.baseLoc.startLine = s.loc.linnum; |
| p.linnum = s.loc.linnum; |
| |
| // Parse the gcc asm statement. |
| const errors = global.errors; |
| s = p.parseGccAsm(s); |
| if (errors != global.errors) |
| return null; |
| s.stc = sc.stc; |
| |
| // Fold the instruction template string. |
| s.insn = semanticAsmString(sc, s.insn, "asm instruction template"); |
| |
| if (s.labels && s.outputargs) |
| p.eSink.error(s.loc, "extended asm statements with labels cannot have output constraints"); |
| |
| // Analyse all input and output operands. |
| if (s.args) |
| { |
| foreach (i; 0 .. s.args.length) |
| { |
| Expression ec = (*s.constraints)[i]; |
| (*s.constraints)[i] = semanticAsmString(sc, ec, "asm operand"); |
| |
| Expression earg = (*s.args)[i]; |
| earg = earg.expressionSemantic(sc); |
| // Check argument is a valid lvalue/rvalue. |
| if (i < s.outputargs) |
| earg = earg.modifiableLvalue(sc); |
| else if (earg.checkValue()) |
| earg = ErrorExp.get(); |
| (*s.args)[i] = earg; |
| } |
| } |
| |
| // Analyse all clobbers. |
| if (s.clobbers) |
| { |
| foreach (i; 0 .. s.clobbers.length) |
| { |
| Expression ec = (*s.clobbers)[i]; |
| (*s.clobbers)[i] = semanticAsmString(sc, ec, "asm clobber"); |
| } |
| } |
| |
| // Analyse all goto labels. |
| if (s.labels) |
| { |
| foreach (i; 0 .. s.labels.length) |
| { |
| Identifier ident = (*s.labels)[i]; |
| GotoStatement gs = new GotoStatement(s.loc, ident); |
| if (!s.gotos) |
| s.gotos = new GotoStatements(); |
| s.gotos.push(gs); |
| gs.statementSemantic(sc); |
| } |
| } |
| |
| return s; |
| } |
| |
| /*********************************** |
| * Run semantic analysis on an CAsmDeclaration. |
| * Params: |
| * ad = asm declaration |
| * sc = the scope where the asm declaration is located |
| */ |
| public void gccAsmSemantic(CAsmDeclaration ad, Scope* sc) |
| { |
| import dmd.typesem : pointerTo; |
| ad.code = semanticString(sc, ad.code, "asm definition"); |
| ad.code.type = ad.code.type.nextOf().pointerTo(); |
| |
| // Asm definition always needs emitting into the root module. |
| import dmd.dmodule : Module; |
| if (sc._module && sc._module.isRoot()) |
| return; |
| if (Module m = Module.rootModule) |
| m.members.push(ad); |
| } |
| |
| private: |
| |
| |
| /*********************************** |
| * Issue error if the current token is not `value`. |
| * Otherwise, advance to next token. |
| * Params: |
| * p = parser state |
| * value = token value to compare with |
| * Returns: |
| * true advanced to next token |
| * false error was issued |
| */ |
| bool requireToken(Parser)(Parser p, TOK value) |
| { |
| if (p.token.value == value) |
| { |
| p.nextToken(); |
| return true; |
| } |
| |
| p.eSink.error(p.token.loc, "found `%s` when expecting `%s`", |
| p.token.toChars(), Token.toChars(value)); |
| return false; |
| } |
| |
| /*********************************** |
| * Run semantic analysis on `exp`, resolving it as a compile-time string. |
| * Params: |
| * sc = scope |
| * exp = Expression which expected as a string |
| * s = What the string is expected for, used in error diagnostic |
| * Returns: |
| * StringExp or ErrorExp |
| */ |
| Expression semanticAsmString(Scope* sc, Expression exp, const char *s) |
| { |
| import dmd.dcast : implicitCastTo; |
| import dmd.dsymbolsem : resolveAliasThis; |
| import dmd.mtype : Type; |
| import dmd.typesem : isAggregate; |
| |
| exp = expressionSemantic(exp, sc); |
| |
| // Resolve `alias this` if we were given a struct literal. |
| if (auto ad = isAggregate(exp.type)) |
| { |
| if (ad.aliasthis && ad.type && !ad.type.isTypeError()) |
| exp = resolveAliasThis(sc, exp); |
| } |
| |
| // Evaluate the expression as a string now or error trying. |
| if (auto se = semanticString(sc, exp, s)) |
| exp = implicitCastTo(se, sc, Type.tstring); |
| |
| return exp; |
| } |
| |
| /*********************************** |
| * Parse a D or ImportC assignment expression |
| */ |
| Expression parseAssignment(Parser)(Parser p) |
| { |
| if (p.Ccompile) |
| return (cast(CParser!ASTCodegen)p).cparseAssignExp(); |
| |
| return p.parseAssignExp(); |
| } |
| |
| /*********************************** |
| * Parse a D or ImportC conditional expression |
| */ |
| Expression parseConditional(Parser)(Parser p) |
| { |
| if (p.Ccompile) |
| return (cast(CParser!ASTCodegen)p).cparseCondExp(); |
| |
| return p.parseCondExp(); |
| } |
| |
| /*********************************** |
| * Parse a D or ImportC primary expression |
| */ |
| Expression parsePrimary(Parser)(Parser p) |
| { |
| if (p.Ccompile) |
| return (cast(CParser!ASTCodegen)p).cparsePrimaryExp(); |
| |
| return p.parsePrimaryExp(); |
| } |
| |
| /*********************************** |
| * Parse an expression that evaluates to a string. |
| * Grammar: |
| * | AsmStringExpr: |
| * | StringLiteral |
| * | ( ConditionalExpression ) |
| * Params: |
| * p = parser state |
| * Returns: |
| * the parsed string expression |
| */ |
| Expression parseAsmString(Parser)(Parser p) |
| { |
| if (p.token.value == TOK.leftParenthesis) |
| { |
| // Skip over opening `(` |
| p.nextToken(); |
| Expression insn = p.parseConditional(); |
| if (insn.isErrorExp()) |
| return insn; |
| |
| // Look for closing `)`. |
| if (!p.requireToken(TOK.rightParenthesis)) |
| return ErrorExp.get(); |
| |
| return insn; |
| } |
| else if (p.token.value != TOK.string_) |
| { |
| p.eSink.error(p.token.loc, "expected string literal or expression in parentheses"); |
| return ErrorExp.get(); |
| } |
| |
| return p.parsePrimary(); |
| } |
| |
| /*********************************** |
| * Parse list of extended asm input or output operands. |
| * Grammar: |
| * | Operands: |
| * | SymbolicName(opt) AsmStringExpr ( AssignExpression ) |
| * | SymbolicName(opt) AsmStringExpr ( AssignExpression ), Operands |
| * | |
| * | SymbolicName: |
| * | [ Identifier ] |
| * Params: |
| * p = parser state |
| * s = asm statement to parse |
| * Returns: |
| * number of operands added to the gcc asm statement |
| */ |
| int parseExtAsmOperands(Parser)(Parser p, GccAsmStatement s) |
| { |
| int numargs = 0; |
| |
| if (p.token.value == TOK.colon || |
| p.token.value == TOK.colonColon || |
| p.token.value == TOK.semicolon || |
| p.token.value == TOK.endOfFile) |
| return numargs; |
| |
| while (1) |
| { |
| Expression arg; |
| Identifier name; |
| |
| if (p.token.value == TOK.leftBracket) |
| { |
| // Skip over opening `[` |
| p.nextToken(); |
| if (p.token.value == TOK.identifier) |
| { |
| // Store the symbolic name |
| name = p.token.ident; |
| p.nextToken(); |
| } |
| else |
| { |
| p.eSink.error(p.token.loc, "identifier expected after `[`"); |
| goto Lerror; |
| } |
| // Look for closing `]` |
| if (!p.requireToken(TOK.rightBracket)) |
| goto Lerror; |
| } |
| |
| // Look for the constraint string. |
| Expression constraint = p.parseAsmString(); |
| if (constraint.isErrorExp()) |
| goto Lerror; |
| |
| // Look for the opening `(` |
| if (!p.requireToken(TOK.leftParenthesis)) |
| goto Lerror; |
| |
| // Parse the assign expression |
| arg = p.parseAssignment(); |
| if (arg.isErrorExp()) |
| goto Lerror; |
| |
| // Look for the closing `)` |
| if (!p.requireToken(TOK.rightParenthesis)) |
| goto Lerror; |
| |
| // Add this operand to the list. |
| if (!s.args) |
| { |
| s.names = new Identifiers(); |
| s.constraints = new Expressions(); |
| s.args = new Expressions(); |
| } |
| s.names.push(name); |
| s.args.push(arg); |
| s.constraints.push(constraint); |
| numargs++; |
| |
| // If the next token is not a `,`, there are no more operands. |
| if (p.token.value != TOK.comma) |
| return numargs; |
| |
| // Skip over the `,` token. |
| p.nextToken(); |
| } |
| Lerror: |
| while (p.token.value != TOK.semicolon && |
| p.token.value != TOK.endOfFile) |
| p.nextToken(); |
| |
| return 0; |
| } |
| |
| /*********************************** |
| * Parse list of extended asm clobbers. |
| * Grammar: |
| * | Clobbers: |
| * | AsmStringExpr |
| * | AsmStringExpr , Clobbers |
| * Params: |
| * p = parser state |
| * Returns: |
| * array of parsed clobber expressions |
| */ |
| Expressions* parseExtAsmClobbers(Parser)(Parser p) |
| { |
| Expressions* clobbers; |
| |
| if (p.token.value == TOK.colon || |
| p.token.value == TOK.colonColon || |
| p.token.value == TOK.semicolon || |
| p.token.value == TOK.endOfFile) |
| return clobbers; |
| |
| while (1) |
| { |
| // Look for the clobbers string |
| Expression clobber = p.parseAsmString(); |
| if (clobber.isErrorExp()) |
| goto Lerror; |
| |
| // Add it to the list. |
| if (!clobbers) |
| clobbers = new Expressions(); |
| clobbers.push(clobber); |
| |
| // If the next token is not a `,`, there are no more clobbers. |
| if (p.token.value != TOK.comma) |
| return clobbers; |
| |
| // Skip over the `,` token. |
| p.nextToken(); |
| } |
| Lerror: |
| while (p.token.value != TOK.semicolon && |
| p.token.value != TOK.endOfFile) |
| p.nextToken(); |
| |
| return null; |
| } |
| |
| /*********************************** |
| * Parse list of extended asm goto labels. |
| * Grammar: |
| * | GotoLabels: |
| * | Identifier |
| * | Identifier , GotoLabels |
| * Params: |
| * p = parser state |
| * Returns: |
| * array of parsed goto labels |
| */ |
| Identifiers* parseExtAsmGotoLabels(Parser)(Parser p) |
| { |
| Identifiers* labels; |
| |
| while (1) |
| { |
| if (p.token.value == TOK.identifier) |
| { |
| if (!labels) |
| labels = new Identifiers(); |
| labels.push(p.token.ident); |
| |
| // If the next token is not a `,`, there are no more labels. |
| if (p.nextToken() != TOK.comma) |
| return labels; |
| |
| // Skip over the `,` token. |
| p.nextToken(); |
| } |
| else |
| { |
| p.eSink.error(p.token.loc, "identifier expected for goto label name, not `%s`", |
| p.token.toChars()); |
| goto Lerror; |
| } |
| } |
| Lerror: |
| while (p.token.value != TOK.semicolon && |
| p.token.value != TOK.endOfFile) |
| p.nextToken(); |
| |
| return null; |
| } |
| |
| /*********************************** |
| * Parse a gcc asm statement. |
| * There are three forms of inline asm statements, basic, extended, and goto. |
| * Grammar: |
| * | AsmInstruction: |
| * | BasicAsmInstruction |
| * | ExtAsmInstruction |
| * | GotoAsmInstruction |
| * | |
| * | BasicAsmInstruction: |
| * | AsmStringExpr |
| * | |
| * | ExtAsmInstruction: |
| * | AsmStringExpr : Operands(opt) : Operands(opt) : Clobbers(opt) |
| * | |
| * | GotoAsmInstruction: |
| * | AsmStringExpr : : Operands(opt) : Clobbers(opt) : GotoLabels(opt) |
| * Params: |
| * p = parser state |
| * s = asm statement to parse |
| * Returns: |
| * the parsed gcc asm statement |
| */ |
| GccAsmStatement parseGccAsm(Parser)(Parser p, GccAsmStatement s) |
| { |
| s.insn = p.parseAsmString(); |
| if (s.insn.isErrorExp()) |
| return s; |
| |
| // No semicolon followed after instruction template, treat as extended asm. |
| if (p.token.value == TOK.colon || p.token.value == TOK.colonColon) |
| { |
| bool inputs; |
| bool clobbers; |
| bool labels; |
| |
| // Look for outputs. |
| if (p.token.value == TOK.colon) |
| { |
| // Skip over the `:` token. |
| p.nextToken(); |
| // Parse the output operands. |
| s.outputargs = p.parseExtAsmOperands(s); |
| } |
| else if (p.token.value == TOK.colonColon) |
| inputs = true; |
| |
| // Look for inputs. |
| if (inputs || p.token.value == TOK.colon) |
| { |
| // Skip over the `:` or `::` token. |
| p.nextToken(); |
| // Parse the input operands. |
| p.parseExtAsmOperands(s); |
| } |
| else if (p.token.value == TOK.colonColon) |
| clobbers = true; |
| |
| // Look for clobbers. |
| if (clobbers || p.token.value == TOK.colon) |
| { |
| // Skip over the `:` or `::` token. |
| p.nextToken(); |
| // Parse the clobbers. |
| s.clobbers = p.parseExtAsmClobbers(); |
| } |
| else if (p.token.value == TOK.colonColon) |
| labels = true; |
| |
| // Look for labels. |
| if (labels || p.token.value == TOK.colon) |
| { |
| // Skip over the `:` or `::` token. |
| p.nextToken(); |
| // Parse the labels. |
| s.labels = p.parseExtAsmGotoLabels(); |
| } |
| } |
| |
| if (p.token.value == TOK.endOfFile) |
| assert(global.errors); |
| else |
| p.requireToken(TOK.semicolon); |
| |
| return s; |
| } |
| |
| unittest |
| { |
| import dmd.mtype : TypeBasic; |
| import dmd.typesem : merge; |
| |
| if (!global.errorSink) |
| global.errorSink = new ErrorSinkCompiler; |
| |
| const errors = global.startGagging(); |
| scope(exit) global.endGagging(errors); |
| |
| // If this check fails, then Type_init() was called before reaching here, |
| // and the entire chunk of code that follows can be removed. |
| assert(ASTCodegen.Type.tint32 is null); |
| // Minimally initialize the cached types in ASTCodegen.Type, as they are |
| // dependencies for some fail asm tests to succeed. |
| ASTCodegen.Type.stringtable._init(); |
| scope(exit) |
| { |
| ASTCodegen.Type.deinitialize(); |
| ASTCodegen.Type.tint32 = null; |
| ASTCodegen.Type.tchar = null; |
| } |
| scope tint32 = new TypeBasic(ASTCodegen.Tint32); |
| tint32.merge(); |
| ASTCodegen.Type.tint32 = tint32; |
| scope tchar = new TypeBasic(ASTCodegen.Tchar); |
| tchar.merge(); |
| ASTCodegen.Type.tchar = tchar; |
| |
| // Imitates asmSemantic if version = IN_GCC. |
| static int semanticAsm(Token* tokens, bool importC) |
| { |
| const errors = global.errors; |
| scope gas = new GccAsmStatement(Loc.initial, tokens); |
| const bool doUnittests = false; |
| scope p = importC |
| ? new CParser!ASTCodegen(null, ";", false, global.errorSink, target.c, null, &global.compileEnv) |
| : new Parser!ASTCodegen(null, ";", false, global.errorSink, &global.compileEnv, doUnittests); |
| p.token = *tokens; |
| p.parseGccAsm(gas); |
| return global.errors - errors; |
| } |
| |
| // Imitates parseStatement for asm statements. |
| static void parseAsm(string input, bool expectError, bool importC = false) |
| { |
| // Generate tokens from input test. |
| const bool doUnittests = false; |
| scope p = new Parser!ASTCodegen(null, input, false, global.errorSink, &global.compileEnv, doUnittests); |
| p.nextToken(); |
| |
| Token* toklist = null; |
| Token** ptoklist = &toklist; |
| p.check(TOK.asm_); |
| p.check(TOK.leftCurly); |
| while (1) |
| { |
| if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile) |
| break; |
| if (p.token.value == TOK.colonColon) |
| { |
| *ptoklist = p.allocateToken(); |
| memcpy(*ptoklist, &p.token, Token.sizeof); |
| (*ptoklist).value = TOK.colon; |
| ptoklist = &(*ptoklist).next; |
| |
| *ptoklist = p.allocateToken(); |
| memcpy(*ptoklist, &p.token, Token.sizeof); |
| (*ptoklist).value = TOK.colon; |
| ptoklist = &(*ptoklist).next; |
| } |
| else |
| { |
| *ptoklist = p.allocateToken(); |
| memcpy(*ptoklist, &p.token, Token.sizeof); |
| ptoklist = &(*ptoklist).next; |
| } |
| *ptoklist = null; |
| p.nextToken(); |
| } |
| p.check(TOK.rightCurly); |
| |
| auto res = semanticAsm(toklist, importC); |
| // Checks for both unexpected passes and failures. |
| assert((res == 0) != expectError, input); |
| } |
| |
| /// Assembly Tests, all should pass. |
| /// Note: Frontend is not initialized, use only strings and identifiers. |
| immutable string[] passAsmTests = [ |
| // Basic asm statement |
| q{ asm { "nop"; |
| } }, |
| |
| // Extended asm statement |
| q{ asm { "cpuid" |
| : "=a" (a), "=b" (b), "=c" (c), "=d" (d) |
| : "a" (input); |
| } }, |
| |
| // Assembly with symbolic names |
| q{ asm { "bts %[base], %[offset]" |
| : [base] "+rm" (*ptr) |
| : [offset] "Ir" (bitnum); |
| } }, |
| |
| // Assembly with clobbers |
| q{ asm { "cpuid" |
| : "=a" (a) |
| : "a" (input) |
| : "ebx", "ecx", "edx"; |
| } }, |
| |
| // Goto asm statement |
| q{ asm { "jmp %l0" |
| : |
| : |
| : |
| : Ljmplabel; |
| } }, |
| |
| // Any CTFE-able string allowed as instruction template. |
| q{ asm { (generateAsm); |
| } }, |
| |
| // Likewise mixins, permissible so long as the result is a string. |
| q{ asm { (mixin(`"repne"`, `~ "scasb"`)); |
| } }, |
| |
| // :: token tests |
| q{ asm { "" : : : "memory"; } }, |
| q{ asm { "" :: : "memory"; } }, |
| q{ asm { "" : :: "memory"; } }, |
| q{ asm { "" ::: "memory"; } }, |
| q{ asm { "" :::: label; } }, |
| |
| // https://github.com/dlang/dmd/issues/21299 |
| q{ asm { (insn) : (output) (a) : (input) (1) : (clobber); } }, |
| q{ asm { (['t','e','s','t']) : (['=','r']) (a) : (['r']) (1) : (['m','e','m','o','r','y']); } }, |
| |
| // https://github.com/dlang/dmd/issues/21679 |
| q{ asm { "" : "=r" (s.x); } }, |
| ]; |
| |
| immutable string[] failAsmTests = [ |
| // Found 'h' when expecting ';' |
| q{ asm { ""h; |
| } }, |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20592 |
| q{ asm { "nop" : [name] string (expr); } }, |
| |
| // Expression expected, not ';' |
| q{ asm { ""[; |
| } }, |
| |
| // Expression expected, not ':' |
| q{ asm { "" |
| : |
| : "g" (a ? b : : c); |
| } }, |
| |
| // Found ',' when expecting ':' |
| q{ asm { "", ""; |
| } }, |
| |
| // Identifier expected, not ';' |
| q{ asm { "" : ::: ; } }, |
| q{ asm { "" :: :: ; } }, |
| q{ asm { "" ::: : ; } }, |
| q{ asm { "" :::: ; } }, |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20593 |
| q{ asm { "instruction" : : "operand" 123; } }, |
| |
| // https://github.com/dlang/dmd/issues/21298 |
| q{ asm { 1; } }, |
| q{ asm { int; } }, |
| q{ asm { : "=r" (i); } }, |
| q{ asm { (; } }, |
| q{ asm { (""; } }, |
| q{ asm { "" ,; } }, |
| q{ asm { "" d; } }, |
| q{ asm { "" : (; } }, |
| q{ asm { "" : (""; } }, |
| q{ asm { "" : ""; } }, |
| q{ asm { "" : "" (; } }, |
| q{ asm { "" : "" (a; } }, |
| q{ asm { "" : "" (a) ,; } }, |
| q{ asm { "" : "" (a) d; } }, |
| q{ asm { "" : "" (a) : (; } }, |
| q{ asm { "" : "" (a) : (""; } }, |
| q{ asm { "" : "" (a) : ""; } }, |
| q{ asm { "" : "" (a) : "" (; } }, |
| q{ asm { "" : "" (a) : "" (b; } }, |
| q{ asm { "" : "" (a) : "" (b) ,; } }, |
| q{ asm { "" : "" (a) : "" (b) d; } }, |
| q{ asm { "" : "" (a) : "" (b) : (; } }, |
| q{ asm { "" : "" (a) : "" (b) : (""; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" ,; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" d; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : (; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c ,; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c d; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c :; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c : (; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c : (""; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c : "" (; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c : "" (d; } }, |
| q{ asm { "" : "" (a) : "" (b) : "" : c : "" (d); } }, |
| |
| // https://github.com/dlang/dmd/issues/21679 |
| q{ asm { "" : "=r" (s->x); } }, |
| ]; |
| |
| immutable string[] passCAsmTests = [ |
| // https://github.com/dlang/dmd/issues/21679 |
| q{ asm { "" : "=r" (s->x); } }, |
| q{ asm { "" : "=r" (s.x); } } |
| ]; |
| |
| foreach (test; passAsmTests) |
| parseAsm(test, false); |
| |
| foreach (test; failAsmTests) |
| parseAsm(test, true); |
| |
| foreach (test; passCAsmTests) |
| parseAsm(test, false, /*importC*/true); |
| } |