diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-12-17 19:26:32 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-17 19:26:32 -0800 |
| commit | 583c72af28d2dde5c564d5b56d3c5eb4ae4844f6 (patch) | |
| tree | d9af64a5d226a72c11e8f8ece9345180d07dd55a /source/slang/lower-to-ir.cpp | |
| parent | 3a02c590afdd2624b2c729e989ada9393d708f75 (diff) | |
First step toward supporting use of interfaces as existential types (#716)
* First step toward supporting use of interfaces as existential types
Traditional generics involve universal quantification. E.g., a declaration like:
```
void drive<T : IVehicle>(T vehicle);
```
indicates for *for all* types `T` that implement the `IVehicle` interface, the `drive()` function is available.
In contrast, whend directly using an interface type like:
```
IVehicle v = ...;
v.doSomething();
```
we only know that there *exists* some concrete type (we could call it `E`) such that `v` refers to a value of type `E`, and `E` implements the `IVehicle` interface. In order to perform an operation like `v.doSomething()` we need to "open" the existential value so that we can look at the concrete type and how it implements the `IVehicle.doSomething` requirement.
This change adds a very explicit representation of existentials to Slang's IR. An operation like `e = makeExistential(v, w)` creates a value of some existential type (interfaces being our only existential types for now), by wrapping a concrete value `v` (the type of `v` can be seen as an implicit operand) and a witness table `w` showing that the type of `v` implements the requirements of the chosen interface type.
In turn, opening of an existential is handled with operations `extractExistential{Value|Type|WitnessTable}` which pull the corresponding piece of information out of a value of existential type (which somewhere in the code had to have been created with `makeExistential`).
The change includes a trivial simplification pass that can detect cases where an `extractExistential*` operation is applied direclty to a `makeExistential` operation, so that there is only one possible result that could be extracted. This allows for simplification of existential types used in trivial ways for local variables (this is mostly so I can check in a functional test, rather than to actually support useful code involving interfaces right now).
The logic in the semantic checking phase of the compiler is comparatively more complex.
When we are about to perform member lookup given an expression like `obj.member` we will first check if `obj` has an existential type, and if it does we will construct a suitable local context in which we extract the value, type, and witness table from the existential (these all become explicit AST expression nodes), and then use the extracted value as the base of the lookup operation.
The nature of existential values is that two different values with the same existential (interface) type could wrap concrete values with differnt types, so that we need to carefully refer only to the extracted type/value/witness-table of specific *values*. We handle this right now by conceptually moving the existential-type value into a local variable (by introducing a `LetExpr` that amounts to `let v = <init> in <body>`) and then require that the extract expressions must refer to the (immutable) variable declaration from which they are extracting a value.
(Eventually we should expand this so that when using an immutable local variable of existential type we just use that variable as-is rather than introduce a new temporary)
A simple test case is included that uses an interface type in an almost trivial way for a local variable; this test can be run and produces the expected results.
A more complex test case that passes an existential into a function is included, but left disabled because a more aggressive simplification approach is required to generate working code from it.
* Add missing file for expected test output
* Fixups for merge from top-of-tree
Diffstat (limited to 'source/slang/lower-to-ir.cpp')
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 102 |
1 files changed, 94 insertions, 8 deletions
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. |
