summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2021-02-05 09:01:36 -0800
committerGitHub <noreply@github.com>2021-02-05 09:01:36 -0800
commitadb1131d08f28f0bc5f729e88b73cf22846c86c5 (patch)
tree28139e39f16a7375baa42b41b0a523bfc87f667b
parentfb053433ef64bbae50a8a10ea4381a5695019fac (diff)
Initial implementation of interface conjunctions (#1691)
The basic feature here is the ability to use the `&` operator to produce the conjunction/intersection of two interfaces. That is, you can have interfaces: interface IFirst { int getFirst(); } interface ISecond { int getSecoond(); } and if you need a generic function where the type parameter `T` must conform to *both* of these interfaces, you express that by constraining the parameter to the intersection of the interfaces: void someFunction<T : IFirst & ISecond>(T value) { ... } Without this feature, the main alternative an application would have is to define an intermediate interface, like: interface IBoth : IFirst, ISecond {} Forcing users to deal with an intermediate interface creates more work for type authors (they need to remember to inherit from the right combined interface(s)), or for `extension` authors (when you add `ISecond` to a type that used to just support `IFirst`, you had better also add `IBoth`). In the worst case, a family of N related "leaf" interfaces would give rise to an exponential number of intermediate interfaces to represnt the possible combinations. A conjunction like `IFirst & ISecond` is officially its own type, and can be used to declare a type alias: typealias IBoth = IFirst & ISecond; This change only includes the first pass of work on this feature, so there are several caveats to be aware of: * Using a conjunction as part of an inheritance clause is not yet supported (e.g., `struct X : IFirst & ISecond`). This is true even if the conjunction was introduced by an intermediate `typealias` * The `&` syntax introduced here is only parsed in places where only a type (not an expression) is possible. This means you cannot do things like cast to a conjunction with `(IFirst & ISecond)(someValue)`. * This work *should* apply to conjunctions of more than two interfaces (like `IA & IB & IC`) but that has not yet been tested * In the long run it may be sensible to allow conjunctions that use concrete types, but we really ought to have the semantic checking logic rule that out for now. * During testing, I encountered compiler crashes when trying to use this feature together with `property` declarations. Further investigation and debugging is called for. * The handling of conjunction types is currently incomplete, in that there are many equivalences the compiler does not yet understand. For example, it is clear that `IA & IB` is equivalent to `IB & IA`, but the compiler currently does not understand this and will treat them as different types. A deeper implementation approach is called for. * Conjunctions are currently only supported for generic type parameter constraints, when performing full specialization. Use of conjunctions for existential-type value parameters or with dynamic dispatch is not yet supported.
-rw-r--r--source/core/slang-hash.h22
-rw-r--r--source/slang/slang-ast-builder.cpp8
-rw-r--r--source/slang/slang-ast-builder.h2
-rw-r--r--source/slang/slang-ast-expr.h9
-rw-r--r--source/slang/slang-ast-type.cpp93
-rw-r--r--source/slang/slang-ast-type.h18
-rw-r--r--source/slang/slang-ast-val.h28
-rw-r--r--source/slang/slang-check-conformance.cpp18
-rw-r--r--source/slang/slang-check-constraint.cpp33
-rw-r--r--source/slang/slang-check-expr.cpp27
-rw-r--r--source/slang/slang-check-impl.h6
-rw-r--r--source/slang/slang-ir-inst-defs.h2
-rw-r--r--source/slang/slang-ir-insts.h34
-rw-r--r--source/slang/slang-ir-lower-generic-function.cpp16
-rw-r--r--source/slang/slang-ir.cpp30
-rw-r--r--source/slang/slang-ir.h8
-rw-r--r--source/slang/slang-lookup.cpp57
-rw-r--r--source/slang/slang-lower-to-ir.cpp212
-rw-r--r--source/slang/slang-parser.cpp60
-rw-r--r--source/slang/slang.natvis5
-rw-r--r--tests/language-feature/interfaces/interface-conjunction.slang49
-rw-r--r--tests/language-feature/interfaces/interface-conjunction.slang.expected.txt4
22 files changed, 712 insertions, 29 deletions
diff --git a/source/core/slang-hash.h b/source/core/slang-hash.h
index 5a0766c98..b2a583744 100644
--- a/source/core/slang-hash.h
+++ b/source/core/slang-hash.h
@@ -174,18 +174,40 @@ namespace Slang
public:
Hasher() {}
+ /// Hash the given `value` and combine it into this hash state
template<typename T>
void hashValue(T const& value)
{
+ // TODO: Eventually, we should replace `getHashCode`
+ // with a "hash into" operation that takes the value
+ // and a `Hasher`.
+
m_hashCode = combineHash(m_hashCode, getHashCode(value));
}
+ /// Hash the given `object` and combine it into this hash state
template<typename T>
void hashObject(T const& object)
{
+ // TODO: Eventually, we should replace `getHashCode`
+ // with a "hash into" operation that takes the value
+ // and a `Hasher`.
+
m_hashCode = combineHash(m_hashCode, object->getHashCode());
}
+ /// Combine the given `hash` code into the hash state.
+ ///
+ /// Note: users should prefer to use `hashValue` or `hashObject`
+ /// when possible, as they may be able to ensure a higher-quality
+ /// hash result (e.g., by using more bits to represent the state
+ /// during hashing than are used for the final hash code).
+ ///
+ void addHash(HashCode hash)
+ {
+ m_hashCode = combineHash(m_hashCode, hash);
+ }
+
HashCode getResult() const
{
return m_hashCode;
diff --git a/source/slang/slang-ast-builder.cpp b/source/slang/slang-ast-builder.cpp
index 3b97bfbb7..240b39d6c 100644
--- a/source/slang/slang-ast-builder.cpp
+++ b/source/slang/slang-ast-builder.cpp
@@ -257,6 +257,14 @@ VectorExpressionType* ASTBuilder::getVectorType(
return as<VectorExpressionType>(DeclRefType::create(this, declRef));
}
+Type* ASTBuilder::getAndType(Type* left, Type* right)
+{
+ auto type = create<AndType>();
+ type->left = left;
+ type->right = right;
+ return type;
+}
+
TypeType* ASTBuilder::getTypeType(Type* type)
{
return create<TypeType>(type);
diff --git a/source/slang/slang-ast-builder.h b/source/slang/slang-ast-builder.h
index 08d22f6ca..702b02e0a 100644
--- a/source/slang/slang-ast-builder.h
+++ b/source/slang/slang-ast-builder.h
@@ -153,6 +153,8 @@ public:
VectorExpressionType* getVectorType(Type* elementType, IntVal* elementCount);
+ Type* getAndType(Type* left, Type* right);
+
TypeType* getTypeType(Type* type);
/// Helpers to get type info from the SharedASTBuilder
diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h
index e2e64e2a2..a5024460b 100644
--- a/source/slang/slang-ast-expr.h
+++ b/source/slang/slang-ast-expr.h
@@ -325,4 +325,13 @@ class ThisTypeExpr: public Expr
RefPtr<Scope> scope;
};
+ /// A type expression of the form `Left & Right`.
+class AndTypeExpr : public Expr
+{
+ SLANG_AST_CLASS(AndTypeExpr);
+
+ TypeExp left;
+ TypeExp right;
+};
+
} // namespace Slang
diff --git a/source/slang/slang-ast-type.cpp b/source/slang/slang-ast-type.cpp
index 9c00c13ba..79df8e48b 100644
--- a/source/slang/slang-ast-type.cpp
+++ b/source/slang/slang-ast-type.cpp
@@ -938,5 +938,98 @@ Val* ThisType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet s
return substType;
}
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! AndType !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+String AndType::_toStringOverride()
+{
+ String result;
+ result.append(left->toString());
+ result.append(" & ");
+ result.append(right->toString());
+ return result;
+}
+
+bool AndType::_equalsImplOverride(Type * type)
+{
+ auto other = as<AndType>(type);
+ if (!other)
+ return false;
+
+ if(!left->equals(other->left))
+ return false;
+ if(!right->equals(other->right))
+ return false;
+
+ return true;
+}
+
+HashCode AndType::_getHashCodeOverride()
+{
+ Hasher hasher;
+ hasher.hashObject(left);
+ hasher.hashObject(right);
+ return hasher.getResult();
+}
+
+Type* AndType::_createCanonicalTypeOverride()
+{
+ AndType* canType = m_astBuilder->create<AndType>();
+
+ // TODO: proper canonicalization of an `&` type relies on
+ // several different things:
+ //
+ // * We need to re-associate types that might involve
+ // nesting of `&`, such as `(A & B) & (C & D)`, into
+ // a canonical form where the nesting is consistent
+ // (i.e., always left- or right-associative).
+ //
+ // * We need to commute types so that they are in a
+ // consistent order, so that `A & B` and `B & A` both
+ // result in the same canonicalization. This requirement
+ // implies that we must invent a total order on types.
+ //
+ // * We need to canonicalize `&` types where one of the
+ // elements might be implied by another. E.g., if we
+ // have `interface IDerived : IBase`, then a type like
+ // `IDerived & IBase` is equivalent to just `IDerived`
+ // because the presence of an `IBase` conformance is
+ // implied. A special case of the above is the possibility
+ // of duplicates in the list of types (e.g., `A & B & A`).
+ //
+ // * The previous requirement raises the problem that
+ // the relationships between `interface`s might either
+ // evolve over time, or be subject to `extension`
+ // declarations in other modules. The canonicalization
+ // algorithm must be clear about what information it
+ // is allowed to make use of, as this can/will affect
+ // binary interfaces (via mangled names).
+ //
+ // We are going to completely ignore these issues for
+ // right now, in the name of getting something up and running.
+ //
+ canType->left = left->getCanonicalType();
+ canType->right = right->getCanonicalType();
+
+ return canType;
+}
+
+Val* AndType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff)
+{
+ int diff = 0;
+
+ auto substLeft = as<Type>(left ->substituteImpl(astBuilder, subst, &diff));
+ auto substRight = as<Type>(right->substituteImpl(astBuilder, subst, &diff));
+
+ if(!diff)
+ return this;
+
+ (*ioDiff)++;
+
+ AndType* substType = m_astBuilder->create<AndType>();
+ substType->left = substLeft;
+ substType->right = substRight;
+ return substType;
+}
+
} // namespace Slang
diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h
index 4c2242794..d4d626182 100644
--- a/source/slang/slang-ast-type.h
+++ b/source/slang/slang-ast-type.h
@@ -676,4 +676,22 @@ class ThisType : public Type
Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff);
};
+ /// The type of `A & B` where `A` and `B` are types
+ ///
+ /// A value `v` is of type `A & B` if it is both of type `A` and of type `B`.
+class AndType : public Type
+{
+ SLANG_AST_CLASS(AndType)
+
+ Type* left;
+ Type* right;
+
+ // Overrides should be public so base classes can access
+ String _toStringOverride();
+ bool _equalsImplOverride(Type* type);
+ HashCode _getHashCodeOverride();
+ Type* _createCanonicalTypeOverride();
+ Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff);
+};
+
} // namespace Slang
diff --git a/source/slang/slang-ast-val.h b/source/slang/slang-ast-val.h
index af137395b..dc32b3faa 100644
--- a/source/slang/slang-ast-val.h
+++ b/source/slang/slang-ast-val.h
@@ -213,4 +213,32 @@ class DynamicSubtypeWitness : public SubtypeWitness
SLANG_AST_CLASS(DynamicSubtypeWitness)
};
+ /// A witness that `T : L & R` because `T : L` and `T : R`
+class ConjunctionSubtypeWitness : public SubtypeWitness
+{
+ SLANG_AST_CLASS(ConjunctionSubtypeWitness)
+
+ /// Witness that `sub : sup->left`
+ Val* leftWitness;
+
+ /// Witness that `sub : sup->right`
+ Val* rightWitness;
+};
+
+ /// A witness that `T : X` because `T : X & Y` or `T : Y & X`
+class ExtractFromConjunctionSubtypeWitness : public SubtypeWitness
+{
+ SLANG_AST_CLASS(ExtractFromConjunctionSubtypeWitness)
+
+ /// Witness that `T : L & R` for some `R`
+ Val* conunctionWitness;
+
+ /// The zero-based index of the super-type we care about in the conjunction
+ ///
+ /// If `conunctionWitness` is `T : X & Y` then this index should be zero if
+ /// we want to represent `T : X` and one if we want `T : Y`.
+ ///
+ int indexInConjunction;
+};
+
} // namespace Slang
diff --git a/source/slang/slang-check-conformance.cpp b/source/slang/slang-check-conformance.cpp
index 6a397e8b6..174d4be89 100644
--- a/source/slang/slang-check-conformance.cpp
+++ b/source/slang/slang-check-conformance.cpp
@@ -429,6 +429,24 @@ namespace Slang
}
}
}
+ else if( auto andType = as<AndType>(sup) )
+ {
+ // A type `T` is a subtype of `A & B` if `T` is a
+ // subtype of `A` and `T` is a subtype of `B`.
+ //
+ auto leftWitness = tryGetSubtypeWitness(sub, andType->left);
+ if(!leftWitness) return nullptr;
+
+ auto rightWitness = tryGetSubtypeWitness(sub, andType->right);
+ if(!rightWitness) return nullptr;
+
+ ConjunctionSubtypeWitness* w = m_astBuilder->create<ConjunctionSubtypeWitness>();
+ w->leftWitness = leftWitness;
+ w->rightWitness = rightWitness;
+ w->sub = sub;
+ w->sup = sup;
+ return w;
+ }
return nullptr;
}
diff --git a/source/slang/slang-check-constraint.cpp b/source/slang/slang-check-constraint.cpp
index 4d1379016..a03043f31 100644
--- a/source/slang/slang-check-constraint.cpp
+++ b/source/slang/slang-check-constraint.cpp
@@ -659,6 +659,21 @@ namespace Slang
return false;
}
+ bool SemanticsVisitor::TryUnifyConjunctionType(
+ ConstraintSystem& constraints,
+ AndType* fst,
+ Type* snd)
+ {
+ // Unifying a type `T` with `A & B` amounts to unifying
+ // `T` with `A` and also `T` with `B`.
+ //
+ // If either unification is impossible, then the full
+ // case is also impossible.
+ //
+ return TryUnifyTypes(constraints, fst->left, snd)
+ && TryUnifyTypes(constraints, fst->right, snd);
+ }
+
bool SemanticsVisitor::TryUnifyTypes(
ConstraintSystem& constraints,
Type* fst,
@@ -674,6 +689,24 @@ namespace Slang
if (auto sndErrorType = as<ErrorType>(snd))
return true;
+ // If one or the other of the types is a conjunction `X & Y`,
+ // then we want to recurse on both `X` and `Y`.
+ //
+ // Note that we check this case *before* we check if one of
+ // the types is a generic parameter below, so that we should
+ // never end up trying to match up a type parameter with
+ // a conjunction directly, and will instead find all of the
+ // "leaf" types we need to constrain it to.
+ //
+ if( auto fstAndType = as<AndType>(fst) )
+ {
+ return TryUnifyConjunctionType(constraints, fstAndType, snd);
+ }
+ if( auto sndAndType = as<AndType>(snd) )
+ {
+ return TryUnifyConjunctionType(constraints, sndAndType, fst);
+ }
+
// A generic parameter type can unify with anything.
// TODO: there actually needs to be some kind of "occurs check" sort
// of thing here...
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index 47f5436c5..736eee6fb 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -2112,4 +2112,31 @@ namespace Slang
return CreateErrorExpr(expr);
}
+ Expr* SemanticsExprVisitor::visitAndTypeExpr(AndTypeExpr* expr)
+ {
+ // The left and right sides of an `&` for types must both be types.
+ //
+ expr->left = CheckProperType(expr->left);
+ expr->right = CheckProperType(expr->right);
+
+ // TODO: We should enforce some rules here about what is allowed
+ // for the `left` and `right` types.
+ //
+ // For now, the right rule is that they probably need to either
+ // be interfaces, or conjunctions thereof.
+ //
+ // Eventually it may be valuable to support more flexible
+ // types in conjunctions, especialy in cases where inheritance
+ // gets involved.
+
+ // The result of this expression is an `AndType`, which we need
+ // to wrap in a `TypeType` to indicate that the result is the type
+ // itself and not a value of that type.
+ //
+ auto andType = m_astBuilder->getAndType(expr->left.type, expr->right.type);
+ expr->type = m_astBuilder->getTypeType(andType);
+
+ return expr;
+ }
+
}
diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h
index b3c5e46f9..f1aae6a10 100644
--- a/source/slang/slang-check-impl.h
+++ b/source/slang/slang-check-impl.h
@@ -1387,6 +1387,11 @@ namespace Slang
Type* fst,
Type* snd);
+ bool TryUnifyConjunctionType(
+ ConstraintSystem& constraints,
+ AndType* fst,
+ Type* snd);
+
// Is the candidate extension declaration actually applicable to the given type
DeclRef<ExtensionDecl> ApplyExtensionToType(
ExtensionDecl* extDecl,
@@ -1573,6 +1578,7 @@ namespace Slang
Expr* visitThisExpr(ThisExpr* expr);
Expr* visitThisTypeExpr(ThisTypeExpr* expr);
+ Expr* visitAndTypeExpr(AndTypeExpr* expr);
};
struct SemanticsStmtVisitor
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 2cab493bd..082de1c42 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -51,6 +51,8 @@ INST(Nop, nop, 0, 0)
INST(TaggedUnionType, TaggedUnion, 0, 0)
+ INST(ConjunctionType, Conjunction, 0, 0)
+
/* BindExistentialsTypeBase */
// A `BindExistentials<B, T0,w0, T1,w1, ...>` represents
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 3c7406d3b..bde7be24c 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -1850,6 +1850,11 @@ struct IRBuilder
IRDynamicType* getDynamicType();
IRTupleType* getTupleType(UInt count, IRType* const* types);
+ IRTupleType* getTupleType(List<IRType*> const& types)
+ {
+ return getTupleType(types.getCount(), types.getBuffer());
+ }
+
IRTupleType* getTupleType(IRType* type0, IRType* type1);
IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2);
IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2, IRType* type3);
@@ -1940,6 +1945,18 @@ struct IRBuilder
IRType* getPseudoPtrType(
IRType* concreteType);
+ IRType* getConjunctionType(
+ UInt typeCount,
+ IRType* const* types);
+
+ IRType* getConjunctionType(
+ IRType* type0,
+ IRType* type1)
+ {
+ IRType* types[] = { type0, type1 };
+ return getConjunctionType(2, types);
+ }
+
// Set the data type of an instruction, while preserving
// its rate, if any.
void setDataType(IRInst* inst, IRType* dataType);
@@ -2018,6 +2035,23 @@ struct IRBuilder
IRInst* emitMakeRTTIObject(IRInst* typeInst);
IRInst* emitMakeTuple(IRType* type, UInt count, IRInst* const* args);
+ IRInst* emitMakeTuple(UInt count, IRInst* const* args);
+
+ IRInst* emitMakeTuple(IRType* type, List<IRInst*> const& args)
+ {
+ return emitMakeTuple(type, args.getCount(), args.getBuffer());
+ }
+
+ IRInst* emitMakeTuple(List<IRInst*> const& args)
+ {
+ return emitMakeTuple(args.getCount(), args.getBuffer());
+ }
+
+ IRInst* emitMakeTuple(IRInst* arg0, IRInst* arg1)
+ {
+ IRInst* args[] = { arg0, arg1 };
+ return emitMakeTuple(SLANG_COUNT_OF(args), args);
+ }
IRInst* emitGetTupleElement(IRType* type, IRInst* tuple, UInt element);
diff --git a/source/slang/slang-ir-lower-generic-function.cpp b/source/slang/slang-ir-lower-generic-function.cpp
index 11e1c0f04..185655905 100644
--- a/source/slang/slang-ir-lower-generic-function.cpp
+++ b/source/slang/slang-ir-lower-generic-function.cpp
@@ -159,22 +159,30 @@ namespace Slang
{
if (auto entry = as<IRInterfaceRequirementEntry>(interfaceType->getOperand(i)))
{
+ // Note: The logic that creates the `IRInterfaceRequirementEntry`s does
+ // not currently guarantee that the *value* part of each key-value pair
+ // gets filled in. We thus need to defend against a null `requirementVal`
+ // here, at least until the underlying issue gets resolved.
+ //
+ IRInst* requirementVal = entry->getRequirementVal();
IRInst* loweredVal = nullptr;
- if (auto funcType = as<IRFuncType>(entry->getRequirementVal()))
+ if(!requirementVal)
+ {}
+ else if (auto funcType = as<IRFuncType>(requirementVal))
{
loweredVal = lowerFuncType(&builder, funcType, Dictionary<IRInst*, IRInst*>(), ArrayView<IRInst*>());
}
- else if (auto genericFuncType = as<IRGeneric>(entry->getRequirementVal()))
+ else if (auto genericFuncType = as<IRGeneric>(requirementVal))
{
loweredVal = lowerGenericFuncType(&builder, genericFuncType);
}
- else if (entry->getRequirementVal()->op == kIROp_AssociatedType)
+ else if (requirementVal->op == kIROp_AssociatedType)
{
loweredVal = builder.getRTTIHandleType();
}
else
{
- loweredVal = entry->getRequirementVal();
+ loweredVal = requirementVal;
}
auto newEntry = builder.createInterfaceRequirementEntry(entry->getRequirementKey(), loweredVal);
newEntries.add(newEntry);
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 7e84ca66a..313e48502 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -2774,6 +2774,14 @@ namespace Slang
return getType(kIROp_PseudoPtrType, SLANG_COUNT_OF(operands), operands);
}
+ IRType* IRBuilder::getConjunctionType(
+ UInt typeCount,
+ IRType* const* types)
+ {
+ return getType(kIROp_ConjunctionType, typeCount, (IRInst* const*)types);
+ }
+
+
void IRBuilder::setDataType(IRInst* inst, IRType* dataType)
{
@@ -3043,8 +3051,30 @@ namespace Slang
return emitIntrinsicInst(type, kIROp_MakeTuple, count, args);
}
+ IRInst* IRBuilder::emitMakeTuple(UInt count, IRInst* const* args)
+ {
+ List<IRType*> types;
+ for(UInt i = 0; i < count; ++i)
+ types.add(args[i]->getFullType());
+
+ auto type = getTupleType(types);
+ return emitMakeTuple(type, count, args);
+ }
+
IRInst* IRBuilder::emitGetTupleElement(IRType* type, IRInst* tuple, UInt element)
{
+ // As a quick simplification/optimization, if the user requests
+ // `getTupleElement(makeTuple(a_0, a_1, ... a_N), i)` then we should
+ // just return `a_i`, provided that the index is properly in range.
+ //
+ if( auto makeTuple = as<IRMakeTuple>(tuple) )
+ {
+ if( element < makeTuple->getOperandCount() )
+ {
+ return makeTuple->getOperand(element);
+ }
+ }
+
IRInst* args[] = { tuple, getIntValue(getIntType(), element) };
return emitIntrinsicInst(type, kIROp_GetTupleElement, 2, args);
}
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index c41fa9708..787f06fa0 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -1252,6 +1252,14 @@ struct IRTaggedUnionType : IRType
IR_LEAF_ISA(TaggedUnionType)
};
+struct IRConjunctionType : IRType
+{
+ IR_LEAF_ISA(ConjunctionType)
+
+ Int getCaseCount() { return getOperandCount(); }
+ IRType* getCaseType(Int index) { return (IRType*) getOperand(index); }
+};
+
/// Represents a tuple. Tuples are created by `IRMakeTuple` and its elements
/// are accessed via `GetTupleElement(tupleValue, IRIntLit)`.
struct IRTupleType : IRType
diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp
index b54b09d63..c50364201 100644
--- a/source/slang/slang-lookup.cpp
+++ b/source/slang/slang-lookup.cpp
@@ -636,6 +636,63 @@ static void _lookUpMembersInSuperTypeImpl(
_lookUpMembersInSuperType(astBuilder, name, leafType, interfaceType, leafIsInterfaceWitness, request, ioResult, inBreadcrumbs);
}
+ else if( auto andType = as<AndType>(superType) )
+ {
+ // We have a type of the form `leftType & rightType` and we need to perform
+ // lookup in both `leftType` and `rightType`.
+ //
+ auto leftType = andType->left;
+ auto rightType = andType->right;
+
+ // Operationally, we are in a situation where we have a witness
+ // that the `leafType` we are doing lookup on is an subtype
+ // of `superType` (which is `leftType & rightType`) and now we need
+ // to construct a witness that `leafType` is a subtype of
+ // the `Left` type.
+ //
+ // Effectively, we have a witness that `T : X & Y` and we
+ // need to extract from it a witness that `T : X`.
+ // Fortunately, we have a class of subtype witness that does
+ // *precisely* this:
+ //
+ auto leafIsLeftWitness = astBuilder->create<ExtractFromConjunctionSubtypeWitness>();
+ //
+ // Our witness will be to the fact that `leafType` is a subtype of `leftType`
+ //
+ leafIsLeftWitness->sub = leafType;
+ leafIsLeftWitness->sup = leftType;
+ //
+ // The evidence for the subtype relationship will be a witness
+ // proving that `leafType : leftType & rightType`:
+ //
+ leafIsLeftWitness->conunctionWitness = leafIsSuperWitness;
+ //
+ // ... along with the index of the desired super-type in
+ // that conjunction. The index of `leftType` in `leftType & rightType`
+ // is zero.
+ //
+ leafIsLeftWitness->indexInConjunction = 0;
+
+ // The witness for the fact that `leafType : rightType` is the
+ // same as for the left case, just with a different index into
+ // the conjunction.
+ //
+ auto leafIsRightWitness = astBuilder->create<ExtractFromConjunctionSubtypeWitness>();
+ leafIsRightWitness->conunctionWitness = leafIsSuperWitness;
+ leafIsRightWitness->indexInConjunction = 1;
+ leafIsRightWitness->sub = leafType;
+ leafIsRightWitness->sup = rightType;
+
+ // We then perform lookup on both sides of the conjunction, and
+ // accumulate whatever items are found on either/both sides.
+ //
+ // For each recursive lookup, we pass the appropriate pair of
+ // the type to look up in and the witness of the subtype
+ // relationship.
+ //
+ _lookUpMembersInSuperType(astBuilder, name, leafType, leftType, leafIsLeftWitness, request, ioResult, inBreadcrumbs);
+ _lookUpMembersInSuperType(astBuilder, name, leafType, rightType, leafIsRightWitness, request, ioResult, inBreadcrumbs);
+ }
}
/// Perform lookup for `name` in the context of `type`.
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index f1a7c477b..159b2478c 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -1438,6 +1438,61 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
return LoweredValInfo::simple(context->thisTypeWitness);
}
+ LoweredValInfo visitConjunctionSubtypeWitness(ConjunctionSubtypeWitness* val)
+ {
+ // A witness `T : L & R` for a conformance of `T` to a conjunction of
+ // types `L` and `R` will be lowered as a tuple of two witnesses: one
+ // for `T : L` and one for `T : R`. Luckily, those two conformances
+ // are exactly what the `ConjunctionSubtypeWitness` stores, so we just
+ // need to lower them individually and make a tuple.
+ //
+ auto left = lowerSimpleVal(context, val->leftWitness);
+ auto right = lowerSimpleVal(context, val->rightWitness);
+ return LoweredValInfo::simple(getBuilder()->emitMakeTuple(left, right));
+ }
+
+ LoweredValInfo visitExtractFromConjunctionSubtypeWitness(ExtractFromConjunctionSubtypeWitness* val)
+ {
+ auto builder = getBuilder();
+
+ // We know from `visitConjunctionSubtypeWitness` that a witness for a relationship
+ // like `T : L & R` will be a tuple `(w_l, w_r)` where `w_l` is a witness
+ // for `T : L` and `w_r` will be a witness for `T : R`.
+ //
+ // An `ExtractFromConjunctionSubtypeWitness` represents the intention to
+ // extract one of those two sub-witnesses. It directly stores the original
+ // witness that `T : L & R`, so lower that first and expect it to be
+ // a value of tuple type.
+ //
+ auto conjunctionWitness = lowerSimpleVal(context, val->conunctionWitness);
+ auto conjunctionTupleType = as<IRTupleType>(conjunctionWitness->getDataType());
+ SLANG_ASSERT(conjunctionTupleType);
+
+ // The `ExtractFromConjunctionSubtypeWitness` also stores the index of
+ // the witness/supertype we want in the conjunction `L & R`.
+ //
+ auto indexInConjunction = val->indexInConjunction;
+
+ // We want to extract the appropriate element from the tuple based on
+ // the index, but to know the type of the result we need to look up
+ // the element type that corresponds to that index.
+ //
+ // TODO: `IRTupleType` should really have `getElementCount()` and
+ // `getElementType(index)` accessors.
+ //
+ auto elementType = (IRType*) conjunctionTupleType->getOperand(indexInConjunction);
+
+ // With the information we've extracted above, we now just need to
+ // extract the appropriate element from the `(w_l, w_r)` tuple of
+ // witnesses, and we will have our desired result.
+ //
+ return LoweredValInfo::simple(builder->emitGetTupleElement(
+ elementType,
+ conjunctionWitness,
+ indexInConjunction));
+ }
+
+
LoweredValInfo visitConstantIntVal(ConstantIntVal* val)
{
// TODO: it is a bit messy here that the `ConstantIntVal` representation
@@ -1714,6 +1769,15 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
return emitDeclRef(context, type->interfaceDeclRef, getBuilder()->getTypeKind());
}
+ LoweredValInfo visitAndType(AndType* type)
+ {
+ auto left = lowerType(context, type->left);
+ auto right = lowerType(context, type->right);
+
+ auto irType = getBuilder()->getConjunctionType(left, right);
+ return LoweredValInfo::simple(irType);
+ }
+
// 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); }
@@ -3234,6 +3298,18 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
continue;
}
}
+ else if( auto andType = as<AndType>(e->type) )
+ {
+ // TODO: We might eventually need to tell the difference
+ // between conjunctions of interfaces and conjunctions
+ // that might include non-interface types.
+ //
+ // For now we assume that any case to a conjunction
+ // is effectively a cast to an interface type.
+ //
+ e = castExpr->valueArg;
+ continue;
+ }
break;
}
return e;
@@ -3536,6 +3612,12 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
UNREACHABLE_RETURN(LoweredValInfo());
}
+ LoweredValInfo visitAndTypeExpr(AndTypeExpr* /*expr*/)
+ {
+ SLANG_UNIMPLEMENTED_X("'&' type expression during code generation");
+ UNREACHABLE_RETURN(LoweredValInfo());
+ }
+
LoweredValInfo visitAssocTypeDecl(AssocTypeDecl* decl)
{
SLANG_UNIMPLEMENTED_X("associatedtype expression during code generation");
@@ -6228,6 +6310,79 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return Slang::maybeGetConstExprType(getBuilder(), type, decl);
}
+ /// Emit appropriate generic parameters for a constraint, and return the value of that constraint.
+ ///
+ /// The `supType` paramete represents the super-type that a parameter is constrained to.
+ IRInst* emitGenericConstraintValue(
+ IRGenContext* subContext,
+ GenericTypeConstraintDecl* constraintDecl,
+ IRType* supType)
+ {
+ auto subBuilder = subContext->irBuilder;
+
+ // There are two cases we care about here.
+ //
+ if(auto andType = as<IRConjunctionType>(supType))
+ {
+ // The non-trivial case is when the constraint on a generic parameter
+ // was of the form `T : A & B`. In this case, we really want to
+ // emit the function with parameters for each of the two independent
+ // constraints `T : A` and `T : B`.
+ //
+ // We will loop over the "cases" of the conjunction (since
+ // the `IRConunctionType` can support more than just binary
+ // conjunctions) and recursively add constraints for each.
+ //
+ List<IRInst*> caseVals;
+ auto caseCount = andType->getCaseCount();
+ for(Int i = 0; i < caseCount; ++i)
+ {
+ auto caseType = andType->getCaseType(i);
+ auto caseVal = emitGenericConstraintValue(subContext, constraintDecl, caseType);
+ caseVals.add(caseVal);
+ }
+
+ return subBuilder->emitMakeTuple(caseVals);
+ }
+ else
+ {
+ // The case case is any other type being used as the constraint.
+ //
+ // The constraint will then map to a single generic parameter passing
+ // a witness table for conformance to the given `supType`.
+ //
+ auto param = subBuilder->emitParam(subBuilder->getWitnessTableType(supType));
+ addNameHint(context, param, constraintDecl);
+
+ // In order to support some of the "any-value" work in dynamic dispatch
+ // we have to attach the interface that was used as a constraint onto the
+ // type that is being constrained (which we expect to be a generic type
+ // parameter).
+ //
+ // TODO: It feels a bit gross to be doing this here; perhaps the front-end
+ // should handle propgation of value-size information from constraints
+ // back to generic parameters?
+ //
+ if (auto declRefType = as<DeclRefType>(constraintDecl->sub.type))
+ {
+ auto typeParamDeclVal = subContext->findLoweredDecl(declRefType->declRef.decl);
+ SLANG_ASSERT(typeParamDeclVal && typeParamDeclVal->val);
+ subBuilder->addTypeConstraintDecoration(typeParamDeclVal->val, supType);
+ }
+
+ return param;
+ }
+ }
+
+ void emitGenericConstraintDecl(
+ IRGenContext* subContext,
+ GenericTypeConstraintDecl* constraintDecl)
+ {
+ auto supType = lowerType(context, constraintDecl->sup.type);
+ auto value = emitGenericConstraintValue(subContext, constraintDecl, supType);
+ setValue(subContext, constraintDecl, LoweredValInfo::simple(value));
+ }
+
IRGeneric* emitOuterGeneric(
IRGenContext* subContext,
GenericDecl* genericDecl,
@@ -6274,22 +6429,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
{
if (auto constraintDecl = as<GenericTypeConstraintDecl>(member))
{
- // TODO: use a `WitnessTableKind` to represent the
- // classifier of the parameter.
- auto supType = lowerType(context, constraintDecl->sup.type);
- auto param = subBuilder->emitParam(subBuilder->getWitnessTableType(supType));
- addNameHint(context, param, constraintDecl);
- setValue(subContext, constraintDecl, LoweredValInfo::simple(param));
-
- // Attach the constraint interface type as a decoration to the IRParam value
- // representing the generic parameter, to provide downstream passes knowledge
- // of the correspondence.
- if (auto declRefType = as<DeclRefType>(constraintDecl->sub.type))
- {
- auto typeParamDeclVal = subContext->findLoweredDecl(declRefType->declRef.decl);
- SLANG_ASSERT(typeParamDeclVal && typeParamDeclVal->val);
- subBuilder->addTypeConstraintDecoration(typeParamDeclVal->val, supType);
- }
+ emitGenericConstraintDecl(subContext, constraintDecl);
}
}
@@ -7230,6 +7370,32 @@ static bool isInterfaceRequirement(Decl* decl)
return false;
}
+ /// Add flattened "leaf" elements from `val` to the `ioArgs` list
+static void _addFlattenedTupleArgs(
+ List<IRInst*>& ioArgs,
+ IRInst* val)
+{
+ if( auto tupleVal = as<IRMakeTuple>(val) )
+ {
+ // If the value is a tuple, we can add its element directly.
+ auto elementCount = tupleVal->getOperandCount();
+ for( UInt i = 0; i < elementCount; ++i )
+ {
+ _addFlattenedTupleArgs(ioArgs, tupleVal->getOperand(i));
+ }
+ }
+ //
+ // TODO: We should handle the case here where `val`
+ // is not a `makeTuple` instruction, but still has
+ // a tuple *type*. In that case we should apply `getTupleElement`
+ // for each of its elements and then recurse on them.
+ //
+ else
+ {
+ ioArgs.add(val);
+ }
+}
+
LoweredValInfo emitDeclRef(
IRGenContext* context,
Decl* decl,
@@ -7293,7 +7459,19 @@ LoweredValInfo emitDeclRef(
{
auto irArgVal = lowerSimpleVal(context, argVal);
SLANG_ASSERT(irArgVal);
- irArgs.add(irArgVal);
+
+ // It is possible that some of the arguments to the generic
+ // represent conformances to conjunction types like `A & B`.
+ // These conjunction conformances will appear as tuples in
+ // the IR, and we want to "flatten" them here so that we
+ // pass each "leaf" witness table as its own argument (to
+ // match the way that generic parameters are being emitted
+ // to the IR).
+ //
+ // TODO: This isn't a robust strategy if we ever have to deal
+ // with tuples as ordinary values.
+ //
+ _addFlattenedTupleArgs(irArgs, irArgVal);
}
// Once we have both the generic and its arguments,
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index 12fa90480..b15d215da 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -4066,21 +4066,67 @@ namespace Slang
return parameter;
}
- Expr* Parser::ParseType()
+ /// Parse an "atomic" type expression.
+ ///
+ /// An atomic type expression is a type specifier followed by an optional
+ /// body in the case of a `struct`, `enum`, etc.
+ ///
+ static Expr* _parseAtomicTypeExpr(Parser* parser)
{
- auto typeSpec = parseTypeSpec(this);
+ auto typeSpec = parseTypeSpec(parser);
if( typeSpec.decl )
{
- AddMember(currentScope, typeSpec.decl);
+ AddMember(parser->currentScope, typeSpec.decl);
}
- auto typeExpr = typeSpec.expr;
-
- typeExpr = parsePostfixTypeSuffix(this, typeExpr);
+ return typeSpec.expr;
+ }
- return typeExpr;
+ /// Parse a postfix type expression.
+ ///
+ /// A postfix type expression is an atomic type expression followed
+ /// by zero or more postifx suffixes like array brackets.
+ ///
+ static Expr* _parsePostfixTypeExpr(Parser* parser)
+ {
+ auto typeExpr = _parseAtomicTypeExpr(parser);
+ return parsePostfixTypeSuffix(parser, typeExpr);
}
+ /// Parse an infix type expression.
+ ///
+ /// Currently, the only infix type expression we support is the `&`
+ /// operator for forming interface conjunctions.
+ ///
+ static Expr* _parseInfixTypeExpr(Parser* parser)
+ {
+ auto leftExpr = _parsePostfixTypeExpr(parser);
+
+ for(;;)
+ {
+ // As long as the next token is an `&`, we will try
+ // to gobble up another type expression and form
+ // a conjunction type expression.
+
+ auto loc = peekToken(parser).loc;
+ if(!AdvanceIf(parser, TokenType::OpBitAnd))
+ break;
+
+ auto rightExpr = _parsePostfixTypeExpr(parser);
+
+ auto andExpr = parser->astBuilder->create<AndTypeExpr>();
+ andExpr->loc = loc;
+ andExpr->left = TypeExp(leftExpr);
+ andExpr->right = TypeExp(rightExpr);
+ leftExpr = andExpr;
+ }
+
+ return leftExpr;
+ }
+ Expr* Parser::ParseType()
+ {
+ return _parseInfixTypeExpr(this);
+ }
TypeExp Parser::ParseTypeExp()
{
diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis
index bf4e325e3..63ee313da 100644
--- a/source/slang/slang.natvis
+++ b/source/slang/slang.natvis
@@ -115,6 +115,8 @@
<Variable Name="pOperandInst" InitialValue="(IRInst*)nullptr"/>
<Loop Condition="index &lt; operandCount">
<Exec>pOperandInst = ((IRUse*)(&amp;(typeUse) + 1 + index))->usedValue </Exec>
+ <Item Condition="pOperandInst == 0" Name="[operand{index}]">pOperandInst</Item>
+ <If Condition="pOperandInst != 0">
<Exec>child = pOperandInst->m_decorationsAndChildren.first</Exec>
<Exec>nameDecoration = 0</Exec>
<Loop Condition="child != 0">
@@ -132,7 +134,8 @@
</Loop>
<Item Condition="nameDecoration != 0" Name="[operand{index}] : {((Slang::IRStringLit*)(((Slang::IRUse*)(nameDecoration + 1))->usedValue))->value.stringVal.chars,[((Slang::IRStringLit*)(((Slang::IRUse*)(nameDecoration + 1))->usedValue))->value.stringVal.numChars]s8}">*pOperandInst</Item>
<Item Condition="nameDecoration == 0" Name="[operand{index}]">*pOperandInst</Item>
- <Exec>index = index + 1</Exec>
+ </If>
+ <Exec>index = index + 1</Exec>
</Loop>
</CustomListItems>
<Synthetic Name="[decorations/children]">
diff --git a/tests/language-feature/interfaces/interface-conjunction.slang b/tests/language-feature/interfaces/interface-conjunction.slang
new file mode 100644
index 000000000..b5ec708a5
--- /dev/null
+++ b/tests/language-feature/interfaces/interface-conjunction.slang
@@ -0,0 +1,49 @@
+// interface-conjunction.slang
+
+// Test that we can compose interfaces with `&`
+
+//TEST(compute):COMPARE_COMPUTE: -shaderobj
+
+interface IFirst
+{
+ int getFirst();
+}
+
+interface ISecond
+{
+ int getSecond();
+}
+
+struct Pair : IFirst, ISecond
+{
+ int first;
+ int second;
+
+ int getFirst() { return first; }
+ int getSecond() { return second; }
+}
+
+typealias IBoth = IFirst & ISecond;
+
+int add<T : IBoth>(T input)
+{
+ return 2*input.getFirst() + input.getSecond();
+}
+
+int test(int value)
+{
+ Pair p = { value, (value+1) * 256 };
+ return add(p);
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+ int inVal = tid;
+ int outVal = test(inVal);
+ outputBuffer[tid] = outVal;
+}
diff --git a/tests/language-feature/interfaces/interface-conjunction.slang.expected.txt b/tests/language-feature/interfaces/interface-conjunction.slang.expected.txt
new file mode 100644
index 000000000..23f910a90
--- /dev/null
+++ b/tests/language-feature/interfaces/interface-conjunction.slang.expected.txt
@@ -0,0 +1,4 @@
+100
+202
+304
+406