Forums

Bug in latest IAR MSP430 compiler optimization???

Started by larwe April 27, 2008
In message 
<cd5e2c7a-497e-4f23-abfa-4c2a5cc83b2b@m3g2000hsc.googlegroups.com>, 
ian.okey@gmail.com writes
>On Apr 27, 1:52 pm, larwe <zwsdot...@gmail.com> wrote: >> On Apr 27, 8:33 am, Chris H <ch...@phaedsys.org> wrote: >> >> > >I haven't yet groveled through the assembly output to see exactly what >> > >the difference between the two output flavors is. But this is such an >> > >astoundingly show-stopping bug I'm appalled it escaped. >> >> > What did IAR support say about it when you told them? >> >> Kickstart has no support. Of course, last time I was working with a >> bought version of their compiler, and found a [different] bug, the >> answer was "buy the latest upgrade". > >I have found two bugs, one in each of the last two releases of their >MSP430X compiler and on each occasion I have had a new version of the >compiler sent to me with the bug fixed. These bugs then appear on the >release notes for the next version published on the web-site. Where >the problem was a code generation error then a short compilable >example together with my compiler listing was all that was required to >convince IAR that there was a problem and the corrected tools appeared >a few days later. > >If the bug has been found and corrected in a more up to date version >of the tools then the answer to get a legal (as defined in the >vendor's licence agreement) copy of the newer toolset is the *right* >answer. > >Ian
It is interesting that Lewin apparently NEVER gets this sort of service out of any compiler or silicon company. He has ranted at me several times when I say that the sort of help IAR have given you, as described above, is the norm. Actually it is the norm for most of the tools vendors. Perhaps the problem is more with Leiwn's approach than anything else. After al his first action on finding a bug was to publicly attack IAR before even asking them for a response or a fix. I note that elsewhere in this thread an IAR support person has asked Lewin for an example code to look at. It's not because he has made a fuss here either. It is because this is the fist time they have been made aware of it. -- \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
On 29 Apr, 16:57, larwe <zwsdot...@gmail.com> wrote:

> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> > wrote: > > > Are you going to give us a clue as to what this bug is? Until you post > > something more concrete, this is merely FUD. But if it is a real bug, > > I don't have the code here at work, but the gist of it is that I have > a subroutine which does something like this: > > [...] > > As shown, built in maximum speed optimization mode, the code fails. > The reason appears to be that on the second iteration through the i- > loop, the value for "src" (which was in a register) got corrupted, > probably during call_another_func().
Thanks for the code, I managed to add some parts to the code to get it to compile. I can confirm that you indeed found a problem in the code generator. The cause was much more complex than you guessed, it contained a problem with register allocation, in the presence of post- inc:s and unrolling, where "tmpsrc" and "src" variables were placed in the same register, which clearly is wrong. It has been assigned bug id EW20095, and it will be fixed in the V4.11A release which we are in the process of finishing. It will be available relatively soon. Thanks for finding this problem! -- Anders Lindgren, IAR Systems -- Disclaimer: Opinions expressed in this posting are strictly my own and not necessarily those of my employer.
larwe wrote:
> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> > wrote: > >> Are you going to give us a clue as to what this bug is? Until you post >> something more concrete, this is merely FUD. But if it is a real bug, > > I don't have the code here at work, but the gist of it is that I have > a subroutine which does something like this: > > void mysub(int a, int b) > { > const unsigned char *src, *tmpsrc; > int i,j; > > src = pointer_to_const_data_struct; > > for (i=0;i<8;i++) { > tmpsrc = src; // BAD LINE > for (j=0;j<3;j++) { > call_another_func(*(tmpsrc++),0); > } // j-loop > src += 3; > } // i-loop > } // mysub > > [the reason for doing the arithmetic this way is because the code will > have to support some more complicated cases in future - I realize > there are other ways of doing the same thing]. > > As shown, built in maximum speed optimization mode, the code fails. > The reason appears to be that on the second iteration through the i- > loop, the value for "src" (which was in a register) got corrupted, > probably during call_another_func(). Either of the following will fix > the problem: > > - move the src declaration so it becomes a global variable, or > - turn off speed optimization > > I can post the whole project somewhere if someone actually wants to > look in detail. It's going to be public domain code anyway. >
Any chance of posting the generated assembly for this? It should not be hard to follow, to see where the problem lies, if call_another_func is declared extern (to avoid it being inlined by the optimiser).
In message <C6Kdnd_ktoaTDorVnZ2dnUVZ8uOdnZ2d@lyse.net>, David Brown 
<david.brown@hesbynett.removethisbit.no> writes
>larwe wrote: >> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> >> wrote: >> >>> Are you going to give us a clue as to what this bug is? Until you >>>post >>> something more concrete, this is merely FUD. But if it is a real bug, >> I don't have the code here at work, but the gist of it is that I >>have >> a subroutine which does something like this: >> void mysub(int a, int b) >> { >> const unsigned char *src, *tmpsrc; >> int i,j; >> src = pointer_to_const_data_struct; >> for (i=0;i<8;i++) { >> tmpsrc = src; // BAD LINE >> for (j=0;j<3;j++) { >> call_another_func(*(tmpsrc++),0); >> } // j-loop >> src += 3; >> } // i-loop >> } // mysub >> [the reason for doing the arithmetic this way is because the code >>will >> have to support some more complicated cases in future - I realize >> there are other ways of doing the same thing]. >> As shown, built in maximum speed optimization mode, the code fails. >> The reason appears to be that on the second iteration through the i- >> loop, the value for "src" (which was in a register) got corrupted, >> probably during call_another_func(). Either of the following will fix >> the problem: >> - move the src declaration so it becomes a global variable, or >> - turn off speed optimization >> I can post the whole project somewhere if someone actually wants to >> look in detail. It's going to be public domain code anyway. >> > >Any chance of posting the generated assembly for this? It should not >be hard to follow, to see where the problem lies, if call_another_func >is declared extern (to avoid it being inlined by the optimiser).
The problem has been found see below:- In message <3b2e11dc-393f-4eef-8790-8ea6d4245d02@d1g2000hsg.googlegroups.com>, "andlind@gmail.com" <andlind@gmail.com> writes
>Thanks for the code, I managed to add some parts to the code to get it >to compile. I can confirm that you indeed found a problem in the code >generator. The cause was much more complex than you guessed, it >contained a problem with register allocation, in the presence of post- >inc:s and unrolling, where "tmpsrc" and "src" variables were placed in >the same register, which clearly is wrong. > >It has been assigned bug id EW20095, and it will be fixed in the >V4.11A release which we are in the process of finishing. It will be >available relatively soon. >Thanks for finding this problem! > -- Anders Lindgren, IAR Systems >Disclaimer: Opinions expressed in this posting are strictly my own and >not necessarily those of my employer.
As Anders says it is not as simple as you might think. To fix something like this is more than a simple patch. Well the code may end up being a simple patch but the rigorous checking and cross checking required for side effects is substantial before you do full regression testing. The tests compiler companies do are quite extensive. Usually one or both of Plum-Hall or Perennial plus several sets of in house test suites for things like maths, assembler etc One thing all the compiler writers I know say is the problem is not in finding the bug but how to fix it with not putting more in. The level of testing required afterwards is out of the reach of most programmers. -- \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
"Chris H" <chris@phaedsys.org> wrote in message 
news:3iDru1EA7BGIFAuh@phaedsys.demon.co.uk...
> In message <C6Kdnd_ktoaTDorVnZ2dnUVZ8uOdnZ2d@lyse.net>, David Brown > <david.brown@hesbynett.removethisbit.no> writes >>larwe wrote: >>> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> >>> wrote: >>> >>>> Are you going to give us a clue as to what this bug is? Until you post >>>> something more concrete, this is merely FUD. But if it is a real bug, >>> I don't have the code here at work, but the gist of it is that I have >>> a subroutine which does something like this: >>> void mysub(int a, int b) >>> { >>> const unsigned char *src, *tmpsrc; >>> int i,j; >>> src = pointer_to_const_data_struct; >>> for (i=0;i<8;i++) { >>> tmpsrc = src; // BAD LINE >>> for (j=0;j<3;j++) { >>> call_another_func(*(tmpsrc++),0); >>> } // j-loop >>> src += 3; >>> } // i-loop >>> } // mysub >>> [the reason for doing the arithmetic this way is because the code will >>> have to support some more complicated cases in future - I realize >>> there are other ways of doing the same thing]. >>> As shown, built in maximum speed optimization mode, the code fails. >>> The reason appears to be that on the second iteration through the i- >>> loop, the value for "src" (which was in a register) got corrupted, >>> probably during call_another_func(). Either of the following will fix >>> the problem: >>> - move the src declaration so it becomes a global variable, or >>> - turn off speed optimization >>> I can post the whole project somewhere if someone actually wants to >>> look in detail. It's going to be public domain code anyway. >>> >> >>Any chance of posting the generated assembly for this? It should not be >>hard to follow, to see where the problem lies, if call_another_func is >>declared extern (to avoid it being inlined by the optimiser). > > The problem has been found see below:- > > In message > <3b2e11dc-393f-4eef-8790-8ea6d4245d02@d1g2000hsg.googlegroups.com>, > "andlind@gmail.com" <andlind@gmail.com> writes >>Thanks for the code, I managed to add some parts to the code to get it >>to compile. I can confirm that you indeed found a problem in the code >>generator. The cause was much more complex than you guessed, it >>contained a problem with register allocation, in the presence of post- >>inc:s and unrolling, where "tmpsrc" and "src" variables were placed in >>the same register, which clearly is wrong. >> >>It has been assigned bug id EW20095, and it will be fixed in the >>V4.11A release which we are in the process of finishing. It will be >>available relatively soon. >>Thanks for finding this problem! >> -- Anders Lindgren, IAR Systems >>Disclaimer: Opinions expressed in this posting are strictly my own and >>not necessarily those of my employer. > > As Anders says it is not as simple as you might think. To fix something > like this is more than a simple patch. Well the code may end up being a > simple patch but the rigorous checking and cross checking required for > side effects is substantial before you do full regression testing. > > The tests compiler companies do are quite extensive. Usually one or both > of Plum-Hall or Perennial plus several sets of in house test suites for > things like maths, assembler etc > > One thing all the compiler writers I know say is the problem is not in > finding the bug but how to fix it with not putting more in. The level of > testing required afterwards is out of the reach of most programmers.
So, with this level of testing how come something so serious got though the release process? Yes, I know, these things happen and another couple of tests are added to the validation suite. Peter
In message <RcWRj.98190$4f4.36105@newsfe6-win.ntli.net>, Peter Dickerson 
<firstname.lastname@REMOVE.tesco.net> writes
>"Chris H" <chris@phaedsys.org> wrote in message >news:3iDru1EA7BGIFAuh@phaedsys.demon.co.uk... >> In message <C6Kdnd_ktoaTDorVnZ2dnUVZ8uOdnZ2d@lyse.net>, David Brown >> <david.brown@hesbynett.removethisbit.no> writes >>>larwe wrote: >>>> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> >>>> wrote: >>>> >>>>> Are you going to give us a clue as to what this bug is? Until you post >>>>> something more concrete, this is merely FUD. But if it is a real bug, >>>> I don't have the code here at work, but the gist of it is that I have >>>> a subroutine which does something like this: >>>> void mysub(int a, int b) >>>> { >>>> const unsigned char *src, *tmpsrc; >>>> int i,j; >>>> src = pointer_to_const_data_struct; >>>> for (i=0;i<8;i++) { >>>> tmpsrc = src; // BAD LINE >>>> for (j=0;j<3;j++) { >>>> call_another_func(*(tmpsrc++),0); >>>> } // j-loop >>>> src += 3; >>>> } // i-loop >>>> } // mysub >>>> [the reason for doing the arithmetic this way is because the code will >>>> have to support some more complicated cases in future - I realize >>>> there are other ways of doing the same thing]. >>>> As shown, built in maximum speed optimization mode, the code fails. >>>> The reason appears to be that on the second iteration through the i- >>>> loop, the value for "src" (which was in a register) got corrupted, >>>> probably during call_another_func(). Either of the following will fix >>>> the problem: >>>> - move the src declaration so it becomes a global variable, or >>>> - turn off speed optimization >>>> I can post the whole project somewhere if someone actually wants to >>>> look in detail. It's going to be public domain code anyway. >>>> >>> >>>Any chance of posting the generated assembly for this? It should not be >>>hard to follow, to see where the problem lies, if call_another_func is >>>declared extern (to avoid it being inlined by the optimiser). >> >> The problem has been found see below:- >> >> In message >> <3b2e11dc-393f-4eef-8790-8ea6d4245d02@d1g2000hsg.googlegroups.com>, >> "andlind@gmail.com" <andlind@gmail.com> writes >>>Thanks for the code, I managed to add some parts to the code to get it >>>to compile. I can confirm that you indeed found a problem in the code >>>generator. The cause was much more complex than you guessed, it >>>contained a problem with register allocation, in the presence of post- >>>inc:s and unrolling, where "tmpsrc" and "src" variables were placed in >>>the same register, which clearly is wrong. >>> >>>It has been assigned bug id EW20095, and it will be fixed in the >>>V4.11A release which we are in the process of finishing. It will be >>>available relatively soon. >>>Thanks for finding this problem! >>> -- Anders Lindgren, IAR Systems >>>Disclaimer: Opinions expressed in this posting are strictly my own and >>>not necessarily those of my employer. >> >> As Anders says it is not as simple as you might think. To fix something >> like this is more than a simple patch. Well the code may end up being a >> simple patch but the rigorous checking and cross checking required for >> side effects is substantial before you do full regression testing. >> >> The tests compiler companies do are quite extensive. Usually one or both >> of Plum-Hall or Perennial plus several sets of in house test suites for >> things like maths, assembler etc >> >> One thing all the compiler writers I know say is the problem is not in >> finding the bug but how to fix it with not putting more in. The level of >> testing required afterwards is out of the reach of most programmers. > >So, with this level of testing how come something so serious got though the >release process?
Because with the number of variations and permutations both of the language and the optimisations it is always possible to miss things. It is interesting that "something so serious" and a "show stopping" bug has only been found by one person. No one else has apparently reported it yet. So clearly it is not something everyone is doing or show stopping despite this compiler being used by many. Also as you can see from Anders comment it was quite complex to do with register allocation when combined with post-incs and rollup. Not trivial at all even if the symptom appeared to be.
> Yes, I know, these things happen and another couple of >tests are added to the validation suite.
I knew you didn't understand. A basic OTPF Sets suite is about 70,000 tests. IAR use an industry standard test suite like this (that you don't edit or add a couple of tests to) and FOUR other extensive test systems they do edit. Quite apart from test compilations on a large set of benchmark programs. Most of which are large applications not benchmarks as such. Compared to many other compilers IAR are very good at testing compilers and fixing any bugs. So in short it was not an obvious problem. They found the cause within 24 hours of being notified They are rolling out an update with the fix in it Any other compiler vendors that swift to respond on eval compilers with "no support" according to Lewin. The problem is? All I can see is Lewin started an unjustified rant against IAR because he doesn't like them. Had he reported the bug to them in the first place this would have been a non event. We could look at the bug- fix- test- release cycle of other compilers to compare? I was reading about a monumental bug in the way *some* GCC compilers handled "volatile". Now that is a show stopper. So lets get things into perspective. -- \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
"Chris H" <chris@phaedsys.org> wrote in message 
news:l0kPbsKdPEGIFA8X@phaedsys.demon.co.uk...
> In message <RcWRj.98190$4f4.36105@newsfe6-win.ntli.net>, Peter Dickerson > <firstname.lastname@REMOVE.tesco.net> writes >>"Chris H" <chris@phaedsys.org> wrote in message >>news:3iDru1EA7BGIFAuh@phaedsys.demon.co.uk... >>> In message <C6Kdnd_ktoaTDorVnZ2dnUVZ8uOdnZ2d@lyse.net>, David Brown >>> <david.brown@hesbynett.removethisbit.no> writes >>>>larwe wrote: >>>>> On Apr 29, 4:04 am, David Brown <da...@westcontrol.removethisbit.com> >>>>> wrote: >>>>> >>>>>> Are you going to give us a clue as to what this bug is? Until you >>>>>> post >>>>>> something more concrete, this is merely FUD. But if it is a real >>>>>> bug, >>>>> I don't have the code here at work, but the gist of it is that I have >>>>> a subroutine which does something like this: >>>>> void mysub(int a, int b) >>>>> { >>>>> const unsigned char *src, *tmpsrc; >>>>> int i,j; >>>>> src = pointer_to_const_data_struct; >>>>> for (i=0;i<8;i++) { >>>>> tmpsrc = src; // BAD LINE >>>>> for (j=0;j<3;j++) { >>>>> call_another_func(*(tmpsrc++),0); >>>>> } // j-loop >>>>> src += 3; >>>>> } // i-loop >>>>> } // mysub >>>>> [the reason for doing the arithmetic this way is because the code >>>>> will >>>>> have to support some more complicated cases in future - I realize >>>>> there are other ways of doing the same thing]. >>>>> As shown, built in maximum speed optimization mode, the code fails. >>>>> The reason appears to be that on the second iteration through the i- >>>>> loop, the value for "src" (which was in a register) got corrupted, >>>>> probably during call_another_func(). Either of the following will fix >>>>> the problem: >>>>> - move the src declaration so it becomes a global variable, or >>>>> - turn off speed optimization >>>>> I can post the whole project somewhere if someone actually wants to >>>>> look in detail. It's going to be public domain code anyway. >>>>> >>>> >>>>Any chance of posting the generated assembly for this? It should not be >>>>hard to follow, to see where the problem lies, if call_another_func is >>>>declared extern (to avoid it being inlined by the optimiser). >>> >>> The problem has been found see below:- >>> >>> In message >>> <3b2e11dc-393f-4eef-8790-8ea6d4245d02@d1g2000hsg.googlegroups.com>, >>> "andlind@gmail.com" <andlind@gmail.com> writes >>>>Thanks for the code, I managed to add some parts to the code to get it >>>>to compile. I can confirm that you indeed found a problem in the code >>>>generator. The cause was much more complex than you guessed, it >>>>contained a problem with register allocation, in the presence of post- >>>>inc:s and unrolling, where "tmpsrc" and "src" variables were placed in >>>>the same register, which clearly is wrong. >>>> >>>>It has been assigned bug id EW20095, and it will be fixed in the >>>>V4.11A release which we are in the process of finishing. It will be >>>>available relatively soon. >>>>Thanks for finding this problem! >>>> -- Anders Lindgren, IAR Systems >>>>Disclaimer: Opinions expressed in this posting are strictly my own and >>>>not necessarily those of my employer. >>> >>> As Anders says it is not as simple as you might think. To fix >>> something >>> like this is more than a simple patch. Well the code may end up being >>> a >>> simple patch but the rigorous checking and cross checking required for >>> side effects is substantial before you do full regression testing. >>> >>> The tests compiler companies do are quite extensive. Usually one or both >>> of Plum-Hall or Perennial plus several sets of in house test suites for >>> things like maths, assembler etc >>> >>> One thing all the compiler writers I know say is the problem is not in >>> finding the bug but how to fix it with not putting more in. The level of >>> testing required afterwards is out of the reach of most programmers. >> >>So, with this level of testing how come something so serious got though >>the >>release process? > > Because with the number of variations and permutations both of the > language and the optimisations it is always possible to miss things. It > is interesting that "something so serious" and a "show stopping" bug has > only been found by one person.
If I were evaluating a compiler and it failed in some bad way then I'd email the developers with example code then bin the tools or get my money back if any had exchanged hands by then.
> No one else has apparently reported it yet. So clearly it is not something > everyone is doing or show stopping despite this compiler being used by > many.
You mean one person reported it here. Not the same thing. If I can't trust the tools then that's show stopping but others may be happy that it has an easy workaround or avoidance stratagy.
> Also as you can see from Anders comment it was quite complex to do with > register allocation when combined with post-incs and rollup. Not trivial > at all even if the symptom appeared to be.
I am very happy that Anders put his hand up and said t'was me. Respect for that. I think it was loop unrolling he mentioned. Surely register allocation and even loop unrolling are standard bread-and-butter aspects of compilers. No they are not trivial but they are standard fare. Chris, you're the one who says how far behind gcc is V commercial compilers.
>> Yes, I know, these things happen and another couple of >>tests are added to the validation suite. > > I knew you didn't understand. A basic OTPF Sets suite is about 70,000 > tests. IAR use an industry standard test suite like this (that you don't > edit or add a couple of tests to) and FOUR other extensive test systems > they do edit. Quite apart from test compilations on a large set of > benchmark programs. Most of which are large applications not benchmarks as > such.
No, you think you knew that I didn't understand. In fact I've have had reason to communicate with the IAR compiler team in the past (for M16C62). It was some time ago and the outcome after the response from them was to bin the eval disk and avoid IAR. As I say, it was some time ago, maybe 2000, but they had been in business long enough by then to have known better. Perhaps now they do.
> Compared to many other compilers IAR are very good at testing compilers > and fixing any bugs.
I would still prefer them to avoid releasing them...
> So in short it was not an obvious problem. > They found the cause within 24 hours of being notified > They are rolling out an update with the fix in it
Good. So they're learning.
> Any other compiler vendors that swift to respond on eval compilers with > "no support" according to Lewin. > > The problem is? > > All I can see is Lewin started an unjustified rant against IAR because he > doesn't like them. Had he reported the bug to them in the first place > this would have been a non event.
Perhaps. As I said, I would have reported the bug and gone from there. However, if others on this group are using the same compiler and experiencing 'problems' then it might help them.
> We could look at the bug- fix- test- release cycle of other compilers to > compare? > > I was reading about a monumental bug in the way *some* GCC compilers > handled "volatile". Now that is a show stopper.
Have you got a link, because I'd certainly like to know about that. volatile is a bit of a can of worms for any compiler because what is required for standards conformance and what is required for customer acceptance in the embedded field are somewhat different.
> So lets get things into perspective.
Yes, lets. peter
Peter Dickerson wrote:

> If I were evaluating a compiler and it failed in some bad way then I'd email > the developers with example code then bin the tools or get my money back if > any had exchanged hands by then.
You'll end up binning them all in the end- there isn't a serious program in the world without some bug waiting in the depths. And you won't get your money back. Read the small print.
In message <fv9ufu$cf$2@aioe.org>, sprocket <jas@spam.cop.uk> writes
>Peter Dickerson wrote: > >> If I were evaluating a compiler and it failed in some bad way then >>I'd email the developers with example code then bin the tools or get >>my money back if any had exchanged hands by then. > >You'll end up binning them all in the end- there isn't a serious >program in the world without some bug waiting in the depths. And you >won't get your money back. Read the small print.
I am glad some one else said that. :-) There are no compilers that are perfect. Some people are just determined to find fault and blow it up out of all proportion. They still find fault even after a 24 hour response (despite the fault was not reported to IAR) on a compiler that "has no support" -- \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
"sprocket" <jas@spam.cop.uk> wrote in message news:fv9ufu$cf$2@aioe.org...
> Peter Dickerson wrote: > >> If I were evaluating a compiler and it failed in some bad way then I'd >> email the developers with example code then bin the tools or get my money >> back if any had exchanged hands by then. > > You'll end up binning them all in the end- there isn't a serious program > in the world without some bug waiting in the depths. And you won't get > your money back. Read the small print.
Note that I said evaluating with some time limit. Just because software ships with bugs doesn't mean we have to accept them. A product still has to be fit for purpose. Peter