Skip to content

Conversation

@jonwis
Copy link
Member

@jonwis jonwis commented Dec 30, 2025

We have a lot of tracelogging, and we have a lot of things that are string views to log. Unfortunately, without direct support for counted strings in TLG, we see people doing this:

void do_thing(std::wstring_view x) {
    if (condition) {
        TraceLoggingWrite(g_Provider, TraceLoggingValue(std::wstring(x).c_str()));
    }
}

Or worse, winrt::hstring(x).c_str() - all to get the null-terminated form that TraceLoggingWrite/TraceLoggingXString require.

This change adds specializations for the _tlgAuto templates, so wil::zwstring_view, std::wstring_view, std::string_view, std::wstring, std::string, and winrt::hstring can all be used with TraceLoggingValue(). They can also be used as the parameter-type for the DEFINE_EVENT_* family of macro-stamped-out events.

A better test would use one of the ETW watchers to verify that the event got logged, but a manual use of xperf -start MyLogger -on bd2bf191-ac1a-4732-b563-bb3e3006f617 then run the tests, then xperf -stop -d something.etl and open something.etl with WPA.

The infrastructure is also present to support passing arrays/spans of scalar types, but left unimplemented for now.

Note: This change updates CMakePresets.json - in a stock install of Kitware.Cmake and LLVM.LLVM, the current presets fail; putting cacheVariables into a buildPreset is not support - it has to go into a configurePreset instead.

Note: This change updates the test script to allow selective disablement of the ASAN tests. My local machine is already on Win11, and hits the issues referenced in llvm/llvm-project#111638 and related. Sounds like my NTDLL has the new compiler codegen enabled.

@jonwis
Copy link
Member Author

jonwis commented Dec 30, 2025

The exact output from the asan tests (x64) are:

Running sanitize-address tests...
==17176==interception_win: unhandled instruction at 0x7ffa0d133000: 44 0f b6 1a 4c 8b d2 48
==17176==interception_win: unhandled instruction at 0x7ffa0d133000: 44 0f b6 1a 4c 8b d2 48

Which maps to:

0:000> ln 0x7ffa0d133000
Browse module
Set bu breakpoint

(00007ffa`0d133000)   ucrtbase!strstr   |  (00007ffa`0d133120)   ucrtbase!initialize_c

... so this is llvm/llvm-project#125105

@jonwis jonwis changed the title User/jonwis/tracelogging for strings Enable string-views for use with Tracelogging Dec 30, 2025
#include <string>
#include <vector>
#include <utility>
#include <TraceLoggingProvider.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is illegally reaching into internal implementation details of TraceLoggingProvider.h.

Probably the right thing to do is:

  1. Modify TraceLoggingProvider.h to expose enough "supported" machinery to support some C++ constructs.
  2. Modify WIL to use the supported mechanism if present, or to poly-fill the machinery if not present. (The poly-fill will only be used on old versions of TraceLoggingProvider.h, so future changes to TraceLoggingProvider.h internals will not break the poly-fill.

#if __cpp_lib_string_view >= 201606L

template <typename TContainer>
struct _tlgWrapBufferStlContainer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _tlg prefix is used to signal "TraceLoggingProvider.h-internal machinery". This prefix should not be used for "WIL-internal machinery", even if it is "WIL-internal TraceLogging-related" machinery.

};

template <typename TChar>
struct _tlgTypeMapStlString

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not use _tlg prefix for this.

};

template <typename TChar, typename TTraits>
TLG_INLINE auto _tlg_CALL _tlgWrapAuto(std::basic_string_view<TChar, TTraits> const& value)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use _tlg_CALL here -- it's an unnecessary use of TraceLoggingProvider.h internal machinery. Instead, use WINAPI or STDCALL or similar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we need a calling convention for this template function? is it passed as a function pointer somewhere?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, it's probably not particularly significant. However, it is possible for two LIBs to use different default calling conventions and both include the header. In that case, the linker will see two distinct inline functions, and that can be confusing or sub-optimal. I just find it to be best practice to put a calling convention on any non-member function declaration in a header.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. I don't think we put calling conventions on standalone functions anywhere else in wil :)

}

#define TraceLoggingStringView(pValue, ...) _tlgArgAuto(static_cast<std::string_view>(pValue), __VA_ARGS__)
#define TraceLoggingWideStringView(pValue, ...) _tlgArgAuto(static_cast<std::wstring_view>(pValue), __VA_ARGS__)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter to this macro should not be a pointer, so probably shouldn't be named pValue.

typedef UINT8 _tlgTypeType0;
typedef UINT16 _tlgTypeType1;
static bool const _tlgIsSimple = false;
static TlgIn_t const _tlgViewIn = wistd::is_same_v<TChar, char> ? TlgInCOUNTEDANSISTRING : TlgInCOUNTEDSTRING;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be based on sizeof(TChar) so it can do something reasonable with char8_t and char16_t. (And it should error-out for sizes other than 1 and 2.)

};

template <typename TChar>
struct _tlgTypeMapStlString

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably have a specialization for char8_t that sets OutType to UTF8.

};

template <typename TChar, typename TTraits>
TLG_INLINE auto _tlg_CALL _tlgWrapAuto(std::basic_string_view<TChar, TTraits> const& value)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need TLG_INLINE here -- it's an unnecessary obfuscation. Just use inline.

:: so we _can_ build the ASan tests, which is at least something. For now we're going to handle this limitation here and
:: avoid running the ASan test in this specific scenario
set RUN_ASAN_TEST=1
if not %RUN_ASAN_TEST%=='' set RUN_ASAN_TEST=1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume your intent is to initialize if not set, in which case the not seems out of place. Additionally, you probably want to surround with quotes so that once variable substitution takes place, you don't have something empty on the left hand side, which normally causes errors and is probably why CI is failing. E.g.

Suggested change
if not %RUN_ASAN_TEST%=='' set RUN_ASAN_TEST=1
if "%RUN_ASAN_TEST%"=="" set RUN_ASAN_TEST=1


TContainer const& view;

__forceinline explicit _tlgWrapBufferStlContainer(_In_ TContainer const& ref) : view(ref)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need our special __forceinline keyword? can we just use the standard 'inline' keyword?


TContainer const& view;

__forceinline explicit _tlgWrapBufferStlContainer(_In_ TContainer const& ref) : view(ref)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as this is a template parameter, can we use a 'Universal Reference' with std::forward?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that achieves anything here. Universal reference is so that you can accept both const and non-const references, or so you can accept both non-const-rvalue and non-const-lvalue references. We only need to be able to accept a const reference, so no fancy universal reference is needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's because moves don't buy us anything for these templated types vs. making copies?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no copies. It's a const ref all the way down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops. yep. sorry, missed that!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jon - feel free to close this. I can't seem to resolve/close it myself :(

{
typedef UINT8 _tlgTypeType0;
typedef UINT16 _tlgTypeType1;
static bool const _tlgIsSimple = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can these be constexpr?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't really any significant difference between class scope static bool const and static bool constexpr. A constexpr would allow more complex variables to be used here (const static only allows trivial scalars to be used without a corresponding extern), but we only need a bool.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. I tend to make everything constexpr that can be supported, as I don't assume what the compiler can optimize / change where it puts such things - so I make it the rule, not the exception :)

it's clearly not a bit deal to go either way to me.

};

template <typename TChar, typename TTraits>
TLG_INLINE auto _tlg_CALL _tlgWrapAuto(std::basic_string_view<TChar, TTraits> const& value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we need a calling convention for this template function? is it passed as a function pointer somewhere?

};

template <typename TChar, typename TTraits>
TLG_INLINE auto _tlg_CALL _tlgWrapAuto(std::basic_string_view<TChar, TTraits> const& value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably don't need TLG_INLINE either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants