summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/slang/check.cpp164
-rw-r--r--source/slang/emit.cpp23
-rw-r--r--source/slang/expr-defs.h24
-rw-r--r--source/slang/ir-existential.cpp114
-rw-r--r--source/slang/ir-existential.h12
-rw-r--r--source/slang/ir-inst-defs.h8
-rw-r--r--source/slang/ir-insts.h32
-rw-r--r--source/slang/ir.cpp76
-rw-r--r--source/slang/ir.h4
-rw-r--r--source/slang/lower-to-ir.cpp102
-rw-r--r--source/slang/slang.vcxproj2
-rw-r--r--source/slang/slang.vcxproj.filters6
-rw-r--r--source/slang/syntax.cpp91
-rw-r--r--source/slang/type-defs.h14
-rw-r--r--source/slang/val-defs.h15
-rw-r--r--tests/compute/interface-local.slang48
-rw-r--r--tests/compute/interface-local.slang.expected.txt4
-rw-r--r--tests/compute/interface-param.slang48
-rw-r--r--tests/compute/interface-param.slang.expected.txt4
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