/** * 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); }