diff --git a/src/dparse/ast.d b/src/dparse/ast.d index 696ab6cf..d2bba337 100644 --- a/src/dparse/ast.d +++ b/src/dparse/ast.d @@ -242,6 +242,9 @@ abstract class ASTVisitor /** */ void visit(const FunctionContract functionContract) { functionContract.accept(this); } /** */ void visit(const FunctionDeclaration functionDeclaration) { functionDeclaration.accept(this); } /** */ void visit(const FunctionLiteralExpression functionLiteralExpression) { functionLiteralExpression.accept(this); } + /** */ void visit(const GccAsmInstruction gccAsmInstruction) { gccAsmInstruction.accept(this); } + /** */ void visit(const GccAsmOperandList gccAsmOperands) { gccAsmOperands.accept(this); } + /** */ void visit(const GccAsmOperand gccAsmOperand) { gccAsmOperand.accept(this); } /** */ void visit(const GotoStatement gotoStatement) { gotoStatement.accept(this); } /** */ void visit(const IdentifierChain identifierChain) { identifierChain.accept(this); } /** */ void visit(const DeclaratorIdentifierList identifierList) { identifierList.accept(this); } @@ -314,6 +317,7 @@ abstract class ASTVisitor /** */ void visit(const StaticDestructor staticDestructor) { staticDestructor.accept(this); } /** */ void visit(const StaticIfCondition staticIfCondition) { staticIfCondition.accept(this); } /** */ void visit(const StorageClass storageClass) { storageClass.accept(this); } + /** */ void visit(const StringLiteralList stringLiteralList) { stringLiteralList.accept(this); } /** */ void visit(const StructBody structBody) { structBody.accept(this); } /** */ void visit(const StructDeclaration structDeclaration) { structDeclaration.accept(this); } /** */ void visit(const StructInitializer structInitializer) { structInitializer.accept(this); } @@ -800,9 +804,10 @@ final class AsmStatement : BaseNode { override void accept(ASTVisitor visitor) const { - mixin (visitIfNotNull!asmInstructions); + mixin (visitIfNotNull!(functionAttributes, asmInstructions, gccAsmInstructions)); } /** */ AsmInstruction[] asmInstructions; + /** */ GccAsmInstruction[] gccAsmInstructions; /** */ FunctionAttribute[] functionAttributes; mixin OpEquals; } @@ -1789,6 +1794,48 @@ final class FunctionLiteralExpression : ExpressionNode mixin OpEquals; } +/// +final class GccAsmInstruction : BaseNode +{ + override void accept(ASTVisitor visitor) const + { + mixin (visitIfNotNull!(assemblerTemplate, inputOperands, outputOperands, registers, gotos)); + } + + /** */ Expression assemblerTemplate; + /** */ GccAsmOperandList inputOperands; + /** */ GccAsmOperandList outputOperands; + /** */ StringLiteralList registers; + /** */ DeclaratorIdentifierList gotos; + mixin OpEquals; +} + +/// +final class GccAsmOperand : BaseNode +{ + override void accept(ASTVisitor visitor) const + { + mixin (visitIfNotNull!(expression, constraint, symbolicName)); + } + + /** */ Token symbolicName; + /** */ Token constraint; + /** */ ExpressionNode expression; + mixin OpEquals; +} + +/// +final class GccAsmOperandList : BaseNode +{ + override void accept(ASTVisitor visitor) const + { + mixin (visitIfNotNull!(items)); + } + + /** */ GccAsmOperand[] items; + mixin OpEquals; +} + /// final class GotoStatement : BaseNode { @@ -2773,6 +2820,18 @@ final class StorageClass : BaseNode mixin OpEquals; } +/// +final class StringLiteralList : BaseNode +{ + override void accept(ASTVisitor visitor) const + { + mixin (visitIfNotNull!(items)); + } + + /** */ Token[] items; + mixin OpEquals; +} + /// final class StructBody : BaseNode { @@ -3748,3 +3807,133 @@ unittest // Differentiate between no and empty DDOC comments, e.g. for DDOC unit visitor.visit(m); assert(visitor.found.length == 6); } + +unittest // Support GCC-sytle asm statements +{ + static void verify(T)(const string code, void function(scope const T) handler) + { + import dparse.lexer, dparse.parser, dparse.rollback_allocator; + + RollbackAllocator ra; + LexerConfig cf = LexerConfig("", StringBehavior.source); + StringCache ca = StringCache(16); + Module m = parseModule(getTokensForParser("void main() { " ~ code ~ '}', cf, &ca), "", &ra); + + final class AsmVisitor : ASTVisitor + { + alias visit = ASTVisitor.visit; + bool found; + + override void visit(const T node) + { + assert(!found); + found = true; + handler(node); + } + } + + scope visitor = new AsmVisitor(); + visitor.visit(m); + assert(visitor.found); + } + + static void first(scope const AsmStatement stmt) + { + assert(stmt.asmInstructions.length == 0); + assert(stmt.gccAsmInstructions.length == 1); + with (stmt.gccAsmInstructions[0]) + { + assert(assemblerTemplate); + assert(assemblerTemplate.tokens.length == 1); + assert(assemblerTemplate.tokens[0].type == tok!"stringLiteral"); + assert(assemblerTemplate.tokens[0].text == `"mov %0, EAX"`); + + assert(outputOperands); + assert(outputOperands.items.length == 1); + with (outputOperands.items[0]) + { + assert(constraint.type == tok!"stringLiteral"); + assert(constraint.text == `"=r"`); + + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var1"); + } + } + } + + verify(q{ asm { "mov %0, EAX" : "=r" (var1) ; } }, &first); + + static void second(scope const AsmStatement stmt) + { + first(stmt); + + with (stmt.gccAsmInstructions[0]) + { + assert(inputOperands); + assert(inputOperands.items.length == 2); + with (inputOperands.items[0]) + { + assert(symbolicName.type == tok!"identifier"); + assert(symbolicName.text == "xy"); + + assert(constraint.type == tok!"stringLiteral"); + assert(constraint.text == `"=w"`); + + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var2"); + } + + with (inputOperands.items[1]) + { + assert(constraint.type == tok!"stringLiteral"); + assert(constraint.text == `"g"`); + + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var3"); + } + } + } + + verify(q{ asm { "mov %0, EAX" : "=r" (var1) : [xy] "=w" (var2), "g" (var3); } }, &second); + + verify(q{ asm { "mov %0, EAX" : "=r" (var1) : [xy] "=w" (var2), "g" (var3) : "r0" ; } }, (scope const AsmStatement stmt) + { + second(stmt); + + with (stmt.gccAsmInstructions[0]) + { + assert(registers); + assert(registers.items.length == 1); + assert(registers.items[0].type == tok!"stringLiteral"); + assert(registers.items[0].text == `"r0"`); + } + }); + + verify(q{ asm { "mov EBX, EAX" : : : "r0", "r1" ; } }, (scope const GccAsmInstruction instr) + { + with (instr) + { + assert(registers); + assert(registers.items.length == 2); + assert(registers.items[0].type == tok!"stringLiteral"); + assert(registers.items[0].text == `"r0"`); + assert(registers.items[1].type == tok!"stringLiteral"); + assert(registers.items[1].text == `"r1"`); + } + }); + + verify(q{ asm { "jmp LEnd" : : : : LEnd ; } }, (scope const GccAsmInstruction instr) + { + with (instr) + { + assert(gotos); + assert(gotos.identifiers.length == 1); + assert(gotos.identifiers[0].type == tok!"identifier"); + assert(gotos.identifiers[0].text == `LEnd`); + } + }); +} + diff --git a/src/dparse/parser.d b/src/dparse/parser.d index 855a8e94..3f7a3b78 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -610,7 +610,7 @@ class Parser * | $(LITERAL ';') * ;) */ - AsmInstruction parseAsmInstruction() + AsmInstruction parseAsmInstruction(ref bool maybeGccASm) { mixin (traceEnterAndExit!(__FUNCTION__)); auto startIndex = index; @@ -654,14 +654,15 @@ class Parser node.tokens = tokens[startIndex .. index]; return node; } - mixin(parseNodeQ!(`node.asmInstruction`, `AsmInstruction`)); + node.asmInstruction = parseAsmInstruction(maybeGccASm); + if (node.asmInstruction is null) return null; } else if (!currentIs(tok!";")) mixin(parseNodeQ!(`node.operands`, `Operands`)); } else { - error("identifier or `align` expected"); + maybeGccASm = true; return null; } node.tokens = tokens[startIndex .. index]; @@ -809,7 +810,7 @@ class Parser * Parses an AsmStatement * * $(GRAMMAR $(RULEDEF asmStatement): - * $(LITERAL 'asm') $(RULE functionAttributes)? $(LITERAL '{') $(RULE asmInstruction)+ $(LITERAL '}') + * $(LITERAL 'asm') $(RULE functionAttributes)? $(LITERAL '{') ( $(RULE asmInstruction)+ | $(RULE gccAsmInstruction)+ ) $(LITERAL '}') * ;) */ AsmStatement parseAsmStatement() @@ -829,16 +830,52 @@ class Parser } ownArray(node.functionAttributes, functionAttributes); expect(tok!"{"); + + // DMD-style and GCC-style assembly might look identical in the beginning. + // Try DMD style first and restart with GCC if it fails because of GCC elements + bool maybeGccStyle; + const instrStart = allocator.setCheckpoint(); + const instrStartIdx = index; + StackBuffer instructions; + while (moreTokens() && !currentIs(tok!"}")) { auto c = allocator.setCheckpoint(); - if (!instructions.put(parseAsmInstruction())) + if (!instructions.put(parseAsmInstruction(maybeGccStyle))) + { + if (maybeGccStyle) + break; + allocator.rollback(c); + } else expect(tok!";"); } - ownArray(node.asmInstructions, instructions); + + if (!maybeGccStyle) + { + ownArray(node.asmInstructions, instructions); + } + else + { + // Revert to the beginning of the first instruction + destroy(instructions); + allocator.rollback(instrStart); + index = instrStartIdx; + + while (moreTokens() && !currentIs(tok!"}")) + { + auto c = allocator.setCheckpoint(); + if (!instructions.put(parseGccAsmInstruction())) + allocator.rollback(c); + else + expect(tok!";"); + } + + ownArray(node.gccAsmInstructions, instructions); + } + expect(tok!"}"); node.tokens = tokens[startIndex .. index]; return node; @@ -3556,6 +3593,136 @@ class Parser return node; } + /** + * Parses an AsmInstruction using GCC Assembler + * + * $(GRAMMAR $(RULEDEF gccAsmInstruction): + * | $(RULE expression) $(LITERAL ':') $(RULE gccAsmOperandList)? ($(LITERAL ':') $(RULE gccAsmOperandList)? ($(LITERAL ':') $(RULE stringLiteralList))? )? $(LITERAL ';') + * | $(RULE expression) $(LITERAL ':') $(LITERAL ':') $(RULE gccAsmOperandList)? $(LITERAL ':') $(RULE stringLiteralList) $(LITERAL ';') $(LITERAL ':') $(RULE declaratorIdentifierList) $(LITERAL ';') + * ;) + */ + /* + * References: + * - [1] https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html + * - [2] https://wiki.dlang.org/Using_GDC + * - [3] https://github.com/dlang/dmd/blob/master/src/dmd/iasmgcc.d + * + * Separated into a different method because one cannot interleave DMD & GCC asm + * (volatile, inline, goto) not supperted (yet?) + */ + GccAsmInstruction parseGccAsmInstruction() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + const startIndex = index; + auto node = allocator.make!GccAsmInstruction(); + + // Allow empty asm instructions + if (currentIs(tok!";")) + { + warn("Empty asm instruction"); + node.tokens = tokens[startIndex .. index]; + return node; + } + + mixin(parseNodeQ!("node.assemblerTemplate", "Expression")); + + // GDC allows e.g. asm { mixin(); } + if (!currentIs(tok!";")) + { + mixin(tokenCheck!":"); + + if (!currentIsOneOf(tok!":", tok!";")) + mixin(parseNodeQ!(`node.outputOperands`, `GccAsmOperandList`)); + + if (skip(tok!":")) + { + if (!currentIsOneOf(tok!":", tok!";")) + mixin(parseNodeQ!(`node.inputOperands`, `GccAsmOperandList`)); + + if (skip(tok!":")) + { + if (!currentIs(tok!":")) + mixin(parseNodeQ!("node.registers", "StringLiteralList")); + + if (skip(tok!":")) + { + size_t cp; + + if (node.outputOperands) + { + error("goto-labels only allowed without output operands!", false); + cp = allocator.setCheckpoint(); + } + + // Parse even with the error above for better error reporting + mixin(parseNodeQ!("node.gotos", "DeclaratorIdentifierList")); + + if (cp) + { + allocator.rollback(cp); + return null; + } + } + } + } + } + + node.tokens = tokens[startIndex .. index]; + return node; + } + + /** + * Parses a GccAsmOperandList + * + * $(GRAMMAR $(RULEDEF gccAsmOperandList): + * $(RULE gccAsmOperand) ($(LITERAL ',') $(RULE gccAsmOperand))* + * ;) + */ + GccAsmOperandList parseGccAsmOperandList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(GccAsmOperandList, GccAsmOperand)(); + } + + /** + * Parses a GccAsmOperand + * + * $(GRAMMAR $(RULEDEF gccAsmOperand): + * ($(LITERAL '[') $(RULE identifier) $(LITERAL ']'))? $(RULE stringLiteral) $(LITERAL '(') $(RULE assignExpression) $(LITERAL ')') + * ;) + */ + GccAsmOperand parseGccAsmOperand() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + + const startIndex = index; + auto node = allocator.make!GccAsmOperand(); + + if (currentIs(tok!"[")) + { + advance(); + if (auto t = expect(tok!"identifier")) + node.symbolicName = *t; + mixin(tokenCheck!"]"); + } + + mixin(tokenCheck!("node.constraint", "stringLiteral")); + + // GCC actually requires braces but GDC didn't for quite some time, + // see https://github.com/dlang/dmd/pull/10820 + const hasParens = skip(tok!"("); + if (!hasParens) + warn("Omitting parenthesis around operands is deprecated!"); + + mixin(parseNodeQ!("node.expression", "AssignExpression")); + + if (hasParens) + expect(tok!")"); + + node.tokens = tokens[startIndex .. index]; + return node; + } + /** * Parses a GotoStatement * @@ -5999,6 +6166,41 @@ class Parser return node; } + /** + * Parses a StringLiteralList + * + * $(GRAMMAR $(RULEDEF stringLiteralList): + * $(RULE stringLiteral) ($(LITERAL ',') $(RULE stringLiteral))* + * ;) + */ + private StringLiteralList parseStringLiteralList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + const startIndex = index; + auto node = allocator.make!(StringLiteralList)(); + StackBuffer sb; + + while (true) + { + if (!currentIs(tok!"stringLiteral")) + { + error("Expected `stringLiteral` instead of `" ~ current.text ~ '`'); + return null; + } + + sb.put(advance()); + + if (currentIsOneOf(tok!":", tok!";")) + break; + + mixin(tokenCheck!","); + } + + node.tokens = tokens[startIndex .. index]; + ownArray(node.items, sb); + return node; + } + /** * Parses a StructBody * @@ -8200,6 +8402,15 @@ protected: final: } } + /// Skips token if present and returns whether token was skipped + bool skip(IdType token) + { + const found = currentIs(token); + if (found) + advance(); + return found; + } + void skip(alias O, alias C)() { assert (currentIs(O), current().text); diff --git a/test/fail_files/asm-gcc.d b/test/fail_files/asm-gcc.d new file mode 100644 index 00000000..885164f3 --- /dev/null +++ b/test/fail_files/asm-gcc.d @@ -0,0 +1,21 @@ +void main() +{ + asm + { + "mov A B"; + ; + "mov A B" : (a); + "mov A B" : xx "rw" (a); + + "mov A B" : "rw" (a), ; + "mov A B" : "rw" (a), : ; + "mov A B" : "rw" (a) : , : ; + + "mov A B" : "rw" (a) : "r" (b) : this; + + + "mov A B" : "rw" (a) : "r" (b) : "xxx" : 0; + + "mov A B" : "rw" (a) : "r" (b) : "xxx" : LEnd ; + } +} \ No newline at end of file diff --git a/test/pass_files/asm-gcc.d b/test/pass_files/asm-gcc.d new file mode 100644 index 00000000..da7209b5 --- /dev/null +++ b/test/pass_files/asm-gcc.d @@ -0,0 +1,59 @@ +module asm_gcc; + +ref T store(T)(); + +enum asm1 = "mov %0, %0;"; +string asm2(int i) { return "mov %0, %0;"; } + +void main() +{ + int var1, var2, var3, var4; + int* ptr1; + + asm + { + // Some tests as found in dmd's iasmgcc.d + "nop"; + asm1; + asm2(1); + mixin(`"repne"`, `~ "scasb"`); + + // GCC examples + "notl %[iov]" : [iov] "=r" (var1) : "0" (var2) ; + ; + "mov %1, %0\n\t" + "add $1, %0" : "=r" (var1) : "r" (var2) ; + + // DRuntime + "cpuid" : "=a" (var1), "=c" (var2), "=d" (var3) : "a" (0x8000_0006) : "ebx" ; + + // Deprecated: Missing parens + "cpuid" : "=a" var1, "=b" var2 : "a" 0x8000_001E : "ecx", "edx"; + + "str x29, %0" : "=m" (var1) ; + + "mrs %0, cntvct_el0" : "=r" *ptr1; + + "mov %1, %0" : : "r" (var2) : "cc" : LCarry ; + "mov %0, %0" : : "r" (var2) : "cc" ; + + "mov %0, %0" : "=r" (*ptr1) : "r" (store!int = 1) : "cc"; + } + + LCarry: + asm /*goto*/ { + "btl %1, %0\n\t" + "jc %l2" + : /* No outputs. */ + : "r" (var1), "r" (var2) + : "cc" + : LCarry + ; + } + + asm { + ; + ; + "jmp LCarry" : : : : LCarry ; + } +}