Skip to content

Commit c4a8e24

Browse files
authored
Update C++/WinRT to consider the full contract version history for fast ABI (#1132)
1 parent 0400867 commit c4a8e24

File tree

7 files changed

+427
-19
lines changed

7 files changed

+427
-19
lines changed

cppwinrt/helpers.h

Lines changed: 255 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ namespace cppwinrt
8080
// by the caller and callee. The exception to this rule is property setters where the callee may simply store a
8181
// reference to the collection. The collection thus becomes async in the sense that it is expected to remain
8282
// valid beyond the duration of the call.
83-
83+
8484
if (is_put_overload(m_method))
8585
{
8686
return true;
@@ -148,10 +148,54 @@ namespace cppwinrt
148148
return static_cast<bool>(get_attribute(row, type_namespace, type_name));
149149
}
150150

151+
namespace impl
152+
{
153+
template <typename T, typename... Types>
154+
struct variant_index;
155+
156+
template <typename T, typename First, typename... Types>
157+
struct variant_index<T, First, Types...>
158+
{
159+
static constexpr bool found = std::is_same_v<T, First>;
160+
static constexpr std::size_t value = std::conditional_t<found,
161+
std::integral_constant<std::size_t, 0>,
162+
variant_index<T, Types...>>::value + (found ? 0 : 1);
163+
};
164+
}
165+
166+
template <typename Variant, typename T>
167+
struct variant_index;
168+
169+
template <typename... Types, typename T>
170+
struct variant_index<std::variant<Types...>, T> : impl::variant_index<T, Types...>
171+
{
172+
};
173+
174+
template <typename Variant, typename T>
175+
constexpr std::size_t variant_index_v = variant_index<Variant, T>::value;
176+
177+
template <typename T>
178+
auto get_integer_attribute(FixedArgSig const& signature)
179+
{
180+
auto variant = std::get<ElemSig>(signature.value).value;
181+
switch (variant.index())
182+
{
183+
case variant_index_v<decltype(variant), std::make_unsigned_t<T>>: return static_cast<T>(std::get<std::make_unsigned_t<T>>(variant));
184+
case variant_index_v<decltype(variant), std::make_signed_t<T>>: return static_cast<T>(std::get<std::make_signed_t<T>>(variant));
185+
default: return std::get<T>(variant); // Likely throws, but that's intentional
186+
}
187+
}
188+
189+
template <typename T>
190+
auto get_attribute_value(FixedArgSig const& signature)
191+
{
192+
return std::get<T>(std::get<ElemSig>(signature.value).value);
193+
}
194+
151195
template <typename T>
152196
auto get_attribute_value(CustomAttribute const& attribute, uint32_t const arg)
153197
{
154-
return std::get<T>(std::get<ElemSig>(attribute.Value().FixedArgs()[arg].value).value);
198+
return get_attribute_value<T>(attribute.Value().FixedArgs()[arg]);
155199
}
156200

157201
static auto get_abi_name(MethodDef const& method)
@@ -283,33 +327,200 @@ namespace cppwinrt
283327
return bases;
284328
}
285329

286-
static std::pair<uint16_t, uint16_t> get_version(TypeDef const& type)
330+
struct contract_version
331+
{
332+
std::string_view name;
333+
uint32_t version;
334+
};
335+
336+
struct previous_contract
337+
{
338+
std::string_view contract_from;
339+
std::string_view contract_to;
340+
uint32_t version_low;
341+
uint32_t version_high;
342+
};
343+
344+
struct contract_history
345+
{
346+
contract_version current_contract;
347+
348+
// Sorted such that the first entry is the first contract the type was introduced in
349+
std::vector<previous_contract> previous_contracts;
350+
};
351+
352+
static contract_version decode_contract_version_attribute(CustomAttribute const& attribute)
353+
{
354+
// ContractVersionAttribute has three constructors, but only two we care about here:
355+
// .ctor(string contract, uint32 version)
356+
// .ctor(System.Type contract, uint32 version)
357+
auto signature = attribute.Value();
358+
auto& args = signature.FixedArgs();
359+
assert(args.size() == 2);
360+
361+
contract_version result{};
362+
result.version = get_integer_attribute<uint32_t>(args[1]);
363+
call(std::get<ElemSig>(args[0].value).value,
364+
[&](ElemSig::SystemType t)
365+
{
366+
result.name = t.name;
367+
},
368+
[&](std::string_view name)
369+
{
370+
result.name = name;
371+
},
372+
[](auto&&)
373+
{
374+
assert(false);
375+
});
376+
377+
return result;
378+
}
379+
380+
static previous_contract decode_previous_contract_attribute(CustomAttribute const& attribute)
287381
{
288-
uint32_t version{};
382+
// PreviousContractVersionAttribute has two constructors:
383+
// .ctor(string fromContract, uint32 versionLow, uint32 versionHigh)
384+
// .ctor(string fromContract, uint32 versionLow, uint32 versionHigh, string contractTo)
385+
auto signature = attribute.Value();
386+
auto& args = signature.FixedArgs();
387+
assert(args.size() >= 3);
388+
389+
previous_contract result{};
390+
result.contract_from = get_attribute_value<std::string_view>(args[0]);
391+
result.version_low = get_integer_attribute<uint32_t>(args[1]);
392+
result.version_high = get_integer_attribute<uint32_t>(args[2]);
393+
if (args.size() == 4)
394+
{
395+
result.contract_to = get_attribute_value<std::string_view>(args[3]);
396+
}
397+
398+
return result;
399+
}
289400

401+
static contract_version get_initial_contract_version(TypeDef const& type)
402+
{
403+
// Most types don't have previous contracts, so optimize for that scenario to avoid unnecessary allocations
404+
contract_version current_contract{};
405+
406+
// The initial contract, assuming the type has moved contracts, is the only contract name that doesn't appear as
407+
// a "to contract" argument to a PreviousContractVersionAttribute. Note that this assumes that a type does not
408+
// "return" to a prior contract, however this is a restriction enforced by midlrt
409+
std::vector<contract_version> previous_contracts;
410+
std::vector<std::string_view> to_contracts;
290411
for (auto&& attribute : type.CustomAttribute())
291412
{
292-
auto name = attribute.TypeNamespaceAndName();
413+
auto [ns, name] = attribute.TypeNamespaceAndName();
414+
if (ns != "Windows.Foundation.Metadata")
415+
{
416+
continue;
417+
}
293418

294-
if (name.first != "Windows.Foundation.Metadata")
419+
if (name == "ContractVersionAttribute")
420+
{
421+
assert(current_contract.name.empty());
422+
current_contract = decode_contract_version_attribute(attribute);
423+
}
424+
else if (name == "PreviousContractVersionAttribute")
425+
{
426+
auto prev = decode_previous_contract_attribute(attribute);
427+
428+
// If this contract was the target of an earlier contract change, we know this isn't the initial one
429+
if (std::find(to_contracts.begin(), to_contracts.end(), prev.contract_from) == to_contracts.end())
430+
{
431+
previous_contracts.push_back(contract_version{ prev.contract_from, prev.version_low });
432+
}
433+
434+
if (!prev.contract_to.empty())
435+
{
436+
auto itr = std::find_if(previous_contracts.begin(), previous_contracts.end(), [&](auto const& ver)
437+
{
438+
return ver.name == prev.contract_to;
439+
});
440+
if (itr != previous_contracts.end())
441+
{
442+
*itr = previous_contracts.back();
443+
previous_contracts.pop_back();
444+
}
445+
446+
to_contracts.push_back(prev.contract_to);
447+
}
448+
}
449+
else if (name == "VersionAttribute")
450+
{
451+
// Prefer contract versioning, if present. Otherwise, use an empty contract name to indicate that this
452+
// is not a contract version
453+
if (current_contract.name.empty())
454+
{
455+
current_contract.version = get_attribute_value<uint32_t>(attribute, 0);
456+
}
457+
}
458+
}
459+
460+
if (!previous_contracts.empty())
461+
{
462+
assert(previous_contracts.size() == 1);
463+
return previous_contracts[0];
464+
}
465+
466+
return current_contract;
467+
}
468+
469+
static contract_history get_contract_history(TypeDef const& type)
470+
{
471+
contract_history result{};
472+
for (auto&& attribute : type.CustomAttribute())
473+
{
474+
auto [ns, name] = attribute.TypeNamespaceAndName();
475+
if (ns != "Windows.Foundation.Metadata")
295476
{
296477
continue;
297478
}
298479

299-
if (name.second == "ContractVersionAttribute")
480+
if (name == "ContractVersionAttribute")
300481
{
301-
version = get_attribute_value<uint32_t>(attribute, 1);
302-
break;
482+
assert(result.current_contract.name.empty());
483+
result.current_contract = decode_contract_version_attribute(attribute);
303484
}
485+
else if (name == "PreviousContractVersionAttribute")
486+
{
487+
result.previous_contracts.push_back(decode_previous_contract_attribute(attribute));
488+
}
489+
// We could report the version that the type was introduced if the type is not contract versioned, however
490+
// that information is not useful to us anywhere, so just skip it
491+
}
492+
493+
if (result.previous_contracts.empty())
494+
{
495+
return result;
496+
}
497+
assert(!result.current_contract.name.empty());
304498

305-
if (name.second == "VersionAttribute")
499+
// There's no guarantee that the contract history will be sorted in metadata (in fact it's unlikely to be)
500+
for (auto& prev : result.previous_contracts)
501+
{
502+
if (prev.contract_to.empty() || (prev.contract_to == result.current_contract.name))
306503
{
307-
version = get_attribute_value<uint32_t>(attribute, 0);
504+
// No 'to' contract indicates that this was the last contract before the current one
505+
prev.contract_to = result.current_contract.name;
506+
std::swap(prev, result.previous_contracts.back());
308507
break;
309508
}
310509
}
510+
assert(result.previous_contracts.back().contract_to == result.current_contract.name);
511+
512+
for (size_t size = result.previous_contracts.size() - 1; size; --size)
513+
{
514+
auto& last = result.previous_contracts[size];
515+
auto itr = std::find_if(result.previous_contracts.begin(), result.previous_contracts.begin() + size, [&](auto const& prev)
516+
{
517+
return prev.contract_to == last.contract_from;
518+
});
519+
assert(itr != result.previous_contracts.end());
520+
std::swap(*itr, result.previous_contracts[size - 1]);
521+
}
311522

312-
return { HIWORD(version), LOWORD(version) };
523+
return result;
313524
}
314525

315526
struct interface_info
@@ -321,7 +532,11 @@ namespace cppwinrt
321532
bool base{};
322533
bool exclusive{};
323534
bool fastabi{};
324-
std::pair<uint16_t, uint16_t> version{};
535+
// A pair of (relativeContract, version) where 'relativeContract' is the contract the interface was introduced
536+
// in relative to the contract history of the class. E.g. if a class goes from contract 'A' to 'B' to 'C',
537+
// 'relativeContract' would be '0' for an interface introduced in contract 'A', '1' for an interface introduced
538+
// in contract 'B', etc. This is only set/valid for 'fastabi' interfaces
539+
std::pair<uint32_t, uint32_t> relative_version{};
325540
std::vector<std::vector<std::string>> generic_param_stack{};
326541
};
327542

@@ -421,7 +636,6 @@ namespace cppwinrt
421636
}
422637

423638
info.exclusive = has_attribute(info.type, "Windows.Foundation.Metadata", "ExclusiveToAttribute");
424-
info.version = get_version(info.type);
425639
get_interfaces_impl(w, result, info.defaulted, info.overridable, base, info.generic_param_stack, info.type.InterfaceImpl());
426640
insert_or_assign(result, name, std::move(info));
427641
}
@@ -443,10 +657,32 @@ namespace cppwinrt
443657
return result;
444658
}
445659

446-
auto count = std::count_if(result.begin(), result.end(), [](auto&& pair)
660+
auto history = get_contract_history(type);
661+
size_t count = 0;
662+
for (auto& pair : result)
447663
{
448-
return pair.second.exclusive && !pair.second.base && !pair.second.overridable;
449-
});
664+
if (pair.second.exclusive && !pair.second.base && !pair.second.overridable)
665+
{
666+
++count;
667+
668+
auto introduced = get_initial_contract_version(pair.second.type);
669+
pair.second.relative_version.second = introduced.version;
670+
671+
auto itr = std::find_if(history.previous_contracts.begin(), history.previous_contracts.end(), [&](previous_contract const& prev)
672+
{
673+
return prev.contract_from == introduced.name;
674+
});
675+
if (itr != history.previous_contracts.end())
676+
{
677+
pair.second.relative_version.first = static_cast<uint32_t>(itr - history.previous_contracts.begin());
678+
}
679+
else
680+
{
681+
assert(history.current_contract.name == introduced.name);
682+
pair.second.relative_version.first = static_cast<uint32_t>(history.previous_contracts.size());
683+
}
684+
}
685+
}
450686

451687
std::partial_sort(result.begin(), result.begin() + count, result.end(), [](auto&& left_pair, auto&& right_pair)
452688
{
@@ -482,9 +718,9 @@ namespace cppwinrt
482718
return left_enabled;
483719
}
484720

485-
if (left.version != right.version)
721+
if (left.relative_version != right.relative_version)
486722
{
487-
return left.version < right.version;
723+
return left.relative_version < right.relative_version;
488724
}
489725

490726
return left_pair.first < right_pair.first;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "pch.h"
2+
#include "Nomadic.h"
3+
#include "Nomadic.g.cpp"
4+
5+
namespace winrt::test_component_fast::implementation
6+
{
7+
hstring Nomadic::FirstMethod()
8+
{
9+
return L"FirstMethod";
10+
}
11+
hstring Nomadic::SecondMethod()
12+
{
13+
return L"SecondMethod";
14+
}
15+
hstring Nomadic::ThirdMethod()
16+
{
17+
return L"ThirdMethod";
18+
}
19+
hstring Nomadic::FourthMethod()
20+
{
21+
return L"FourthMethod";
22+
}
23+
hstring Nomadic::FifthMethod()
24+
{
25+
return L"FifthMethod";
26+
}
27+
hstring Nomadic::SixthMethod()
28+
{
29+
return L"SixthMethod";
30+
}
31+
hstring Nomadic::SeventhMethod()
32+
{
33+
return L"SeventhMethod";
34+
}
35+
}

0 commit comments

Comments
 (0)