<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Mon, Aug 6, 2018 at 1:21 AM, Chris Johns <span dir="ltr"><<a href="mailto:chrisj@rtems.org" target="_blank">chrisj@rtems.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div class="gmail-HOEnZb"><div class="gmail-h5">On 06/08/2018 16:12, Christian Mauderer wrote:<br>
> Am 06.08.2018 um 07:31 schrieb Chris Johns:<br>
>> On 06/08/2018 10:51, Chris Johns wrote:<br>
>>> On 05/08/2018 19:39, Christian Mauderer wrote:<br>
>>>> Am 05.08.2018 um 04:00 schrieb Chris Johns:<br>
>>>>> Hi,<br>
>>>>><br>
>>>>> I have been working on migrating covoar in the rtems-tools repo to DWARF. The<br>
>>>>> goal is remove objdump parsing and to get accurate details about the functions<br>
>>>>> being covered. This is an unfunded task.<br>
>>>>><br>
>>>>> The work has resulted in a close examination of inlined code in RTEMS and what I<br>
>>>>> saw alarmed me so I have added a report to the rtems-exeinfo tool in rtems-tools<br>
>>>>> (the change is to be posted for review once I get the coverage tests running).<br>
>>>>><br>
>>>>> A summary report for hello.exe on RTEMS 5 for SPARC is:<br>
>>>>><br>
>>>>> inlined funcs : 1412<br>
>>>>> total funcs : 1956<br>
>>>>> % inline funcs : 72%<br>
>>>>> total size : 174616<br>
>>>>> inline size : 81668<br>
>>>>> % inline size : 46%<br>
>>>>><br>
>>>>> This is a small application so it could be argued that skews the figures. A<br>
>>>>> large C/C++ application built with -O2 running on RTEMS 4.11 ARM reports the<br>
>>>>> inline usage as:<br>
>>>>><br>
>>>>> inlined funcs : 10370<br>
>>>>> total funcs : 17700<br>
>>>>> % inline funcs : 58%<br>
>>>>> total size : 3066240<br>
>>>>> inline size : 1249514<br>
>>>>> % inline size : 40%<br>
>>>>><br>
>>>>> This does not seem right to me.<br>
>>>>><br>
>>>>> The report is new and there could be issues in the DWARF handling that feeds<br>
>>>>> this report however I am posting this to start a discussion on the topic of<br>
>>>>> inlining.<br>
>>>>><br>
>>>>> I attach the report for hello.exe. The `-i` option generates the inline report.<br>
>>>>><br>
>>>>> The first section is a summary showing the total number of functions in the<br>
>>>>> executable that have machine code and are flagged as inline. The report lists<br>
>>>>> the percentage of functions that are inlined and the percentage of machine code<br>
>>>>> that is inlined. The values seem high to me.<br>
>>>>><br>
>>>>> The second table lists inline functions that are repeated sorted from the<br>
>>>>> largest foot print to the smallest. The first column the total size of machine<br>
>>>>> code in the executable and the second column the number of instances.<br>
>>>>><br>
>>>>> The third table is the list of inline functions sorted from largest machine code<br>
>>>>> footprint to smallest. The second column are flags of which there is one. A `E`<br>
>>>>> indicates the inline function is also external which means the compiler has<br>
>>>>> created an external reference to this function, ie an address-of is being taken.<br>
>>>>> The third column is the address in the executable so you can take a look with<br>
>>>>> objdump at the machine code.<br>
>>>>><br>
>>>>> We need to ask some important question in relation to inlining. It is cheap to<br>
>>>>> add and we all feel the code we add needs to be fast and needs to be inlined but<br>
>>>>> does it really need to be inlined?<br>
>>>>><br>
>>>>> Some pieces of code do need to be inlined and the overhead is just that an<br>
>>>>> overhead, for example in the large C/C++ application there is a low level<br>
>>>>> volatile hardware write routing with close to 300 instances and a code size of<br>
>>>>> 10K. This code needs to be inlined for performance reasons but should the size<br>
>>>>> on average be 40 bytes, I doubt it.<br>
>>>>><br>
>>>>> Can we be more judicious with our use of the inline keyword?<br>
>>>>><br>
>>>>> Is the performance gain we really expect or is the actual overhead of a call<br>
>>>>> frame not worth saving?<br>
>>>>><br>
>>>>> What are the real costs of inlining a piece of code? It adds size to the<br>
>>>>> executable and depending on the code being inlined it complicates coverage<br>
>>>>> analysis be adding extra branch points.<br>
>>>>><br>
>>>>> The metrics to determine what should be inlined is complicated and I do not<br>
>>>>> think we have a suitable policy in place. I believe it is time we to create one.<br>
>>>>><br>
>>>>> The issue is not limited to our code, gcc, newlib and libstdc++ seem to have<br>
>>>>> some code that should be looked at more closely. For example __udivmoddi4, and<br>
>>>>> __sprint_r.<br>
>>>>><br>
>>>>> Chris<br>
>>>>><br>
>>>>><br>
>>>><br>
>>>> Hello Chris,<br>
>>>><br>
>>>> I just took a look at one of the first function in your list: __sprint_r<br>
>>>><br>
>>>> <a href="https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdio/vfprintf.c;h=c4bf2dbe31da64462ecccec97c8e901e4ffadd44;hb=HEAD#l403" rel="noreferrer" target="_blank">https://sourceware.org/git/<wbr>gitweb.cgi?p=newlib-cygwin.<wbr>git;a=blob;f=newlib/libc/<wbr>stdio/vfprintf.c;h=<wbr>c4bf2dbe31da64462ecccec97c8e90<wbr>1e4ffadd44;hb=HEAD#l403</a><br>
>>>><br>
>>>> As far as I can see, there is no explicit inline key word for that<br>
>>>> function. So in that case, the compiler decided that it would be a good<br>
>>>> idea to inline that function.<br>
>>><br>
>>> Thanks and yes. At this point in time I cannot tell what is happening and I am<br>
>>> not sure the tool is reporting accurate data, I need to investigate.<br>
>><br>
>> I have updated the tool and report to show which inline functions are:<br>
>><br>
>> - inlined by compiler<br>
>> - declared inline and not inlined<br>
>> - declared inline and inlined<br>
>><br>
>> I have also fixed a quick hack I had where the size was the span from the low PC<br>
>> to the high PC, this was wrong. Inlined code can be split and moved when<br>
>> inlining creating a discontinuous address range. The size in the report is now<br>
>> the number of machine code bytes.<br>
>><br>
>> The report will show any functions not inlined when asked to be inlined. We do<br>
>> not have any.<br>
>><br>
>> The 'C' flag in the inlined table shows which functions the compiler has inlined.<br>
>><br>
>> Chris<br>
>><br>
> <br>
> With that list it is now much clearer which functions would be relevant<br>
> for a potential review.<br>
<br>
</div></div>Yes. FYI the C/C++ RTEMS 4.11 app now gives:<br>
<span class="gmail-"><br>
inlined funcs : 10370<br>
total funcs : 17700<br>
% inline funcs : 58%<br>
</span> total size : 2296354<br>
inline size : 479628<br>
% inline size : 20%<br>
<br>
This level of reduction is more inline with what I expected.<br>
<div><div class="gmail-h5"><br>
> <br>
>>><br>
>>>> I'm not sure whether I might just haven't seen it but is there a<br>
>>>> possibility to distinguish between functions that have been inlined by<br>
>>>> the compiler and ones that have been inlined due to the "inline" keyword<br>
>>>> without looking at every definition?<br>
>>><br>
>>> I am not sure. The DWARF data is complex and detailed and I view this initial<br>
>>> step into the area of using DWARF to perform static analysis of RTEMS<br>
>>> executables as green.<br>
>>><br>
>>> DWARF does provide declaration attributes. I need to review the DWARF data and<br>
>>> standard to determine if we can tell what is declared inline and what has been<br>
>>> inlined. I think it would be good to know.<br>
>>><br>
>>>> Did you try compiling with size optimization? I would expect that the<br>
>>>> compiler would inline far less functions and maybe even ignore some<br>
>>>> "inline" keywords. As far as I know it's more of a hint to the compiler.<br>
>>><br>
>>> Not yet. A complete tool build with those options is a lot of effort and I am<br>
>>> still not comfortable the report is accurate. I think this is something that<br>
>>> should be done at some point. I think it would create an interesting data point.<br>
>>><br>
>>>> I would only worry about functions that are still inlined if size<br>
>>>> optimization is selected. <br>
>>><br>
>>> I think we need to review the functions we currently have tagged as inline. I<br>
>>> think the only way we can do this is with real data.<br>
>>><br>
>>>> That's the case when I tell the compiler to<br>
>>>> make the program as small as possible. In all other cases I want some<br>
>>>> well balanced optimum between speed and size. Inlining small functions<br>
>>>> is OK in that case if you ask me.<br>
>>><br>
>>> How do you define this, ie what is the inline policy we use?<br>
>>> How do you audit this?<br>
> <br>
> Both questions are not simple to answer. It is most likely a case by<br>
> case decision. I think there are roughly two reasons for inlining:<br>
> <br>
> - The code is short enough that it is smaller if it is inlined compared<br>
> to a function call.<br>
> <br>
> - There is some performance reason.<br>
> <br>
> Anything else?<br>
<br>
</div></div>Not for inlining rather for not inlining? I had a discussion with Joel last week<br>
about coverage and he said he had previously reviewed the inlines to reduce the<br>
branch counts because it complicates coverage. I would prefer he talks about<br>
this, it is his area.<br></blockquote><div><br></div><div>Thanks for the lead-in Chris.</div><div><br></div><div>First, there are multiple types of coverage. Statement, Decision coverage,</div><div>MC/DC, and object coverage. Based on various limiting factors which I</div><div>am happy to discuss in more detail if someone is interested, we went </div><div>with object coverage. I will briefly remind everyone of the limitations</div><div>and guidelines we went into this with:</div><div><br></div><div>+ We didn't want instrumented code. Test as you fly, fly as you test.</div><div>Plus there would need to be a way to get information generated by</div><div>the instrumentation off the target.</div><div><br></div><div>+ Without gcc support for identifying sub-expressions in complex</div><div>logical expressions, there is no way to do DC or MCDC. Further,</div><div>we would have had to have a tool to interpret the non-existent</div><div>information and know which entries in the truth tables for every</div><div>expression had to be exercised for DC and MCDC.</div><div><br></div><div>+ gcov's reports are primarily statement coverage. And you can't get</div><div>gcov reports without gcov output from runs which we still are working</div><div>on. Also in terms of DO-178, statement coverage is generally Level C</div><div>type coverage.</div><div><br></div><div>We wanted higher level coverage for RTEMS and thus object</div><div>coverage with emphasis on every generated branch statement being</div><div>needed and exercisable as taken and not taken. This approach</div><div>ensured we didn't have dead code.</div><div><br></div><div>Plus this type of information could easily be obtained from simulator</div><div>traces from Qemu and tsim. covoar was written to combine and</div><div>correlate the information back to assembly and source and generate</div><div>the reports.</div><div><br></div><div>That's what led to the current coverage approach. Chris has been</div><div>working to eliminate the use of command line tools (e.g. nm, objdump,</div><div>etc) in favor of using DWARF directly. </div><div><br></div><div>That's the background of the approach and how we got reports.</div><div><br></div><div>Also remember that RTEMS testing is primarily via the public APIs </div><div>which combined with coverage ensures we actually need all the</div><div>internal code. If you can't reach it from a public API, it is dead.</div><div><br></div><div>But the insights into RTEMS coding practices began when we started</div><div>trying to improve the coverage percentages. Key point:</div><div><br></div><div>IF CODE IS DUPLICATED AT THE SOURCE OR OBJECT LEVEL, IT HAS TO</div><div>BE FULLY TESTED AT EACH INSTANCE OR INSTANTIATION.</div><div><br></div><div>At the source level, sometimes we factored out a common helper method</div><div>to avoid testing the same logic via multiple APIs. This reduced the number</div><div>of test cases needed.</div><div><br></div><div>We looked at a lot of generated assembly. Sometimes we would see</div><div>large methods being inlined multiple times. This would increase the overall</div><div>size of an RTEMS application. But size is not the only impact of inlining.</div><div>If an inlined method has one or more if's in it, then the branch paths</div><div>it includes are introduced EVERYWHERE it is inlined. When we</div><div>had _Thread_Dispatch_enable, I recall it was used > 100 times and</div><div>includes a branch in it. There was a build option to not inline this</div><div>routine to avoid needing to add over 100 test cases.</div><div><br></div><div>There is a delicate code size and complexity dance when deciding</div><div>whether to inline something or not. There isn't an easy or obvous</div><div>answer.</div><div><br></div><div>+ How large is the method? </div><div><br></div><div>+ How many times is it used?</div><div><br></div><div>+ How many branches/loops does it have (e.g. cyclomatic complexity)?</div><div><br></div><div>In general, very small methods with no branches can be inlined in less </div><div>code than the subroutine call takes. So these tend to be a win.</div><div><br></div><div>Over a certain size, the number of times used multiplied by the inlined</div><div>size can have a negative impact on code size. We saw this a lot when</div><div>doing the code coverage improvements.</div><div><br></div><div>If the inlined method has branches, is it possible or cost effective</div><div>to write test cases for the branches introduced at EVERY use?</div><div><br></div><div>I came away believing a few things:</div><div><br></div><div>+ The MISRA rule for using simple, single condition branch logic</div><div>is really good for code coverage analysis. It simplifies it. Statement </div><div>coverage and branch coverage become closer to equivalent. The</div><div>resulting assembly code is also much easier to match to source.</div><div><br></div><div>+ Inlining a method with conditional logic has to be looked at<br></div><div>very carefully. You don't want to inline something and find yourself</div><div>having exploded the required number of test cases. Again, look</div><div>at the number of times a method is used but be cautious about</div><div>conditional expressions in inline methods.</div><div><br></div><div>+ Inlining doesn't always improve performance measurably. It</div><div>can increase code size, code to branch around, etc.</div><div><br></div><div>For a current example of something that is very likely a problem</div><div>from a object code test coverage and code size increase view,</div><div>consider _Thread_Dispatch_enable. It is inlined 41 times. </div><div>Chris and I looked at a place where it was inlined. It was about</div><div>80 bytes. 41*80 ==> 3280 bytes. Based on size along, it would</div><div>save ~3K across the entire RTEMS API object code to not inline it</div><div>at all.</div><div><br></div><div>Then I looked at the cyclomatic complexity of _Thread_Dispatch_enable().</div><div>From Wikipedia: </div><div><br></div><div>Cyclomatic complexity is a software metric, used to indicate the complexity </div><div>of a program. It is a quantitative measure of the number of linearly independent </div><div>paths through a program's source code.<br></div><div><br></div><div>I used <a href="http://www.lizard.ws/">http://www.lizard.ws/</a> [1] to analyse this method. If you take the conditional</div><div>compilation out of the code, the cyclomatic complexity is 4. This means that</div><div>every place this is inlined, it introduces 3 extra paths to test. 41*3 ==> 123.</div><div><br></div><div>My recommendation would be to evaluate the impact of not inlining this</div><div>method at all. The negative impact on testing and code size are pretty high.</div><div><br></div><div>Unfortunately, what I wrote up is just the guidelines. Things change over</div><div>time. An inlined method that is a nice encapsulation when written could</div><div>turn out to be too large to be inlined if 10 other use cases get added.</div><div><br></div><div>I lean to a method can be inlined if it is small and no more than one </div><div>branch statement in assembly. You can be more lenient if used only</div><div>once or twice. But lean to NO branches on an inlined method if the </div><div>number of uses is high.</div><div><br></div><div>Ideally the inlined code is no larger than the code for the call itself</div><div>and there are no branches. But going above that is not a reason to</div><div>not inline. </div><div><br></div><div>I know this was long but there are a lot of trade-offs. We want RTEMS to</div><div>be as small as possible and as testable as possible. We don't have an</div><div>army of people to write test cases. And I don't want to need that.</div><div><br></div><div>Also I have focused on the things that the RTEMS community has control</div><div>over. Chris' tool gives us a broader view and we can likely help improve</div><div>things more broadly.</div><div><br></div><div>[1] You can cut and paste code into this via the web. So pretty easy to</div><div>look at a single method.</div><div><br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div class="gmail-HOEnZb"><div class="gmail-h5"><br>
Chris<br>
______________________________<wbr>_________________<br>
devel mailing list<br>
<a href="mailto:devel@rtems.org">devel@rtems.org</a><br>
<a href="http://lists.rtems.org/mailman/listinfo/devel" rel="noreferrer" target="_blank">http://lists.rtems.org/<wbr>mailman/listinfo/devel</a><br>
</div></div></blockquote></div><br></div></div>