From 0afa24a3fe7d0e1787cc909f9c7641f477c30e5c Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Fri, 18 Jun 2021 17:09:35 -0400 Subject: StructTag versioning (#1888) * #include an absolute path didn't work - because paths were taken to always be relative. * WIP Abi struct. * Use AbiSystem on SessionDesc. * Use mask/shift constants. * Fix issue causing warning on linux. * Abi -> Api. * Fix typo. * Refactor to use StructTag. * Mechanism to be able to follow fields. * Field adding is working. * WIP with StructTagConverter. * First pass of StructTag appears to work. Still needs diagnostics. * Small tidy up around Field. * Use bit field to record what fields are recorded to remove allocation around the m_stack. Use ScopeStack for RAII. * Return SlangResult instead of pointers. * Use SlangResult with copy. * Split StructTagConverter implementations. * Fix some bugs around lazy converting. * First pass at unit test for StructTag. * Testing StructTag going backwards in time. * First pass as StructTag diagnostics. * Make Traits a namespace. * Fix some issues with Traits not being a class. * Fix 32 bit warning. --- .../compiler-core/compiler-core.vcxproj | 4 + .../compiler-core/compiler-core.vcxproj.filters | 12 + build/visual-studio/slang-test/slang-test.vcxproj | 1 + .../slang-test/slang-test.vcxproj.filters | 3 + examples/hello-world/main.cpp | 3 +- slang.h | 185 ++++++- source/compiler-core/slang-misc-diagnostic-defs.h | 5 + .../compiler-core/slang-struct-tag-converter.cpp | 538 +++++++++++++++++++++ source/compiler-core/slang-struct-tag-converter.h | 153 ++++++ source/compiler-core/slang-struct-tag-system.cpp | 126 +++++ source/compiler-core/slang-struct-tag-system.h | 259 ++++++++++ source/slang/slang-compiler.h | 9 +- source/slang/slang.cpp | 66 ++- tools/gfx/slang-context.h | 4 +- tools/slang-test/unit-test-struct-tag.cpp | 255 ++++++++++ 15 files changed, 1596 insertions(+), 27 deletions(-) create mode 100644 source/compiler-core/slang-struct-tag-converter.cpp create mode 100644 source/compiler-core/slang-struct-tag-converter.h create mode 100644 source/compiler-core/slang-struct-tag-system.cpp create mode 100644 source/compiler-core/slang-struct-tag-system.h create mode 100644 tools/slang-test/unit-test-struct-tag.cpp diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj index a5ad9a372..029cd7fb1 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj @@ -191,6 +191,8 @@ + + @@ -215,6 +217,8 @@ + + diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters index da0ca5d63..2d88ae29d 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters @@ -72,6 +72,12 @@ Header Files + + Header Files + + + Header Files + Header Files @@ -140,6 +146,12 @@ Source Files + + Source Files + + + Source Files + Source Files diff --git a/build/visual-studio/slang-test/slang-test.vcxproj b/build/visual-studio/slang-test/slang-test.vcxproj index 22a8aae5f..10c8fb1c3 100644 --- a/build/visual-studio/slang-test/slang-test.vcxproj +++ b/build/visual-studio/slang-test/slang-test.vcxproj @@ -189,6 +189,7 @@ + diff --git a/build/visual-studio/slang-test/slang-test.vcxproj.filters b/build/visual-studio/slang-test/slang-test.vcxproj.filters index 1e5b6e4af..99735f156 100644 --- a/build/visual-studio/slang-test/slang-test.vcxproj.filters +++ b/build/visual-studio/slang-test/slang-test.vcxproj.filters @@ -86,5 +86,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index e635b3a48..db243749b 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -115,8 +115,9 @@ int HelloWorldExample::createComputePipelineFromShader() slang::TargetDesc targetDesc = {}; targetDesc.format = SLANG_SPIRV; targetDesc.profile = slangGlobalSession->findProfile("glsl440"); - sessionDesc.targetCount = 1; + sessionDesc.targets = &targetDesc; + sessionDesc.targetCount = 1; ComPtr session; RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, session.writeRef())); diff --git a/slang.h b/slang.h index edf294ab4..944e42b0e 100644 --- a/slang.h +++ b/slang.h @@ -450,6 +450,178 @@ convention for interface methods. #include #endif // ! SLANG_NO_STDDEF +namespace slang +{ + +/* Slang provides a mechanism for value types (such as structures) to be able to provide forward and backward +ABI compatibility through. + +The type is made up of several parts. +0) The kind primary or extension +1) The category the type is in +2) An id for the type in the category +3) The major semantic version +4) The minor semantic version + +Within the 'semantic versioning' a structure that has only a larger minor semantic version can be used (with some +caveats) as is when being passed to a previous version of Slang. It may contain more fields. +*/ + +enum class StructTagKind : uint8_t +{ + Primary, ///< A primary struct (contains optional extensions) + Extension, ///< An extension struct +}; + +#define SLANG_STRUCT_TAG_ENUM(x) x, + +#define SLANG_STRUCT_TAG_CATEGORIES(x) \ + x(Core) \ + x(Slang) \ + x(Gfx) + + +enum class StructTagCategory : uint8_t +{ + SLANG_STRUCT_TAG_CATEGORIES(SLANG_STRUCT_TAG_ENUM) + CountOf, +}; + +/* AbiStructType is laid out as follows + + |Primary | Category | Type | Major version | Minor Version +-----|--------|--------------|-----------|---------------|---------------- +Bits | 31 | 30-24 | 23-16 | 15-8 | 0-7 +Type | | AbiCategory | Cat Spec | | + +Type will be specified as something specific for the category. A typical implementation will have an enum that lists +types for that category. + +TODO(JS): This layout may need to be altered - too may bits are perhaps used by major/minor version for example. This should +be fine for the immediate future though. +*/ + +typedef uint32_t StructTagInt; +typedef uint32_t StructSize; + +enum class StructTag : StructTagInt; + +enum : uint32_t +{ + kStructTagPrimaryMask = 0x80000000, + + kStructTagCategoryMask = 0x7f000000, + kStructTagCategoryShift = 24, + + // Combination of category and TypeIndex + kStructTagCategoryTypeIndexMask = 0x7fff0000, + kStructTagCategoryTypeIndexShift = 16, + + kStructTagCategoryTypeMajorMask = 0xffffff00, + + kStructTagTypeIndexMask = 0x00ff0000, + kStructTagTypeIndexShift = 16, + + kStructTagVersionMask = 0x0000ffff, + kStructTagVersionShift = 0, + + kStructTagMajorMask = 0x0000ff00, + kStructTagMajorShift = 8, + + kStructTagMinorMask = 0x000000ff, + kStructTagMinorShift = 0, +}; + +// Types purely for use in template type identification +// Any Primary or Extension TaggedStruct must have a typedef of one of these as Tag. +enum class PrimaryTag; +enum class ExtensionTag; + +#define SLANG_MAKE_PRIMARY_STRUCT_TAG(CATEGORY, TYPE_ID, MAJOR, MINOR) slang::StructTag(slang::StructTagInt(slang::kStructTagPrimaryMask) | (slang::StructTagInt(CATEGORY) << 24) | (slang::StructTagInt(TYPE_ID) << 16) | (slang::StructTagInt(MAJOR) << 8) | slang::StructTagInt(MINOR)) +#define SLANG_MAKE_EXTENSION_STRUCT_TAG(CATEGORY, TYPE_ID, MAJOR, MINOR) slang::StructTag((slang::StructTagInt(CATEGORY) << 24) | (slang::StructTagInt(TYPE_ID) << 16) | (slang::StructTagInt(MAJOR) << 8) | slang::StructTagInt(MINOR)) + +/* +`PrimaryStruct`s are structs that are passed directly into API calls. They always contain a 'structType' that identifies the exact version +and type of that is being passed in. `PrimaryStruct`s can also specify optional `ExtensionStruct` types that modify and/or add to the values +in the `PrimaryStruct`. + +`ExtensionStruct` cannot contain optional other extensions as PrimaryStructs do - all required extensions have to be specified via the list set on the +`PrimaryStruct`. + +`ExtensionStruct` is typically used when it is necessary to provide some special additional information that is not appropriate to place within +the `PrimaryStruct`. This also provides a mechanism such that a `PrimaryStruct` derived type does not need to include all the fields that will +every be needed. Cross cutting aspects can have their own uniquely identified structs. + +As a mechanism to provide extensibility without having to modify a struct or to allow optional and/or additional information through +ExtensionStruct set via the `exts` and `extsCount` members. + +A type that is ABI compatible with this mechanism must start with exactly the same fields as specified. +When using extensions, the exts and extsCount should be set. `exts` points to the extension ids (which must be at the start of the types) +to provide some kind of type safely without requiring inheritance. +*/ + +#define SLANG_EXTENSION_TAGGED_STRUCT_IMPL(TYPE_NAME, CATEGORY, TYPE_ID, MAJOR, MINOR) \ + typedef TYPE_NAME ThisType; \ + typedef slang::ExtensionTag Tag; \ + static const slang::StructTag kStructTag = SLANG_MAKE_EXTENSION_STRUCT_TAG(CATEGORY, TYPE_ID, MAJOR, MINOR); \ + slang::StructTag structTag = kStructTag; \ + slang::StructSize structSize = slang::StructSize(sizeof(ThisType)); + +#define SLANG_PRIMARY_TAGGED_STRUCT_IMPL(TYPE_NAME, CATEGORY, TYPE_ID, MAJOR, MINOR) \ + typedef TYPE_NAME ThisType; \ + typedef ::slang::PrimaryTag Tag; \ + static const ::slang::StructTag kStructTag = SLANG_MAKE_PRIMARY_STRUCT_TAG(CATEGORY, TYPE_ID, MAJOR, MINOR); \ + slang::StructTag structTag = kStructTag; \ + slang::StructSize structSize = slang::StructSize(sizeof(ThisType)); \ + const slang::StructTag** exts = nullptr; \ + int32_t extsCount = 0; + +#define SLANG_API_TYPE_ENUM(x) x, + +struct TaggedStructBase +{ + StructTag structTag; ///< Identity for the type + StructSize structSize; ///< Size of the type in bytes +}; + +/* Layout for a PrimaryTaggedStruct */ +struct PrimaryTaggedStruct +{ + StructTag structTag; ///< Identity for the type + StructSize structSize; ///< Size of the type in bytes + const StructTag** exts; ///< Extensions - StructTag type should be the first member of Extension structs + int32_t extsCount = 0; ///< The number of extensions +}; + +/* Layout for a Extension TaggedStruct */ +struct ExtensionTaggedStruct +{ + StructTag structTag; ///< Identity for the type + StructSize structSize; ///< Size of the type in bytes +}; + +// Enumerate all of the types that want ABI handling as part of Slang API. +// +// NOTE! That care is needed using the enumeration, because we want IDs to remain stable. +// New types should be added to the end. Removed types should become Depreciated_Name (say). +// +// NOTE! The names must match the type name for an active type exactly for the SLANG_PRIMARY_TAGGED_STRUCT and SLANG_EXTENSION_TAGGED_STRUCT +// macros to work. +#define SLANG_TAGGED_STRUCTS(x) \ + x(TargetDesc) \ + x(SessionDesc) + +// Define Slangs types in the enum +enum class SlangTaggedStruct +{ + SLANG_TAGGED_STRUCTS(SLANG_STRUCT_TAG_ENUM) +}; + +#define SLANG_PRIMARY_TAGGED_STRUCT(TYPE_NAME, MAJOR, MINOR) SLANG_PRIMARY_TAGGED_STRUCT_IMPL(TYPE_NAME, StructTagCategory::Slang, SlangTaggedStruct::TYPE_NAME, MAJOR, MINOR) +#define SLANG_EXTENSION_TAGGED_STRUCT(TYPE_NAME, MAJOR, MINOR) SLANG_EXTENSION_TAGGED_STRUCT_IMPL(TYPE_NAME, StructTagCategory::Slang, SlangTaggedStruct::TYPE_NAME, MAJOR, MINOR) + +} // namespace slang + #ifdef __cplusplus extern "C" { @@ -478,7 +650,7 @@ extern "C" typedef bool SlangBool; - + /*! @brief Severity of a diagnostic generated by the compiler. Values come from the enum below, with higher values representing more severe @@ -831,6 +1003,9 @@ extern "C" //! Could not complete because some underlying feature (hardware or software) was not available #define SLANG_E_NOT_AVAILABLE SLANG_MAKE_CORE_ERROR(7) + //! A type specified is StructTag incompatible with this version of slang +#define SLANG_E_STRUCT_TAG_INCOMPATIBLE SLANG_MAKE_CORE_ERROR(8) + /** A "Universally Unique Identifier" (UUID) The Slang API uses UUIDs to identify interfaces when @@ -3804,9 +3979,7 @@ namespace slang */ struct TargetDesc { - /** The size of this structure, in bytes. - */ - size_t structureSize = sizeof(TargetDesc); + SLANG_PRIMARY_TAGGED_STRUCT(TargetDesc, 0, 0) /** The target format to generate code for (e.g., SPIR-V, DXIL, etc.) */ @@ -3856,9 +4029,7 @@ namespace slang struct SessionDesc { - /** The size of this structure, in bytes. - */ - size_t structureSize = sizeof(SessionDesc); + SLANG_PRIMARY_TAGGED_STRUCT(SessionDesc, 0, 0) /** Code generation targets to include in the session. */ diff --git a/source/compiler-core/slang-misc-diagnostic-defs.h b/source/compiler-core/slang-misc-diagnostic-defs.h index aa87f02f9..f3ac0bae5 100644 --- a/source/compiler-core/slang-misc-diagnostic-defs.h +++ b/source/compiler-core/slang-misc-diagnostic-defs.h @@ -30,6 +30,11 @@ DIAGNOSTIC(100002, Error, unbalancedDownstreamArguments, "unbalanced downstream DIAGNOSTIC(100003, Error, closeOfUnopenDownstreamArgs, "close of an unopen downstream argument scope") DIAGNOSTIC(100004, Error, downstreamToolNameNotDefined, "downstream tool name not defined") +DIAGNOSTIC(110001, Error, structTagConversionFailureNoArena, "StructTag conversion failed - no arena") +DIAGNOSTIC(110002, Error, unknownStructTag, "unknown StructTag $0") +DIAGNOSTIC(110003, Error, cannotConvertStructTag, "cannot convert StructTag $0 to $1") +DIAGNOSTIC(110004, Error, cannotConvertDifferentStructTag, "cannot convert different StructTag types $0 to $1") + DIAGNOSTIC(99999, Note, noteLocationOfInternalError, "an internal error threw an exception while working on code near this location") #undef DIAGNOSTIC diff --git a/source/compiler-core/slang-struct-tag-converter.cpp b/source/compiler-core/slang-struct-tag-converter.cpp new file mode 100644 index 000000000..9e891e642 --- /dev/null +++ b/source/compiler-core/slang-struct-tag-converter.cpp @@ -0,0 +1,538 @@ +#include "slang-struct-tag-converter.h" + +#include "slang-core-diagnostics.h" + +namespace Slang { + +static Index _getCount(const StructTagType::Field& field, const void* in) +{ + typedef StructTagField::Type FieldType; + + const uint8_t* ptr = (const uint8_t*)in; + + switch (field.m_countType) + { + case FieldType::I32: return Index(*(const int32_t*)(ptr + field.m_countOffset)); + case FieldType::I64: return Index(*(const int64_t*)(ptr + field.m_countOffset)); + default: break; + } + + SLANG_ASSERT(!"Cannot access as count"); + return -1; +} + +SlangResult StructTagConverterBase::_requireArena() +{ + if (!m_arena) + { + if (m_sink) + { + // Diagnose that we need an arena + m_sink->diagnose(SourceLoc(), MiscDiagnostics::structTagConversionFailureNoArena); + } + return SLANG_FAIL; + } + + return SLANG_OK; +} + +SlangResult StructTagConverterBase::_diagnoseCantConvert(slang::StructTag tag, StructTagType* structType) +{ + SLANG_UNUSED(tag); + SLANG_UNUSED(structType); + + if (m_sink) + { + // Diagnose why the tag couldn't be converted + StringBuilder from, to; + m_system->appendName(tag, from); + m_system->appendName(structType->m_tag, to); + + m_sink->diagnose(SourceLoc(), MiscDiagnostics::cannotConvertStructTag, from, to); + } + + return SLANG_E_STRUCT_TAG_INCOMPATIBLE; +} + +SlangResult StructTagConverterBase::_diagnoseUnknownType(slang::StructTag tag) +{ + SLANG_UNUSED(tag); + + if (m_sink) + { + StringBuilder buf; + m_system->appendName(tag, buf); + m_sink->diagnose(SourceLoc(), MiscDiagnostics::unknownStructTag, buf); + } + + // Perhaps this isn't quite right - but it's probably the most suitable error + return SLANG_E_STRUCT_TAG_INCOMPATIBLE; +} + +SlangResult StructTagConverterBase::_diagnoseDifferentTypes(slang::StructTag tagA, slang::StructTag tagB) +{ + if (m_sink) + { + StringBuilder a, b; + m_system->appendName(tagA, a); + m_system->appendName(tagB, b); + + m_sink->diagnose(SourceLoc(), MiscDiagnostics::cannotConvertDifferentStructTag, a, b); + } + + // Perhaps this isn't quite right - but it's probably the most suitable error + return SLANG_E_STRUCT_TAG_INCOMPATIBLE; +} + +bool StructTagConverterBase::canConvertToCurrent(slang::StructTag tag, StructTagType* type) const +{ + // Means can be used without any modification + if (StructTagUtil::isReadCompatible(tag, type->m_tag)) + { + return true; + } + + // We may want to allow zero extension, or initialization + // We can accept for conversion if it's the same type with only difference being the minor version. + return StructTagUtil::areSameMajorType(tag, type->m_tag); +} + +void StructTagConverterBase::copy(const StructTagType* structType, const void* src, void* dst) +{ + const slang::TaggedStructBase* srcBase = reinterpret_cast(src); + + const slang::StructSize size = std::min(structType->m_sizeInBytes, srcBase->structSize); + + // Copy + ::memcpy(dst, src, size); + + // TODO(JS): Alternatively if we have the default set on the structType, we could initialize + // other fields with the default values. For the moment we zero + + // Zero any extra + if (size < structType->m_sizeInBytes) + { + ::memset((char*)dst + size, 0, structType->m_sizeInBytes - size); + } + + // Set the type and the size + slang::TaggedStructBase* dstBase = reinterpret_cast(dst); + dstBase->structTag = structType->m_tag; + dstBase->structSize = structType->m_sizeInBytes; +} + +void* StructTagConverterBase::allocateAndCopy(const StructTagType* structType, const void* src) +{ + uint8_t* dst = (uint8_t*)m_arena->allocate(structType->m_sizeInBytes); + copy(structType, src, dst); + return dst; +} + +SlangResult StructTagConverterBase::convertCurrent(slang::StructTag tag, const void* in, void*& out) +{ + auto base = reinterpret_cast(in); + auto inTag = base->structTag; + + // Currently we can only convert if same major type. + // In future it might be possible to improve on this + if (!StructTagUtil::areSameMajorType(inTag, tag)) + { + return _diagnoseDifferentTypes(inTag, tag); + } + + return convertCurrent(in, out); +} + +SlangResult StructTagConverterBase::convertArrayField(const FieldType type, const void* in, Index count, void*& out) +{ + if (count <= 0) + { + out = const_cast(in); + return SLANG_OK; + } + + SLANG_ASSERT(in); + switch (type) + { + case FieldType::PtrTaggedStruct: return convertCurrentArray(in, count, out); + case FieldType::PtrPtrTaggedStruct: return convertCurrentPtrArray((const void*const*)in, count, (void**&)out); + default: break; + } + return SLANG_FAIL; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + CopyStructTagConverter + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +SlangResult CopyStructTagConverter::convertCurrentPtrArray(const void*const* in, Index count, void**& out) +{ + if (count == 0) + { + out = nullptr; + return SLANG_OK; + } + void** dst = (void**)m_arena->allocate(count * sizeof(void*)); + for (Index i = 0; i < count; ++i) + { + SLANG_RETURN_ON_FAIL(convertCurrent(in[i], dst[i])); + } + out = dst; + return SLANG_OK; +} + +SlangResult CopyStructTagConverter::convertCurrentArray(const void* in, Index count, void*& out) +{ + if (count <= 0) + { + out = nullptr; + return SLANG_OK; + } + + if (count == 1) + { + return convertCurrent(in, out); + } + + const slang::TaggedStructBase* arr = reinterpret_cast(in); + + // We assume all have the same size/type + auto tag = arr[0].structTag; + + auto structType = m_system->getType(tag); + + if (!structType) + { + return _diagnoseUnknownType(tag); + } + if (!canConvertToCurrent(tag, structType)) + { + return _diagnoseCantConvert(tag, structType); + } + + const size_t dstStride = structType->m_sizeInBytes; + uint8_t*const dstStart = (uint8_t*)m_arena->allocate(dstStride * count); + uint8_t* dst = dstStart; + + size_t srcStride = arr[0].structSize; + const uint8_t* src = reinterpret_cast(in); + + for (Index i = 0; i < count; ++i) + { + copy(structType, src, dst); + SLANG_RETURN_ON_FAIL(convertCurrentContained(structType, dst)); + + src += srcStride; + dst += dstStride; + } + + out = dstStart; + return SLANG_OK; +} + +SlangResult CopyStructTagConverter::convertCurrentContained(const StructTagType* structType, void* inout) +{ + // Convert primary + if (StructTagUtil::isPrimary(structType->m_tag)) + { + // Copy extensions if needed + slang::PrimaryTaggedStruct* primary = reinterpret_cast(inout); + if (primary->extsCount > 0) + { + void** dstExts; + SLANG_RETURN_ON_FAIL(convertCurrentPtrArray((const void*const*)primary->exts, primary->extsCount, dstExts)); + primary->exts = (const slang::StructTag**)dstExts; + } + } + + // It may have fields that need to be converted + for (const auto& field : structType->m_fields) + { + const Index count = _getCount(field, inout); + if (count) + { + void*& ptrRef = *(void**)(reinterpret_cast(inout) + field.m_offset); + SLANG_RETURN_ON_FAIL(convertArrayField(field.m_type, ptrRef, count, ptrRef)); + } + } + + return SLANG_OK; +} + +SlangResult CopyStructTagConverter::convertCurrent(const void* in, void*& out) +{ + auto tag = reinterpret_cast(in)->structTag; + + auto structType = m_system->getType(tag); + if (!structType) + { + return _diagnoseUnknownType(tag); + } + if (!canConvertToCurrent(tag, structType)) + { + return _diagnoseCantConvert(tag, structType); + } + + slang::TaggedStructBase* dst = (slang::TaggedStructBase*)allocateAndCopy(structType, in); + SLANG_RETURN_ON_FAIL(convertCurrentContained(structType, dst)); + out = dst; + return SLANG_OK; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + LazyStructTagConverter + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +SlangResult LazyStructTagConverter::convertCurrentPtrArray(const void*const* in, Index count, void**& out) +{ + if (count == 0) + { + out = (void**)in; + return SLANG_OK; + } + + Index numConverted = 0; + ScopeStack stackScope(this); + + for (Index i = 0; i < count; ++i) + { + const void* src = in[i]; + void* dst; + SLANG_RETURN_ON_FAIL(convertCurrent(src, dst)); + + // Make space if not set up + numConverted += Index(dst != src); + m_convertStack.add(dst); + } + + // We need to make a copy of the exts + if (numConverted) + { + SLANG_RETURN_ON_FAIL(_requireArena()); + out = (void**)m_arena->allocateAndCopyArray(m_convertStack.getBuffer() + stackScope.getStartIndex(), count); + } + else + { + out = (void**)in; + } + + return SLANG_OK; +} + +void LazyStructTagConverter::setContainedConverted(const StructTagType* structType, Index stackIndex, BitField fieldsSet, void* out) +{ + if (fieldsSet == 0) + { + return; + } + + if (StructTagUtil::isPrimary(structType->m_tag)) + { + if (fieldsSet & 1) + { + // Copy extensions if needed + slang::PrimaryTaggedStruct* primary = reinterpret_cast(out); + primary->exts = (const slang::StructTag**)m_convertStack[stackIndex++]; + } + fieldsSet >>= 1; + } + + { + for (const auto& field : structType->m_fields) + { + if (fieldsSet == 0) + { + return; + } + + // If the field is set, copy + if (fieldsSet & 1) + { + switch (field.m_type) + { + case FieldType::PtrTaggedStruct: + case FieldType::PtrPtrTaggedStruct: + { + *(const void**)(reinterpret_cast(out) + field.m_offset) = m_convertStack[stackIndex++]; + break; + } + default: break; + } + } + + // Remove the bit + fieldsSet >>= 1; + } + } +} + + +SlangResult LazyStructTagConverter::maybeConvertCurrentContained(const StructTagType* structType, const void* in, BitField* outFieldsSet) +{ + BitField fieldsSet = 0; + BitField bit = 1; + + if (StructTagUtil::isPrimary(structType->m_tag)) + { + // Copy extensions if needed + const slang::PrimaryTaggedStruct* primary = reinterpret_cast(in); + if (primary->extsCount > 0) + { + auto srcExts = (const void*const*)primary->exts; + void** dstExts; + SLANG_RETURN_ON_FAIL(convertCurrentPtrArray(srcExts, primary->extsCount, dstExts)); + + if (dstExts != srcExts) + { + m_convertStack.add(dstExts); + fieldsSet |= bit; + } + } + + bit += bit; + } + + // It may have fields that need to be converted + + { + for (const auto& field : structType->m_fields) + { + const Index count = _getCount(field, in); + if (count > 0) + { + void* dst = nullptr; + const void* src = *(const void**)(reinterpret_cast(in) + field.m_offset); + SLANG_RETURN_ON_FAIL(convertArrayField(field.m_type, src, count, dst)); + + if (dst != src) + { + // Set the and add to the stack + m_convertStack.add(dst); + fieldsSet |= bit; + } + } + + bit += bit; + } + } + + *outFieldsSet = fieldsSet; + return SLANG_OK; +} + +SlangResult LazyStructTagConverter::convertCurrentArray(const void* in, Index count, void*& out) +{ + if (count == 0) + { + out = (void*)in; + return SLANG_OK; + } + + if (count == 1) + { + return convertCurrent(in, out); + } + + const slang::TaggedStructBase* arr = reinterpret_cast(in); + + // We assume all have the same size/type + auto tag = arr[0].structTag; + + auto structType = m_system->getType(tag); + + if (!structType) + { + return _diagnoseUnknownType(tag); + } + + if (StructTagUtil::areSameMajorType(tag, structType->m_tag) && structType->m_sizeInBytes == arr[0].structSize) + { + // Can just use what was passed in + out = (void*)in; + return SLANG_OK; + } + + if (!canConvertToCurrent(tag, structType)) + { + return _diagnoseCantConvert(tag, structType); + } + + SLANG_RETURN_ON_FAIL(_requireArena()); + + const size_t dstStride = structType->m_sizeInBytes; + uint8_t*const dstStart = (uint8_t*)m_arena->allocate(dstStride * count); + uint8_t* dst = dstStart; + + size_t srcStride = arr[0].structSize; + const uint8_t* src = reinterpret_cast(in); + + for (Index i = 0; i < count; ++i) + { + // Do the straight copy + copy(structType, src, dst); + + // Work out what was converted + ScopeStack scopeStack(this); + BitField fieldsSet; + SLANG_RETURN_ON_FAIL(maybeConvertCurrentContained(structType, in, &fieldsSet)); + + // Copy anything converted + setContainedConverted(structType, scopeStack, fieldsSet, dst); + + src += srcStride; + dst += dstStride; + } + + out = dstStart; + return SLANG_OK; +} + +SlangResult LazyStructTagConverter::convertCurrent(const void* in, void*& out) +{ + auto base = reinterpret_cast(in); + auto tag = base->structTag; + + // If can't find the type it's incompatible + auto structType = m_system->getType(tag); + if (!structType) + { + return _diagnoseUnknownType(tag); + } + + if (!canConvertToCurrent(tag, structType)) + { + return _diagnoseCantConvert(tag, structType); + } + + // Let's see if how everything contained converts + ScopeStack stackScope(this); + BitField fieldsSet; + SLANG_RETURN_ON_FAIL(maybeConvertCurrentContained(structType, in, &fieldsSet)); + + // If there were no fields set + if (fieldsSet == 0) + { + // And it's the same major type, and is at least as big a the current struct + // We can just use as is + if (StructTagUtil::areSameMajorType(tag, structType->m_tag) && base->structSize >= structType->m_sizeInBytes) + { + out = (void*)in; + return SLANG_OK; + } + } + + // Okay we will need to allocate and copy + void* dst = allocateAndCopy(structType, in); + + // Copy anything converted + setContainedConverted(structType, stackScope, fieldsSet, dst); + + out = dst; + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/compiler-core/slang-struct-tag-converter.h b/source/compiler-core/slang-struct-tag-converter.h new file mode 100644 index 000000000..5f0ec7087 --- /dev/null +++ b/source/compiler-core/slang-struct-tag-converter.h @@ -0,0 +1,153 @@ +#ifndef SLANG_COMPILER_CORE_STRUCT_TAG_CONVERTER_H +#define SLANG_COMPILER_CORE_STRUCT_TAG_CONVERTER_H + +#include "slang-struct-tag-system.h" +#include "slang-diagnostic-sink.h" + +namespace Slang { + +class StructTagConverterBase +{ +public: + typedef StructTagConverterBase ThisType; + typedef StructTagField Field; + typedef Field::Type FieldType; + + virtual SlangResult convertCurrent(const void* in, void*& out) = 0; + virtual SlangResult convertCurrentArray(const void* in, Index count, void*& out) = 0; + virtual SlangResult convertCurrentPtrArray(const void*const* in, Index count, void**& out) = 0; + + SlangResult convertCurrent(slang::StructTag tag, const void* in, void*& out); + + /// Allocates of type and copies src to dst + void* allocateAndCopy(const StructTagType* type, const void* src); + /// Copy from src to dst, zero extending or shrinking however structType requires + void copy(const StructTagType* structType, const void* src, void* dst); + + /// Returns true if it's possible to convert tag to current type + bool canConvertToCurrent(slang::StructTag tag, StructTagType* type) const; + + template + const T* convertToCurrent(const void* in) + { + void* dst; + return SLANG_SUCCEEDED(convertCurrent(T::kStructTag, in, dst)) ? (const T*)dst : nullptr; + } + + template + SlangResult convertToCurrent(const void* in, const T** out) + { + void* dst; + SLANG_RETURN_ON_FAIL(convertCurrent(T::kStructTag, in, dst)); + *out = (const T*)dst; + return SLANG_OK; + } + + /// Convert a single field which is an array type + SlangResult convertArrayField(const FieldType type, const void* in, Index count, void*& out); + + /// Ctor. Arena and sink can be optionally set (pass nullptr if not wanted) + StructTagConverterBase(StructTagSystem* system, MemoryArena* arena, DiagnosticSink* sink) : + m_system(system), + m_arena(arena), + m_sink(sink) + { + } + +protected: + StructTagConverterBase(const ThisType& rhs) = delete; + void operator=(const ThisType& rhs) = delete; + + SlangResult _requireArena(); + SlangResult _diagnoseCantConvert(slang::StructTag tag, StructTagType* type); + SlangResult _diagnoseUnknownType(slang::StructTag tag); + SlangResult _diagnoseDifferentTypes(slang::StructTag tagA, slang::StructTag tagB); + + StructTagSystem* m_system; + DiagnosticSink* m_sink; + MemoryArena* m_arena; +}; + +class CopyStructTagConverter : public StructTagConverterBase +{ +public: + typedef StructTagConverterBase Super; + + // StructTagConverterBase + virtual SlangResult convertCurrent(const void* in, void*& out) SLANG_OVERRIDE; + virtual SlangResult convertCurrentArray(const void* in, Index count, void*& out) SLANG_OVERRIDE; + virtual SlangResult convertCurrentPtrArray(const void*const* in, Index count, void**& out) SLANG_OVERRIDE; + + /// Convert the items contained in inout + SlangResult convertCurrentContained(const StructTagType* structType, void* inout); + + CopyStructTagConverter(StructTagSystem* system, MemoryArena* arena, DiagnosticSink* sink) : + Super(system, arena, sink) + { + // If we are going to copy -> we have to have an arena + SLANG_ASSERT(arena); + } +}; + +class LazyStructTagConverter : public StructTagConverterBase +{ +public: + typedef StructTagConverterBase Super; + + typedef uint32_t BitField; + + struct ScopeStack + { + ScopeStack(LazyStructTagConverter* converter): + m_stack(converter->m_convertStack), + m_startIndex(converter->m_convertStack.getCount()) + { + } + ~ScopeStack() + { + m_stack.setCount(m_startIndex); + } + + Index getStartIndex() const { return m_startIndex; } + operator Index() const { return m_startIndex; } + + protected: + List& m_stack; + Index m_startIndex; + }; + + // StructTagConverterBase + virtual SlangResult convertCurrent(const void* in, void*& out) SLANG_OVERRIDE; + virtual SlangResult convertCurrentArray(const void* in, Index count, void*& out) SLANG_OVERRIDE; + virtual SlangResult convertCurrentPtrArray(const void*const* in, Index count, void**& out) SLANG_OVERRIDE; + + /// Convert all the referenced items starting at in. + /// Items that are converted are stored on the m_convertStack. + /// The BitField records a bit for every 'field' (where exts is the 0 field) where there is something converted. + /// If the BitField has no bits set -> then nothing was converted and can be used as is. + /// To write the converted data, use setContainedConverted. + /// + /// NOTE! This method adds items to the end of the m_convertStack, it is the responsibility of the caller to clean up + /// This can be made simpler by just using ScopeStack. + SlangResult maybeConvertCurrentContained(const StructTagType* structType, const void* in, BitField* outFieldsSet); + + /// For every fieldSet bit set, copys over the data held in the m_convertStack (indexed from stackStartIndex). + void setContainedConverted(const StructTagType* structType, Index stackIndex, BitField fieldsSet, void* dst); + + /// Ctor. The sink and arena are optional. If the arena isn't set then it is not possible to copy convert anything + /// and so if a copy convert is required, it will fail. + /// The sink is optional - if it's set failures will occur silently. + LazyStructTagConverter(StructTagSystem* system, MemoryArena* arena, DiagnosticSink* sink): + Super(system, arena, sink) + { + } + +protected: + + /// Used to hold pointers to things that have been converted. + List m_convertStack; +}; + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_STRUCT_TAG_CONVERTER_H diff --git a/source/compiler-core/slang-struct-tag-system.cpp b/source/compiler-core/slang-struct-tag-system.cpp new file mode 100644 index 000000000..405d17360 --- /dev/null +++ b/source/compiler-core/slang-struct-tag-system.cpp @@ -0,0 +1,126 @@ +#include "slang-struct-tag-system.h" + + +namespace Slang { + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StructTagCategoryInfo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +StructTagCategoryInfo::~StructTagCategoryInfo() +{ + for (auto type : m_types) + { + if (type) + { + type->~StructTagType(); + } + } + +} + +void StructTagCategoryInfo::addType(StructTagType* type) +{ + auto typeIndex = StructTagUtil::getTypeIndex(type->m_tag); + + if (typeIndex >= m_types.getCount()) + { + Index prevCount = m_types.getCount(); + m_types.setCount(typeIndex + 1); + // Zero it + ::memset(m_types.getBuffer() + prevCount, 0, sizeof(StructTagType*) * (m_types.getCount() - prevCount)); + } + + SLANG_ASSERT(m_types[typeIndex] == nullptr); + m_types[typeIndex] = type; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StructTagSystem !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +StructTagSystem::~StructTagSystem() +{ + for (auto category : m_categories) + { + if (category) + { + category->~StructTagCategoryInfo(); + } + } +} + +StructTagCategoryInfo* StructTagSystem::getCategoryInfo(slang::StructTagCategory category) +{ + const Index index = Index(category); + return (index < m_categories.getCount()) ? m_categories[index] : nullptr; +} + +StructTagCategoryInfo* StructTagSystem::addCategoryInfo(slang::StructTagCategory category, const String& name) +{ + StructTagCategoryInfo* categoryInfo = new (m_arena.allocateAligned(sizeof(StructTagCategoryInfo), SLANG_ALIGN_OF(StructTagCategoryInfo))) StructTagCategoryInfo(category, name); + + const Index index = Index(category); + + if (index >= m_categories.getCount()) + { + m_categories.setCount(index + 1); + } + m_categories[index] = categoryInfo; + return categoryInfo; +} + +StructTagType* StructTagSystem::addType(slang::StructTag tag, const String& name, size_t sizeInBytes) +{ + auto category = StructTagUtil::getCategory(tag); + auto categoryInfo = getCategoryInfo(category); + + auto structType = new (m_arena.allocate()) StructTagType(tag, name, sizeInBytes); + categoryInfo->addType(structType); + + return structType; +} + +StructTagType* StructTagSystem::getType(slang::StructTag tag) +{ + const auto category = StructTagUtil::getCategory(tag); + auto categoryInfo = getCategoryInfo(category); + if (categoryInfo) + { + auto typeIndex = StructTagUtil::getTypeIndex(tag); + return categoryInfo->getType(typeIndex); + } + + return nullptr; +} + +void StructTagSystem::appendName(slang::StructTag tag, StringBuilder& out) +{ + auto info = StructTagUtil::getTypeInfo(tag); + + auto categoryInfo = getCategoryInfo(info.category); + if (categoryInfo) + { + out << categoryInfo->m_name; + } + else + { + out << Index(info.category); + } + + out << "::"; + + auto type = categoryInfo->getType(info.typeIndex); + + if (type) + { + out << type->m_name; + } + else + { + out << "~" << Index(info.typeIndex) << "~"; + } + + out << "_"; + out << Index(info.majorVersion); + out << "."; + out << Index(info.minorVersion); +} + +} // namespace Slang diff --git a/source/compiler-core/slang-struct-tag-system.h b/source/compiler-core/slang-struct-tag-system.h new file mode 100644 index 000000000..20581cec7 --- /dev/null +++ b/source/compiler-core/slang-struct-tag-system.h @@ -0,0 +1,259 @@ +#ifndef SLANG_COMPILER_CORE_STRUCT_TAG_SYSTEM_H +#define SLANG_COMPILER_CORE_STRUCT_TAG_SYSTEM_H + +#include "../../slang.h" + +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" + +#include "../core/slang-smart-pointer.h" + +#include "../core/slang-dictionary.h" +#include "../core/slang-semantic-version.h" +#include "../core/slang-memory-arena.h" + +namespace Slang { + +struct StructTagUtil +{ + struct TypeInfo + { + slang::StructTagKind kind; ///< The kind + slang::StructTagCategory category; ///< The category + uint8_t typeIndex; ///< Type index for the category type + uint8_t majorVersion; ///< The major semantic version + uint8_t minorVersion; ///< The minor semantic version + }; + + /// True if it's a primary struct + static bool isPrimary(slang::StructTag tag) { return (slang::StructTagInt(tag) & slang::kStructTagPrimaryMask) != 0; } + /// True if it's an extension + static bool isExtension(slang::StructTag tag) { return !isPrimary(tag); } + + inline static TypeInfo getTypeInfo(slang::StructTag tag); + + /// Get the category and type from the value + static slang::StructTagInt getCategoryTypeIndex(slang::StructTag tag) { return (slang::StructTagInt(tag) & slang::kStructTagCategoryTypeIndexMask) >> slang::kStructTagCategoryTypeIndexShift; } + + /// Get the type index + static Index getTypeIndex(slang::StructTag tag) { return Index((slang::StructTagInt(tag) & slang::kStructTagTypeIndexMask) >> slang::kStructTagTypeIndexShift); } + + /// Get the category + static slang::StructTagCategory getCategory(slang::StructTag tag) { return slang::StructTagCategory((slang::StructTagInt(tag) & slang::kStructTagCategoryMask) >> slang::kStructTagCategoryShift); } + + /// They are the same type and have same major version + static bool areSameMajorType(slang::StructTag a, slang::StructTag b) + { + const auto typeMask = slang::StructTagInt(slang::kStructTagCategoryTypeMajorMask); + return ((slang::StructTagInt(a) ^ slang::StructTagInt(b)) & typeMask) == 0; + } + + /// This will *only* determine if *just* this type is compatible for read and not if it contains other types (say in the form of extensions) + static bool isReadCompatible(slang::StructTag inTag, slang::StructTag inCurrentTag) + { + // Uniquely identifies the 'type'. + const auto typeMask = slang::StructTagInt(slang::kStructTagCategoryTypeMajorMask); + const auto minorMask = slang::StructTagInt(slang::kStructTagMinorMask); + + const auto tag = slang::StructTagInt(inTag); + const auto currentTag = slang::StructTagInt(inCurrentTag); + + // If they are the same type, and the input types minor is greater than equal to current minor we can accept for read (singly) + return ((tag ^ currentTag) & typeMask) == 0 && (tag & minorMask) >= (currentTag & minorMask); + } +}; + +/* static */ inline StructTagUtil::TypeInfo StructTagUtil::getTypeInfo(slang::StructTag tag) +{ + const auto intTag = slang::StructTagInt(tag); + + TypeInfo info; + info.kind = (intTag & slang::kStructTagPrimaryMask) ? slang::StructTagKind::Primary : slang::StructTagKind::Extension; + info.category = getCategory(tag); + info.typeIndex = uint8_t(getTypeIndex(tag)); + info.majorVersion = uint8_t((intTag & slang::kStructTagMajorMask) >> slang::kStructTagMajorShift); + info.minorVersion = uint8_t((intTag & slang::kStructTagMinorMask) >> slang::kStructTagMinorShift); + return info; +} + +/// We can have a 'field' that is made up of 2 elements, so we have two entries. +/// If m_countType is Unknown, then the entry can be ignored +struct StructTagField +{ + enum class Type : uint8_t + { + Unknown, + TaggedStruct, + PtrTaggedStruct, + PtrPtrTaggedStruct, + I32, + I64, + }; + + SLANG_FORCE_INLINE static bool isInRange(Type type, Type start, Type end) { return Index(type) >= Index(start) && Index(type) <= Index(end); } + + /// True if it's an integral + static bool isIntegral(Type type) { return isInRange(type, Type::I32, Type::I64); } + /// True if it's a pointer or pointer to a pointer + static bool isPtrLike(Type type) { return isInRange(type, Type::PtrTaggedStruct, Type::PtrPtrTaggedStruct); } + + Type m_type; + Type m_countType; + uint16_t m_offset; + uint16_t m_countOffset; +}; + +struct StructTagType +{ +public: + typedef StructTagField Field; + + StructTagType(slang::StructTag tag, const String& name, size_t sizeInBytes): + m_tag(tag), + m_name(name), + m_sizeInBytes(slang::StructSize(sizeInBytes)) + { + } + + slang::StructTag m_tag; ///< The type/current version + String m_name; ///< The name of the type + slang::StructSize m_sizeInBytes; ///< The size of this version in bytes + + List m_fields; ///< Fields that need to be followed +}; + +namespace StructTagTypeTraits +{ + typedef StructTagField Field; + typedef Field::Type Type; + + // Helper that works out what a pointer to the inner type is. + SLANG_FORCE_INLINE Type getPtrType(Type innerType) + { + switch (innerType) + { + case Type::TaggedStruct: return Type::PtrTaggedStruct; + case Type::PtrTaggedStruct: return Type::PtrPtrTaggedStruct; + default: return Type::Unknown; + } + } + + template + SLANG_FORCE_INLINE uint16_t getOffset(T* obj, const F* f) + { + return uint16_t((const char*)f - (const char*)obj); + } + + // Use `substitution failure is not an error` (SFINAE) to detect tagged struct types + template + struct IsTaggedStruct + { + typedef int32_t True; + typedef int8_t False; + + template + static True check(typename C::Tag*); + template + static False check(...); + + // Is != 0 if it is a TaggedStruct type + enum { kValue = int(sizeof(check(nullptr)) == sizeof(True)) }; + }; + + template + struct Impl { static Type getType() { return IsTaggedStruct::kValue ? Type::TaggedStruct : Type::Unknown; } }; + + // Doesn't currently handle fixed arrays, but could be added quite easily, with say a byte for the fixed size. + + // Integer types + // We won't bother with sign for now + template <> struct Impl { static Type getType() { return Type::I64; } }; + template <> struct Impl { static Type getType() { return Type::I64; } }; + template <> struct Impl { static Type getType() { return Type::I32; } }; + template <> struct Impl { static Type getType() { return Type::I32; } }; + + // StructTag is used to indicate it can be any 'tagged struct type' + template <> struct Impl { static Type getType() { return Type::TaggedStruct; } }; + + // Pointer + template struct Impl { static Type getType() { return getPtrType(Impl::getType()); } }; + + /// f1 should hold the count + template + Field getFieldWithCount(const T* obj, const F0* ptr, const F1* count) + { + Field field; + field.m_type = Impl::getType(); + field.m_countType = Impl::getType(); + field.m_offset = getOffset(obj, ptr); + field.m_countOffset = getOffset(obj, count); + + SLANG_ASSERT(StructTagField::isPtrLike(field.m_type)); + SLANG_ASSERT(StructTagField::isIntegral(field.m_countType)); + + return field; + } +} + +class StructTagCategoryInfo +{ +public: + + /// Add a type. Will replace a type if there is already one setup for the m_Type + void addType(StructTagType* type); + + /// Get a type + StructTagType* getType(Index typeIndex) const { return typeIndex < m_types.getCount() ? m_types[typeIndex] : nullptr; } + + StructTagCategoryInfo(slang::StructTagCategory category, const String& name) : + m_category(category), + m_name(name) + { + } + ~StructTagCategoryInfo(); + + slang::StructTagCategory m_category; ///< The category type + String m_name; ///< The name + + // All the types in this category + List m_types; +}; + +/* Holds the information about TaggedStruct types. Use the StructTagConverter to actually convert to conforming +types. */ +class StructTagSystem : public RefObject +{ +public: + + /// Add a category + StructTagCategoryInfo* addCategoryInfo(slang::StructTagCategory category, const String& name); + StructTagCategoryInfo* getCategoryInfo(slang::StructTagCategory category); + + /// Get struct type + StructTagType* getType(slang::StructTag tag); + + /// Add the struct type + StructTagType* addType(slang::StructTag tag, const String& name, size_t sizeInBytes); + + void appendName(slang::StructTag tag, StringBuilder& out); + String getName(slang::StructTag tag) { StringBuilder buf; appendName(tag, buf); return buf.ProduceString(); } + + StructTagSystem(): + m_arena(1024) + { + } + + ~StructTagSystem(); + +protected: + + /// Arena stores all of the types + MemoryArena m_arena; + + /// All of the categories + List m_categories; +}; + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_STRUCT_TAG_SYSTEM_H diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 0ee63fd09..e6684108b 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -11,6 +11,8 @@ #include "../compiler-core/slang-include-system.h" #include "../compiler-core/slang-command-line-args.h" +#include "../compiler-core/slang-struct-tag-system.h" + #include "../core/slang-std-writers.h" #include "../../slang-com-ptr.h" @@ -1433,7 +1435,7 @@ namespace Slang Type* const* args, DiagnosticSink* sink); - /// Add a mew target amd return its index. + /// Add a mew target and return its index. UInt addTarget( CodeGenTarget target); @@ -2318,6 +2320,9 @@ namespace Slang /// Get the built in linkage -> handy to get the stdlibs from Linkage* getBuiltinLinkage() const { return m_builtinLinkage; } + /// Get the Abi system used for managing binary compatibility of interface types (outside of COM mechanisms) + StructTagSystem* getStructTagSystem() const { return m_structTagSystem; } + void init(); void addBuiltinSource( @@ -2343,6 +2348,8 @@ namespace Slang /// Linkage used for all built-in (stdlib) code. RefPtr m_builtinLinkage; + RefPtr m_structTagSystem; + String m_downstreamCompilerPaths[int(PassThroughMode::CountOf)]; ///< Paths for each pass through String m_languagePreludes[int(SourceLanguage::CountOf)]; ///< Prelude for each source language PassThroughMode m_defaultDownstreamCompilers[int(SourceLanguage::CountOf)]; diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index eed172bbf..8d82ca1b7 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -23,6 +23,7 @@ #include "../core/slang-writer.h" #include "../compiler-core/slang-source-loc.h" +#include "../compiler-core/slang-struct-tag-converter.h" #include "slang-ast-dump.h" @@ -114,6 +115,32 @@ const char* getBuildTagString() return SLANG_TAG_VERSION; } +static RefPtr _createStructTagSystem() +{ + RefPtr system = new StructTagSystem; + + { +#define SLANG_STRUCT_TAG_ADD_CATEGORY(x) system->addCategoryInfo(slang::StructTagCategory::x, #x); + SLANG_STRUCT_TAG_CATEGORIES(SLANG_STRUCT_TAG_ADD_CATEGORY) + } + + { +#define SLANG_STRUCT_TAG_ADD_TYPE(X) system->addType(slang::X::kStructTag, "slang::" #X, sizeof(slang::X)); +SLANG_TAGGED_STRUCTS(SLANG_STRUCT_TAG_ADD_TYPE) + + // Add field that references more tagged structs + { + slang::SessionDesc desc; + auto field = StructTagTypeTraits::getFieldWithCount(&desc, &desc.targets, &desc.targetCount); + + auto type = system->getType(slang::SessionDesc::kStructTag); + type->m_fields.add(field); + } + } + + return system; +} + void Session::init() { SLANG_ASSERT(BaseTypeInfo::check()); @@ -131,6 +158,8 @@ void Session::init() m_sharedASTBuilder = new SharedASTBuilder; m_sharedASTBuilder->init(this); + m_structTagSystem = _createStructTagSystem(); + // Use to create a ASTBuilder RefPtr builtinAstBuilder(new ASTBuilder(m_sharedASTBuilder, "m_builtInLinkage::m_astBuilder")); @@ -438,42 +467,45 @@ ISlangUnknown* Session::getInterface(const Guid& guid) } SLANG_NO_THROW SlangResult SLANG_MCALL Session::createSession( - slang::SessionDesc const& desc, + slang::SessionDesc const& inDesc, slang::ISession** outSession) { + MemoryArena arena(1024); + + LazyStructTagConverter converter(getStructTagSystem(), &arena, nullptr); + + const slang::SessionDesc* desc = nullptr; + SLANG_RETURN_ON_FAIL(converter.convertToCurrent(&inDesc, &desc)); + RefPtr astBuilder(new ASTBuilder(m_sharedASTBuilder, "Session::astBuilder")); RefPtr linkage = new Linkage(this, astBuilder, getBuiltinLinkage()); - Int targetCount = desc.targetCount; - const uint8_t* targetDescPtr = reinterpret_cast(desc.targets); - for(Int ii = 0; ii < targetCount; ++ii) { - slang::TargetDesc targetDesc; - // Copy the size field first. - memcpy(&targetDesc.structureSize, targetDescPtr, sizeof(size_t)); - // Copy the entire desc structure. - memcpy(&targetDesc, targetDescPtr, targetDesc.structureSize); - linkage->addTarget(targetDesc); - targetDescPtr += targetDesc.structureSize; + const Index targetCount = Index(desc->targetCount); + for(Index ii = 0; ii < targetCount; ++ii) + { + const auto& targetDesc = desc->targets[ii]; + linkage->addTarget(targetDesc); + } } - if(desc.flags & slang::kSessionFlag_FalcorCustomSharedKeywordSemantics) + if(desc->flags & slang::kSessionFlag_FalcorCustomSharedKeywordSemantics) { linkage->m_useFalcorCustomSharedKeywordSemantics = true; } - linkage->setMatrixLayoutMode(desc.defaultMatrixLayoutMode); + linkage->setMatrixLayoutMode(desc->defaultMatrixLayoutMode); - Int searchPathCount = desc.searchPathCount; + Int searchPathCount = desc->searchPathCount; for(Int ii = 0; ii < searchPathCount; ++ii) { - linkage->addSearchPath(desc.searchPaths[ii]); + linkage->addSearchPath(desc->searchPaths[ii]); } - Int macroCount = desc.preprocessorMacroCount; + Int macroCount = desc->preprocessorMacroCount; for(Int ii = 0; ii < macroCount; ++ii) { - auto& macro = desc.preprocessorMacros[ii]; + auto& macro = desc->preprocessorMacros[ii]; linkage->addPreprocessorDefine(macro.name, macro.value); } diff --git a/tools/gfx/slang-context.h b/tools/gfx/slang-context.h index 39a457cf6..0a878e0f5 100644 --- a/tools/gfx/slang-context.h +++ b/tools/gfx/slang-context.h @@ -41,8 +41,10 @@ namespace gfx targetDesc.floatingPointMode = desc.floatingPointMode; targetDesc.lineDirectiveMode = desc.lineDirectiveMode; targetDesc.flags = desc.targetFlags; - slangSessionDesc.targetCount = 1; + slangSessionDesc.targets = &targetDesc; + slangSessionDesc.targetCount = 1; + SLANG_RETURN_ON_FAIL(globalSession->createSession(slangSessionDesc, session.writeRef())); return SLANG_OK; } diff --git a/tools/slang-test/unit-test-struct-tag.cpp b/tools/slang-test/unit-test-struct-tag.cpp new file mode 100644 index 000000000..cc22d9b1d --- /dev/null +++ b/tools/slang-test/unit-test-struct-tag.cpp @@ -0,0 +1,255 @@ +// unit-test-struct-tag.cpp + +#include "../../source/core/slang-memory-arena.h" + +#include +#include + +#include "test-context.h" + +#include "../../source/core/slang-random-generator.h" +#include "../../source/core/slang-list.h" + +#include "../../source/compiler-core/slang-struct-tag-system.h" +#include "../../source/compiler-core/slang-struct-tag-converter.h" + +using namespace Slang; + +namespace { // anonymous + +#define TAGGED_STRUCTS(x) \ + x(A) \ + x(B) \ + x(ExtensionA) \ + x(Desc) + +enum class TaggedStruct +{ + TAGGED_STRUCTS(SLANG_STRUCT_TAG_ENUM) +}; + +#define PRIMARY_TAGGED_STRUCT(TYPE_NAME, MAJOR, MINOR) SLANG_PRIMARY_TAGGED_STRUCT_IMPL(TYPE_NAME##MAJOR##_##MINOR, slang::StructTagCategory::Core, TaggedStruct::TYPE_NAME, MAJOR, MINOR) +#define EXTENSION_TAGGED_STRUCT(TYPE_NAME, MAJOR, MINOR) SLANG_EXTENSION_TAGGED_STRUCT_IMPL(TYPE_NAME##MAJOR##_##MINOR, slang::StructTagCategory::Core, TaggedStruct::TYPE_NAME, MAJOR, MINOR) + +struct A0_0 +{ + PRIMARY_TAGGED_STRUCT(A, 0, 0) + + int a = 10; +}; + +struct A0_1 +{ + PRIMARY_TAGGED_STRUCT(A, 0, 1) + + int a = 10; + int b = 20; +}; + +struct B0_0 +{ + PRIMARY_TAGGED_STRUCT(B, 0, 0) + + float v = -1.0f; +}; + +struct B0_1 +{ + PRIMARY_TAGGED_STRUCT(B, 0, 1) + + float v = -1.0f; + float u = 20.0f; +}; + +struct A1_0 +{ + PRIMARY_TAGGED_STRUCT(A, 1, 0) +}; + + +struct ExtensionA0_0 +{ + EXTENSION_TAGGED_STRUCT(ExtensionA, 0, 0) + + int b = 20; +}; + +// I guess a Desc could be implemented as 'Primary' or 'Extension' with what we have, but probably makes more sense +// for 'Desc' like things to be primary + +struct Desc0_0 +{ + EXTENSION_TAGGED_STRUCT(Desc, 0, 0) + int a = 1; +}; + +struct Desc0_1 +{ + EXTENSION_TAGGED_STRUCT(Desc, 0, 1) + int a = 1; + int b = 2; +}; + + +struct A0_2 +{ + PRIMARY_TAGGED_STRUCT(A, 0, 2) + + int a = 10; + int b = 20; + + Desc0_0* descs = nullptr; + Index descsCount = 0; +}; + +} // anonymous + +static RefPtr _createSystem() +{ + RefPtr system = new StructTagSystem; + + { +#define SLANG_STRUCT_TAG_ADD_CATEGORY(x) system->addCategoryInfo(slang::StructTagCategory::x, #x); + SLANG_STRUCT_TAG_CATEGORIES(SLANG_STRUCT_TAG_ADD_CATEGORY) + } + + return system; +} + +static void structTagUnitTest() +{ + SourceManager sourceManager; + sourceManager.initialize(nullptr, nullptr); + + DiagnosticSink sink; + sink.init(&sourceManager, nullptr); + + { + StructTagUtil::TypeInfo info = StructTagUtil::getTypeInfo(A0_1::kStructTag); + + SLANG_CHECK(info.kind == slang::StructTagKind::Primary); + SLANG_CHECK(info.category == slang::StructTagCategory::Core); + SLANG_CHECK(info.majorVersion == 0); + SLANG_CHECK(info.minorVersion == 1); + } + + { + StructTagUtil::TypeInfo info = StructTagUtil::getTypeInfo(ExtensionA0_0::kStructTag); + + SLANG_CHECK(info.kind == slang::StructTagKind::Extension); + SLANG_CHECK(info.category == slang::StructTagCategory::Core); + SLANG_CHECK(info.majorVersion == 0); + SLANG_CHECK(info.minorVersion == 0); + } + + { + // Set up the system with the versions + auto system = _createSystem(); + + system->addType(B0_1::kStructTag, "B", sizeof(B0_1)); + system->addType(A0_1::kStructTag, "A", sizeof(A0_1)); + system->addType(ExtensionA0_0::kStructTag, "ExtensionA", sizeof(ExtensionA0_0)); + + { + //The null operation means we are converting everything that is current (as defined by the system) + + A0_1 a; + ExtensionA0_0 extA; + const slang::StructTag* exts[] = { &extA.structTag }; + a.exts = exts; + a.extsCount = SLANG_COUNT_OF(exts); + + + LazyStructTagConverter converter(system, nullptr, nullptr); + + auto dstA = converter.convertToCurrent(&a); + + // We shouldn't have to convert anything, so we should be done + SLANG_CHECK(dstA == &a); + } + + { + A0_0 a; + ExtensionA0_0 extA; + const slang::StructTag* exts[] = { &extA.structTag }; + a.exts = exts; + a.extsCount = SLANG_COUNT_OF(exts); + + + // Actually do a conversion from past + MemoryArena arena(1024); + LazyStructTagConverter converter(system, &arena, nullptr); + + auto dstA = converter.convertToCurrent(&a); + + SLANG_CHECK(dstA != nullptr); + SLANG_CHECK(dstA->a == 10 && dstA->b == 0); + + SLANG_CHECK(dstA->extsCount == 1); + SLANG_CHECK(((ExtensionA0_0*)dstA->exts[0])->b == 20); + } + } + + // Let's try going from the future backwards + { + // Set up the system with the versions + auto system = _createSystem(); + + system->addType(B0_1::kStructTag, "B", sizeof(B0_1)); + system->addType(A0_2::kStructTag, "A", sizeof(A0_2)); + system->addType(ExtensionA0_0::kStructTag, "ExtensionA", sizeof(ExtensionA0_0)); + system->addType(Desc0_0::kStructTag, "Desc", sizeof(Desc0_0)); + + // Add the fields + { + auto type = system->getType(A0_2::kStructTag); + A0_2 a; + auto field = StructTagTypeTraits::getFieldWithCount(&a, &a.descs, &a.descsCount); + type->m_fields.add(field); + } + + // + + A0_2 a; + Desc0_1 descs[2]; + descs[0].a = 27; + descs[1].a = -1; + + a.descs = (Desc0_0*)descs; + a.descsCount = SLANG_COUNT_OF(descs); + + // Actually do a conversion from future + MemoryArena arena(1024); + LazyStructTagConverter converter(system, &arena, nullptr); + + auto dstA = converter.convertToCurrent(&a); + + SLANG_CHECK(dstA->descsCount == a.descsCount); + + SLANG_CHECK(dstA->descs[0].a == 27 && dstA->descs[1].a == -1); + } + + // Lets try some invalid conversions + + { + // Set up the system with the versions + auto system = _createSystem(); + + system->addType(B0_1::kStructTag, "B", sizeof(B0_1)); + system->addType(A0_2::kStructTag, "A", sizeof(A0_2)); + system->addType(ExtensionA0_0::kStructTag, "ExtensionA", sizeof(ExtensionA0_0)); + system->addType(Desc0_0::kStructTag, "Desc", sizeof(Desc0_0)); + + A1_0 a; + + MemoryArena arena(1024); + LazyStructTagConverter converter(system, &arena, &sink); + + void* dst; + SLANG_CHECK(SLANG_FAILED(converter.convertCurrent(&a, dst))); + + } + +} + +SLANG_UNIT_TEST("StructTag", structTagUnitTest); -- cgit v1.2.3