Wael Yehia via llvm-dev
2020-Apr-02 18:24 UTC
[llvm-dev] RFC: dynamic_cast optimization in LTO
<font face="Verdana,Arial,Helvetica,sans-serif" size="2"> <span><div>Hi,<br>There was a mention of optimizing away C++ dynamic_casts in LTO in this presentation: <a href="https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306" target="_blank">https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306</a><br>I couldn't find any discussion on llvm-dev pertaining to this optimization.<br><br>What is the optimization (TL;DR version):<br>The tranformation tries to convert a <font face="Default Monospace,Courier New,Courier,monospace">__dynamic_cast</font> function call, into an address comparison and VFT-like lookup, when the following conditions are met:<br> 1. the destination type is a leaf type, i.e. is never derived from (similar to C++ final semantics) in the entire program.<br> 2. the static type of the expression being casted is a public base (potentially multi-base and never private) class of the destination type.<br><br>Example:<br>Given a the C++ expression:<br><font face="Default Monospace,Courier New,Courier,monospace"> NULL != dynamic_cast<A*>(ptr) // where B* ptr;</font><br>which coming out of clang would look like so:<br><font face="Default Monospace,Courier New,Courier,monospace"> NULL ! = __dynamic_cast(ptr,<br> &_ZTI1B, // typeinfo of B, the static type of ptr.<br> &_ZTI1A, // typeinfo of A, the destination type.<br> hint) // a static hint about the location of the source subobject w.r.t the complete object.</font><br> <br>If the above conditions can be proven to be true, then an equivalent expression is:<br><font face="Default Monospace,Courier New,Courier,monospace"> (destType == dynamicType) where: std::typeinfo *destType = &_ZTI1A;<br> std::typeinfo *dynamicType = ((void**)ptr)[-1];</font><br> <br><br>Detailed description:<br>A C++ dynamic_cast<A*>(ptr) expression can either <br> (1) be folded by the FE into a static_cast, or<br> or (2) converted to a runtime call to <font face="Default Monospace,Courier New,Courier,monospace">__dynamic_cast</font> if the FE does not have enough information (which is the common case for dynamic_cast).<br><br>The crux of the transformation is trying to prove that a type is a leaf. <br>We utilize the <font face="Default Monospace,Courier New,Courier,monospace">!type</font> metadata (<a href="https://llvm.org/docs/TypeMetadata.html)" target="_blank">https://llvm.org/docs/TypeMetadata.html)</a> that is attached to the virtual function table (VFT) globals to answer this question.<br>For each VFT, the <font face="Default Monospace,Courier New,Courier,monospace">!type</font> MD lists the other VFTs that are "compatible" with it. In general, the VFT of a class B is considered to be "compatible" with the VFT of a class A, iff A derives (publicly or privately) from B.<br>This means that the VFT of a leaf class type is never compatible with any other VFT, and we use this fact to decide which type is a leaf.<br>The second fact that we need to prove is the accessibility of the base type in the derived object. <br>Unfortunately we couldn't find a way to compute this information from the existing IR, and had to introduce a custom attribute that the Frontend would place on the <font face="Default Monospace,Courier New,Courier,monospace">__dynamic_cast</font> call. The presence of the attribute implies that the static type (B in our example) is a public base class and never a private base class (in case there are multiple subobjects of the static_type inside the complete object) of the destination type (A in our example). Hence, if the attribute gets deleted by some pass, our transformation will simply do nothing for that <font face="Default Monospace,Courier New,Courier,monospace">__dynamic_cast</font> call.<br><br>There are two issues that I could think of that might cause a problem in our approach:<br> 1. the !type MD gets removed by some pass which will erase the evidence that class types, corresponding to the VFTs that were listed in the MD, are non-leaf.<br> 2. the supposedly leaf class is actually derived from in a shared library, and the transformation would become invalid. <br> I'm hoping this problem is not unique to my situation, and there must be an existing solution to such a scenario. For example, bail out if we know we're linking any shared libaries or if we're producing a shared library.<br><br>Questions:<br>1. Is there interest in adding such an optimization pass to the LTO pipeline?<br>2. We implemented the optimization locally and are interested in upstreaming it. However, from what I read the community prefers that we don't just post a patch and expect it to be reviewed and approved. So this RFC is to get comments on the approach we've taken and whether there's room for improvement (if the approach was correct).<br>Specifically I would appreciate comments from people from the AMD compiler since they are the ones who presented the optimization.<br><br>Thanks.<br></div><div><br></div>Wael Yehia<br>Compiler Development<br>IBM Canada Lab<br></span></font><BR>
Wael Yehia via llvm-dev
2020-Apr-06 15:53 UTC
[llvm-dev] RFC: dynamic_cast optimization in LTO
Quiet Ping (thanks) Hi, There was a mention of optimizing away C++ dynamic_casts in LTO in this presentation: https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306 I couldn't find any discussion on llvm-dev pertaining to this optimization. What is the optimization (TL;DR version): The tranformation tries to convert a __dynamic_cast function call, into an address comparison and VFT-like lookup, when the following conditions are met: 1. the destination type is a leaf type, i.e. is never derived from (similar to C++ final semantics) in the entire program. 2. the static type of the expression being casted is a public base (potentially multi-base and never private) class of the destination type. Example: Given a the C++ expression: NULL != dynamic_cast<A*>(ptr) // where B* ptr; which coming out of clang would look like so: NULL ! = __dynamic_cast(ptr, &_ZTI1B, // typeinfo of B, the static type of ptr. &_ZTI1A, // typeinfo of A, the destination type. hint) // a static hint about the location of the source subobject w.r.t the complete object. If the above conditions can be proven to be true, then an equivalent expression is: (destType == dynamicType) where: std::typeinfo *destType = &_ZTI1A; std::typeinfo *dynamicType = ((void**)ptr)[-1]; Detailed description: A C++ dynamic_cast<A*>(ptr) expression can either (1) be folded by the FE into a static_cast, or or (2) converted to a runtime call to __dynamic_cast if the FE does not have enough information (which is the common case for dynamic_cast). The crux of the transformation is trying to prove that a type is a leaf. We utilize the !type metadata (https://llvm.org/docs/TypeMetadata.html) that is attached to the virtual function table (VFT) globals to answer this question. For each VFT, the !type MD lists the other VFTs that are "compatible" with it. In general, the VFT of a class B is considered to be "compatible" with the VFT of a class A, iff A derives (publicly or privately) from B. This means that the VFT of a leaf class type is never compatible with any other VFT, and we use this fact to decide which type is a leaf. The second fact that we need to prove is the accessibility of the base type in the derived object. Unfortunately we couldn't find a way to compute this information from the existing IR, and had to introduce a custom attribute that the Frontend would place on the __dynamic_cast call. The presence of the attribute implies that the static type (B in our example) is a public base class and never a private base class (in case there are multiple subobjects of the static_type inside the complete object) of the destination type (A in our example). Hence, if the attribute gets deleted by some pass, our transformation will simply do nothing for that __dynamic_cast call. There are two issues that I could think of that might cause a problem in our approach: 1. the !type MD gets removed by some pass which will erase the evidence that class types, corresponding to the VFTs that were listed in the MD, are non-leaf. 2. the supposedly leaf class is actually derived from in a shared library, and the transformation would become invalid. I'm hoping this problem is not unique to my situation, and there must be an existing solution to such a scenario. For example, bail out if we know we're linking any shared libaries or if we're producing a shared library. Questions: 1. Is there interest in adding such an optimization pass to the LTO pipeline? 2. We implemented the optimization locally and are interested in upstreaming it. However, from what I read the community prefers that we don't just post a patch and expect it to be reviewed and approved. So this RFC is to get comments on the approach we've taken and whether there's room for improvement (if the approach was correct). Specifically I would appreciate comments from people from the AMD compiler since they are the ones who presented the optimization. Thanks. Wael Yehia Compiler Development IBM Canada Lab -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20200406/787d22ea/attachment.html>
Teresa Johnson via llvm-dev
2020-Apr-06 16:15 UTC
[llvm-dev] RFC: dynamic_cast optimization in LTO
Hi Wael, Sorry for the slow reply. +Peter who is a good person to comment as well. This sounds interesting, and very related to the analysis needed for WholeProgramDevirt. What kind of gains do you see from the optimization in practice? This is essentially the same type of analysis with some of the same constraints on type metadata etc we need to do for WPD. Is your patch implementing this for regular LTO or Thin LTO or both? You could take a look at WPD to see how it implements this. It handles WPD for pure regular LTO, for pure ThinLTO (the single implementation devirt only), and for a hybrid mode where modules are split and the vtables are all in a regular LTO module. Specifically, on the two issues you mention:> 1. the !type MD gets removed by some pass which will erase the evidencethat class types, corresponding to the VFTs that were listed in the MD, are non-leaf. We also have this constraint for WPD, so it shouldn't be a problem here.> 2. the supposedly leaf class is actually derived from in a sharedlibrary, and the transformation would become invalid.> I'm hoping this problem is not unique to my situation, and there mustbe an existing solution to such a scenario. For example, bail out if we know we're linking any shared libaries or if we're producing a shared library. We have this issue as well for WPD. It is handled a couple of ways. The first is that by default only vtables with hidden LTO visibility are considered. See https://clang.llvm.org/docs/LTOVisibility.html for more info on that. Secondly, I recently added a mechanism to allow refining the LTO visibility to hidden at link time if it is known then that the LTO link is safe from this constraint, leveraging some vcall_visibility metadata added for another whole program vtable related optimization (Dead Virtual Function Elimination). See the discussion on the RFC: http://lists.llvm.org/pipermail/llvm-dev/2019-December/137543.html, which was subsequently implemented upstream with these patches: D71907: [WPD/VFE] Always emit vcall_visibility metadata for -fwhole-program-vtables D71911: [ThinLTO] Summarize vcall_visibility metadata D71913: [LTO/WPD] Enable aggressive WPD under LTO option Thanks, Teresa On Mon, Apr 6, 2020 at 8:54 AM Wael Yehia via llvm-dev < llvm-dev at lists.llvm.org> wrote:> Quiet Ping (thanks) > > > Hi, > There was a mention of optimizing away C++ dynamic_casts in LTO in this > presentation: https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306 > I couldn't find any discussion on llvm-dev pertaining to this optimization. > > What is the optimization (TL;DR version): > The tranformation tries to convert a __dynamic_cast function call, into an > address comparison and VFT-like lookup, when the following conditions are > met: > 1. the destination type is a leaf type, i.e. is never derived from > (similar to C++ final semantics) in the entire program. > 2. the static type of the expression being casted is a public base > (potentially multi-base and never private) class of the destination type. > > Example: > Given a the C++ expression: > NULL != dynamic_cast<A*>(ptr) // where B* ptr; > which coming out of clang would look like so: > NULL ! = __dynamic_cast(ptr, > &_ZTI1B, // typeinfo of B, the static type of > ptr. > &_ZTI1A, // typeinfo of A, the destination type. > hint) // a static hint about the location of > the source subobject w.r.t the complete object. > > If the above conditions can be proven to be true, then an equivalent > expression is: > (destType == dynamicType) where: std::typeinfo *destType = &_ZTI1A; > std::typeinfo *dynamicType > ((void**)ptr)[-1]; > > > Detailed description: > A C++ dynamic_cast<A*>(ptr) expression can either > (1) be folded by the FE into a static_cast, or > or (2) converted to a runtime call to __dynamic_cast if the FE does not > have enough information (which is the common case for dynamic_cast). > > The crux of the transformation is trying to prove that a type is a leaf. > We utilize the !type metadata (https://llvm.org/docs/TypeMetadata.html) > that is attached to the virtual function table (VFT) globals to answer this > question. > For each VFT, the !type MD lists the other VFTs that are "compatible" with > it. In general, the VFT of a class B is considered to be "compatible" with > the VFT of a class A, iff A derives (publicly or privately) from B. > This means that the VFT of a leaf class type is never compatible with any > other VFT, and we use this fact to decide which type is a leaf. > The second fact that we need to prove is the accessibility of the base > type in the derived object. > Unfortunately we couldn't find a way to compute this information from the > existing IR, and had to introduce a custom attribute that the Frontend > would place on the __dynamic_cast call. The presence of the attribute > implies that the static type (B in our example) is a public base class and > never a private base class (in case there are multiple subobjects of the > static_type inside the complete object) of the destination type (A in our > example). Hence, if the attribute gets deleted by some pass, our > transformation will simply do nothing for that __dynamic_cast call. > > There are two issues that I could think of that might cause a problem in > our approach: > 1. the !type MD gets removed by some pass which will erase the evidence > that class types, corresponding to the VFTs that were listed in the MD, are > non-leaf. > 2. the supposedly leaf class is actually derived from in a shared > library, and the transformation would become invalid. > I'm hoping this problem is not unique to my situation, and there must > be an existing solution to such a scenario. For example, bail out if we > know we're linking any shared libaries or if we're producing a shared > library. > > Questions: > 1. Is there interest in adding such an optimization pass to the LTO > pipeline? > 2. We implemented the optimization locally and are interested in > upstreaming it. However, from what I read the community prefers that we > don't just post a patch and expect it to be reviewed and approved. So this > RFC is to get comments on the approach we've taken and whether there's room > for improvement (if the approach was correct). > Specifically I would appreciate comments from people from the AMD compiler > since they are the ones who presented the optimization. > > Thanks. > > Wael Yehia > Compiler Development > IBM Canada Lab > > > _______________________________________________ > LLVM Developers mailing list > llvm-dev at lists.llvm.org > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev >-- Teresa Johnson | Software Engineer | tejohnson at google.com | -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20200406/b333fa74/attachment.html>
Wael Yehia via llvm-dev
2020-Apr-07 16:04 UTC
[llvm-dev] RFC: dynamic_cast optimization in LTO
<font face="Verdana,Arial,Helvetica,sans-serif" size="2"><div>Hi Teresa, thank you for your reply and for the valuable pointers (reading through them now)</div><div><br></div><div>> <font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000"> What kind of gains do you see from the optimization in practice?</font></div><div><font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000">The gains are in the few percent range for benchmarks that heavily use dynamic_cast.</font></div><div><font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000"><br></font></div><div><font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000">> </font><font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000">Is your patch implementing this for regular LTO or Thin LTO or both?</font></div><div>I only did it for regular LTO for now. Did not attempt Thin LTO, mainly because I haven't dabbled in that part of LLVM yet :)</div><div><br></div><br><font size="2" face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" color="#000000"><font color="#990099">-----Teresa Johnson <<a href="mailto:tejohnson@google.com" target="_blank">tejohnson@google.com</a>> wrote: -----</font><div class="iNotesHistory" style="padding-left:5px;"><div style="padding-right:0px;padding-left:5px;border-left:solid black 2px;">To: Wael Yehia <<a href="mailto:wmyehia2001@yahoo.com" target="_blank">wmyehia2001@yahoo.com</a>>, Peter Collingbourne <<a href="mailto:peter@pcc.me.uk" target="_blank">peter@pcc.me.uk</a>><br>From: Teresa Johnson <<a href="mailto:tejohnson@google.com" target="_blank">tejohnson@google.com</a>><br>Date: 04/06/2020 12:15PM<br>Cc: llvm-dev <<a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a>>, Wael Yehia <<a href="mailto:wyehia@ca.ibm.com" target="_blank">wyehia@ca.ibm.com</a>><br>Subject: [EXTERNAL] Re: [llvm-dev] RFC: dynamic_cast optimization in LTO<br><br><div dir="ltr">Hi Wael,<div><br></div><div>Sorry for the slow reply. +Peter who is a good person to comment as well.</div><div><br></div><div>This sounds interesting, and very related to the analysis needed for WholeProgramDevirt. What kind of gains do you see from the optimization in practice?</div><div><br></div><div>This is essentially the same type of analysis with some of the same constraints on type metadata etc we need to do for WPD. Is your patch implementing this for regular LTO or Thin LTO or both? You could take a look at WPD to see how it implements this. It handles WPD for pure regular LTO, for pure ThinLTO (the single implementation devirt only), and for a hybrid mode where modules are split and the vtables are all in a regular LTO module.</div><div><br></div><div>Specifically, on the two issues you mention:</div><div><br></div><div><span style="font-family:Verdana,Arial,Helvetica,sans-serif">> 1. the !type MD gets removed by some pass which will erase the evidence that class types, corresponding to the VFTs that were listed in the MD, are non-leaf.</span></div><div><br></div><div>We also have this constraint for WPD, so it shouldn't be a problem here.</div><div><br><span style="font-family:Verdana,Arial,Helvetica,sans-serif">> 2. the supposedly leaf class is actually derived from in a shared library, and the transformation would become invalid.</span><br style="font-family:Verdana,Arial,Helvetica,sans-serif"><span style="font-family:Verdana,Arial,Helvetica,sans-serif">> I'm hoping this problem is not unique to my situation, and there must be an existing solution to such a scenario. For example, bail out if we know we're linking any shared libaries or if we're producing a shared library.</span><br></div><div><span style="font-family:Verdana,Arial,Helvetica,sans-serif"><br></span></div><div><span style="font-family:Verdana,Arial,Helvetica,sans-serif">We have this issue as well for WPD. It is handled a couple of ways. The first is that by default only vtables with hidden LTO visibility are considered. See </span><a href="https://clang.llvm.org/docs/LTOVisibility.html" target="_blank">https://clang.llvm.org/docs/LTOVisibility.html</a> for more info on that. Secondly, I recently added a mechanism to allow refining the LTO visibility to hidden at link time if it is known then that the LTO link is safe from this constraint, leveraging some vcall_visibility metadata added for another whole program vtable related optimization (Dead Virtual Function Elimination). See the discussion on the RFC: <a href="http://lists.llvm.org/pipermail/llvm-dev/2019-December/137543.html" target="_blank">http://lists.llvm.org/pipermail/llvm-dev/2019-December/137543.html</a>, which was subsequently implemented upstream with these patches:</div><div><div>D71907: [WPD/VFE] Always emit vcall_visibility metadata for -fwhole-program-vtables<br></div><div>D71911: [ThinLTO] Summarize vcall_visibility metadata<br></div><div>D71913: [LTO/WPD] Enable aggressive WPD under LTO option</div></div><div><br></div><div>Thanks,</div><div>Teresa</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Apr 6, 2020 at 8:54 AM Wael Yehia via llvm-dev <<a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a>> wrote:<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><div style="font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px"><div></div> <div dir="ltr">Quiet Ping (thanks)</div><div dir="ltr"><br></div><div dir="ltr"><div><br>Hi,<br>There was a mention of optimizing away C++ dynamic_casts in LTO in this presentation: <a href="https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306" target="_blank">https://www.youtube.com/watch?v=Fd3afoM3UOE&t=1306</a><br>I couldn't find any discussion on llvm-dev pertaining to this optimization.<br><br>What is the optimization (TL;DR version):<br>The tranformation tries to convert a __dynamic_cast function call, into an address comparison and VFT-like lookup, when the following conditions are met:<br> 1. the destination type is a leaf type, i.e. is never derived from (similar to C++ final semantics) in the entire program.<br> 2. the static type of the expression being casted is a public base (potentially multi-base and never private) class of the destination type.<br><br>Example:<br>Given a the C++ expression:<br> NULL != dynamic_cast<A*>(ptr) // where B* ptr;<br>which coming out of clang would look like so:<br> NULL ! = __dynamic_cast(ptr,<br> &_ZTI1B, // typeinfo of B, the static type of ptr.<br> &_ZTI1A, // typeinfo of A, the destination type.<br> hint) // a static hint about the location of the source subobject w.r.t the complete object.<br> <br>If the above conditions can be proven to be true, then an equivalent expression is:<br> (destType == dynamicType) where: std::typeinfo *destType = &_ZTI1A;<br> std::typeinfo *dynamicType = ((void**)ptr)[-1];<br> <br><br>Detailed description:<br>A C++ dynamic_cast<A*>(ptr) expression can either<br> (1) be folded by the FE into a static_cast, or<br> or (2) converted to a runtime call to __dynamic_cast if the FE does not have enough information (which is the common case for dynamic_cast).<br><br>The crux of the transformation is trying to prove that a type is a leaf.<br>We utilize the !type metadata (<a href="https://llvm.org/docs/TypeMetadata.html" target="_blank">https://llvm.org/docs/TypeMetadata.html</a>) that is attached to the virtual function table (VFT) globals to answer this question.<br>For each VFT, the !type MD lists the other VFTs that are "compatible" with it. In general, the VFT of a class B is considered to be "compatible" with the VFT of a class A, iff A derives (publicly or privately) from B.<br>This means that the VFT of a leaf class type is never compatible with any other VFT, and we use this fact to decide which type is a leaf.<br>The second fact that we need to prove is the accessibility of the base type in the derived object.<br>Unfortunately we couldn't find a way to compute this information from the existing IR, and had to introduce a custom attribute that the Frontend would place on the __dynamic_cast call. The presence of the attribute implies that the static type (B in our example) is a public base class and never a private base class (in case there are multiple subobjects of the static_type inside the complete object) of the destination type (A in our example). Hence, if the attribute gets deleted by some pass, our transformation will simply do nothing for that __dynamic_cast call.<br><br>There are two issues that I could think of that might cause a problem in our approach:<br> 1. the !type MD gets removed by some pass which will erase the evidence that class types, corresponding to the VFTs that were listed in the MD, are non-leaf.<br> 2. the supposedly leaf class is actually derived from in a shared library, and the transformation would become invalid.<br> I'm hoping this problem is not unique to my situation, and there must be an existing solution to such a scenario. For example, bail out if we know we're linking any shared libaries or if we're producing a shared library.<br><br>Questions:<br>1. Is there interest in adding such an optimization pass to the LTO pipeline?<br>2. We implemented the optimization locally and are interested in upstreaming it. However, from what I read the community prefers that we don't just post a patch and expect it to be reviewed and approved. So this RFC is to get comments on the approach we've taken and whether there's room for improvement (if the approach was correct).<br>Specifically I would appreciate comments from people from the AMD compiler since they are the ones who presented the optimization.<br><br>Thanks.<br><br>Wael Yehia<br>Compiler Development<br>IBM Canada Lab<br><br></div><div><br></div></div></div></div>_______________________________________________<br>LLVM Developers mailing list<br><a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a><br><a href="https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev" rel="noreferrer" target="_blank">https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev</a><br></blockquote></div><br clear="all"><div><br></div>-- <br><div dir="ltr"><div dir="ltr"><div><span style="font-family:Times;font-size:medium"><table cellspacing="0" cellpadding="0"><tbody><tr style="font-family: sans-serif; font-size: small;"><font color="#555555"></font><td style="border-top:2px solid rgb(213,15,37)">Teresa Johnson |</td><td style="border-top:2px solid rgb(51,105,232)"> Software Engineer |</td><td style="border-top:2px solid rgb(0,153,57)"> <a href="mailto:tejohnson@google.com" target="_blank">tejohnson@google.com</a> |</td><td style="border-top:2px solid rgb(238,178,17)"><br></td></tr></tbody></table></span></div></div></div></div></div></font></font><BR>