summaryrefslogtreecommitdiff
path: root/source/slang/slang-lower-to-ir.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-06-04 11:53:13 -0700
committerGitHub <noreply@github.com>2020-06-04 11:53:13 -0700
commitf3d637ba4d90bc2e23db07f1a9df5a6be7533f08 (patch)
tree3dfe2fd73309ed4caf4ad6d4e8ee7a138296dfc3 /source/slang/slang-lower-to-ir.cpp
parent1b8731c809761c4e2dbec81dcee207f8a4621903 (diff)
First steps toward inheritance for struct types (#1366)
* First steps toward inheritance for struct types This change adds the ability for a `struct` type to declare a base type that is another `struct`: ```hlsl struct Base { int baseMember; } struct Derived : Base { int derivedMember; } ``` The semantics of the feature are that code like the above desugars into code like: ```hlsl struct Base { int baseMember; } struct Derived { Base _base; int derivedMember; } ``` At points where a member from the base type is being projected out, or the value is being implicitly cast to the base type, the compiler transforms the code to reference the implicitly-generated `_base` member. That means code like this: ```hlsl void f(Base b); ... Derived d = ...; int x = d.baseMember; f(d); ``` gets transformed into a form like this: ```hlsl void f(Base b); ... Derived d = ...; int x = d._base.baseMember; f(d._base); ``` Note that as a result of this choice, the behavior when passing a `Derived` value to a function that expects a `Base` (including to inherited member functions) is that of "object shearing" from the C++ world: the called function can only "see" the `Base` part of the argument, and any operations performed on it will behave as if the value was indeed a `Base`. There is no polymorphism going on because Slang doesn't currently have `virtual` methods. In an attempt to work toward inheritance being a robust feature, this change adds a bunch of more detailed logic for checking the bases of various declarations: * An `interface` declaration is only allowed to inherit from other `interface`s * An `extension` declaration can only introduce inheritance from `interface`s * A `struct` declaration can only inherit from at most one other `struct`, and that `struct` must be the first entry in the list of bases This change also adds a mechanism to control whether a `struct` or `interface` in one module can inherit from a `struct` or `interface` declared in another module: * If the base declaration is marked `[open]`, then the inheritance is allowed * If the base declaration is marked `[sealed]`, then the inheritance is allowed * If it is not marked otherwise, a `struct` is implicitly `[sealed]` * If it is not marked otherwise, an `interface` is implicitly `[open]` These seem like reasonable defaults. In order to safeguard the standard library a bit, the interfaces for builtin types have been marked `[sealed]` to make sure that a user cannot declare a `struct` and then mark it as a `BuiltinFloatingPointType`. This step should bring us a bit closer to being able to document and expose these interfaces for built-in types so that users can write code that is generic over them. There are some big caveats with this work, such that it really only represents a stepping-stone toward a usable inheritance feature. The most important caveats are: * If a `Derived` type tries to conform to an interface, such that one or more interface requirements are satisfied with members inherited from the `Base` type, that is likely to cause a crash or incorrect code generation. * If a `Derived` type tries to inherit from a `Base` type that conforms to one or more interfaces, the witness table generated for the conformance of `Derived` to that interface is likely to lead to a crash or incorrect code generation. It is clear that solving both of those issues will be necessary before we can really promote `struct` inheritance as a feature for users to try out. * fixup: trying to appease clang error * fixups: review feedback
Diffstat (limited to 'source/slang/slang-lower-to-ir.cpp')
-rw-r--r--source/slang/slang-lower-to-ir.cpp168
1 files changed, 138 insertions, 30 deletions
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index f369729d2..c52025244 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -701,17 +701,17 @@ LoweredValInfo emitCallToDeclRef(
}
IRInst* getFieldKey(
- IRGenContext* context,
- DeclRef<VarDecl> field)
+ IRGenContext* context,
+ DeclRef<Decl> field)
{
return getSimpleVal(context, emitDeclRef(context, field, context->irBuilder->getKeyType()));
}
LoweredValInfo extractField(
- IRGenContext* context,
- IRType* fieldType,
- LoweredValInfo base,
- DeclRef<VarDecl> field)
+ IRGenContext* context,
+ IRType* fieldType,
+ LoweredValInfo base,
+ DeclRef<Decl> field)
{
IRBuilder* builder = context->irBuilder;
@@ -2054,6 +2054,21 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
else if(auto constraintDeclRef = declRef.as<TypeConstraintDecl>())
{
+ auto superType = getSup(getASTBuilder(), constraintDeclRef);
+ if(auto superDeclRefType = as<DeclRefType>(superType))
+ {
+ if(auto superStructDeclRef = superDeclRefType->declRef.template as<StructDecl>())
+ {
+ // The constraint is saying that the given type inherits
+ // from a concrete `struct` type, which means it should
+ // be satisfied by a witness that represents a field
+ // (TODO: or a chain of fields) to fetch to get the
+ // final value.
+ //
+ return extractField(loweredType, loweredBase, constraintDeclRef);
+ }
+ }
+
// The code is making use of a "witness" that a value of
// some generic type conforms to an interface.
//
@@ -2748,30 +2763,87 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
UNREACHABLE_RETURN(LoweredValInfo());
}
- LoweredValInfo visitCastToInterfaceExpr(
- CastToInterfaceExpr* expr)
+ /// Emit code to cast `value` to a concrete `superType` (e.g., a `struct`).
+ ///
+ /// The `subTypeWitness` is expected to witness the sub-type relationship
+ /// by naming a field (or chain of fields) that leads from the type of
+ /// `value` to the field that stores its members for `superType`.
+ ///
+ LoweredValInfo emitCastToConcreteSuperTypeRec(
+ LoweredValInfo const& value,
+ IRType* superType,
+ Val* subTypeWitness)
{
- // 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.
+ if( auto declaredSubtypeWitness = as<DeclaredSubtypeWitness>(subTypeWitness) )
+ {
+ return extractField(superType, value, declaredSubtypeWitness->declRef);
+ }
+ else
+ {
+ SLANG_ASSERT(!"unhandled");
+ return nullptr;
+ }
+ }
+
+ LoweredValInfo visitCastToSuperTypeExpr(
+ CastToSuperTypeExpr* expr)
+ {
+ auto superType = lowerType(context, expr->type);
+ auto value = lowerRValueExpr(context, expr->valueArg);
+
+ // The actual operation that we need to perform here
+ // depends on the kind of subtype relationship we
+ // are making use of.
//
- // 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.
+ // The first important case is when the super type is
+ // an interface type, such that casting from a concrete
+ // value to that type creates a value of existential
+ // type that binds together the concrete value and the
+ // witness table that represents the subtype relationship.
//
- 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);
+ if( auto declRefType = as<DeclRefType>(expr->type) )
+ {
+ auto declRef = declRefType->declRef;
+ if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() )
+ {
+ // 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.
+ //
+ auto witnessTable = lowerSimpleVal(context, expr->witnessArg);
+
+ // 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 concreteValue = getSimpleVal(context, value);
+ auto existentialValue = getBuilder()->emitMakeExistential(
+ superType,
+ concreteValue,
+ witnessTable);
+ return LoweredValInfo::simple(existentialValue);
+ }
+ else if( auto structDeclRef = declRef.as<StructDecl>() )
+ {
+ // We are up-casting to a concrete `struct` super-type,
+ // such that the witness will represent a field of the super-type
+ // that is stored in instances of the sub-type (or a chain
+ // of such fields for a transitive witness).
+ //
+ return emitCastToConcreteSuperTypeRec(value, superType, expr->witnessArg);
+ }
+ }
+
+ SLANG_UNEXPECTED("unexpected case of subtype relationship");
+ UNREACHABLE_RETURN(LoweredValInfo());
}
LoweredValInfo subscriptValue(
@@ -2815,9 +2887,9 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
LoweredValInfo extractField(
- IRType* fieldType,
- LoweredValInfo base,
- DeclRef<VarDecl> field)
+ IRType* fieldType,
+ LoweredValInfo base,
+ DeclRef<Decl> field)
{
return Slang::extractField(context, fieldType, base, field);
}
@@ -4524,6 +4596,21 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// What is the super-type that we have declared we inherit from?
RefPtr<Type> superType = inheritanceDecl->base.type;
+ if(auto superDeclRefType = as<DeclRefType>(superType))
+ {
+ if( auto superStructDeclRef = superDeclRefType->declRef.as<StructDecl>() )
+ {
+ // TODO: the witness that a type inherits from a `struct`
+ // type should probably be a key that will be used for
+ // a field that holds the base type...
+ //
+ auto irKey = getBuilder()->createStructKey();
+ auto keyVal = LoweredValInfo::simple(irKey);
+ setGlobalValue(context, inheritanceDecl, keyVal);
+ return keyVal;
+ }
+ }
+
// Construct the mangled name for the witness table, which depends
// on the type that is conforming, and the type that it conforms to.
//
@@ -5270,6 +5357,27 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
subBuilder->setInsertInto(irStruct);
+ // A `struct` that inherits from another `struct` must start
+ // with a member for the direct base type.
+ //
+ for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
+ {
+ auto superType = inheritanceDecl->base;
+ if(auto superDeclRefType = as<DeclRefType>(superType))
+ {
+ if(auto superStructDeclRef = superDeclRefType->declRef.as<StructDecl>())
+ {
+ auto superKey = (IRStructKey*) getSimpleVal(context, ensureDecl(context, inheritanceDecl));
+ auto irSuperType = lowerType(context, superType.type);
+ subBuilder->createStructField(
+ irStruct,
+ superKey,
+ irSuperType);
+ }
+ }
+ }
+
+
for (auto fieldDecl : decl->getMembersOfType<VarDeclBase>())
{
if (fieldDecl->hasModifier<HLSLStaticModifier>())