Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite loops are optimized out in languages without forward progress guarantees (C, Rust) #1337

Closed
npatil2 opened this issue Oct 23, 2006 · 33 comments
Labels
bugzilla Issues migrated from bugzilla ipa Interprocedural analysis miscompilation

Comments

@npatil2
Copy link

npatil2 commented Oct 23, 2006

Bugzilla Link 965
Resolution FIXED
Resolved on Jan 24, 2021 14:54
Version trunk
OS All
CC @Slach,@lattner,@efriedma-quic,@fhahn,@jdm,@jryans,@kalvdans,@sunfishcode,@nlewycky,@nikic,@pjaaskel,@RalfJung,@programmerjake,@regehr,@rnk,@nagisa,@TNorthover,@uenoku,@yuanfang-chen

Extended Description

Hi,

I have been playing with functions marked attribute((noreturn));
Specifically consider a following code:

void f(void) attribute((noreturn));

int call_f()
{
f();
}

void f()
{
while(1)
;
}

With "opt -globalsmodref-aa -adce" this gets compiled to:

int %call_f() {
entry:
%retval = alloca int, align 4 ; <int*> [#uses=1]
unreachable ; **** WRONG!!

return: ; No predecessors!
%retval = load int* %retval ; [#uses=1]
ret int %retval

void %f() {
entry:
br label %bb

bb: ; preds = %bb, %entry
br label %bb

return: ; No predecessors!
ret void
}

@lattner
Copy link
Collaborator

lattner commented Oct 23, 2006

Please attach the .bc file you are inputting to opt here.  I get:

void %f() {
entry:
        br label %bb

bb:             ; preds = %bb, %entry
        br label %bb
}

int %call_f() {
entry:
        br label %bb

bb:             ; preds = %bb, %entry
        br label %bb
}

which is right.

@lattner
Copy link
Collaborator

lattner commented Oct 23, 2006

Ah, n/m I figured it out, you're using -O0.

@lattner
Copy link
Collaborator

lattner commented Oct 23, 2006

In this case, it don't really have anything to do with noreturn.  Here globalmodrefaa determines that f()
has no side effects and produces no return value, so it deletes the call.

For example:

int call_f() {
    f();
    return 17;
}

void f() {
    while(1);
}  

Compiles to:

int %call_f() {
entry:
        %retval = alloca int, align 4           ; <int*> [#uses=2]
        %tmp = alloca int, align 4              ; <int*> [#uses=2]
        store int 17, int* %tmp
        %tmp = load int* %tmp           ; [#uses=1]
        store int %tmp, int* %retval
        br label %return

return:         ; preds = %entry
        %retval = load int* %retval             ; [#uses=1]
        ret int %retval
}

With: llvm-gcc dce.c -emit-llvm -S -o - | llvm-as | opt -globalsmodref-aa -adce | llvm-dis
-Chris

@efriedma-quic
Copy link
Collaborator

How is this an issue with alias analysis? The function doesn't read or write memory. The issue is that ADCE is making bad assumptions.

The new version of ADCE has switched to using "mayWriteToMemory" instead of alias analysis... it still has the same issue, though, if you put the readnone attribute on f. I hesitate to suggest a new function attribute, but having an attribute which guarantees a function will eventually return (the opposite of noreturn, I guess), would make some optimizations more feasible, like a correct ADCE and LICM with potentially trapping instructions.

A similar sort of testcase for LICM (where it assumes calls always return):
int b(void), c(int);
int a(int a) {
int k; for (int i = 0; i < 10; i++) {b(); k = 5/a; c(k);} return k;
}

@efriedma-quic
Copy link
Collaborator

Same issue in LoopStrengthReduce:

declare i32 @​a(i32) readonly

define void @​b() {
entry:
br label %loopstart

loopstart:
%indvar = phi i32 [0, %entry], [%indvarinc, %loopstart]
%randomphi = phi i32 [0, %entry], [%randomvar, %loopstart]
%randomvar = call i32 @​a(i32 %randomphi)
%indvarinc = add i32 %indvar, 1
%cmp = icmp eq i32 %indvarinc, 10
br i1 %cmp, label %loopstart, label %exit

exit:
ret void
}

Run opt -loop-reduce to reproduce.

Maybe we should add a function to Instruction, something like hasNoSideEffects, which returns false for all call instructions. Then, we can make it a bit smarter if there's eventually a way to indicate that a function doesn't have any infinite loops.

@efriedma-quic
Copy link
Collaborator

This is likely to trigger much more frequently with the new addreadattrs pass.

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 19, 2008

Is this actually a bug?

@efriedma-quic
Copy link
Collaborator

Is this actually a bug?

I'm pretty sure turning a well-defined infinite loop into a program with undefined behavior qualifies as a bug.

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 21, 2008

Fair enough. There is a similar issue with
raising exceptions: a function containing an
unwind instruction can be marked readnone/readonly.
But it should not be deleted if the result is not
used, since calls to it can modify control flow.

Here's what I suggest
(1) a new function attribute "willreturn" indicating that
calls to the function are sure to return.
(2) mark intrinsics with this, plus generate it in llvm-gcc
for a bunch of standard library functions.
(3) add a pass like PruneEH and AddReadAttrs which marks
functions willreturn if it can prove that they will not
infinite loop.
(4) add a mayChangeControlFlow method which says that
calls may change control flow except if they are marked
both nounwind and willreturn.
(5) create a hasNoSideEffects method which checks
mayWriteToMemory (or whatever) and mayChangeControlFlow.
Replace isTriviallyDead with this, or use it in
isTriviallyDead.

@pjaaskel
Copy link
Contributor

This affects our embedded target's simulator where we stop simulation immediately when program enters _exit() from _start() with a crt0() like
this:

void _start(void) attribute((noinline));
void _exit(int) attribute((noinline,noreturn));

void _start(void)
{
_exit(main());
}

/* override normal exit. simulator should stop when _exit() is seen /
void _exit(int retval) {
while(1); /
less instructions are generated, when this while exists */
}

When compiled with llvm-gcc -O0 this results in:

define void @​_start() nounwind noinline {
entry:
%0 = call i32 (...)* @​main() nounwind ; [#uses=1]
call void @​_exit(i32 %0) noreturn nounwind
unreachable

return: ; No predecessors!
ret void
}

declare i32 @​main(...)

define void @​_exit(i32 %retval) noreturn nounwind noinline {
entry:
%retval_addr = alloca i32 ; <i32*> [#uses=1]
%"alloca point" = bitcast i32 0 to i32 ; [#uses=0]
store i32 %retval, i32* %retval_addr
br label %bb

bb: ; preds = %bb, %entry
br label %bb

return: ; No predecessors!
ret void
}

When compiling with -O2 the call to _exit() is removed and our simulator
flows from _start() after returning to it from main() which causes an infinite loop to main() (as it's the next function in memory) and not being able to stop simulation automatically:

define void @​_start() nounwind noinline {
entry:
%0 = tail call i32 (...)* @​main() nounwind ; [#uses=0]
unreachable
}

define void @​_exit(i32 %retval) noreturn nounwind readnone noinline {
entry:
br label %bb

bb: ; preds = %bb, %entry
br label %bb
}

We can workaround this issue by compiling crt0() with -O0 so it's not a serious problem for us.

@nlewycky
Copy link
Contributor

*** Bug llvm/llvm-bugzilla-archive#3593 has been marked as a duplicate of this bug. ***

@nlewycky
Copy link
Contributor

Most functions that are obviously readnone/readonly will also be obviously halting. I think adding that annotation and a utility method for 'hasSideEffect' is the right way to go. Just make sure that it's invalid to declare noreturn+halting at the same time.

@llvmbot
Copy link
Collaborator

llvmbot commented Nov 4, 2009

*** Bug llvm/llvm-bugzilla-archive#5387 has been marked as a duplicate of this bug. ***

@regehr
Copy link
Contributor

regehr commented Nov 4, 2009

As a random note, we recently started looking for wrongful-termination and wrongful-nontermination code generation bugs in LLVM, and ran across this.

I don't want to be a whiner but since we hit this bug a lot, we have to avoid doing any more of this kind of testing until this problem is fixed.

@lattner
Copy link
Collaborator

lattner commented Jan 1, 2010

*** Bug llvm/llvm-bugzilla-archive#5644 has been marked as a duplicate of this bug. ***

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 8, 2010

rdar://8401758

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 8, 2010

A fix for this was posted to the mailing list:
http://lists.cs.uiuc.edu/pipermail/llvm-commits/Week-of-Mon-20100705/103672.html

@llvmbot
Copy link
Collaborator

llvmbot commented Nov 23, 2010

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 4, 2011

*** Bug llvm/llvm-bugzilla-archive#10858 has been marked as a duplicate of this bug. ***

@d0k
Copy link
Member

d0k commented Feb 19, 2012

*** Bug llvm/llvm-bugzilla-archive#12033 has been marked as a duplicate of this bug. ***

@efriedma-quic
Copy link
Collaborator

*** Bug llvm/llvm-bugzilla-archive#14454 has been marked as a duplicate of this bug. ***

@zygoloid
Copy link
Mannequin

zygoloid mannequin commented Jan 17, 2013

*** Bug llvm/llvm-bugzilla-archive#14973 has been marked as a duplicate of this bug. ***

@llvmbot
Copy link
Collaborator

llvmbot commented Jul 29, 2013

*** Bug llvm/llvm-bugzilla-archive#16733 has been marked as a duplicate of this bug. ***

@rnk
Copy link
Collaborator

rnk commented Nov 30, 2016

*** Bug llvm/llvm-bugzilla-archive#31217 has been marked as a duplicate of this bug. ***

@rnk
Copy link
Collaborator

rnk commented Nov 30, 2016

I think the last time we discussed this seriously was in July 2015:
http://lists.llvm.org/pipermail/llvm-dev/2015-July/088095.html

I have a different proposal. The forward progress required by C++ is the odd duck here. We should ask frontends to put a function attribute on functions that they want to receive this kind of optimization. It should be safe to remove this attribute when doing cross-language inlining during LTO. Passes like ADCE or functionattrs can power down when a function lacks this attribute, so we don't delete infinite loops or infer that infinite loop functions are readnone in C.

@sunfishcode
Copy link
Member

I posted a patch implementing @​llvm.sideeffect(): https://reviews.llvm.org/D38336

@nagisa
Copy link
Member

nagisa commented Feb 17, 2019

Light testing suggests that considering noreturn side-effectful and not infering readnone, readonly or writeonly attributes for such functions (in the derive function attributes pass) yields fairly good results and goes most of the way towards resolving this issue.

Would a patch implementing this idea be accepted?

@nlewycky
Copy link
Contributor

Simon, for your proposal to be correct you would have to treat all non-returning functions as side-effecting, but functions which lack the 'noreturn' attribute might not return. As is, your proposal would decrease the chance that a user encounters a miscompile, but not fix it entirely. I don't know whether llvm-commits would accept such a patch.

There's a more broad rule to follow when designing IR attributes. LLVM IR is designed to be cross-language at function call boundaries (and possibly within a function after inlining), so you shouldn't assume anything about a function's behaviour unless you can see the definition, or have an attribute on the declaration or call site. It follows that we have to make the most pessimistic assumptions in the absence of attributes. Function attributes should be designed to constrain the possible behaviour of the function, and we can only optimize in the presence of the attributes.

Reid's proposal is correct. We can keep 'noreturn' since it constrains the function (it must not return) while also adding a 'forwards-progress' which constrains the function to either perform a side-effect or return. A single function could have both attributes, for example a function containing the select loop of a server.

In C++ mode clang could add 'forwards-progress' on all function declarations except those marked extern "C".

@llvmbot
Copy link
Collaborator

llvmbot commented Jul 19, 2019

Are there any plans to move ahead with Reid Kleckner's proposal to add a "forwards-progress" attribute to a C / C++ function?

I confess that I am one of the clang users/developers that would prefer that clang provide some way of leaving a noreturn function which contains only an infinite loop intact.

Some applications that we've encountered use such functions as "parking" functions where an error condition can go to wait until serviced by an interrupt.

We currently insert an asm("") statement into such functions to fake a side effect so that the optimizer will not remove calls in the same compilation unit to the "parking" function.

If the "forwards-progress" attribute that Reid proposes were implemented, it seems that if we want a "parking" function to stay intact in C++ mode, we'd still need some way to turn off the "forwards-progress" attribute, maybe a "waiting-for-service" attribute? Maybe an C/C++-callabel intrinsic that maps to the @​llvm.side-effect() intinsic?

@fhahn
Copy link
Contributor

fhahn commented Aug 17, 2020

IICU there is another push towards resolving this, by adding a no progress attribute https://reviews.llvm.org/D85393

@dwblaikie
Copy link
Collaborator

*** Bug llvm/llvm-bugzilla-archive#47189 has been marked as a duplicate of this bug. ***

@nikic
Copy link
Contributor

nikic commented Jan 24, 2021

This issue is fixed in LLVM 12 by the introduction of the mustprogress attribute and loop metadata, inference of willreturn and finally limitation of call DCE to willreturn functions.

There might still be some incorrect assumptions left over here and there, but the core issue is fixed now, and we should track remaining issues separately.

@lattner
Copy link
Collaborator

lattner commented Jan 24, 2021

Very nice, congratulations!

@llvmbot llvmbot transferred this issue from llvm/llvm-bugzilla-archive Dec 3, 2021
clementval pushed a commit to clementval/llvm-project that referenced this issue Jan 17, 2022
* [flang][driver] Add support for `--target`/`--triple`

This patch adds support for:
  * `--target` in the compiler driver (`flang-new`)
  * `--triple` in the frontend driver (`flang-new -fc1`)
The semantics of these flags are inherited from `clangDriver`, i.e.
consistent with `clang --target` and `clang -cc1 --triple`,
respectively.

A new structure is defined, `TargetOptions`, that will hold various
Frontend options related to the target. Currently, this is mostly a
placeholder that contains the target triple. In the future, it will be
used for storing e.g. the CPU to tune for or the target features to
enable.

Additionally, the following target/triple related options are enabled
[*]: `-print-effective-triple`, `-print-target-triple` and
`-print-targets`.

[*] These options were actually available before (like all other options
defined in `clangDriver`), but not included in `flang-new --help`.
`flang-new` would just use `native` for defining the target, so these
options were of little value. With `--target`/`--triple` enabled, these
options become very helpful in identifying the available targets. This
patch makes sure that these are "advertised" by including them in
the output of `--help`.

* Add a bit of documentation

* Update the test requirements
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugzilla Issues migrated from bugzilla ipa Interprocedural analysis miscompilation
Projects
None yet
Development

No branches or pull requests