diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-06-04 11:53:13 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-04 11:53:13 -0700 |
| commit | f3d637ba4d90bc2e23db07f1a9df5a6be7533f08 (patch) | |
| tree | 3dfe2fd73309ed4caf4ad6d4e8ee7a138296dfc3 /source/slang/slang-lower-to-ir.cpp | |
| parent | 1b8731c809761c4e2dbec81dcee207f8a4621903 (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.cpp | 168 |
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>()) |
