diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1250,6 +1250,32 @@ Query for this feature with ``__has_extension(blocks)``. +ASM Goto with Output Constraints +================================ + +Clang provides support for the `goto form of GCC's extended +assembly`_ with output +constraints. + +The goto form of GCC's extended assembly allows the programmer to branch to a C +label from within an inline assembly block. Clang extends this behavior by +allowing the programmer to use output constriants: + +.. code-block:: c++ + + int foo(int x) { + int y; + asm goto("# %0 %1 %2" : "=r"(y) : "r"(x) : : err); + return y; + err: + return -1; + } + +It's important to note that outputs are valid only on the "fallthrough" branch. +Using outputs on an indirect branch may result in undefined behavior. For +example, in the function above, use of the value assigned to `y` in the `err` +block is undefined behavior. + Objective-C Features ==================== diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3017,7 +3017,7 @@ } IdentifierInfo *getLabelIdentifier(unsigned i) const { - return Names[i + NumInputs]; + return Names[i + NumOutputs + NumInputs]; } AddrLabelExpr *getLabelExpr(unsigned i) const; @@ -3028,11 +3028,11 @@ using labels_const_range = llvm::iterator_range; labels_iterator begin_labels() { - return &Exprs[0] + NumInputs; + return &Exprs[0] + NumOutputs + NumInputs; } labels_iterator end_labels() { - return &Exprs[0] + NumInputs + NumLabels; + return &Exprs[0] + NumOutputs + NumInputs + NumLabels; } labels_range labels() { @@ -3040,11 +3040,11 @@ } const_labels_iterator begin_labels() const { - return &Exprs[0] + NumInputs; + return &Exprs[0] + NumOutputs + NumInputs; } const_labels_iterator end_labels() const { - return &Exprs[0] + NumInputs + NumLabels; + return &Exprs[0] + NumOutputs + NumInputs + NumLabels; } labels_const_range labels() const { diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -456,7 +456,7 @@ } AddrLabelExpr *GCCAsmStmt::getLabelExpr(unsigned i) const { - return cast(Exprs[i + NumInputs]); + return cast(Exprs[i + NumOutputs + NumInputs]); } StringRef GCCAsmStmt::getLabelName(unsigned i) const { @@ -522,7 +522,7 @@ for (unsigned i = 0, e = getNumLabels(); i != e; ++i) if (getLabelName(i) == SymbolicName) - return i + getNumInputs(); + return i + getNumOutputs() + getNumInputs(); // Not found. return -1; @@ -620,11 +620,13 @@ const char *Begin = CurPtr - 1; // Points to the character following '%'. const char *Percent = Begin - 1; // Points to '%'. + bool IsLabel = false; if (isLetter(EscapedChar)) { if (CurPtr == StrEnd) { // Premature end. DiagOffs = CurPtr-StrStart-1; return diag::err_asm_invalid_escape; } + IsLabel = EscapedChar == 'l'; EscapedChar = *CurPtr++; } @@ -641,6 +643,10 @@ while (CurPtr != StrEnd && isDigit(*CurPtr)) N = N*10 + ((*CurPtr++)-'0'); + // Labels are placed after "InOut" operands. Adjust accordingly. + if (IsLabel) + N += getNumPlusOperands(); + unsigned NumOperands = getNumOutputs() + getNumPlusOperands() + getNumInputs() + getNumLabels(); if (N >= NumOperands) { diff --git a/clang/lib/Analysis/UninitializedValues.cpp b/clang/lib/Analysis/UninitializedValues.cpp --- a/clang/lib/Analysis/UninitializedValues.cpp +++ b/clang/lib/Analysis/UninitializedValues.cpp @@ -536,6 +536,7 @@ void VisitCallExpr(CallExpr *ce); void VisitDeclRefExpr(DeclRefExpr *dr); void VisitDeclStmt(DeclStmt *ds); + void VisitGCCAsmStmt(GCCAsmStmt *as); void VisitObjCForCollectionStmt(ObjCForCollectionStmt *FS); void VisitObjCMessageExpr(ObjCMessageExpr *ME); void VisitOMPExecutableDirective(OMPExecutableDirective *ED); @@ -821,6 +822,16 @@ } } +void TransferFunctions::VisitGCCAsmStmt(GCCAsmStmt *as) { + // An "asm goto" statement is a terminator that may initialize some variables. + if (!as->isAsmGoto()) + return; + + for (const auto &o : as->outputs()) + if (const VarDecl *VD = findVar(o).getDecl()) + vals[VD] = Initialized; +} + void TransferFunctions::VisitObjCMessageExpr(ObjCMessageExpr *ME) { // If the Objective-C message expression is an implicit no-return that // is not modeled in the CFG, set the tracked dataflow values to Unknown. @@ -858,6 +869,10 @@ if (Optional cs = I.getAs()) tf.Visit(const_cast(cs->getStmt())); } + CFGTerminator terminator = block->getTerminator(); + if (GCCAsmStmt *as = dyn_cast_or_null(terminator.getStmt())) + if (as->isAsmGoto()) + tf.Visit(as); return vals.updateValueVectorWithScratch(block); } diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -2237,8 +2237,7 @@ Constraints += ','; Constraints += 'X'; } - StringRef Name = "asm.fallthrough"; - Fallthrough = createBasicBlock(Name); + Fallthrough = createBasicBlock("asm.fallthrough"); } } @@ -2288,9 +2287,9 @@ if (IsGCCAsmGoto) { llvm::CallBrInst *Result = Builder.CreateCallBr(IA, Fallthrough, Transfer, Args); + EmitBlock(Fallthrough); UpdateAsmCallInst(cast(*Result), HasSideEffect, ReadOnly, ReadNone, S, ResultRegTypes, *this, RegResults); - EmitBlock(Fallthrough); } else { llvm::CallInst *Result = Builder.CreateCall(IA, Args, getBundlesForFunclet(IA)); diff --git a/clang/lib/Parse/ParseStmtAsm.cpp b/clang/lib/Parse/ParseStmtAsm.cpp --- a/clang/lib/Parse/ParseStmtAsm.cpp +++ b/clang/lib/Parse/ParseStmtAsm.cpp @@ -784,12 +784,6 @@ AteExtraColon = Tok.is(tok::coloncolon); ConsumeToken(); - if (!AteExtraColon && isGotoAsm && Tok.isNot(tok::colon)) { - Diag(Tok, diag::err_asm_goto_cannot_have_output); - SkipUntil(tok::r_paren, StopAtSemi); - return StmtError(); - } - if (!AteExtraColon && ParseAsmOperandsOpt(Names, Constraints, Exprs)) return StmtError(); } diff --git a/clang/lib/Sema/SemaStmtAsm.cpp b/clang/lib/Sema/SemaStmtAsm.cpp --- a/clang/lib/Sema/SemaStmtAsm.cpp +++ b/clang/lib/Sema/SemaStmtAsm.cpp @@ -473,10 +473,10 @@ // Look for the correct constraint index. unsigned ConstraintIdx = Piece.getOperandNo(); - // Labels are the last in the Exprs list. - if (NS->isAsmGoto() && ConstraintIdx >= NS->getNumInputs()) - continue; unsigned NumOperands = NS->getNumOutputs() + NS->getNumInputs(); + // Labels are the last in the Exprs list. + if (NS->isAsmGoto() && ConstraintIdx >= NumOperands) + continue; // Look for the (ConstraintIdx - NumOperands + 1)th constraint with // modifier '+'. if (ConstraintIdx >= NumOperands) { diff --git a/clang/test/Analysis/uninit-asm-goto.cpp b/clang/test/Analysis/uninit-asm-goto.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/uninit-asm-goto.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -std=c++11 -Wuninitialized -verify %s +// expected-no-diagnostics + +int test1(int x) { + int y; + asm goto("# %0 %1 %2" : "=r"(y) : "r"(x) : : err); + return y; + err: + return -1; +} diff --git a/clang/test/CodeGen/asm-goto.c b/clang/test/CodeGen/asm-goto.c --- a/clang/test/CodeGen/asm-goto.c +++ b/clang/test/CodeGen/asm-goto.c @@ -2,13 +2,13 @@ // RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O0 -emit-llvm %s -o - | FileCheck %s // RUN: %clang_cc1 -triple i386-pc-linux-gnu -O0 -emit-llvm %s -o - | FileCheck %s -int foo(int cond) -{ +int test1(int cond) { + // CHECK-LABEL: define i32 @test1( // CHECK: callbr void asm sideeffect // CHECK: to label %asm.fallthrough [label %label_true, label %loop] // CHECK: asm.fallthrough: asm volatile goto("testl %0, %0; jne %l1;" :: "r"(cond)::label_true, loop); - asm volatile goto("testl %0, %0; jne %l1;" :: "r"(cond)::label_true, loop); + asm volatile goto("testl %0, %0; jne %l2;" :: "r"(cond)::label_true, loop); // CHECK: callbr void asm sideeffect // CHECK: to label %asm.fallthrough1 [label %label_true, label %loop] // CHECK: asm.fallthrough1: @@ -18,3 +18,74 @@ label_true: return 1; } + +int test2(int cond) { + // CHECK-LABEL: define i32 @test2( + // CHECK: callbr i32 asm sideeffect + // CHECK: to label %asm.fallthrough [label %label_true, label %loop] + // CHECK: asm.fallthrough: + asm volatile goto("testl %0, %0; jne %l2;" : "=r"(cond) : "r"(cond) :: label_true, loop); + asm volatile goto("testl %0, %0; jne %l3;" : "=r"(cond) : "r"(cond) :: label_true, loop); + // CHECK: callbr i32 asm sideeffect + // CHECK: to label %asm.fallthrough1 [label %label_true, label %loop] + // CHECK: asm.fallthrough1: + return 0; +loop: + return 0; +label_true: + return 1; +} + +int test3(int out1, int out2) { + // CHECK-LABEL: define i32 @test3( + // CHECK: callbr { i32, i32 } asm sideeffect + // CHECK: to label %asm.fallthrough [label %label_true, label %loop] + // CHECK: asm.fallthrough: + asm volatile goto("testl %0, %0; jne %l3;" : "=r"(out1), "=r"(out2) : "r"(out1) :: label_true, loop); + asm volatile goto("testl %0, %0; jne %l4;" : "=r"(out1), "=r"(out2) : "r"(out1) :: label_true, loop); + // CHECK: callbr { i32, i32 } asm sideeffect + // CHECK: to label %asm.fallthrough2 [label %label_true, label %loop] + // CHECK: asm.fallthrough2: + return 0; +loop: + return 0; +label_true: + return 1; +} + +int test4(int out1, int out2) { + // CHECK-LABEL: define i32 @test4( + // CHECK: callbr { i32, i32 } asm sideeffect "jne ${5:l}", "={si},={di},r,0,1,X,X + // CHECK: to label %asm.fallthrough [label %label_true, label %loop] + // CHECK: asm.fallthrough: + if (out1 < out2) + asm volatile goto("jne %l3" : "+S"(out1), "+D"(out2) : "r"(out1) :: label_true, loop); + else + asm volatile goto("jne %l5" : "+S"(out1), "+D"(out2) : "r"(out1), "r"(out2) :: label_true, loop); + // CHECK: callbr { i32, i32 } asm sideeffect "jne ${7:l}", "={si},={di},r,r,0,1,X,X + // CHECK: to label %asm.fallthrough2 [label %label_true, label %loop] + // CHECK: asm.fallthrough2: + return out1 + out2; +loop: + return -1; +label_true: + return -2; +} + +int test5(int addr, int size, int limit) { + // CHECK-LABEL: define i32 @test5( + // CHECK: callbr i32 asm "add $1,$0 ; jc ${3:l} ; cmp $2,$0 ; ja ${3:l} ; ", "=r,imr,imr,0,X + // CHECK: to label %asm.fallthrough [label %t_err] + // CHECK: asm.fallthrough: + asm goto( + "add %1,%0 ; " + "jc %l[t_err] ; " + "cmp %2,%0 ; " + "ja %l[t_err] ; " + : "+r" (addr) + : "g" (size), "g" (limit) + : : t_err); + return 0; +t_err: + return 1; +} diff --git a/clang/test/Parser/asm-goto.c b/clang/test/Parser/asm-goto.c --- a/clang/test/Parser/asm-goto.c +++ b/clang/test/Parser/asm-goto.c @@ -5,12 +5,9 @@ #error Extension 'gnu_asm' should be available by default #endif - int a, b, c, d, e, f, g, h, i, j, k, l; -void -fgoto1 (void) -{ +void test(void) { __asm__ volatile goto ("" :: [a] "r" (a), [b] "r" (b), [c] "r" (c), [d] "r" (d), [e] "r" (e), [f] "r" (f), [g] "r" (g), [h] "r" (h), @@ -20,9 +17,7 @@ lab2: return; } -void -fgoto2 (void) -{ +void test2(void) { __asm__ volatile goto ("" :: [a] "r,m" (a), [b] "r,m" (b), [c] "r,m" (c), [d] "r,m" (d), [e] "r,m" (e), [f] "r,m" (f), [g] "r,m" (g), [h] "r,m" (h), @@ -31,14 +26,33 @@ lab: return; } -int zoo () -{ +int test3(int x) { + __asm__ volatile goto ("decl %0; jnz %l[a]" + : "=r" (x) : "m" (x) : "memory" : a); +a: + return -x; +} + +int test4(int x) { + int y; + if (x > 42) + __asm__ volatile goto ("decl %0; jnz %l[a]" + : "=r" (x), "=r" (y) : "m" (x) : "memory" : a); + else + __asm__ volatile goto ("decl %0; jnz %l[b]" + : "=r" (x), "=r" (y) : "m" (x) : "memory" : b); + x = y + 42; +a: + return -x; +b: + return +x; +} + +int test5(void) { int x,cond,*e; // expected-error@+1 {{expected ')'}} asm ("mov %[e], %[e]" : : [e] "rm" (*e)::a) - // expected-error@+1 {{'asm goto' cannot have output constraints}} - asm goto ("decl %0; jnz %l[a]" :"=r"(x): "m"(x) : "memory" : a); - // expected-error@+1 {{expected identifie}} + // expected-error@+1 {{expected identifier}} asm goto ("decl %0;" :: "m"(x) : "memory" : ); // expected-error@+1 {{expected ':'}} asm goto ("decl %0;" :: "m"(x) : "memory" ); @@ -55,3 +69,25 @@ loop: return 0; } + +int test6(int y) { + int x,cond,*e; + // expected-error@+1 {{expected ')'}} + asm ("mov %[e], %[e]" : "=r" (y) : [e] "rm" (*e), "r" (y) :: a) + // expected-error@+1 {{expected identifier}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" :); + // expected-error@+1 {{expected ':'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory"); + // expected-error@+1 {{use of undeclared label 'x'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" : x); + // expected-error@+1 {{use of undeclared label 'b'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" : b); + // expected-error@+1 {{invalid operand number in inline asm string}} + asm goto ("testl %0, %0; jne %l5;" : "=r" (y) : "r" (cond), "r" (y) :: label_true, loop); + // expected-error@+1 {{unknown symbolic operand name in inline assembly string}} + asm goto ("decl %0; jnz %l[b]" : "=r" (y) : "m" (x), "r" (y) : "memory" : a); +label_true: +loop: +a: + return 0; +} diff --git a/clang/test/Parser/asm-goto.cpp b/clang/test/Parser/asm-goto.cpp --- a/clang/test/Parser/asm-goto.cpp +++ b/clang/test/Parser/asm-goto.cpp @@ -1,14 +1,54 @@ // RUN: %clang_cc1 -triple i386-pc-linux-gnu -fsyntax-only -verify -std=c++11 %s // RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fsyntax-only -verify -std=c++11 %s -int zoo () -{ +int a, b, c, d, e, f, g, h, i, j, k, l; + +void test1(void) { + __asm__ volatile goto ("" + :: [a] "r" (a), [b] "r" (b), [c] "r" (c), [d] "r" (d), + [e] "r" (e), [f] "r" (f), [g] "r" (g), [h] "r" (h), + [i] "r" (i), [j] "r" (j), [k] "r" (k), [l] "r" (l) + ::lab1,lab2); +lab1: return; +lab2: return; +} + +void test2(void) { + __asm__ volatile goto ("" + :: [a] "r,m" (a), [b] "r,m" (b), [c] "r,m" (c), [d] "r,m" (d), + [e] "r,m" (e), [f] "r,m" (f), [g] "r,m" (g), [h] "r,m" (h), + [i] "r,m" (i), [j] "r,m" (j), [k] "r,m" (k), [l] "r,m" (l) + :: lab); + lab: return; +} + +int test3(int x) { + __asm__ volatile goto ("decl %0; jnz %l[a]" + : "=r" (x) : "m" (x) : "memory" : a); +a: + return -x; +} + +int test4(int x) { + int y; + if (x > 42) + __asm__ volatile goto ("decl %0; jnz %l[a]" + : "=r" (x), "=r" (y) : "m" (x) : "memory" : a); + else + __asm__ volatile goto ("decl %0; jnz %l[b]" + : "=r" (x), "=r" (y) : "m" (x) : "memory" : b); + x = y + 42; +a: + return -x; +b: + return +x; +} + +int test5(void) { int x,cond,*e; // expected-error@+1 {{expected ')'}} asm ("mov %[e], %[e]" : : [e] "rm" (*e)::a) - // expected-error@+1 {{'asm goto' cannot have output constraints}} - asm goto ("decl %0; jnz %l[a]" :"=r"(x): "m"(x) : "memory" : a); - // expected-error@+1 {{expected identifie}} + // expected-error@+1 {{expected identifier}} asm goto ("decl %0;" :: "m"(x) : "memory" : ); // expected-error@+1 {{expected ':'}} asm goto ("decl %0;" :: "m"(x) : "memory" ); @@ -26,28 +66,24 @@ return 0; } - -int a, b, c, d, e, f, g, h, i, j, k, l; - -void -fgoto1 (void) -{ - __asm__ volatile goto ("" - :: [a] "r" (a), [b] "r" (b), [c] "r" (c), [d] "r" (d), - [e] "r" (e), [f] "r" (f), [g] "r" (g), [h] "r" (h), - [i] "r" (i), [j] "r" (j), [k] "r" (k), [l] "r" (l) - ::lab1,lab2); -lab1: return; -lab2: return; -} - -void -fgoto2 (void) -{ - __asm__ volatile goto ("" - :: [a] "r,m" (a), [b] "r,m" (b), [c] "r,m" (c), [d] "r,m" (d), - [e] "r,m" (e), [f] "r,m" (f), [g] "r,m" (g), [h] "r,m" (h), - [i] "r,m" (i), [j] "r,m" (j), [k] "r,m" (k), [l] "r,m" (l) - :: lab); - lab: return; +int test6(int y) { + int x,cond,*e; + // expected-error@+1 {{expected ')'}} + asm ("mov %[e], %[e]" : "=r" (y) : [e] "rm" (*e), "r" (y) :: a) + // expected-error@+1 {{expected identifier}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" :); + // expected-error@+1 {{expected ':'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory"); + // expected-error@+1 {{use of undeclared label 'x'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" : x); + // expected-error@+1 {{use of undeclared label 'b'}} + asm goto ("decl %0;" : "=r" (y) : "m" (x), "r" (y) : "memory" : b); + // expected-error@+1 {{invalid operand number in inline asm string}} + asm goto ("testl %0, %0; jne %l5;" : "=r" (y) : "r" (cond), "r" (y) :: label_true, loop); + // expected-error@+1 {{unknown symbolic operand name in inline assembly string}} + asm goto ("decl %0; jnz %l[b]" : "=r" (y) : "m" (x), "r" (y) : "memory" : a); +label_true: +loop: +a: + return 0; } diff --git a/clang/test/Sema/asm-goto.cpp b/clang/test/Sema/asm-goto.cpp --- a/clang/test/Sema/asm-goto.cpp +++ b/clang/test/Sema/asm-goto.cpp @@ -1,38 +1,38 @@ // RUN: %clang_cc1 %s -triple i386-pc-linux-gnu -verify -fsyntax-only // RUN: %clang_cc1 %s -triple x86_64-pc-linux-gnu -verify -fsyntax-only -struct NonTrivial { - ~NonTrivial(); +struct S { + ~S(); int f(int); private: int k; }; -void JumpDiagnostics(int n) { +void test1(int n) { // expected-error@+1 {{cannot jump from this goto statement to its label}} goto DirectJump; // expected-note@+1 {{jump bypasses variable with a non-trivial destructor}} - NonTrivial tnp1; + S s1; DirectJump: // expected-error@+1 {{cannot jump from this asm goto statement to one of its possible targets}} asm goto("jmp %l0;" ::::Later); // expected-note@+1 {{jump bypasses variable with a non-trivial destructor}} - NonTrivial tnp2; + S s2; // expected-note@+1 {{possible target of asm goto statement}} Later: return; } -struct S { ~S(); }; -void foo(int a) { +struct T { ~T(); }; +void test2(int a) { if (a) { FOO: // expected-note@+2 {{jump exits scope of variable with non-trivial destructor}} // expected-note@+1 {{jump exits scope of variable with non-trivial destructor}} - S s; + T t; void *p = &&BAR; // expected-error@+1 {{cannot jump from this asm goto statement to one of its possible targets}} - asm goto("jmp %l0;" ::::BAR); + asm goto("jmp %l0;" ::::BAR); // expected-error@+1 {{cannot jump from this indirect goto statement to one of its possible targets}} goto *p; p = &&FOO; @@ -45,9 +45,7 @@ return; } - -//Asm goto: -int test16(int n) +int test3(int n) { // expected-error@+2 {{cannot jump from this asm goto statement to one of its possible targets}} // expected-error@+1 {{cannot jump from this asm goto statement to one of its possible targets}}