Commit d6d06e54 authored by Harris Hancock's avatar Harris Hancock

Implement atomic operations for MSVC

Reads and writes of volatile aligned words are automatically blessed with
atomic acquire and release semantics at compile-time by MSVC, leaving only
CPU operation reordering to worry about. x86 and x64 CPUs will not reorder
the operations, but MSVC targets Xbox, which notably will reorder them,
thus I added fences out of an abundance of caution. While Cap'n Proto
likely will not compile for Xbox as-is, I would hate for someone to port
it only to have to debug obscure atomic-operation-related crashes later.

I implemented the fences using std::atomic_thread_fence rather than
MemoryBarrier(), because including windows.h in raw-schema.h is a
non-starter, and it would be silly to reimplement it with in-line assembly
and intrinsics for every targeted CPU when <atomic> is available.

Another possible implementation could have been to use the InterlockedXxx
functions, however they present a few issues:

1. They're defined in windows.h. We could define them in terms of their
   underlying _InterlockedXxx intrinsics, but we'd need more #if blocks to
   handle both 32-bit and 64-bit pointers. If we go this route, it'd
   probably be better to go all-in and define some kj::atomic{Load,Store}
   functions.
2. We cannot implement atomic load-acquire semantics with them for const
   variables without const_casting.
parent ecb0c9d6
......@@ -41,7 +41,13 @@ static BrokenCapFactory* brokenCapFactory = nullptr;
void setGlobalBrokenCapFactoryForLayoutCpp(BrokenCapFactory& factory) {
// Called from capability.c++ when the capability API is used, to make sure that layout.c++
// is ready for it. May be called multiple times but always with the same value.
#if __GNUC__
__atomic_store_n(&brokenCapFactory, &factory, __ATOMIC_RELAXED);
#elif _MSC_VER
*static_cast<BrokenCapFactory* volatile*>(&brokenCapFactory) = &factory;
#else
#error "Platform not supported"
#endif
}
} // namespace _ (private)
......
......@@ -28,6 +28,10 @@
#include "common.h" // for uint and friends
#if _MSC_VER
#include <atomic>
#endif
namespace capnp {
namespace _ { // private
......@@ -144,7 +148,14 @@ struct RawBrandedSchema {
// is required in particular when traversing the dependency list. RawSchemas for compiled-in
// types are always initialized; only dynamically-loaded schemas may be lazy.
#if __GNUC__
const Initializer* i = __atomic_load_n(&lazyInitializer, __ATOMIC_ACQUIRE);
#elif _MSC_VER
const Initializer* i = *static_cast<Initializer const* const volatile*>(&lazyInitializer);
std::atomic_thread_fence(std::memory_order_acquire);
#else
#error "Platform not supported"
#endif
if (i != nullptr) i->init(this);
}
......@@ -203,7 +214,14 @@ struct RawSchema {
// is required in particular when traversing the dependency list. RawSchemas for compiled-in
// types are always initialized; only dynamically-loaded schemas may be lazy.
#if __GNUC__
const Initializer* i = __atomic_load_n(&lazyInitializer, __ATOMIC_ACQUIRE);
#elif _MSC_VER
const Initializer* i = *static_cast<Initializer const* const volatile*>(&lazyInitializer);
std::atomic_thread_fence(std::memory_order_acquire);
#else
#error "Platform not supported"
#endif
if (i != nullptr) i->init(this);
}
......
......@@ -32,6 +32,10 @@
#include <kj/vector.h>
#include <algorithm>
#if _MSC_VER
#include <atomic>
#endif
namespace capnp {
namespace {
......@@ -1308,8 +1312,17 @@ _::RawSchema* SchemaLoader::Impl::load(const schema::Node::Reader& reader, bool
// If this schema is not newly-allocated, it may already be in the wild, specifically in the
// dependency list of other schemas. Once the initializer is null, it is live, so we must do
// a release-store here.
#if __GNUC__
__atomic_store_n(&slot->lazyInitializer, nullptr, __ATOMIC_RELEASE);
__atomic_store_n(&slot->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
#elif _MSC_VER
std::atomic_thread_fence(std::memory_order_release);
*static_cast<_::RawSchema::Initializer const* volatile*>(&slot->lazyInitializer) = nullptr;
*static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
&slot->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
}
return slot;
......@@ -1399,8 +1412,17 @@ _::RawSchema* SchemaLoader::Impl::loadNative(const _::RawSchema* nativeSchema) {
// If this schema is not newly-allocated, it may already be in the wild, specifically in the
// dependency list of other schemas. Once the initializer is null, it is live, so we must do
// a release-store here.
#if __GNUC__
__atomic_store_n(&result->lazyInitializer, nullptr, __ATOMIC_RELEASE);
__atomic_store_n(&result->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
#elif _MSC_VER
std::atomic_thread_fence(std::memory_order_release);
*static_cast<_::RawSchema::Initializer const* volatile*>(&result->lazyInitializer) = nullptr;
*static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
&result->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
}
return result;
......@@ -1910,8 +1932,18 @@ void SchemaLoader::InitializerImpl::init(const _::RawSchema* schema) const {
"A schema not belonging to this loader used its initializer.");
// Disable the initializer.
#if __GNUC__
__atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
__atomic_store_n(&mutableSchema->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
#elif _MSC_VER
std::atomic_thread_fence(std::memory_order_release);
*static_cast<_::RawSchema::Initializer const* volatile*>(
&mutableSchema->lazyInitializer) = nullptr;
*static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
&mutableSchema->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
}
}
......@@ -1939,7 +1971,15 @@ void SchemaLoader::BrandedInitializerImpl::init(const _::RawBrandedSchema* schem
mutableSchema->dependencyCount = deps.size();
// It's initialized now, so disable the initializer.
#if __GNUC__
__atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
#elif _MSC_VER
std::atomic_thread_fence(std::memory_order_release);
*static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
&mutableSchema->lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
}
// =======================================================================================
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment