[llvm-dev] [RFC] "Properly" Derive Function/Argument/Parameter Attributes

Johannes Doerfert via llvm-dev llvm-dev at lists.llvm.org
Thu Aug 23 08:25:18 PDT 2018


After I spend some time working with the function attribute* deduction
pass** [1,3], I would like to propose a "proper" organization***.



Why?

  Because we do not derive nearly as many attributes as we could****,
  while we do maintain various (separate and diffently organized)
  "data-flow-like analyses" to do so.



What else?

  I propose a single optimistic data-flow (fixpoint) loop to perform the
  deduction (similar to [2,3] but for all propagation forms and not only
  call sites -> callee). Given that a pessimistic initialization allows
  to terminate early, we can then also vary the compile time investment.



Where is the catch?

  It will require a substantial rewrite with (some) parts that cannot be
  split in small independent patches. While I believe these to be easy
  to review*****, I want to know if
    (1) there is interest in having better attribute deduction, and
    (2) there are volunteers to review such patches.



I do appreciate any input on this proposal.

Cheers,
  Johannes

--------

* It derives function attributes but also parameter and argument
  attributes.

** lib/Transforms/IPO/FunctionAttrs.cpp

*** This statement is intended to sound harsh. I believe it to be
    appropriate because it is hard to find consistency in the current
    implementation. Different parts perform argument deduction similarly
    but still different. This does lead to code duplication and missed
    deductions as there is no information exchange going on.

****  This includes missing attributes in existing propagations
      e.g., dereferencability (see also [0,1]), missing forms of
      propagation, e.g., call sites -> callee (see [2,3]), as well as
      missing deductions due to the fact that there is no (global)
      fixpoint iteration. Additional examples to showcase some current
      shortcomings are attached to this mail.

***** In the sense that they will "just contain" the implementation of a
      fixpoint data-flow analysis. The logic to determine/eliminate
      attributes will be similar to the one we have now.


[0] https://reviews.llvm.org/D48387
[1] https://reviews.llvm.org/D50107
[2] https://reviews.llvm.org/D4609
[3] https://reviews.llvm.org/D50125

-- 

Johannes Doerfert
PhD Student / Researcher

Compiler Design Lab (Professor Hack) / Argonne National Laboratory
Saarland Informatics Campus, Germany / Lemont, IL 60439, USA
Building E1.3, Room 4.31

Tel. +49 (0)681 302-57521 : doerfert at cs.uni-saarland.de / jdoerfert at anl.gov
Fax. +49 (0)681 302-3065  : http://www.cdl.uni-saarland.de/people/doerfert
-------------- next part --------------
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s
;
; This is an evolved example to stress test SCC parameter attribute propagation.
; The SCC in this test is made up of the following six function, three of which
; are internal and three externally visible:
;
; static int* internal_ret0_nw(int *n0, int *w0);
; static int* internal_ret1_rw(int *r0, int *w0);
; static int* internal_ret1_rrw(int *r0, int *r1, int *w0);
;        int* external_ret2_nrw(int *n0, int *r0, int *w0);
;        int* external_sink_ret2_nrw(int *n0, int *r0, int *w0);
;        int* external_source_ret2_nrw(int *n0, int *r0, int *w0);
;
; The top four functions call each other while the "sink" function will not
; call anything and the "source" function will not be called in this module.
; The names of the functions define the returned parameter (X for "_retX_"),
; as well as how the parameters are (transitively) used (n = readnone,
; r = readonly, w = writeonly).
;
; Currently we get the following statistics:
;   1 functionattrs - Number of functions marked as norecurse
;   6 functionattrs - Number of functions marked as nounwind
;   1 functionattrs - Number of arguments marked nocapture
;   1 functionattrs - Number of arguments marked readnone
;   1 functionattrs - Number of arguments marked readonly
;   1 functionattrs - Number of arguments marked returned
;  --
;  11
;
; This basically describes that we only derived attributes for the "sink"
; function (except nounwind which is properly derived).
;
;
; What we should get is something along the lines of:
;   1 functionattrs - Number of functions marked as norecurse
;   6 functionattrs - Number of functions marked argmemonly
;   6 functionattrs - Number of functions marked as nounwind
;  16 functionattrs - Number of arguments marked nocapture
;   4 functionattrs - Number of arguments marked readnone
;   6 functionattrs - Number of arguments marked writeonly
;   6 functionattrs - Number of arguments marked readonly
;   6 functionattrs - Number of arguments marked returned
;  --
;  51
;
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

; Function Attrs: noinline nounwind uwtable
define dso_local i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) #0 {
entry:
  %call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0)
  %call1 = call i32* @internal_ret1_rrw(i32* %r0, i32* %r0, i32* %w0)
  %call2 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
  %call3 = call i32* @internal_ret1_rw(i32* %r0, i32* %w0)
  ret i32* %call3
}

; Function Attrs: noinline nounwind uwtable
define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) #0 {
entry:
  %r0 = alloca i32, align 4
  %r1 = alloca i32, align 4
  %tobool = icmp ne i32* %n0, null
  br i1 %tobool, label %if.end, label %if.then

if.then:                                          ; preds = %entry
  br label %return

if.end:                                           ; preds = %entry
  store i32 3, i32* %r0, align 4
  store i32 5, i32* %r1, align 4
  store i32 1, i32* %w0, align 4
  %call = call i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0)
  %call1 = call i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
  %call2 = call i32* @external_ret2_nrw(i32* %n0, i32* %r1, i32* %w0)
  %call3 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
  %call4 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r1, i32* %w0)
  %call5 = call i32* @internal_ret0_nw(i32* %n0, i32* %w0)
  br label %return

return:                                           ; preds = %if.end, %if.then
  %retval.0 = phi i32* [ %call5, %if.end ], [ %n0, %if.then ]
  ret i32* %retval.0
}

; Function Attrs: noinline nounwind uwtable
define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) #0 {
entry:
  %0 = load i32, i32* %r0, align 4
  %tobool = icmp ne i32 %0, 0
  br i1 %tobool, label %if.end, label %if.then

if.then:                                          ; preds = %entry
  br label %return

if.end:                                           ; preds = %entry
  %call = call i32* @internal_ret1_rw(i32* %r0, i32* %w0)
  %1 = load i32, i32* %r0, align 4
  %2 = load i32, i32* %r1, align 4
  %add = add nsw i32 %1, %2
  store i32 %add, i32* %w0, align 4
  %call1 = call i32* @internal_ret1_rw(i32* %r1, i32* %w0)
  %call2 = call i32* @internal_ret0_nw(i32* %r0, i32* %w0)
  %call3 = call i32* @internal_ret0_nw(i32* %w0, i32* %w0)
  %call4 = call i32* @external_ret2_nrw(i32* %r0, i32* %r1, i32* %w0)
  %call5 = call i32* @external_ret2_nrw(i32* %r1, i32* %r0, i32* %w0)
  %call6 = call i32* @external_sink_ret2_nrw(i32* %r0, i32* %r1, i32* %w0)
  %call7 = call i32* @external_sink_ret2_nrw(i32* %r1, i32* %r0, i32* %w0)
  %call8 = call i32* @internal_ret0_nw(i32* %r1, i32* %w0)
  br label %return

return:                                           ; preds = %if.end, %if.then
  %retval.0 = phi i32* [ %call8, %if.end ], [ %r1, %if.then ]
  ret i32* %retval.0
}

; Function Attrs: noinline nounwind uwtable
define dso_local i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) #0 {
entry:
  %tobool = icmp ne i32* %n0, null
  br i1 %tobool, label %if.end, label %if.then

if.then:                                          ; preds = %entry
  br label %return

if.end:                                           ; preds = %entry
  %0 = load i32, i32* %r0, align 4
  store i32 %0, i32* %w0, align 4
  br label %return

return:                                           ; preds = %if.end, %if.then
  ret i32* %w0
}

; Function Attrs: noinline nounwind uwtable
define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) #0 {
entry:
  %0 = load i32, i32* %r0, align 4
  %tobool = icmp ne i32 %0, 0
  br i1 %tobool, label %if.end, label %if.then

if.then:                                          ; preds = %entry
  br label %return

if.end:                                           ; preds = %entry
  %call = call i32* @internal_ret1_rrw(i32* %r0, i32* %r0, i32* %w0)
  %1 = load i32, i32* %r0, align 4
  store i32 %1, i32* %w0, align 4
  %call1 = call i32* @internal_ret0_nw(i32* %r0, i32* %w0)
  %call2 = call i32* @internal_ret0_nw(i32* %w0, i32* %w0)
  %call3 = call i32* @external_sink_ret2_nrw(i32* %r0, i32* %r0, i32* %w0)
  %call4 = call i32* @external_ret2_nrw(i32* %r0, i32* %r0, i32* %w0)
  br label %return

return:                                           ; preds = %if.end, %if.then
  %retval.0 = phi i32* [ %call4, %if.end ], [ %w0, %if.then ]
  ret i32* %retval.0
}

; Function Attrs: noinline nounwind uwtable
define dso_local i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) #0 {
entry:
  %call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
  %call1 = call i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
  ret i32* %call1
}
-------------- next part --------------
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s

; In the following SCC, one function (scc_bottom_vs_top_fn_top) cannot be
; marked nonnull. We should still mark the others as they will always return
; nonnull pointers. However, this only works if we initialize our abstract
; state with bottom (not top), thus optimistically assume nonnull until proven
; otherwise.

declare nonnull i8* @ret_nonnull()

; CHECK: define i8* @scc_bottom_vs_top_fn_top
define i8* @scc_bottom_vs_top_fn_top() {
  call i8* @scc_bottom_vs_top_fn_scc0()
  call i8* @scc_bottom_vs_top_fn_scc1()
  ret i8* null
}

; CHECK: define nonnull i8* @scc_bottom_vs_top_fn_scc0
define i8* @scc_bottom_vs_top_fn_scc0() {

; The null returing function is called here but the result is unused.
  call i8* @scc_bottom_vs_top_fn_top()

  br i1 undef, label %bb0, label %bb1
bb0:
  %ret0 = call i8* @ret_nonnull()
  ret i8* %ret0
bb1:
  %ret1 = call i8* @scc_bottom_vs_top_fn_scc1()
  ret i8* %ret1
}

; CHECK: define nonnull i8* @scc_bottom_vs_top_fn_scc1
define i8* @scc_bottom_vs_top_fn_scc1() {

; The null returing function is called here but the result is unused.
  call i8* @scc_bottom_vs_top_fn_top()

  br i1 undef, label %bb0, label %bb1
bb0:
  %ret0 = call i8* @ret_nonnull()
  ret i8* %ret0
bb1:
  %ret1 = call i8* @scc_bottom_vs_top_fn_scc0()
  ret i8* %ret1
}

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: Digital signature
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20180823/b4ab1a06/attachment.sig>


More information about the llvm-dev mailing list