microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
10.05k stars 1.48k forks source link

STL: Investigate using the Optional attribute to simplify visualizers #319

Open StephanTLavavej opened 4 years ago

StephanTLavavej commented 4 years ago

See: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019#optional-attribute

The visualizers in stl.natvis predate this Optional attribute. I believe that it could be used to simplify some of the machinery there.

CaseyCarter commented 4 years ago

I've done some investigation of this while resolving #315, with amazing results. For reference, the "old" atomic visualizer looks like:

<Type Name="std::atomic&lt;*&gt;">
  <DisplayString>{*($T1*)&amp;_My_val}</DisplayString>
  <Expand>
    <Item Name="[value]">*($T1*)&amp;_My_val</Item>
  </Expand>
</Type>

After the atomic refactoring from VS 16.1, we need:

<Type Name="std::atomic&lt;*&gt;">
  <DisplayString>{*($T1*)&amp;_Storage}</DisplayString>
  <Expand>
    <Item Name="[value]">*($T1*)&amp;_Storage</Item>
  </Expand>
</Type>

(Yes, the cast is still needed. There's always a member _Storage, but it's sometimes an object of type $T1 (the first template argument) and sometimes an object whose first subobject has type $T1.)

In the past, we'd keep both visualizers (it's nice to be able to visualize objects in "old" programs) and mark the "old" visualizer with a lower priority:

<Type Name="std::atomic&lt;*&gt;" Priority="MediumLow">
  <DisplayString>{*($T1*)&amp;_My_val}</DisplayString>
  <Expand>
    <Item Name="[value]">*($T1*)&amp;_My_val</Item>
  </Expand>
</Type>

<Type Name="std::atomic&lt;*&gt;">
  <DisplayString>{*($T1*)&amp;_Storage}</DisplayString>
  <Expand>
    <Item Name="[value]">*($T1*)&amp;_Storage</Item>
  </Expand>
</Type>

which works with the caveats that (1) the Debugger logs a warning about the "new" visualizer not working when debugging an "old" program, and (2) there's twice as much code to maintain. It's straightforward to factor these two implementations together with Optional attributes:

<Type Name="std::atomic&lt;*&gt;">
  <DisplayString Optional="true">{*($T1*)&amp;_My_val}</DisplayString>
  <DisplayString Optional="true">{*($T1*)&amp;_Storage}</DisplayString>
  <Expand>
    <Item Optional="true" Name="[value]">*($T1*)&amp;_My_val</Item>
    <Item Optional="true" Name="[value]">*($T1*)&amp;_Storage</Item>
  </Expand>
</Type>

which works as expected, nicely eliminates the warning message, but sadly still requires roughly twice as much code despite being mashed into a single Type element.

Enter Intrinsic functions! The Debugger allows us to define simple named parametric expressions using Intrinsic elements in natvis files. If we encapsulate knowledge of the actual layout into Optional Intrinsic functions, then we can reduce the duplication to only those Intrinsics and use a single presentation a la:

<Type Name="std::atomic&lt;*&gt;">
  <!-- VS 2019 16.0 or earlier -->
  <Intrinsic Optional="true" Name="value" Expression="*($T1*)&amp;_My_val"/>
  <!-- VS 2019 16.1 or later -->
  <Intrinsic Optional="true" Name="value" Expression="*($T1*)&amp;_Storage"/>
  <DisplayString>{value()}</DisplayString>
  <Expand>
    <Item Name="[value]">value()</Item>
  </Expand>
</Type>

While this notably doesn't reduce overall line count in this instance it does nicely separate representation from presentation. For more complicated visualizers - and there are many more complicated than this in STL.natvis - the code size reduction from combining our multiple visualizers will be significant.