From 822ed708364b257b7d2f61ecb8a51a4c96f7edaa Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Thu, 13 Dec 2018 12:13:58 -0800 Subject: Move mangled name out of IRGlobalValue (#752) * Move mangled name out of IRGlobalValue Previously the `IRGlobalValue` type was used as a root for all IR instructions that can have "linkage," in the sense that a definition in one module can satisfy a use in another module. The mangled symbol name was stored in state directly on each `IRGlobalValue`, which created some complications, and also forced IR instructions that wanted to support linkage to wedge into the hierarchy at that specific point. This change moves the mangled name out into a decoration: either an `IRImportDecoration` or an `IRExportDecoration`, both of which inherit from `IRLinkageDecoration` which exposes the mangled name. This change has a few benefits: * We can now have any kind of instruction be exported/imported, without having to inherit from `IRGlobalValue`. This could potentially let `IRStructType` and `IRWitnessTable` be simplified to just have operand lists instead of dummy chldren as they do today. * We can now easily have "global values" like functions that explicitly *don't* get linkage, instead of using a null or empty mangled name as a marker. * We can use the exact opcode on a linkage decoration to distinguish imports from exports, which could be used to more accurately resolve symbols during the linking step. Other than adding the decorations and making sure that AST->IR lowering adds them, the main changes here are around any code that used `IRGlobalValue`. Variables and parameters of type `IRGlobalValue*` were changed to `IRInst*` easily, so the main challenge was around code that *casts* to `IRGlobalValue*. In cases where a cast to `IRGlobalValue` also performed a test for the mangled name being non-null/non-empty, we simply switched the code to check for the presence of an `IRLinkageDecoration`, since that is the new way of indicating a value with linakge. Most of the serious complications arose in `ir.cpp` around the "linking"/target-specialization and generic specialization steps. The "linking" logic was checking for `IRGlobalValue` to opt into some more complicated cloning logic, and just checking for a linkage decoration here wasn't sufficient since the front-end *does* produce global values without linkage in some cases (e.g., for a function-`static` variable we produce a global variable without linkage). This logic was updated to just check for the cases that used to amount to `IRGlobalValue`s directly by opcode. It might be simpler in the short term to have kept `IRGlobalValue` around to make the existing casts Just Work, but I'm confident that this logic could actually be rewritten for much greater clarity and simplicity and that is the better way forward. The generic specialization logic was using some really messy code to generate a new mangled name to represent the specialized symbol, and then searching for an existing match for that name. The original idea there was that an IR module could include "pre-specialized" versions of certain generics to speed up back-end compilation by eliminating the need to specialize in some cases, but this feature has never been implemented so the overhead here is just a waste. Instead, I moved generic specialization to use a simpler dictionary to map the operands to a `specialize` instruction over to the resulting specialized value. This allows for some simplifications in the name mangling logic, because it no longer needs to figure out how to produce mangled names from IR instructions representing types/values. As part of this change I also overhauled the IR emit logic to produce cleaner output by default, borrowing some of the ideas from the logic in `emit.cpp`. IR values are now automatically given names based on their "name hint" decoration, if any, to make the code easier to follow, and I also made it so that types and literals get collapsed into their use sites in a new "simplified" IR dump mode (which is currently the default, with no way to opt into the other mode without tweaking the code). The resulting IR dumps are much nicer to look at, but as a result the one test that involves IR dumping (`ir/string-literal`) doesn't really test what it used to. One weird issue that came up during testing is that the `transitive-interface` test had previously been producing output that made no sense (that is, the expected output file wasn't really sensible), and somehow these changes were altering its behavior. Changing the test to use `int` values instead of `float` was enough to make the output be what I'd expect, and hand inspection of generating DXBC has me convinced we were compiling the `float` case correctly too. There appears to be some issue around tests with floating-point outputs that we should investigate. * fixup: C++ declaration order --- source/slang/lower-to-ir.cpp | 191 ++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 91 deletions(-) (limited to 'source/slang/lower-to-ir.cpp') diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 2804854b7..bf5aeff24 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -384,6 +384,48 @@ void setValue(IRGenContext* context, Decl* decl, LoweredValInfo value) context->env->mapDeclToValue[decl] = value; } +ModuleDecl* findModuleDecl(Decl* decl) +{ + for (auto dd = decl; dd; dd = dd->ParentDecl) + { + if (auto moduleDecl = dynamic_cast(dd)) + return moduleDecl; + } + return nullptr; +} + +bool isFromStdLib(Decl* decl) +{ + for (auto dd = decl; dd; dd = dd->ParentDecl) + { + if (dd->HasModifier()) + return true; + } + return false; +} + +bool isImportedDecl(IRGenContext* context, Decl* decl) +{ + ModuleDecl* moduleDecl = findModuleDecl(decl); + if (!moduleDecl) + return false; + + // HACK: don't treat standard library code as + // being imported for right now, just because + // we don't load its IR in the same way as + // for other imports. + // + // TODO: Fix this the right way, by having standard + // library declarations have IR modules that we link + // in via the normal means. + if (isFromStdLib(decl)) + return false; + + if (moduleDecl != context->shared->mainModuleDecl) + return true; + + return false; +} /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration? bool isEffectivelyStatic( @@ -954,6 +996,51 @@ IRType* getIntType( return context->irBuilder->getBasicType(BaseType::Int); } +static IRGeneric* getOuterGeneric(IRInst* gv) +{ + auto parentBlock = as(gv->getParent()); + if (!parentBlock) return nullptr; + + auto parentGeneric = as(parentBlock->getParent()); + return parentGeneric; +} + +static void addLinkageDecoration( + IRGenContext* context, + IRInst* inInst, + Decl* decl, + UnownedStringSlice const& mangledName) +{ + // If the instruction is nested inside one or more generics, + // then the mangled name should really apply to the outer-most + // generic, and not the declaration nested inside. + + auto builder = context->irBuilder; + + IRInst* inst = inInst; + while (auto outerGeneric = getOuterGeneric(inst)) + { + inst = outerGeneric; + } + + if(isImportedDecl(context, decl)) + { + builder->addImportDecoration(inst, mangledName); + } + else + { + builder->addExportDecoration(inst, mangledName); + } +} + +static void addLinkageDecoration( + IRGenContext* context, + IRInst* inst, + Decl* decl) +{ + addLinkageDecoration(context, inst, decl, getMangledName(decl).getUnownedSlice()); +} + IRStructKey* getInterfaceRequirementKey( IRGenContext* context, Decl* requirementDecl) @@ -973,8 +1060,8 @@ IRStructKey* getInterfaceRequirementKey( // this requirement in the IR, and to allow lookup // into the declaration. requirementKey = builder->createStructKey(); - requirementKey->mangledName = context->getSession()->getNameObj( - getMangledName(requirementDecl)); + + addLinkageDecoration(context, requirementKey, requirementDecl); context->shared->interfaceRequirementKeys.Add(requirementDecl, requirementKey); @@ -3461,7 +3548,7 @@ struct DeclLoweringVisitor : DeclVisitor // alias is somehow generic. if(outerGeneric) { - setMangledName(outerGeneric, getMangledName(decl)); + addLinkageDecoration(context, outerGeneric, decl); } auto type = lowerType(subContext, decl->type.type); @@ -3499,7 +3586,7 @@ struct DeclLoweringVisitor : DeclVisitor // and so it should lower as a parameter of its own. auto inst = getBuilder()->emitGlobalGenericParam(); - setMangledName(inst, getMangledName(decl)); + addLinkageDecoration(context, inst, decl); return LoweredValInfo::simple(inst); } @@ -3515,7 +3602,7 @@ struct DeclLoweringVisitor : DeclVisitor LoweredValInfo visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl) { auto inst = getBuilder()->emitGlobalGenericParam(); - setMangledName(inst, getMangledName(decl)); + addLinkageDecoration(context, inst, decl); return LoweredValInfo::simple(inst); } @@ -3643,8 +3730,7 @@ struct DeclLoweringVisitor : DeclVisitor // on the type that is conforming, and the type that it conforms to. // // TODO: This approach doesn't really make sense for generic `extension` conformances. - auto mangledName = context->getSession()->getNameObj( - getMangledNameForConformanceWitness(subType, superType)); + auto mangledName = getMangledNameForConformanceWitness(subType, superType); // A witness table may need to be generic, if the outer // declaration (either a type declaration or an `extension`) @@ -3666,7 +3752,7 @@ struct DeclLoweringVisitor : DeclVisitor // Create the IR-level witness table auto irWitnessTable = subBuilder->createWitnessTable(); - setMangledName(irWitnessTable, mangledName); + addLinkageDecoration(context, irWitnessTable, inheritanceDecl, mangledName.getUnownedSlice()); // Register the value now, rather than later, to avoid any possible infinite recursion. setGlobalValue(context, inheritanceDecl, LoweredValInfo::simple(irWitnessTable)); @@ -3762,7 +3848,7 @@ struct DeclLoweringVisitor : DeclVisitor irGlobal = builder->createGlobalVar(varType); globalVal = LoweredValInfo::ptr(irGlobal); } - irGlobal->mangledName = context->getSession()->getNameObj(getMangledName(decl)); + addLinkageDecoration(context, irGlobal, decl); addNameHint(context, irGlobal, decl); maybeSetRate(context, irGlobal, decl); @@ -4137,35 +4223,6 @@ struct DeclLoweringVisitor : DeclVisitor return LoweredValInfo(); } - IRGeneric* getOuterGeneric(IRGlobalValue* gv) - { - auto parentBlock = as(gv->getParent()); - if (!parentBlock) return nullptr; - - auto parentGeneric = as(parentBlock->getParent()); - return parentGeneric; - } - - void setMangledName(IRGlobalValue* inst, Name* name) - { - // If the instruction is nested inside one or more generics, - // then the mangled name should really apply to the outer-most - // generic, and not the declaration nested inside. - - IRGlobalValue* gv = inst; - while (auto outerGeneric = getOuterGeneric(gv)) - { - gv = outerGeneric; - } - - gv->mangledName = name; - } - - void setMangledName(IRGlobalValue* inst, String const& name) - { - setMangledName(inst, context->getSession()->getNameObj(name)); - } - LoweredValInfo visitEnumCaseDecl(EnumCaseDecl* decl) { // A case within an `enum` decl will lower to a value @@ -4241,7 +4298,7 @@ struct DeclLoweringVisitor : DeclVisitor IRStructType* irStruct = subBuilder->createStructType(); addNameHint(context, irStruct, decl); - setMangledName(irStruct, getMangledName(decl)); + addLinkageDecoration(context, irStruct, decl); subBuilder->setInsertInto(irStruct); @@ -4306,8 +4363,7 @@ struct DeclLoweringVisitor : DeclVisitor addVarDecorations(context, irFieldKey, fieldDecl); - irFieldKey->mangledName = context->getSession()->getNameObj( - getMangledName(fieldDecl)); + addLinkageDecoration(context, irFieldKey, fieldDecl); if (auto semanticModifier = fieldDecl->FindModifier()) { @@ -4582,47 +4638,9 @@ struct DeclLoweringVisitor : DeclVisitor } } - ModuleDecl* findModuleDecl(Decl* decl) - { - for (auto dd = decl; dd; dd = dd->ParentDecl) - { - if (auto moduleDecl = dynamic_cast(dd)) - return moduleDecl; - } - return nullptr; - } - - bool isFromStdLib(Decl* decl) - { - for (auto dd = decl; dd; dd = dd->ParentDecl) - { - if (dd->HasModifier()) - return true; - } - return false; - } - bool isImportedDecl(Decl* decl) { - ModuleDecl* moduleDecl = findModuleDecl(decl); - if (!moduleDecl) - return false; - - // HACK: don't treat standard library code as - // being imported for right now, just because - // we don't load its IR in the same way as - // for other imports. - // - // TODO: Fix this the right way, by having standard - // library declarations have IR modules that we link - // in via the normal means. - if (isFromStdLib(decl)) - return false; - - if (moduleDecl != this->context->shared->mainModuleDecl) - return true; - - return false; + return Slang::isImportedDecl(context, decl); } bool isConstExprVar(Decl* decl) @@ -4659,21 +4677,13 @@ struct DeclLoweringVisitor : DeclVisitor auto subBuilder = subContext->irBuilder; // Of course, a generic might itself be nested inside of other generics... - auto nextOuterGeneric = emitOuterGenerics(subContext, genericDecl, leafDecl); + emitOuterGenerics(subContext, genericDecl, leafDecl); // We need to create an IR generic auto irGeneric = subBuilder->emitGeneric(); subBuilder->setInsertInto(irGeneric); - if (!nextOuterGeneric) - { - // If this is the outer-most generic, then it will be the - // global symbol that gets the mangled name from the inner - // declaration actually being lowered. - irGeneric->mangledName = context->getSession()->getNameObj(getMangledName(leafDecl)); - } - auto irBlock = subBuilder->emitBlock(); subBuilder->setInsertInto(irBlock); @@ -4848,8 +4858,7 @@ struct DeclLoweringVisitor : DeclVisitor IRFunc* irFunc = subBuilder->createFunc(); addNameHint(context, irFunc, decl); - - setMangledName(irFunc, getMangledName(decl)); + addLinkageDecoration(context, irFunc, decl); List paramTypes; -- cgit v1.2.3