From 583c72af28d2dde5c564d5b56d3c5eb4ae4844f6 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Mon, 17 Dec 2018 19:26:32 -0800 Subject: 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 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 = in `) 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 --- source/slang/check.cpp | 164 ++++++++++++++++++++++++++++++------- source/slang/emit.cpp | 23 ++++++ source/slang/expr-defs.h | 24 ++++++ source/slang/ir-existential.cpp | 114 ++++++++++++++++++++++++++ source/slang/ir-existential.h | 12 +++ source/slang/ir-inst-defs.h | 8 +- source/slang/ir-insts.h | 32 ++++++++ source/slang/ir.cpp | 76 +++++++++++++++++ source/slang/ir.h | 4 + source/slang/lower-to-ir.cpp | 102 +++++++++++++++++++++-- source/slang/slang.vcxproj | 2 + source/slang/slang.vcxproj.filters | 6 ++ source/slang/syntax.cpp | 91 ++++++++++++++++++++ source/slang/type-defs.h | 14 ++++ source/slang/val-defs.h | 15 ++++ 15 files changed, 649 insertions(+), 38 deletions(-) create mode 100644 source/slang/ir-existential.cpp create mode 100644 source/slang/ir-existential.h (limited to 'source') 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 maybeOpenExistential(RefPtr expr) + { + auto exprType = expr->type.type; + + if(auto declRefType = exprType->As()) + { + if(auto interfaceDeclRef = declRefType->declRef.As()) + { + // 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(); + 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 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 = new LetExpr(); + letExpr->decl = varDecl; + + RefPtr openedType = new ExtractExistentialType(); + openedType->declRef = varDeclRef; + + RefPtr openedWitness = new ExtractExistentialSubtypeWitness(); + openedWitness->sub = openedType; + openedWitness->sup = expr->type.type; + openedWitness->declRef = varDeclRef; + + RefPtr openedThisType = new ThisTypeSubstitution(); + openedThisType->outer = interfaceDeclRef.substitutions.substitutions; + openedThisType->interfaceDecl = interfaceDecl; + openedThisType->witness = openedWitness; + + DeclRef substDeclRef = DeclRef(interfaceDecl, openedThisType); + auto substDeclRefType = DeclRefType::Create(getSession(), substDeclRef); + + RefPtr 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 ConstructDeclRefExpr( DeclRef declRef, RefPtr 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()) { @@ -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()) { // 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 createCastToInterfaceExpr( + RefPtr toType, + RefPtr fromExpr, + RefPtr witness) + { + RefPtr 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 Coerce( RefPtr toType, @@ -7900,36 +8010,24 @@ namespace Slang // deal with this cases here, even if they are no-ops. // - RefPtr visitDerefExpr(DerefExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; + #define CASE(NAME) \ + RefPtr visit##NAME(NAME* expr) \ + { \ + SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, \ + "should not appear in input syntax"); \ + return expr; \ } - RefPtr 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 visitOverloadedExpr(OverloadedExpr* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } - - RefPtr visitOverloadedExpr2(OverloadedExpr2* expr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); - return expr; - } - - - RefPtr 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 @@ -137,6 +137,17 @@ END_SYNTAX_CLASS() // An implicit type-cast inserted during semantic checking 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 valueArg; + + /// A witness showing that `valueArg` conforms to the chosen interface + RefPtr witnessArg; +) END_SYNTAX_CLASS() SIMPLE_SYNTAX_CLASS(SelectExpr, OperatorExpr) @@ -169,3 +180,16 @@ SYNTAX_CLASS(ThisExpr, Expr) FIELD(RefPtr, scope); END_SYNTAX_CLASS() +// An expression that binds a temporary variable in a local expression context +SYNTAX_CLASS(LetExpr, Expr) +RAW( + RefPtr decl; + RefPtr body; +) +END_SYNTAX_CLASS() + +SYNTAX_CLASS(ExtractExistentialValueExpr, Expr) +RAW( + DeclRef 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 instsToRemove; + +}; + +void simplifyExistentialTypesRec( + ExistentialTypeSimplificationContext* context, + IRInst* inst) +{ + switch( inst->op ) + { + default: + break; + + case kIROp_ExtractExistentialValue: + { + auto arg = inst->getOperand(0); + if( auto makeExistential = as(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(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(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( + this, + kIROp_ExtractExistentialValue, + type, + 1, + &existentialValue); + addInst(inst); + return inst; + } + + IRType* IRBuilder::emitExtractExistentialType( + IRInst* existentialValue) + { + auto type = getTypeKind(); + auto inst = createInst( + this, + kIROp_ExtractExistentialType, + type, + 1, + &existentialValue); + addInst(inst); + return (IRType*) inst; + } + + IRInst* IRBuilder::emitExtractExistentialWitnessTable( + IRInst* existentialValue) + { + auto type = getWitnessTableType(); + auto inst = createInst( + 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( + this, + kIROp_InterfaceType, + nullptr); + addGlobalValue(this, interfaceType); + return interfaceType; + } + IRStructKey* IRBuilder::createStructKey() { IRStructKey* structKey = createInst( @@ -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(originalInst), originalValues); + case kIROp_InterfaceType: + return cloneInterfaceTypeImpl(context, builder, cast(originalInst), originalValues); + case kIROp_Generic: return cloneGenericImpl(context, builder, cast(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 : ValVisitordeclRef; + 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 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 // 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 @@ -2230,7 +2293,6 @@ struct LValueExprLoweringVisitor : ExprLoweringVisitorBaseshared->extValues.Add(swizzledLValue); return LoweredValInfo::swizzledLValue(swizzledLValue); } - }; struct RValueExprLoweringVisitor : ExprLoweringVisitorBase @@ -4220,12 +4282,7 @@ struct DeclLoweringVisitor : DeclVisitor 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 } } - 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()) { + 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 @@ + @@ -234,6 +235,7 @@ + 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 @@ Header Files + + Header Files + Header Files @@ -197,6 +200,9 @@ Source Files + + Source Files + Source Files 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() ) + { + return declRef.Equals(extractExistential->declRef); + } + return false; + } + + int ExtractExistentialType::GetHashCode() + { + return declRef.GetHashCode(); + } + + RefPtr ExtractExistentialType::CreateCanonicalType() + { + return this; + } + + RefPtr ExtractExistentialType::SubstituteImpl(SubstitutionSet subst, int* ioDiff) + { + int diff = 0; + auto substDeclRef = declRef.SubstituteImpl(subst, &diff); + if(!diff) + return this; + + (*ioDiff)++; + + RefPtr substValue = new ExtractExistentialType(); + substValue->declRef = declRef; + return substValue; + } + + // ExtractExistentialSubtypeWitness + + bool ExtractExistentialSubtypeWitness::EqualsVal(Val* val) + { + if( auto extractWitness = val->dynamicCast() ) + { + 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 ExtractExistentialSubtypeWitness::SubstituteImpl(SubstitutionSet subst, int* ioDiff) + { + int diff = 0; + + auto substDeclRef = declRef.SubstituteImpl(subst, &diff); + auto substSub = sub->SubstituteImpl(subst, &diff).As(); + auto substSup = sup->SubstituteImpl(subst, &diff).As(); + + if(!diff) + return this; + + (*ioDiff)++; + + RefPtr 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 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 declRef; + + virtual String ToString() override; + virtual bool EqualsImpl(Type * type) override; + virtual int GetHashCode() override; + virtual RefPtr CreateCanonicalType() override; + virtual RefPtr 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 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 declRef; + + virtual bool EqualsVal(Val* val) override; + virtual String ToString() override; + virtual int GetHashCode() override; + virtual RefPtr SubstituteImpl(SubstitutionSet subst, int * ioDiff) override; +) +END_SYNTAX_CLASS() + -- cgit v1.2.3