diff options
| -rw-r--r-- | source/slang/check.cpp | 164 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 23 | ||||
| -rw-r--r-- | source/slang/expr-defs.h | 24 | ||||
| -rw-r--r-- | source/slang/ir-existential.cpp | 114 | ||||
| -rw-r--r-- | source/slang/ir-existential.h | 12 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 8 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 32 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 76 | ||||
| -rw-r--r-- | source/slang/ir.h | 4 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 102 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 6 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 91 | ||||
| -rw-r--r-- | source/slang/type-defs.h | 14 | ||||
| -rw-r--r-- | source/slang/val-defs.h | 15 | ||||
| -rw-r--r-- | tests/compute/interface-local.slang | 48 | ||||
| -rw-r--r-- | tests/compute/interface-local.slang.expected.txt | 4 | ||||
| -rw-r--r-- | tests/compute/interface-param.slang | 48 | ||||
| -rw-r--r-- | tests/compute/interface-param.slang.expected.txt | 4 |
19 files changed, 753 insertions, 38 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 38a79f1b7..7c60d3cf2 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -561,6 +561,80 @@ namespace Slang return isDeclUsableAsStaticMember(decl); } + RefPtr<Expr> maybeOpenExistential(RefPtr<Expr> expr) + { + auto exprType = expr->type.type; + + if(auto declRefType = exprType->As<DeclRefType>()) + { + if(auto interfaceDeclRef = declRefType->declRef.As<InterfaceDecl>()) + { + // Is there an this-type substitution being applied, so that + // we are referencing the interface type through a concrete + // type (e.g., a type parameter constrainted to this interface)? + // + // Because of the way that substitutions need to mirror the nesting + // hierarchy of declarations, any this-type substitution pertaining + // to the chosen interface decl must be the first substitution on + // the list (which is a linked list from the "inside" out). + // + auto thisTypeSubst = interfaceDeclRef.substitutions.substitutions.As<ThisTypeSubstitution>(); + if(thisTypeSubst && thisTypeSubst->interfaceDecl == interfaceDeclRef.decl) + { + // This isn't really an existential type, because somebody + // has already filled in a this-type substitution. + } + else + { + // Okay, here is the case that matters. + // + + auto interfaceDecl = interfaceDeclRef.getDecl(); + + RefPtr<Variable> varDecl = new Variable(); + varDecl->ParentDecl = nullptr; // TODO: need to fill this in somehow! + varDecl->checkState = DeclCheckState::Checked; + varDecl->nameAndLoc.loc = expr->loc; + varDecl->initExpr = expr; + varDecl->type.type = expr->type.type; + + auto varDeclRef = makeDeclRef(varDecl.Ptr()); + + RefPtr<LetExpr> letExpr = new LetExpr(); + letExpr->decl = varDecl; + + RefPtr<ExtractExistentialType> openedType = new ExtractExistentialType(); + openedType->declRef = varDeclRef; + + RefPtr<ExtractExistentialSubtypeWitness> openedWitness = new ExtractExistentialSubtypeWitness(); + openedWitness->sub = openedType; + openedWitness->sup = expr->type.type; + openedWitness->declRef = varDeclRef; + + RefPtr<ThisTypeSubstitution> openedThisType = new ThisTypeSubstitution(); + openedThisType->outer = interfaceDeclRef.substitutions.substitutions; + openedThisType->interfaceDecl = interfaceDecl; + openedThisType->witness = openedWitness; + + DeclRef<InterfaceDecl> substDeclRef = DeclRef<InterfaceDecl>(interfaceDecl, openedThisType); + auto substDeclRefType = DeclRefType::Create(getSession(), substDeclRef); + + RefPtr<ExtractExistentialValueExpr> openedValue = new ExtractExistentialValueExpr(); + openedValue->declRef = varDeclRef; + openedValue->type = QualType(substDeclRefType); + + letExpr->body = openedValue; + letExpr->type = openedValue->type; + + return letExpr; + } + } + } + + // Default: apply the callback to the original expression; + return expr; + } + RefPtr<Expr> ConstructDeclRefExpr( DeclRef<Decl> declRef, RefPtr<Expr> baseExpr, @@ -577,6 +651,15 @@ namespace Slang { // If there was a base expression, we will have some kind of // member expression. + + // We want to check for the case where the base "expression" + // actually names a type, because in that case we are doing + // a static member reference. + // + // TODO: Should we be checking if the member is static here? + // If it isn't, should we be automatically producing a "curried" + // form (e.g., for a member function, return a value usable + // for referencing it as a free function). // if (baseExpr->type->As<TypeType>()) { @@ -1440,15 +1523,22 @@ namespace Slang // Trying to convert to an interface type. // // We will allow this if the type conforms to the interface. - if (DoesTypeConformToInterface(fromType, interfaceDeclRef)) + + if(auto witness = tryGetInterfaceConformanceWitness(fromType, interfaceDeclRef)) { if (outToExpr) - *outToExpr = CreateImplicitCastExpr(toType, fromExpr); + *outToExpr = createCastToInterfaceExpr(toType, fromExpr, witness); if (outCost) *outCost = kConversionCost_CastToInterface; return true; } } + + // Note: The following seems completely broken, and we should be using + // a `fromTypeDeclRef` here for the case when casting *from* a generic + // type parameter to an interface type... + // +#if 0 else if (auto genParamDeclRef = toTypeDeclRef.As<GenericTypeParamDecl>()) { // We need to enumerate the constraints placed on this type by its outer @@ -1483,6 +1573,7 @@ namespace Slang } } +#endif } @@ -1721,6 +1812,25 @@ namespace Slang return castExpr; } + /// Create an "up-cast" from a value to an interface type + /// + /// This operation logically constructs an "existential" value, + /// which packages up the value, its type, and the witness + /// of its conformance to the interface. + /// + RefPtr<Expr> createCastToInterfaceExpr( + RefPtr<Type> toType, + RefPtr<Expr> fromExpr, + RefPtr<Val> witness) + { + RefPtr<CastToInterfaceExpr> expr = new CastToInterfaceExpr(); + expr->loc = fromExpr->loc; + expr->type = QualType(toType); + expr->valueArg = fromExpr; + expr->witnessArg = witness; + return expr; + } + // Perform type coercion, and emit errors if it isn't possible RefPtr<Expr> Coerce( RefPtr<Type> toType, @@ -7900,36 +8010,24 @@ namespace Slang // deal with this cases here, even if they are no-ops. // - RefPtr<Expr> visitDerefExpr(DerefExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; + #define CASE(NAME) \ + RefPtr<Expr> visit##NAME(NAME* expr) \ + { \ + SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, \ + "should not appear in input syntax"); \ + return expr; \ } - RefPtr<Expr> visitSwizzleExpr(SwizzleExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } + CASE(DerefExpr) + CASE(SwizzleExpr) + CASE(OverloadedExpr) + CASE(OverloadedExpr2) + CASE(AggTypeCtorExpr) + CASE(CastToInterfaceExpr) + CASE(LetExpr) + CASE(ExtractExistentialValueExpr) - RefPtr<Expr> visitOverloadedExpr(OverloadedExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } - - RefPtr<Expr> visitOverloadedExpr2(OverloadedExpr2* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } - - - RefPtr<Expr> visitAggTypeCtorExpr(AggTypeCtorExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } + #undef CASE // // @@ -8091,6 +8189,14 @@ namespace Slang expr->BaseExpression = MaybeDereference(expr->BaseExpression); + // If the base of the member lookup has an interface type + // *without* a suitable this-type substitution, then we are + // trying to perform lookup on a value of existential type, + // and we should "open" the existential here so that we + // can expose its structure. + // + expr->BaseExpression = maybeOpenExistential(expr->BaseExpression); + auto & baseType = expr->BaseExpression->type; // Note: Checking for vector types before declaration-reference types, diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index f8b14b0f3..a1235f03a 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -3,6 +3,7 @@ #include "../core/slang-writer.h" #include "ir-dce.h" +#include "ir-existential.h" #include "ir-insts.h" #include "ir-restructure.h" #include "ir-restructure-scoping.h" @@ -6532,6 +6533,26 @@ String emitEntryPoint( // un-specialized IR. dumpIRIfEnabled(compileRequest, irModule); + // Any code that makes use of existential (interface) types + // needs to be simplified to use concrete types instead, + // wherever this is possible. + // + // Note: we are applying this *before* doing specialization + // of generics because this pass could expose concrete + // types and/or witness tables that allow for further + // specialization. + // + // TODO: Simplification of existential-based and generics-based + // code may each open up opportunities for the other, so + // in the long run these will need to be merged into a + // single pass that looks for all simplification opportunities. + // + // TODO: We also need a legalization pass that will "expose" + // existential values that are nested inside of other types, + // so that the simplifications can be applied. + // + simplifyExistentialTypes(irModule); + // Next, we need to ensure that the code we emit for // the target doesn't contain any operations that would // be illegal on the target platform. For example, @@ -6540,6 +6561,8 @@ String emitEntryPoint( // specializeGenerics(irModule, sharedContext.target); + + // Debugging code for IR transformations... #if 0 dumpIRIfEnabled(compileRequest, irModule, "SPECIALIZED"); diff --git a/source/slang/expr-defs.h b/source/slang/expr-defs.h index 278196421..fb29f64de 100644 --- a/source/slang/expr-defs.h +++ b/source/slang/expr-defs.h @@ -139,6 +139,17 @@ END_SYNTAX_CLASS() SYNTAX_CLASS(ImplicitCastExpr, TypeCastExpr) END_SYNTAX_CLASS() + /// A cast from a value to an interface ("existential") type. +SYNTAX_CLASS(CastToInterfaceExpr, Expr) +RAW( + /// The value being cast to an interface type + RefPtr<Expr> valueArg; + + /// A witness showing that `valueArg` conforms to the chosen interface + RefPtr<Val> witnessArg; +) +END_SYNTAX_CLASS() + SIMPLE_SYNTAX_CLASS(SelectExpr, OperatorExpr) SIMPLE_SYNTAX_CLASS(GenericAppExpr, AppExprBase) @@ -169,3 +180,16 @@ SYNTAX_CLASS(ThisExpr, Expr) FIELD(RefPtr<Scope>, scope); END_SYNTAX_CLASS() +// An expression that binds a temporary variable in a local expression context +SYNTAX_CLASS(LetExpr, Expr) +RAW( + RefPtr<VarDeclBase> decl; + RefPtr<Expr> body; +) +END_SYNTAX_CLASS() + +SYNTAX_CLASS(ExtractExistentialValueExpr, Expr) +RAW( + DeclRef<VarDeclBase> declRef; +) +END_SYNTAX_CLASS() diff --git a/source/slang/ir-existential.cpp b/source/slang/ir-existential.cpp new file mode 100644 index 000000000..af15472bf --- /dev/null +++ b/source/slang/ir-existential.cpp @@ -0,0 +1,114 @@ +// ir-existential.cpp +#include "ir-existential.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang { + +struct ExistentialTypeSimplificationContext +{ + List<IRInst*> instsToRemove; + +}; + +void simplifyExistentialTypesRec( + ExistentialTypeSimplificationContext* context, + IRInst* inst) +{ + switch( inst->op ) + { + default: + break; + + case kIROp_ExtractExistentialValue: + { + auto arg = inst->getOperand(0); + if( auto makeExistential = as<IRMakeExistential>(arg) ) + { + auto value = makeExistential->getWrappedValue(); + inst->replaceUsesWith(value); + context->instsToRemove.Add(inst); + } + } + break; + + case kIROp_ExtractExistentialType: + { + auto arg = inst->getOperand(0); + if( auto makeExistential = as<IRMakeExistential>(arg) ) + { + auto value = makeExistential->getWrappedValue(); + inst->replaceUsesWith(value->getFullType()); + context->instsToRemove.Add(inst); + } + } + break; + + case kIROp_ExtractExistentialWitnessTable: + { + auto arg = inst->getOperand(0); + if( auto makeExistential = as<IRMakeExistential>(arg) ) + { + auto witnessTable = makeExistential->getWitnessTable(); + inst->replaceUsesWith(witnessTable); + context->instsToRemove.Add(inst); + } + } + break; + } + + for( auto childInst : inst->getChildren() ) + { + simplifyExistentialTypesRec(context, childInst); + } +} + +void removeUnusedExistentialsRec( + ExistentialTypeSimplificationContext* context, + IRInst* inst) +{ + switch( inst->op ) + { + default: + break; + + case kIROp_MakeExistential: + { + if( !inst->hasUses() ) + { + context->instsToRemove.Add(inst); + } + } + break; + } + + for( auto childInst : inst->getChildren() ) + { + removeUnusedExistentialsRec(context, childInst); + } +} + +void simplifyExistentialTypes( + IRModule* module) +{ + { + ExistentialTypeSimplificationContext context; + simplifyExistentialTypesRec(&context, module->getModuleInst()); + for( auto inst : context.instsToRemove ) + { + inst->removeAndDeallocate(); + } + } + + { + ExistentialTypeSimplificationContext context; + removeUnusedExistentialsRec(&context, module->getModuleInst()); + for( auto inst : context.instsToRemove ) + { + inst->removeAndDeallocate(); + } + } +} + +} // namespace Slang diff --git a/source/slang/ir-existential.h b/source/slang/ir-existential.h new file mode 100644 index 000000000..2bc94c7b1 --- /dev/null +++ b/source/slang/ir-existential.h @@ -0,0 +1,12 @@ +// ir-existential.h +#pragma once + +namespace Slang +{ + struct IRModule; + + /// Simplify code that makes use of existential types. + void simplifyExistentialTypes( + IRModule* module); +} + diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h index ee390b97b..ba1e79b7e 100644 --- a/source/slang/ir-inst-defs.h +++ b/source/slang/ir-inst-defs.h @@ -150,8 +150,9 @@ INST(Nop, nop, 0, 0) // `field` instructions. // INST(StructType, struct, 0, PARENT) +INST(InterfaceType, interface, 0, PARENT) -INST_RANGE(Type, VoidType, StructType) +INST_RANGE(Type, VoidType, InterfaceType) /*IRGlobalValueWithCode*/ /* IRGlobalValueWIthParams*/ @@ -400,6 +401,11 @@ INST_RANGE(Decoration, HighLevelDeclDecoration, ExportDecoration) // +INST(MakeExistential, makeExistential, 2, 0) +INST(ExtractExistentialValue, extractExistentialValue, 1, 0) +INST(ExtractExistentialType, extractExistentialType, 1, 0) +INST(ExtractExistentialWitnessTable, extractExistentialWitnessTable, 1, 0) + PSEUDO_INST(Pos) PSEUDO_INST(PreInc) diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index 26d5bcf05..29743eebc 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -617,6 +617,17 @@ struct IRBindGlobalGenericParam : IRInst IR_LEAF_ISA(BindGlobalGenericParam) }; + + /// An instruction that packs a concrete value into an existential-type "box" +struct IRMakeExistential : IRInst +{ + IRInst* getWrappedValue() { return getOperand(0); } + IRInst* getWitnessTable() { return getOperand(1); } + + IR_LEAF_ISA(MakeExistential) +}; + + // Description of an instruction to be used for global value numbering struct IRInstKey { @@ -750,6 +761,19 @@ struct IRBuilder // its rate, if any. void setDataType(IRInst* inst, IRType* dataType); + /// Given an existential value, extract the underlying "real" value + IRInst* emitExtractExistentialValue( + IRType* type, + IRInst* existentialValue); + + /// Given an existential value, extract the underlying "real" type + IRType* emitExtractExistentialType( + IRInst* existentialValue); + + /// Given an existential value, extract the witness table showing how the value conforms to the existential type. + IRInst* emitExtractExistentialWitnessTable( + IRInst* existentialValue); + IRInst* emitSpecializeInst( IRType* type, IRInst* genericVal, @@ -793,6 +817,11 @@ struct IRBuilder UInt argCount, IRInst* const* args); + IRInst* emitMakeExistential( + IRType* type, + IRInst* value, + IRInst* witnessTable); + IRUndefined* emitUndefined(IRType* type); @@ -815,6 +844,9 @@ struct IRBuilder // Create an initially empty `struct` type. IRStructType* createStructType(); + // Create an empty `interface` type. + IRInterfaceType* createInterfaceType(); + // Create a global "key" to use for indexing into a `struct` type. IRStructKey* createStructKey(); diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 60e983711..6899e1494 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -1763,6 +1763,48 @@ namespace Slang return inst; } + IRInst* IRBuilder::emitExtractExistentialValue( + IRType* type, + IRInst* existentialValue) + { + auto inst = createInst<IRInst>( + this, + kIROp_ExtractExistentialValue, + type, + 1, + &existentialValue); + addInst(inst); + return inst; + } + + IRType* IRBuilder::emitExtractExistentialType( + IRInst* existentialValue) + { + auto type = getTypeKind(); + auto inst = createInst<IRInst>( + this, + kIROp_ExtractExistentialType, + type, + 1, + &existentialValue); + addInst(inst); + return (IRType*) inst; + } + + IRInst* IRBuilder::emitExtractExistentialWitnessTable( + IRInst* existentialValue) + { + auto type = getWitnessTableType(); + auto inst = createInst<IRInst>( + this, + kIROp_ExtractExistentialWitnessTable, + type, + 1, + &existentialValue); + addInst(inst); + return inst; + } + IRInst* IRBuilder::emitSpecializeInst( IRType* type, IRInst* genericVal, @@ -1871,6 +1913,15 @@ namespace Slang return emitIntrinsicInst(type, kIROp_makeStruct, argCount, args); } + IRInst* IRBuilder::emitMakeExistential( + IRType* type, + IRInst* value, + IRInst* witnessTable) + { + IRInst* args[] = {value, witnessTable}; + return emitIntrinsicInst(type, kIROp_MakeExistential, SLANG_COUNT_OF(args), args); + } + IRModule* IRBuilder::createModule() { auto module = new IRModule(); @@ -2035,6 +2086,16 @@ namespace Slang return structType; } + IRInterfaceType* IRBuilder::createInterfaceType() + { + IRInterfaceType* interfaceType = createInst<IRInterfaceType>( + this, + kIROp_InterfaceType, + nullptr); + addGlobalValue(this, interfaceType); + return interfaceType; + } + IRStructKey* IRBuilder::createStructKey() { IRStructKey* structKey = createInst<IRStructKey>( @@ -5992,6 +6053,18 @@ namespace Slang return clonedStruct; } + + IRInterfaceType* cloneInterfaceTypeImpl( + IRSpecContextBase* context, + IRBuilder* builder, + IRInterfaceType* originalInterface, + IROriginalValuesForClone const& originalValues) + { + auto clonedInterface = builder->createInterfaceType(); + cloneSimpleGlobalValueImpl(context, originalInterface, originalValues, clonedInterface); + return clonedInterface; + } + void cloneGlobalValueWithCodeCommon( IRSpecContextBase* context, IRGlobalValueWithCode* clonedValue, @@ -6431,6 +6504,9 @@ namespace Slang case kIROp_StructType: return cloneStructTypeImpl(context, builder, cast<IRStructType>(originalInst), originalValues); + case kIROp_InterfaceType: + return cloneInterfaceTypeImpl(context, builder, cast<IRInterfaceType>(originalInst), originalValues); + case kIROp_Generic: return cloneGenericImpl(context, builder, cast<IRGeneric>(originalInst), originalValues); diff --git a/source/slang/ir.h b/source/slang/ir.h index 488611675..cf0ccae9a 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -966,6 +966,10 @@ struct IRStructType : IRInst IR_LEAF_ISA(StructType) }; +struct IRInterfaceType : IRInst +{ + IR_LEAF_ISA(InterfaceType) +}; /// @brief A global value that potentially holds executable code. /// diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 74ec35fcd..0d39664c8 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -1288,6 +1288,22 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower return lowerGenericIntrinsicType(type, elementType, count); } + IRType* visitExtractExistentialType(ExtractExistentialType* type) + { + auto declRef = type->declRef; + auto existentialType = lowerType(context, GetType(declRef)); + IRInst* existentialVal = getSimpleVal(context, emitDeclRef(context, declRef, existentialType)); + return getBuilder()->emitExtractExistentialType(existentialVal); + } + + LoweredValInfo visitExtractExistentialSubtypeWitness(ExtractExistentialSubtypeWitness* witness) + { + auto declRef = witness->declRef; + auto existentialType = lowerType(context, GetType(declRef)); + IRInst* existentialVal = getSimpleVal(context, emitDeclRef(context, declRef, existentialType)); + return LoweredValInfo::simple(getBuilder()->emitExtractExistentialWitnessTable(existentialVal)); + } + // We do not expect to encounter the following types in ASTs that have // passed front-end semantic checking. #define UNEXPECTED_CASE(NAME) IRType* visit##NAME(NAME*) { SLANG_UNEXPECTED(#NAME); UNREACHABLE_RETURN(nullptr); } @@ -2088,6 +2104,32 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> UNREACHABLE_RETURN(LoweredValInfo()); } + LoweredValInfo visitCastToInterfaceExpr( + CastToInterfaceExpr* expr) + { + // We have an expression that is "up-casting" some concrete value + // to an existential type (aka interface type), using a subtype witness + // (which will lower as a witness table) to show that the conversion + // is valid. + // + // At the IR level, this will become a `makeExistential` instruction, + // which collects the above information into a single IR-level value. + // A dynamic CPU implementation of Slang might encode an existential + // as a "fat pointer" representation, which includes a pointer to + // data for the concrete value, plus a pointer to the witness table. + // + // Note: if/when Slang supports more general existential types, such + // as compositions of interface (e.g., `IReadable & IWritable`), then + // we should probably extend the AST and IR mechanism here to accept + // a sequence of witness tables. + // + auto existentialType = lowerType(context, expr->type); + auto concreteValue = getSimpleVal(context, lowerRValueExpr(context, expr->valueArg)); + auto witnessTable = lowerSimpleVal(context, expr->witnessArg); + auto existentialValue = getBuilder()->emitMakeExistential(existentialType, concreteValue, witnessTable); + return LoweredValInfo::simple(existentialValue); + } + LoweredValInfo subscriptValue( IRType* type, LoweredValInfo baseVal, @@ -2160,6 +2202,27 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> // to be an l-value). return leftVal; } + + LoweredValInfo visitLetExpr(LetExpr* expr) + { + // TODO: deal with the case where we might want to capture + // a reference to the bound value... + + auto initVal = lowerLValueExpr(context, expr->decl->initExpr); + setGlobalValue(context, expr->decl, initVal); + auto bodyVal = lowerSubExpr(expr->body); + return bodyVal; + } + + LoweredValInfo visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr) + { + auto existentialType = lowerType(context, GetType(expr->declRef)); + auto existentialVal = getSimpleVal(context, emitDeclRef(context, expr->declRef, existentialType)); + + auto openedType = lowerType(context, expr->type); + + return LoweredValInfo::simple(getBuilder()->emitExtractExistentialValue(openedType, existentialVal)); + } }; struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVisitor> @@ -2230,7 +2293,6 @@ struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVis context->shared->extValues.Add(swizzledLValue); return LoweredValInfo::swizzledLValue(swizzledLValue); } - }; struct RValueExprLoweringVisitor : ExprLoweringVisitorBase<RValueExprLoweringVisitor> @@ -4220,12 +4282,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> LoweredValInfo visitInterfaceDecl(InterfaceDecl* decl) { - // The interface decl is not itself a type in the IR - // (yet), so the only thing we need to do here is - // enumerate the requirements that the interface - // imposes on implementations. - // - // These members will turn into the keys that will + // The members of an interface will turn into the keys that will // be used for lookup operations into witness // tables that promise conformance to the interface. // @@ -4254,7 +4311,28 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> } } - return LoweredValInfo(); + + NestedContext nestedContext(this); + auto subBuilder = nestedContext.getBuilder(); + auto subContext = nestedContext.getContet(); + + // Emit any generics that should wrap the actual type. + emitOuterGenerics(subContext, decl, decl); + + IRInterfaceType* irInterface = subBuilder->createInterfaceType(); + addNameHint(context, irInterface, decl); + addLinkageDecoration(context, irInterface, decl); + subBuilder->setInsertInto(irInterface); + + // TODO: are there any interface members that should be + // nested inside the interface type itself? + + irInterface->moveToEnd(); + + addTargetIntrinsicDecorations(irInterface, decl); + + + return LoweredValInfo::simple(finishOuterGenerics(subBuilder, irInterface)); } LoweredValInfo visitEnumCaseDecl(EnumCaseDecl* decl) @@ -5451,6 +5529,14 @@ LoweredValInfo emitDeclRef( } else if(auto thisTypeSubst = subst.As<ThisTypeSubstitution>()) { + if(decl.Ptr() == thisTypeSubst->interfaceDecl) + { + // This is a reference to the interface type itself, + // through the this-type substitution, so it is really + // a reference to the this-type. + return lowerType(context, thisTypeSubst->witness->sub); + } + // Somebody is trying to look up an interface requirement // "through" some concrete type. We need to lower this decl-ref // as a lookup of the corresponding member in a witness table. diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 6ba32f954..3ce172271 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -184,6 +184,7 @@ <ClInclude Include="ir-constexpr.h" /> <ClInclude Include="ir-dce.h" /> <ClInclude Include="ir-dominators.h" /> + <ClInclude Include="ir-existential.h" /> <ClInclude Include="ir-inst-defs.h" /> <ClInclude Include="ir-insts.h" /> <ClInclude Include="ir-missing-return.h" /> @@ -234,6 +235,7 @@ <ClCompile Include="ir-constexpr.cpp" /> <ClCompile Include="ir-dce.cpp" /> <ClCompile Include="ir-dominators.cpp" /> + <ClCompile Include="ir-existential.cpp" /> <ClCompile Include="ir-legalize-types.cpp" /> <ClCompile Include="ir-missing-return.cpp" /> <ClCompile Include="ir-restructure-scoping.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index eaafa6e79..9f3666fc1 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -51,6 +51,9 @@ <ClInclude Include="ir-dominators.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="ir-existential.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="ir-inst-defs.h"> <Filter>Header Files</Filter> </ClInclude> @@ -197,6 +200,9 @@ <ClCompile Include="ir-dominators.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ir-existential.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="ir-legalize-types.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp index 6e57a7a57..21b3f92c2 100644 --- a/source/slang/syntax.cpp +++ b/source/slang/syntax.cpp @@ -2420,5 +2420,96 @@ void Type::accept(IValVisitor* visitor, void* extra) rs = combineHash(rs, substitutions->GetHashCode()); return rs; } + + // ExtractExistentialType + + String ExtractExistentialType::ToString() + { + String result; + result.append(declRef.toString()); + result.append(".This"); + return result; + } + + bool ExtractExistentialType::EqualsImpl(Type* type) + { + if( auto extractExistential = type->As<ExtractExistentialType>() ) + { + return declRef.Equals(extractExistential->declRef); + } + return false; + } + + int ExtractExistentialType::GetHashCode() + { + return declRef.GetHashCode(); + } + + RefPtr<Type> ExtractExistentialType::CreateCanonicalType() + { + return this; + } + + RefPtr<Val> ExtractExistentialType::SubstituteImpl(SubstitutionSet subst, int* ioDiff) + { + int diff = 0; + auto substDeclRef = declRef.SubstituteImpl(subst, &diff); + if(!diff) + return this; + + (*ioDiff)++; + + RefPtr<ExtractExistentialType> substValue = new ExtractExistentialType(); + substValue->declRef = declRef; + return substValue; + } + + // ExtractExistentialSubtypeWitness + + bool ExtractExistentialSubtypeWitness::EqualsVal(Val* val) + { + if( auto extractWitness = val->dynamicCast<ExtractExistentialSubtypeWitness>() ) + { + return declRef.Equals(extractWitness->declRef); + } + return false; + } + + String ExtractExistentialSubtypeWitness::ToString() + { + String result; + result.append("extractExistentialValue("); + result.append(declRef.toString()); + result.append(")"); + return result; + } + + int ExtractExistentialSubtypeWitness::GetHashCode() + { + return declRef.GetHashCode(); + } + + RefPtr<Val> ExtractExistentialSubtypeWitness::SubstituteImpl(SubstitutionSet subst, int* ioDiff) + { + int diff = 0; + + auto substDeclRef = declRef.SubstituteImpl(subst, &diff); + auto substSub = sub->SubstituteImpl(subst, &diff).As<Type>(); + auto substSup = sup->SubstituteImpl(subst, &diff).As<Type>(); + + if(!diff) + return this; + + (*ioDiff)++; + + RefPtr<ExtractExistentialSubtypeWitness> substValue = new ExtractExistentialSubtypeWitness(); + substValue->declRef = declRef; + substValue->sub = substSub; + substValue->sup = substSup; + return substValue; + } + + + } diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h index 25bb5387f..6e067c990 100644 --- a/source/slang/type-defs.h +++ b/source/slang/type-defs.h @@ -439,3 +439,17 @@ protected: virtual RefPtr<Type> CreateCanonicalType() override; ) END_SYNTAX_CLASS() + +// The concrete type for a value wrapped in an existential, accessible +// when the existential is "opened" in some context. +SYNTAX_CLASS(ExtractExistentialType, Type) +RAW( + DeclRef<VarDeclBase> declRef; + + virtual String ToString() override; + virtual bool EqualsImpl(Type * type) override; + virtual int GetHashCode() override; + virtual RefPtr<Type> CreateCanonicalType() override; + virtual RefPtr<Val> SubstituteImpl(SubstitutionSet subst, int* ioDiff) override; +) +END_SYNTAX_CLASS() diff --git a/source/slang/val-defs.h b/source/slang/val-defs.h index 1a277c60c..f96ee026e 100644 --- a/source/slang/val-defs.h +++ b/source/slang/val-defs.h @@ -121,3 +121,18 @@ RAW( virtual RefPtr<Val> SubstituteImpl(SubstitutionSet subst, int * ioDiff) override; ) END_SYNTAX_CLASS() + +// A witness taht `sub : sup` because `sub` was wrapped into +// an existential of type `sup`. +SYNTAX_CLASS(ExtractExistentialSubtypeWitness, SubtypeWitness) +RAW( + // The declaration of the existential value that has been opened + DeclRef<VarDeclBase> declRef; + + virtual bool EqualsVal(Val* val) override; + virtual String ToString() override; + virtual int GetHashCode() override; + virtual RefPtr<Val> SubstituteImpl(SubstitutionSet subst, int * ioDiff) override; +) +END_SYNTAX_CLASS() + diff --git a/tests/compute/interface-local.slang b/tests/compute/interface-local.slang new file mode 100644 index 000000000..d3ee88062 --- /dev/null +++ b/tests/compute/interface-local.slang @@ -0,0 +1,48 @@ +// interface-local.slang + +// Test basic use of an interface as an existential type +// for a local variable, instead of just as a constraint +// on a generic type parameter. +// +// Because the existential is created and then used inside +// the same function, we can eliminate it via simple +// local optimizations. + +// on a generic type parameter. + +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute + +interface IHelper +{ + int getVal(); +} + +struct HelperImpl : IHelper +{ + int storedVal; + + int getVal() { return storedVal; } +} + +int test(int val) +{ + HelperImpl helperImpl = { val }; + + IHelper existentialHelper = helperImpl; + + return existentialHelper.getVal(); +} + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out +RWStructuredBuffer<int> gOutputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inputVal = tid; + int outputVal = test(inputVal); + gOutputBuffer[tid] = outputVal; +}
\ No newline at end of file diff --git a/tests/compute/interface-local.slang.expected.txt b/tests/compute/interface-local.slang.expected.txt new file mode 100644 index 000000000..bc856dafa --- /dev/null +++ b/tests/compute/interface-local.slang.expected.txt @@ -0,0 +1,4 @@ +0 +1 +2 +3 diff --git a/tests/compute/interface-param.slang b/tests/compute/interface-param.slang new file mode 100644 index 000000000..2e97a7fe2 --- /dev/null +++ b/tests/compute/interface-param.slang @@ -0,0 +1,48 @@ +// interface-param.slang + +//TEST_DISABLED: + +// Test basic use of an interface as an existential type +// for a value parameter, instead of just as a constraint +// on a generic type parameter. + +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute + +//TEST:SIMPLE:-target hlsl -entry computeMain -stage compute -profile sm_5_1 -dump-ir + +interface IHelper +{ + int getVal(); +} + +int doTheThing(IHelper helper) +{ + return helper.getVal(); +} + +struct HelperImpl : IHelper +{ + int storedVal; + + int getVal() { return storedVal; } +} + +int test(int val) +{ + HelperImpl helperImpl = { val }; + return doTheThing(helperImpl); +} + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out +RWStructuredBuffer<int> gOutputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inputVal = tid; + int outputVal = test(inputVal); + gOutputBuffer[tid] = outputVal; +}
\ No newline at end of file diff --git a/tests/compute/interface-param.slang.expected.txt b/tests/compute/interface-param.slang.expected.txt new file mode 100644 index 000000000..bc856dafa --- /dev/null +++ b/tests/compute/interface-param.slang.expected.txt @@ -0,0 +1,4 @@ +0 +1 +2 +3 |
