Open StephanTLavavej opened 5 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<*>">
<DisplayString>{*($T1*)&_My_val}</DisplayString>
<Expand>
<Item Name="[value]">*($T1*)&_My_val</Item>
</Expand>
</Type>
After the atomic
refactoring from VS 16.1, we need:
<Type Name="std::atomic<*>">
<DisplayString>{*($T1*)&_Storage}</DisplayString>
<Expand>
<Item Name="[value]">*($T1*)&_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<*>" Priority="MediumLow">
<DisplayString>{*($T1*)&_My_val}</DisplayString>
<Expand>
<Item Name="[value]">*($T1*)&_My_val</Item>
</Expand>
</Type>
<Type Name="std::atomic<*>">
<DisplayString>{*($T1*)&_Storage}</DisplayString>
<Expand>
<Item Name="[value]">*($T1*)&_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<*>">
<DisplayString Optional="true">{*($T1*)&_My_val}</DisplayString>
<DisplayString Optional="true">{*($T1*)&_Storage}</DisplayString>
<Expand>
<Item Optional="true" Name="[value]">*($T1*)&_My_val</Item>
<Item Optional="true" Name="[value]">*($T1*)&_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 Intrinsic
s and use a single presentation a la:
<Type Name="std::atomic<*>">
<!-- VS 2019 16.0 or earlier -->
<Intrinsic Optional="true" Name="value" Expression="*($T1*)&_My_val"/>
<!-- VS 2019 16.1 or later -->
<Intrinsic Optional="true" Name="value" Expression="*($T1*)&_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.
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 thisOptional
attribute. I believe that it could be used to simplify some of the machinery there.