|  | /** | 
|  | * Inline assembler for the GCC D compiler. | 
|  | * | 
|  | *              Copyright (C) 2018-2023 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/src/dmd/iasmgcc.d, _iasmgcc.d) | 
|  | * Documentation:  https://dlang.org/phobos/dmd_iasmgcc.html | 
|  | * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/iasmgcc.d | 
|  | */ | 
|  |  | 
|  | module dmd.iasmgcc; | 
|  |  | 
|  | import core.stdc.string; | 
|  |  | 
|  | import dmd.arraytypes; | 
|  | import dmd.astcodegen; | 
|  | import dmd.dscope; | 
|  | import dmd.errors; | 
|  | import dmd.errorsink; | 
|  | import dmd.expression; | 
|  | import dmd.expressionsem; | 
|  | import dmd.identifier; | 
|  | import dmd.globals; | 
|  | import dmd.location; | 
|  | import dmd.parse; | 
|  | import dmd.tokens; | 
|  | import dmd.statement; | 
|  | import dmd.statementsem; | 
|  |  | 
|  | private: | 
|  |  | 
|  | /*********************************** | 
|  | * Parse list of extended asm input or output operands. | 
|  | * Grammar: | 
|  | *      | Operands: | 
|  | *      |     SymbolicName(opt) StringLiteral ( AssignExpression ) | 
|  | *      |     SymbolicName(opt) StringLiteral ( 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; | 
|  |  | 
|  | while (1) | 
|  | { | 
|  | Expression arg; | 
|  | Identifier name; | 
|  | Expression constraint; | 
|  |  | 
|  | switch (p.token.value) | 
|  | { | 
|  | case TOK.semicolon: | 
|  | case TOK.colon: | 
|  | case TOK.endOfFile: | 
|  | return numargs; | 
|  |  | 
|  | case TOK.leftBracket: | 
|  | if (p.peekNext() == TOK.identifier) | 
|  | { | 
|  | // Skip over opening `[` | 
|  | p.nextToken(); | 
|  | // Store the symbolic name | 
|  | name = p.token.ident; | 
|  | p.nextToken(); | 
|  | } | 
|  | else | 
|  | { | 
|  | p.eSink.error(s.loc, "expected identifier after `[`"); | 
|  | goto Lerror; | 
|  | } | 
|  | // Look for closing `]` | 
|  | p.check(TOK.rightBracket); | 
|  | // Look for the string literal and fall through | 
|  | if (p.token.value == TOK.string_) | 
|  | goto case; | 
|  | else | 
|  | goto default; | 
|  |  | 
|  | case TOK.string_: | 
|  | constraint = p.parsePrimaryExp(); | 
|  | if (p.token.value != TOK.leftParenthesis) | 
|  | { | 
|  | arg = p.parseAssignExp(); | 
|  | error(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars()); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Look for the opening `(` | 
|  | p.check(TOK.leftParenthesis); | 
|  | // Parse the assign expression | 
|  | arg = p.parseAssignExp(); | 
|  | // Look for the closing `)` | 
|  | p.check(TOK.rightParenthesis); | 
|  | } | 
|  |  | 
|  | 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 (p.token.value == TOK.comma) | 
|  | p.nextToken(); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | p.eSink.error(p.token.loc, "expected constant string constraint for operand, not `%s`", | 
|  | p.token.toChars()); | 
|  | goto Lerror; | 
|  | } | 
|  | } | 
|  | Lerror: | 
|  | while (p.token.value != TOK.rightCurly && | 
|  | p.token.value != TOK.semicolon && | 
|  | p.token.value != TOK.endOfFile) | 
|  | p.nextToken(); | 
|  |  | 
|  | return numargs; | 
|  | } | 
|  |  | 
|  | /*********************************** | 
|  | * Parse list of extended asm clobbers. | 
|  | * Grammar: | 
|  | *      | Clobbers: | 
|  | *      |     StringLiteral | 
|  | *      |     StringLiteral , Clobbers | 
|  | * Params: | 
|  | *      p = parser state | 
|  | * Returns: | 
|  | *      array of parsed clobber expressions | 
|  | */ | 
|  | Expressions *parseExtAsmClobbers(Parser)(Parser p) | 
|  | { | 
|  | Expressions *clobbers; | 
|  |  | 
|  | while (1) | 
|  | { | 
|  | Expression clobber; | 
|  |  | 
|  | switch (p.token.value) | 
|  | { | 
|  | case TOK.semicolon: | 
|  | case TOK.colon: | 
|  | case TOK.endOfFile: | 
|  | return clobbers; | 
|  |  | 
|  | case TOK.string_: | 
|  | clobber = p.parsePrimaryExp(); | 
|  | if (!clobbers) | 
|  | clobbers = new Expressions(); | 
|  | clobbers.push(clobber); | 
|  |  | 
|  | if (p.token.value == TOK.comma) | 
|  | p.nextToken(); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | p.eSink.error(p.token.loc, "expected constant string constraint for clobber name, not `%s`", | 
|  | p.token.toChars()); | 
|  | goto Lerror; | 
|  | } | 
|  | } | 
|  | Lerror: | 
|  | while (p.token.value != TOK.rightCurly && | 
|  | p.token.value != TOK.semicolon && | 
|  | p.token.value != TOK.endOfFile) | 
|  | p.nextToken(); | 
|  |  | 
|  | return clobbers; | 
|  | } | 
|  |  | 
|  | /*********************************** | 
|  | * 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) | 
|  | { | 
|  | switch (p.token.value) | 
|  | { | 
|  | case TOK.semicolon: | 
|  | case TOK.endOfFile: | 
|  | return labels; | 
|  |  | 
|  | case TOK.identifier: | 
|  | if (!labels) | 
|  | labels = new Identifiers(); | 
|  | labels.push(p.token.ident); | 
|  |  | 
|  | if (p.nextToken() == TOK.comma) | 
|  | p.nextToken(); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | p.eSink.error(p.token.loc, "expected identifier for goto label name, not `%s`", | 
|  | p.token.toChars()); | 
|  | goto Lerror; | 
|  | } | 
|  | } | 
|  | Lerror: | 
|  | while (p.token.value != TOK.rightCurly && | 
|  | p.token.value != TOK.semicolon && | 
|  | p.token.value != TOK.endOfFile) | 
|  | p.nextToken(); | 
|  |  | 
|  | return labels; | 
|  | } | 
|  |  | 
|  | /*********************************** | 
|  | * Parse a gcc asm statement. | 
|  | * There are three forms of inline asm statements, basic, extended, and goto. | 
|  | * Grammar: | 
|  | *      | AsmInstruction: | 
|  | *      |     BasicAsmInstruction | 
|  | *      |     ExtAsmInstruction | 
|  | *      |     GotoAsmInstruction | 
|  | *      | | 
|  | *      | BasicAsmInstruction: | 
|  | *      |     AssignExpression | 
|  | *      | | 
|  | *      | ExtAsmInstruction: | 
|  | *      |     AssignExpression : Operands(opt) : Operands(opt) : Clobbers(opt) | 
|  | *      | | 
|  | *      | GotoAsmInstruction: | 
|  | *      |     AssignExpression : : 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.parseAssignExp(); | 
|  | if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) | 
|  | goto Ldone; | 
|  |  | 
|  | // No semicolon followed after instruction template, treat as extended asm. | 
|  | foreach (section; 0 .. 4) | 
|  | { | 
|  | p.check(TOK.colon); | 
|  |  | 
|  | final switch (section) | 
|  | { | 
|  | case 0: | 
|  | s.outputargs = p.parseExtAsmOperands(s); | 
|  | break; | 
|  |  | 
|  | case 1: | 
|  | p.parseExtAsmOperands(s); | 
|  | break; | 
|  |  | 
|  | case 2: | 
|  | s.clobbers = p.parseExtAsmClobbers(); | 
|  | break; | 
|  |  | 
|  | case 3: | 
|  | s.labels = p.parseExtAsmGotoLabels(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile) | 
|  | goto Ldone; | 
|  | } | 
|  | Ldone: | 
|  | p.check(TOK.semicolon); | 
|  |  | 
|  | return s; | 
|  | } | 
|  |  | 
|  | /*********************************** | 
|  | * 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 | 
|  | */ | 
|  | extern (C++) public Statement gccAsmSemantic(GccAsmStatement s, Scope *sc) | 
|  | { | 
|  | //printf("GccAsmStatement.semantic()\n"); | 
|  | scope p = new Parser!ASTCodegen(sc._module, ";", false, global.errorSink); | 
|  |  | 
|  | // 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; | 
|  | } | 
|  | p.token = *toklist; | 
|  | p.scanloc = s.loc; | 
|  |  | 
|  | // 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 = semanticString(sc, s.insn, "asm instruction template"); | 
|  |  | 
|  | if (s.labels && s.outputargs) | 
|  | s.error("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 e = (*s.args)[i]; | 
|  | e = e.expressionSemantic(sc); | 
|  | // Check argument is a valid lvalue/rvalue. | 
|  | if (i < s.outputargs) | 
|  | e = e.modifiableLvalue(sc, null); | 
|  | else if (e.checkValue()) | 
|  | e = ErrorExp.get(); | 
|  | (*s.args)[i] = e; | 
|  |  | 
|  | e = (*s.constraints)[i]; | 
|  | e = e.expressionSemantic(sc); | 
|  | assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1); | 
|  | (*s.constraints)[i] = e; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Analyse all clobbers. | 
|  | if (s.clobbers) | 
|  | { | 
|  | foreach (i; 0 .. s.clobbers.length) | 
|  | { | 
|  | Expression e = (*s.clobbers)[i]; | 
|  | e = e.expressionSemantic(sc); | 
|  | assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1); | 
|  | (*s.clobbers)[i] = e; | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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; | 
|  | } | 
|  |  | 
|  | unittest | 
|  | { | 
|  | import dmd.mtype : TypeBasic; | 
|  |  | 
|  | if (!global.errorSink) | 
|  | global.errorSink = new ErrorSinkCompiler; | 
|  |  | 
|  | uint 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; | 
|  | } | 
|  | scope tint32 = new TypeBasic(ASTCodegen.Tint32); | 
|  | ASTCodegen.Type.tint32 = tint32; | 
|  |  | 
|  | // Imitates asmSemantic if version = IN_GCC. | 
|  | static int semanticAsm(Token* tokens) | 
|  | { | 
|  | const errors = global.errors; | 
|  | scope gas = new GccAsmStatement(Loc.initial, tokens); | 
|  | scope p = new Parser!ASTCodegen(null, ";", false, global.errorSink); | 
|  | p.token = *tokens; | 
|  | p.parseGccAsm(gas); | 
|  | return global.errors - errors; | 
|  | } | 
|  |  | 
|  | // Imitates parseStatement for asm statements. | 
|  | static void parseAsm(string input, bool expectError) | 
|  | { | 
|  | // Generate tokens from input test. | 
|  | scope p = new Parser!ASTCodegen(null, input, false, global.errorSink); | 
|  | 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); | 
|  | // Checks for both unexpected passes and failures. | 
|  | assert((res == 0) != expectError); | 
|  | } | 
|  |  | 
|  | /// 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"; } }, | 
|  | ]; | 
|  |  | 
|  | 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 { "", ""; | 
|  | } }, | 
|  |  | 
|  | // https://issues.dlang.org/show_bug.cgi?id=20593 | 
|  | q{ asm { "instruction" : : "operand" 123; } }, | 
|  | ]; | 
|  |  | 
|  | foreach (test; passAsmTests) | 
|  | parseAsm(test, false); | 
|  |  | 
|  | foreach (test; failAsmTests) | 
|  | parseAsm(test, true); | 
|  | } |