From dd9d24d29c4a9e05a4510eb9959fafa0ed36618b Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 28 Feb 2025 22:46:56 -0800 Subject: Allow partial specialization of existential arguments. (#6487) * Allow partial specialization of existential arguments. * Fix. * Add test case for improved diagnostics. * Fix compile error. * Fix tests. * Fix. * Fix test. * Fix compile issue. * Fix typo. * Address comment. --- source/slang/slang-diagnostic-defs.h | 6 +- .../slang/slang-ir-generics-lowering-context.cpp | 13 ++- source/slang/slang-ir-generics-lowering-context.h | 3 +- source/slang/slang-ir-layout.cpp | 1 + source/slang/slang-ir-legalize-types.cpp | 8 +- source/slang/slang-ir-lower-reinterpret.cpp | 2 +- source/slang/slang-ir-specialize.cpp | 111 ++++++++++++++------- source/slang/slang-ir-util.h | 1 + source/slang/slang-ir-witness-table-wrapper.cpp | 40 +++++--- source/slang/slang-legalize-types.cpp | 30 ++++-- source/slang/slang-legalize-types.h | 2 - 11 files changed, 148 insertions(+), 69 deletions(-) (limited to 'source') diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 761dc4768..85ee545a4 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -2203,12 +2203,12 @@ DIAGNOSTIC( Error, typeDoesNotFitAnyValueSize, "type '$0' does not fit in the size required by its conforming interface.") -DIAGNOSTIC(41012, Note, typeAndLimit, "sizeof($0) is $1, limit is $2") +DIAGNOSTIC(-1, Note, typeAndLimit, "sizeof($0) is $1, limit is $2") DIAGNOSTIC( - 41012, + 41014, Error, typeCannotBePackedIntoAnyValue, - "type '$0' contains fields that cannot be packed into an AnyValue.") + "type '$0' contains fields that cannot be packed into ordinary bytes for dynamic dispatch.") DIAGNOSTIC( 41020, Error, diff --git a/source/slang/slang-ir-generics-lowering-context.cpp b/source/slang/slang-ir-generics-lowering-context.cpp index 097fa58b8..199eae2fc 100644 --- a/source/slang/slang-ir-generics-lowering-context.cpp +++ b/source/slang/slang-ir-generics-lowering-context.cpp @@ -405,12 +405,23 @@ bool SharedGenericsLoweringContext::doesTypeFitInAnyValue( IRType* concreteType, IRInterfaceType* interfaceType, IRIntegerValue* outTypeSize, - IRIntegerValue* outLimit) + IRIntegerValue* outLimit, + bool* outIsTypeOpaque) { auto anyValueSize = getInterfaceAnyValueSize(interfaceType, interfaceType->sourceLoc); if (outLimit) *outLimit = anyValueSize; + if (!areResourceTypesBindlessOnTarget(targetProgram->getTargetReq())) + { + IRType* opaqueType = nullptr; + if (isOpaqueType(concreteType, &opaqueType)) + { + if (outIsTypeOpaque) + *outIsTypeOpaque = true; + return false; + } + } IRSizeAndAlignment sizeAndAlignment; Result result = getNaturalSizeAndAlignment(targetProgram->getOptionSet(), concreteType, &sizeAndAlignment); diff --git a/source/slang/slang-ir-generics-lowering-context.h b/source/slang/slang-ir-generics-lowering-context.h index baa1f7757..62848c4b7 100644 --- a/source/slang/slang-ir-generics-lowering-context.h +++ b/source/slang/slang-ir-generics-lowering-context.h @@ -98,7 +98,8 @@ struct SharedGenericsLoweringContext IRType* concreteType, IRInterfaceType* interfaceType, IRIntegerValue* outTypeSize = nullptr, - IRIntegerValue* outLimit = nullptr); + IRIntegerValue* outLimit = nullptr, + bool* outIsTypeOpaque = nullptr); }; List getWitnessTablesFromInterfaceType(IRModule* module, IRInst* interfaceType); diff --git a/source/slang/slang-ir-layout.cpp b/source/slang/slang-ir-layout.cpp index 7ce19bf67..558877aaf 100644 --- a/source/slang/slang-ir-layout.cpp +++ b/source/slang/slang-ir-layout.cpp @@ -341,6 +341,7 @@ static Result _calcSizeAndAlignment( case kIROp_ComPtrType: case kIROp_NativeStringType: case kIROp_HLSLConstBufferPointerType: + case kIROp_RaytracingAccelerationStructureType: { *outSizeAndAlignment = IRSizeAndAlignment(kPointerSize, kPointerSize); return SLANG_OK; diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp index 9b857f899..3d6f18569 100644 --- a/source/slang/slang-ir-legalize-types.cpp +++ b/source/slang/slang-ir-legalize-types.cpp @@ -2053,15 +2053,13 @@ static LegalVal coerceToLegalType(IRTypeLegalizationContext* context, LegalType static LegalVal legalizeUndefined(IRTypeLegalizationContext* context, IRInst* inst) { - List opaqueTypes; - if (isOpaqueType(inst->getFullType(), opaqueTypes)) + IRType* opaqueType = nullptr; + if (isOpaqueType(inst->getFullType(), &opaqueType)) { - auto opaqueType = opaqueTypes[0]; - auto containerType = opaqueTypes.getCount() > 1 ? opaqueTypes[1] : opaqueType; SourceLoc loc = findBestSourceLocFromUses(inst); if (!loc.isValid()) - loc = getDiagnosticPos(containerType); + loc = getDiagnosticPos(opaqueType); context->m_sink->diagnose(loc, Diagnostics::useOfUninitializedOpaqueHandle, opaqueType); } diff --git a/source/slang/slang-ir-lower-reinterpret.cpp b/source/slang/slang-ir-lower-reinterpret.cpp index 1b733d832..88c288f28 100644 --- a/source/slang/slang-ir-lower-reinterpret.cpp +++ b/source/slang/slang-ir-lower-reinterpret.cpp @@ -79,7 +79,7 @@ struct ReinterpretLoweringContext Slang::Diagnostics::typeCannotBePackedIntoAnyValue, toType); } - if (fromTypeSize != toTypeSize && cantPack == false) + if (fromTypeSize != toTypeSize && !cantPack && !as(fromType)) { sink->diagnose( inst->sourceLoc, diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp index a9b0d4412..a200a907b 100644 --- a/source/slang/slang-ir-specialize.cpp +++ b/source/slang/slang-ir-specialize.cpp @@ -1373,11 +1373,16 @@ struct SpecializationContext if (!isExistentialType(param->getDataType())) continue; + // Is arg in the most simplified form for specialization? If not we are + // not ready to consider specialization yet. + if (!isSimplifiedExistentialArg(arg)) + return false; + // We *cannot* specialize unless the argument value corresponding // to such a parameter is one we can specialize. // if (!canSpecializeExistentialArg(arg)) - return false; + continue; argumentNeedSpecialization = true; } @@ -1416,7 +1421,6 @@ struct SpecializationContext auto arg = inst->getArg(argCounter++); if (!isExistentialType(param->getDataType())) continue; - if (auto makeExistential = as(arg)) { // Note that we use the *type* stored in the @@ -1426,25 +1430,32 @@ struct SpecializationContext // call sites that pass in the exact same argument). // auto val = makeExistential->getWrappedValue(); - auto valType = val->getFullType(); - key.vals.add(valType); - - // We are also including the witness table in the key. - // This isn't required with our current language model, - // since a given type can only conform to a given interface - // in one way (so there can be only one witness table). - // That means that the `valType` and the existential - // type of `param` above should uniquely determine - // the witness table we see. - // - // There are forward-looking cases where supporting - // "overlapping conformances" could be required, and - // there is low incremental cost to future-proofing - // this code, so we go ahead and add the witness - // table even if it is redundant. - // - auto witnessTable = makeExistential->getWitnessTable(); - key.vals.add(witnessTable); + auto valType = val->getDataType(); + if (isCompileTimeConstantType(valType)) + { + key.vals.add(valType); + + // We are also including the witness table in the key. + // This isn't required with our current language model, + // since a given type can only conform to a given interface + // in one way (so there can be only one witness table). + // That means that the `valType` and the existential + // type of `param` above should uniquely determine + // the witness table we see. + // + // There are forward-looking cases where supporting + // "overlapping conformances" could be required, and + // there is low incremental cost to future-proofing + // this code, so we go ahead and add the witness + // table even if it is redundant. + // + auto witnessTable = makeExistential->getWitnessTable(); + key.vals.add(witnessTable); + } + else + { + key.vals.add(param->getDataType()); + } } else if (auto wrapExistential = as(arg)) { @@ -1508,7 +1519,11 @@ struct SpecializationContext if (auto makeExistential = as(arg)) { auto val = makeExistential->getWrappedValue(); - newArgs.add(val); + auto valType = val->getDataType(); + if (isCompileTimeConstantType(valType)) + newArgs.add(val); + else + newArgs.add(arg); } else if (auto wrapExistential = as(arg)) { @@ -1634,6 +1649,18 @@ struct SpecializationContext return true; } + + // Returns true if `inst` is a simplified existential argument ready for specialization. + bool isSimplifiedExistentialArg(IRInst* inst) + { + if (as(inst)) + return true; + if (as(inst)) + return true; + return false; + } + + // Similarly, we want to be able to test whether an instruction // used as an argument for an existential-type parameter is // suitable for use in specialization. @@ -1760,21 +1787,33 @@ struct SpecializationContext // created. // auto valType = val->getFullType(); - auto newParam = builder->createParam(valType); - newParams.add(newParam); + if (auto extractExistentialType = as(valType)) + { + valType = extractExistentialType->getOperand(0)->getDataType(); + auto newParam = builder->createParam(valType); + newParams.add(newParam); + replacementVal = newParam; + } + else + { + auto newParam = builder->createParam(valType); + newParams.add(newParam); - // Within the body of the function we cannot just use `val` - // directly, because the existing code expects an existential - // value, including its witness table. - // - // Therefore we will create a `makeExistential(newParam, witnessTable)` - // in the body of the new function and use *that* as the replacement - // value for the original parameter (since it will have the - // correct existential type, and stores the right witness table). - // - auto newMakeExistential = - builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable); - replacementVal = newMakeExistential; + // Within the body of the function we cannot just use `val` + // directly, because the existing code expects an existential + // value, including its witness table. + // + // Therefore we will create a `makeExistential(newParam, witnessTable)` + // in the body of the new function and use *that* as the replacement + // value for the original parameter (since it will have the + // correct existential type, and stores the right witness table). + // + auto newMakeExistential = builder->emitMakeExistential( + oldParam->getFullType(), + newParam, + witnessTable); + replacementVal = newMakeExistential; + } } else if (auto oldWrapExistential = as(arg)) { diff --git a/source/slang/slang-ir-util.h b/source/slang/slang-ir-util.h index aed63da47..549981f58 100644 --- a/source/slang/slang-ir-util.h +++ b/source/slang/slang-ir-util.h @@ -83,6 +83,7 @@ IRType* getMatrixElementType(IRType* type); // True if type is a resource backing memory bool isResourceType(IRType* type); +bool isOpaqueType(IRType* type, IRType** outLeafOpaqueHandleType); // True if type is a pointer to a resource bool isPointerToResourceType(IRType* type); diff --git a/source/slang/slang-ir-witness-table-wrapper.cpp b/source/slang/slang-ir-witness-table-wrapper.cpp index f835b68c3..fabfd1611 100644 --- a/source/slang/slang-ir-witness-table-wrapper.cpp +++ b/source/slang/slang-ir-witness-table-wrapper.cpp @@ -190,19 +190,35 @@ struct GenerateWitnessTableWrapperContext // auto concreteType = witnessTable->getConcreteType(); IRIntegerValue typeSize, sizeLimit; - if (!sharedContext - ->doesTypeFitInAnyValue(concreteType, interfaceType, &typeSize, &sizeLimit)) - { - sharedContext->sink->diagnose( - concreteType, - Diagnostics::typeDoesNotFitAnyValueSize, - concreteType); - sharedContext->sink->diagnoseWithoutSourceView( - concreteType, - Diagnostics::typeAndLimit, + bool isTypeOpaque = false; + if (!sharedContext->doesTypeFitInAnyValue( concreteType, - typeSize, - sizeLimit); + interfaceType, + &typeSize, + &sizeLimit, + &isTypeOpaque)) + { + HashSet visited; + if (isTypeOpaque) + { + sharedContext->sink->diagnose( + concreteType, + Diagnostics::typeCannotBePackedIntoAnyValue, + concreteType); + } + else + { + sharedContext->sink->diagnose( + concreteType, + Diagnostics::typeDoesNotFitAnyValueSize, + concreteType); + sharedContext->sink->diagnoseWithoutSourceView( + concreteType, + Diagnostics::typeAndLimit, + concreteType, + typeSize, + sizeLimit); + } return; } diff --git a/source/slang/slang-legalize-types.cpp b/source/slang/slang-legalize-types.cpp index 7d107bbce..e26475522 100644 --- a/source/slang/slang-legalize-types.cpp +++ b/source/slang/slang-legalize-types.cpp @@ -198,31 +198,40 @@ bool isResourceType(IRType* type) return false; } -bool isOpaqueType(IRType* type, List& opaqueTypes) + +bool isOpaqueTypeImpl(IRType* type, HashSet& visited, IRType** outLeafOpaqueHandleType) { + if (visited.contains(type)) + { + if (outLeafOpaqueHandleType) + *outLeafOpaqueHandleType = type; + return true; + } + if (isResourceType(type)) { - opaqueTypes.add(type); + if (outLeafOpaqueHandleType) + *outLeafOpaqueHandleType = type; return true; } if (auto structType = as(type)) { + visited.add(type); for (auto field : structType->getFields()) { - if (isOpaqueType(field->getFieldType(), opaqueTypes)) + if (isOpaqueTypeImpl(field->getFieldType(), visited, outLeafOpaqueHandleType)) { - opaqueTypes.add(type); return true; } } + visited.remove(type); } if (auto arrayType = as(type)) { - if (isOpaqueType(arrayType->getElementType(), opaqueTypes)) + if (isOpaqueTypeImpl(arrayType->getElementType(), visited, outLeafOpaqueHandleType)) { - opaqueTypes.add(type); return true; } } @@ -233,9 +242,8 @@ bool isOpaqueType(IRType* type, List& opaqueTypes) { if (auto elementType = as(tupleType->getOperand(i))) { - if (isOpaqueType(elementType, opaqueTypes)) + if (isOpaqueTypeImpl(elementType, visited, outLeafOpaqueHandleType)) { - opaqueTypes.add(type); return true; } } @@ -245,6 +253,12 @@ bool isOpaqueType(IRType* type, List& opaqueTypes) return false; } +bool isOpaqueType(IRType* type, IRType** outLeafOpaqueHandleType) +{ + HashSet visited; + return isOpaqueTypeImpl(type, visited, outLeafOpaqueHandleType); +} + SourceLoc findBestSourceLocFromUses(IRInst* inst) { for (auto use = inst->firstUse; use; use = use->nextUse) diff --git a/source/slang/slang-legalize-types.h b/source/slang/slang-legalize-types.h index 17498ce08..ae76cbd39 100644 --- a/source/slang/slang-legalize-types.h +++ b/source/slang/slang-legalize-types.h @@ -703,8 +703,6 @@ void legalizeEmptyTypes(TargetProgram* target, IRModule* module, DiagnosticSink* bool isResourceType(IRType* type); -bool isOpaqueType(IRType* type, List& opaqueTypes); - SourceLoc findBestSourceLocFromUses(IRInst* inst); } // namespace Slang -- cgit v1.2.3