From 49d87391d66a26e7c62541bf86d93008c6d73c6b Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 5 May 2020 02:44:55 +0200 Subject: [PATCH 1/8] Add an utility function to skip a token if present --- src/dparse/parser.d | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dparse/parser.d b/src/dparse/parser.d index 855a8e94..78f1e1bd 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -8200,6 +8200,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); From aa31369833af1ab4a8882603107bb3a2ac39b1f6 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 5 May 2020 02:39:13 +0200 Subject: [PATCH 2/8] Support GCC-sytle asm statements --- src/dparse/ast.d | 188 +++++++++++++++++++++++++++++++++++++++++++- src/dparse/parser.d | 171 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 355 insertions(+), 4 deletions(-) diff --git a/src/dparse/ast.d b/src/dparse/ast.d index 696ab6cf..dce02295 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!(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,130 @@ 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"`); + + // assert(expression.type == tok!"identifier"); + // assert(expression.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"`); + + // assert(expression.type == tok!"identifier"); + // assert(expression.text == "var2"); + } + + with (inputOperands.items[1]) + { + assert(constraint.type == tok!"stringLiteral"); + assert(constraint.text == `"g"`); + + // assert(expression.type == tok!"identifier"); + // assert(expression.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 78f1e1bd..480349e7 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -809,7 +809,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 +829,24 @@ class Parser } ownArray(node.functionAttributes, functionAttributes); expect(tok!"{"); + + // DMD-style assembly cannot start with a string literal + const gccStyle = currentIs(tok!"stringLiteral"); + StackBuffer instructions; while (moreTokens() && !currentIs(tok!"}")) { auto c = allocator.setCheckpoint(); - if (!instructions.put(parseAsmInstruction())) + if (!instructions.put(gccStyle ? parseGccAsmInstruction() : parseAsmInstruction())) allocator.rollback(c); else expect(tok!";"); } - ownArray(node.asmInstructions, instructions); + if (gccStyle) + ownArray(node.gccAsmInstructions, instructions); + else + ownArray(node.asmInstructions, instructions); + expect(tok!"}"); node.tokens = tokens[startIndex .. index]; return node; @@ -3556,6 +3564,128 @@ 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!":"); + mixin(parseNodeQ!(`node.outputOperands`, `GccAsmOperandList`)); + + if (skip(tok!":")) + { + mixin(parseNodeQ!(`node.inputOperands`, `GccAsmOperandList`)); + + if (skip(tok!":")) + { + if (!currentIs(tok!":")) + mixin(parseNodeQ!("node.registers", "StringLiteralList")); + + if (skip(tok!":")) + { + if (node.outputOperands.items.length) + error("goto-labels only allowed without output operands!", false); + + mixin(parseNodeQ!("node.gotos", "DeclaratorIdentifierList")); + } + } + } + } + + node.tokens = tokens[startIndex .. index]; + return node; + } + + /** + * Parses a GccAsmOperandList + * + * $(GRAMMAR $(RULEDEF gccAsmOperandList) + * ($(RULE gccAsmOperand) ($(LITERAL ',') $(RULE gccAsmOperand))* )? + * ) + */ + GccAsmOperandList parseGccAsmOperandList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + + // Disambiguate between an empty and a missing list + if (currentIsOneOf(tok!":", tok!";")) + return allocator.make!GccAsmOperandList(); + + 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__)); + if (currentIsOneOf(tok!":", tok!";")) + return null; + + 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 +6129,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 * From 175bf95dfef96af57549d649655fd6ffb799028d Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 5 May 2020 02:58:29 +0200 Subject: [PATCH 3/8] Add pass/fail tests for GCC style asm --- test/fail_files/asm-gcc.d | 21 +++++++++++++++ test/pass_files/asm-gcc.d | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 test/fail_files/asm-gcc.d create mode 100644 test/pass_files/asm-gcc.d 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..d74aa6e7 --- /dev/null +++ b/test/pass_files/asm-gcc.d @@ -0,0 +1,55 @@ +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(); + 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 ; } +} From 93f95beabfa904dc0e602873e07c46b6979c7321 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Fri, 8 May 2020 12:27:39 +0200 Subject: [PATCH 4/8] More reliable GCC asm detection --- src/dparse/parser.d | 47 +++++++++++++++++++++++++++++++-------- test/pass_files/asm-gcc.d | 6 ++++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/dparse/parser.d b/src/dparse/parser.d index 480349e7..58d50571 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]; @@ -830,22 +831,50 @@ class Parser ownArray(node.functionAttributes, functionAttributes); expect(tok!"{"); - // DMD-style assembly cannot start with a string literal - const gccStyle = currentIs(tok!"stringLiteral"); + // 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(gccStyle ? parseGccAsmInstruction() : parseAsmInstruction())) + if (!instructions.put(parseAsmInstruction(maybeGccStyle))) + { + if (maybeGccStyle) + break; + allocator.rollback(c); + } else expect(tok!";"); } - if (gccStyle) - ownArray(node.gccAsmInstructions, instructions); - else + + 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]; diff --git a/test/pass_files/asm-gcc.d b/test/pass_files/asm-gcc.d index d74aa6e7..d72bcf08 100644 --- a/test/pass_files/asm-gcc.d +++ b/test/pass_files/asm-gcc.d @@ -51,5 +51,9 @@ void main() ; } - asm { "jmp LCarry" : : : : LCarry ; } + asm { + ; + ; + "jmp LCarry" : : : : LCarry ; + } } From c021eca333988e42863a2c46238e735ab4fa5c11 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Fri, 8 May 2020 13:10:35 +0200 Subject: [PATCH 5/8] Re-enable operand identifier tests --- src/dparse/ast.d | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/dparse/ast.d b/src/dparse/ast.d index dce02295..95bf8fbe 100644 --- a/src/dparse/ast.d +++ b/src/dparse/ast.d @@ -3855,8 +3855,9 @@ unittest // Support GCC-sytle asm statements assert(constraint.type == tok!"stringLiteral"); assert(constraint.text == `"=r"`); - // assert(expression.type == tok!"identifier"); - // assert(expression.text == "var1"); + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var1"); } } } @@ -3879,8 +3880,9 @@ unittest // Support GCC-sytle asm statements assert(constraint.type == tok!"stringLiteral"); assert(constraint.text == `"=w"`); - // assert(expression.type == tok!"identifier"); - // assert(expression.text == "var2"); + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var2"); } with (inputOperands.items[1]) @@ -3888,8 +3890,9 @@ unittest // Support GCC-sytle asm statements assert(constraint.type == tok!"stringLiteral"); assert(constraint.text == `"g"`); - // assert(expression.type == tok!"identifier"); - // assert(expression.text == "var3"); + auto una = cast(UnaryExpression) expression; + assert(una); + assert(una.primaryExpression.identifierOrTemplateInstance.identifier.text == "var3"); } } } From c6d17ea01eb93222071583ff25144cbd503e8ad7 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Sun, 17 May 2020 10:50:46 +0200 Subject: [PATCH 6/8] Address initial feedback --- src/dparse/ast.d | 2 +- src/dparse/parser.d | 20 ++++++++++++++++---- test/pass_files/asm-gcc.d | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/dparse/ast.d b/src/dparse/ast.d index 95bf8fbe..d2bba337 100644 --- a/src/dparse/ast.d +++ b/src/dparse/ast.d @@ -804,7 +804,7 @@ final class AsmStatement : BaseNode { override void accept(ASTVisitor visitor) const { - mixin (visitIfNotNull!(asmInstructions, gccAsmInstructions)); + mixin (visitIfNotNull!(functionAttributes, asmInstructions, gccAsmInstructions)); } /** */ AsmInstruction[] asmInstructions; /** */ GccAsmInstruction[] gccAsmInstructions; diff --git a/src/dparse/parser.d b/src/dparse/parser.d index 58d50571..7f4f973d 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -3643,10 +3643,22 @@ class Parser if (skip(tok!":")) { + size_t cp; + if (node.outputOperands.items.length) + { 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; + } } } } @@ -3659,9 +3671,9 @@ class Parser /** * Parses a GccAsmOperandList * - * $(GRAMMAR $(RULEDEF gccAsmOperandList) + * $(GRAMMAR $(RULEDEF gccAsmOperandList): * ($(RULE gccAsmOperand) ($(LITERAL ',') $(RULE gccAsmOperand))* )? - * ) + * ;) */ GccAsmOperandList parseGccAsmOperandList() { @@ -3677,9 +3689,9 @@ class Parser /** * Parses a GccAsmOperand * - * $(GRAMMAR $(RULEDEF gccAsmOperand) + * $(GRAMMAR $(RULEDEF gccAsmOperand): * ($(LITERAL '[') $(RULE identifier) $(LITERAL ']'))? $(RULE stringLiteral) $(LITERAL '(') $(RULE assignExpression) $(LITERAL ')') - * ) + * ;) */ GccAsmOperand parseGccAsmOperand() { diff --git a/test/pass_files/asm-gcc.d b/test/pass_files/asm-gcc.d index d72bcf08..da7209b5 100644 --- a/test/pass_files/asm-gcc.d +++ b/test/pass_files/asm-gcc.d @@ -15,7 +15,7 @@ void main() // Some tests as found in dmd's iasmgcc.d "nop"; asm1; - asm2(); + asm2(1); mixin(`"repne"`, `~ "scasb"`); // GCC examples From 5fb1c1746e97e79a749f57cc52b3efee9523331e Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Sun, 17 May 2020 22:56:29 +0200 Subject: [PATCH 7/8] Feedback v2 --- src/dparse/parser.d | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dparse/parser.d b/src/dparse/parser.d index 7f4f973d..d10d6342 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -3696,8 +3696,6 @@ class Parser GccAsmOperand parseGccAsmOperand() { mixin(traceEnterAndExit!(__FUNCTION__)); - if (currentIsOneOf(tok!":", tok!";")) - return null; const startIndex = index; auto node = allocator.make!GccAsmOperand(); From 87a2b706221728ab4dd5cd6f1aff3e3e4099f9a0 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 19 May 2020 00:15:18 +0200 Subject: [PATCH 8/8] Don't build empty operand lists --- src/dparse/parser.d | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/dparse/parser.d b/src/dparse/parser.d index d10d6342..3f7a3b78 100644 --- a/src/dparse/parser.d +++ b/src/dparse/parser.d @@ -3597,8 +3597,8 @@ class Parser * 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 ';') + * | $(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 ';') * ;) */ /* @@ -3630,11 +3630,14 @@ class Parser if (!currentIs(tok!";")) { mixin(tokenCheck!":"); - mixin(parseNodeQ!(`node.outputOperands`, `GccAsmOperandList`)); + + if (!currentIsOneOf(tok!":", tok!";")) + mixin(parseNodeQ!(`node.outputOperands`, `GccAsmOperandList`)); if (skip(tok!":")) { - mixin(parseNodeQ!(`node.inputOperands`, `GccAsmOperandList`)); + if (!currentIsOneOf(tok!":", tok!";")) + mixin(parseNodeQ!(`node.inputOperands`, `GccAsmOperandList`)); if (skip(tok!":")) { @@ -3645,7 +3648,7 @@ class Parser { size_t cp; - if (node.outputOperands.items.length) + if (node.outputOperands) { error("goto-labels only allowed without output operands!", false); cp = allocator.setCheckpoint(); @@ -3672,17 +3675,12 @@ class Parser * Parses a GccAsmOperandList * * $(GRAMMAR $(RULEDEF gccAsmOperandList): - * ($(RULE gccAsmOperand) ($(LITERAL ',') $(RULE gccAsmOperand))* )? + * $(RULE gccAsmOperand) ($(LITERAL ',') $(RULE gccAsmOperand))* * ;) */ GccAsmOperandList parseGccAsmOperandList() { mixin(traceEnterAndExit!(__FUNCTION__)); - - // Disambiguate between an empty and a missing list - if (currentIsOneOf(tok!":", tok!";")) - return allocator.make!GccAsmOperandList(); - return parseCommaSeparatedRule!(GccAsmOperandList, GccAsmOperand)(); }