diff options
Diffstat (limited to 'source/slang/slang-session.cpp')
| -rw-r--r-- | source/slang/slang-session.cpp | 2068 |
1 files changed, 2068 insertions, 0 deletions
diff --git a/source/slang/slang-session.cpp b/source/slang/slang-session.cpp new file mode 100644 index 000000000..da39949da --- /dev/null +++ b/source/slang/slang-session.cpp @@ -0,0 +1,2068 @@ +// slang-session.cpp +#include "slang-session.h" + +#include "compiler-core/slang-artifact-util.h" +#include "slang-check-impl.h" +#include "slang-compiler.h" +#include "slang-lower-to-ir.h" +#include "slang-mangle.h" +#include "slang-options.h" +#include "slang-parser.h" +#include "slang-preprocessor.h" +#include "slang-serialize-ast.h" +#include "slang-serialize-container.h" +#include "slang-serialize-ir.h" + +namespace Slang +{ + +Linkage::Linkage(Session* session, ASTBuilder* astBuilder, Linkage* builtinLinkage) + : m_session(session) + , m_retainedSession(session) + , m_sourceManager(&m_defaultSourceManager) + , m_astBuilder(astBuilder) + , m_cmdLineContext(new CommandLineContext()) + , m_stringSlicePool(StringSlicePool::Style::Default) +{ + namePool = session->getNamePool(); + + m_defaultSourceManager.initialize(session->getBuiltinSourceManager(), nullptr); + + setFileSystem(nullptr); + + // Copy of the built in linkages modules + if (builtinLinkage) + { + for (const auto& nameToMod : builtinLinkage->mapNameToLoadedModules) + mapNameToLoadedModules.add(nameToMod); + } + + m_semanticsForReflection = new SharedSemanticsContext(this, nullptr, nullptr); +} + +SharedSemanticsContext* Linkage::getSemanticsForReflection() +{ + return m_semanticsForReflection.get(); +} + +ISlangUnknown* Linkage::getInterface(const Guid& guid) +{ + if (guid == ISlangUnknown::getTypeGuid() || guid == ISession::getTypeGuid()) + return asExternal(this); + + return nullptr; +} + +Linkage::~Linkage() +{ + // Upstream type checking cache. + if (m_typeCheckingCache) + { + auto globalSession = getSessionImpl(); + std::lock_guard<std::mutex> lock(globalSession->m_typeCheckingCacheMutex); + if (!globalSession->m_typeCheckingCache || + globalSession->getTypeCheckingCache()->resolvedOperatorOverloadCache.getCount() < + getTypeCheckingCache()->resolvedOperatorOverloadCache.getCount()) + { + globalSession->m_typeCheckingCache = m_typeCheckingCache; + getTypeCheckingCache()->version++; + } + destroyTypeCheckingCache(); + } +} + +SearchDirectoryList& Linkage::getSearchDirectories() +{ + auto list = m_optionSet.getArray(CompilerOptionName::Include); + if (list.getCount() != searchDirectoryCache.searchDirectories.getCount()) + { + searchDirectoryCache.searchDirectories.clear(); + for (auto dir : list) + searchDirectoryCache.searchDirectories.add(SearchDirectory(dir.stringValue)); + } + return searchDirectoryCache; +} + +TypeCheckingCache* Linkage::getTypeCheckingCache() +{ + if (!m_typeCheckingCache) + { + m_typeCheckingCache = new TypeCheckingCache(); + } + return static_cast<TypeCheckingCache*>(m_typeCheckingCache.get()); +} + +void Linkage::destroyTypeCheckingCache() +{ + m_typeCheckingCache = nullptr; +} + +SLANG_NO_THROW slang::IGlobalSession* SLANG_MCALL Linkage::getGlobalSession() +{ + return asExternal(getSessionImpl()); +} + +void Linkage::addTarget(slang::TargetDesc const& desc) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto targetIndex = addTarget(CodeGenTarget(desc.format)); + auto target = targets[targetIndex]; + + auto& optionSet = target->getOptionSet(); + optionSet.inheritFrom(m_optionSet); + + optionSet.set(CompilerOptionName::FloatingPointMode, FloatingPointMode(desc.floatingPointMode)); + optionSet.addTargetFlags(desc.flags); + optionSet.setProfile(Profile(desc.profile)); + optionSet.set(CompilerOptionName::LineDirectiveMode, LineDirectiveMode(desc.lineDirectiveMode)); + optionSet.set(CompilerOptionName::GLSLForceScalarLayout, desc.forceGLSLScalarBufferLayout); + + CompilerOptionSet targetOptions; + targetOptions.load(desc.compilerOptionEntryCount, desc.compilerOptionEntries); + optionSet.overrideWith(targetOptions); +} + +SLANG_NO_THROW slang::IModule* SLANG_MCALL +Linkage::loadModule(const char* moduleName, slang::IBlob** outDiagnostics) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + applySettingsToDiagnosticSink(&sink, &sink, m_optionSet); + + if (isInLanguageServer()) + { + sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); + } + + try + { + auto name = getNamePool()->getName(moduleName); + + auto module = findOrImportModule(name, SourceLoc(), &sink); + sink.getBlobIfNeeded(outDiagnostics); + + return asExternal(module); + } + catch (const AbortCompilationException& e) + { + outputExceptionDiagnostic(e, sink, outDiagnostics); + return nullptr; + } + catch (const Exception& e) + { + outputExceptionDiagnostic(e, sink, outDiagnostics); + return nullptr; + } + catch (...) + { + outputExceptionDiagnostic(sink, outDiagnostics); + return nullptr; + } +} + +slang::IModule* Linkage::loadModuleFromBlob( + const char* moduleName, + const char* path, + slang::IBlob* source, + ModuleBlobType blobType, + slang::IBlob** outDiagnostics) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + applySettingsToDiagnosticSink(&sink, &sink, m_optionSet); + + if (isInLanguageServer()) + { + sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); + } + + + try + { + auto getDigestStr = [](auto x) + { + DigestBuilder<SHA1> digestBuilder; + digestBuilder.append(x); + return digestBuilder.finalize().toString(); + }; + + String moduleNameStr = moduleName; + if (!moduleName) + moduleNameStr = getDigestStr(source); + + auto name = getNamePool()->getName(moduleNameStr); + RefPtr<LoadedModule> loadedModule; + if (mapNameToLoadedModules.tryGetValue(name, loadedModule)) + { + return loadedModule; + } + String pathStr = path; + if (pathStr.getLength() == 0) + { + // If path is empty, use a digest from source as path. + pathStr = getDigestStr(source); + } + auto pathInfo = PathInfo::makeFromString(pathStr); + if (File::exists(pathStr)) + { + String cannonicalPath; + if (SLANG_SUCCEEDED(Path::getCanonical(pathStr, cannonicalPath))) + { + pathInfo = PathInfo::makeNormal(pathStr, cannonicalPath); + } + } + RefPtr<Module> module = + loadModuleImpl(name, pathInfo, source, SourceLoc(), &sink, nullptr, blobType); + sink.getBlobIfNeeded(outDiagnostics); + return asExternal(module.get()); + } + catch (const AbortCompilationException& e) + { + outputExceptionDiagnostic(e, sink, outDiagnostics); + return nullptr; + } + catch (const Exception& e) + { + outputExceptionDiagnostic(e, sink, outDiagnostics); + return nullptr; + } + catch (...) + { + outputExceptionDiagnostic(sink, outDiagnostics); + return nullptr; + } +} + +SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource( + const char* moduleName, + const char* path, + slang::IBlob* source, + slang::IBlob** outDiagnostics) +{ + return loadModuleFromBlob(moduleName, path, source, ModuleBlobType::Source, outDiagnostics); +} + +SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSourceString( + const char* moduleName, + const char* path, + const char* source, + slang::IBlob** outDiagnostics) +{ + auto sourceBlob = StringBlob::create(UnownedStringSlice(source)); + return loadModuleFromSource(moduleName, path, sourceBlob.get(), outDiagnostics); +} + +SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromIRBlob( + const char* moduleName, + const char* path, + slang::IBlob* source, + slang::IBlob** outDiagnostics) +{ + return loadModuleFromBlob(moduleName, path, source, ModuleBlobType::IR, outDiagnostics); +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::loadModuleInfoFromIRBlob( + slang::IBlob* source, + SlangInt& outModuleVersion, + const char*& outModuleCompilerVersion, + const char*& outModuleName) +{ + // We start by reading the content of the file as + // an in-memory RIFF container. + // + auto rootChunk = RIFF::RootChunk::getFromBlob(source); + if (!rootChunk) + { + return SLANG_FAIL; + } + + auto moduleChunk = ModuleChunk::find(rootChunk); + if (!moduleChunk) + { + return SLANG_FAIL; + } + + auto irChunk = moduleChunk->findIR(); + if (!irChunk) + { + return SLANG_FAIL; + } + + RefPtr<IRModule> irModule; + String compilerVersion; + UInt version; + String name; + SLANG_RETURN_ON_FAIL(readSerializedModuleInfo(irChunk, compilerVersion, version, name)); + const auto compilerVersionSlice = m_stringSlicePool.addAndGetSlice(compilerVersion); + const auto nameSlice = m_stringSlicePool.addAndGetSlice(name); + outModuleCompilerVersion = compilerVersionSlice.begin(); + outModuleName = nameSlice.begin(); + outModuleVersion = SlangInt(version); + + return SLANG_OK; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::createCompositeComponentType( + slang::IComponentType* const* componentTypes, + SlangInt componentTypeCount, + slang::IComponentType** outCompositeComponentType, + ISlangBlob** outDiagnostics) +{ + if (outCompositeComponentType == nullptr) + return SLANG_E_INVALID_ARG; + + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + // Attempting to create a "composite" of just one component type should + // just return the component type itself, to avoid redundant work. + // + if (componentTypeCount == 1) + { + auto componentType = componentTypes[0]; + componentType->addRef(); + *outCompositeComponentType = componentType; + return SLANG_OK; + } + + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + applySettingsToDiagnosticSink(&sink, &sink, m_optionSet); + + List<RefPtr<ComponentType>> childComponents; + for (Int cc = 0; cc < componentTypeCount; ++cc) + { + childComponents.add(asInternal(componentTypes[cc])); + } + + RefPtr<ComponentType> composite = CompositeComponentType::create(this, childComponents); + + sink.getBlobIfNeeded(outDiagnostics); + + *outCompositeComponentType = asExternal(composite.detach()); + return SLANG_OK; +} + +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::specializeType( + slang::TypeReflection* inUnspecializedType, + slang::SpecializationArg const* specializationArgs, + SlangInt specializationArgCount, + ISlangBlob** outDiagnostics) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto unspecializedType = asInternal(inUnspecializedType); + + List<Type*> typeArgs; + + for (Int ii = 0; ii < specializationArgCount; ++ii) + { + auto& arg = specializationArgs[ii]; + if (arg.kind != slang::SpecializationArg::Kind::Type) + return nullptr; + + typeArgs.add(asInternal(arg.type)); + } + + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + auto specializedType = + specializeType(unspecializedType, typeArgs.getCount(), typeArgs.getBuffer(), &sink); + sink.getBlobIfNeeded(outDiagnostics); + + return asExternal(specializedType); +} + +DeclRef<GenericDecl> getGenericParentDeclRef( + ASTBuilder* astBuilder, + SemanticsVisitor* visitor, + DeclRef<Decl> declRef) +{ + // Create substituted parent decl ref. + auto decl = declRef.getDecl(); + + while (decl && !as<GenericDecl>(decl)) + { + decl = decl->parentDecl; + } + + if (!decl) + { + // No generic parent + return DeclRef<GenericDecl>(); + } + + auto genericDecl = as<GenericDecl>(decl); + auto genericDeclRef = + createDefaultSubstitutionsIfNeeded(astBuilder, visitor, DeclRef(genericDecl)) + .as<GenericDecl>(); + return substituteDeclRef(SubstitutionSet(declRef), astBuilder, genericDeclRef) + .as<GenericDecl>(); +} + +bool Linkage::isSpecialized(DeclRef<Decl> declRef) +{ + // For now, we only support two 'states': fully applied or not at all. + // If we add support for partial specialization, we will need to update this logic. + // + // If it's not specialized, then declRef will be the one with default substitutions. + // + SemanticsVisitor visitor(getSemanticsForReflection()); + + auto decl = declRef.getDecl(); + while (decl && !as<GenericDecl>(decl)) + { + decl = decl->parentDecl; + } + + if (!decl) + return true; // no generics => always specialized + + auto defaultArgs = getDefaultSubstitutionArgs(getASTBuilder(), &visitor, as<GenericDecl>(decl)); + auto currentArgs = + SubstitutionSet(declRef).findGenericAppDeclRef(as<GenericDecl>(decl))->getArgs(); + + if (defaultArgs.getCount() != currentArgs.getCount()) // should really never happen. + return true; + + for (Index i = 0; i < defaultArgs.getCount(); ++i) + { + if (defaultArgs[i] != currentArgs[i]) + return true; + } + + return false; +} + +bool isFuncGeneric(DeclRef<Decl> declRef) +{ + if (auto funcDecl = as<FuncDecl>(declRef.getDecl())) + { + if (funcDecl->parentDecl && as<GenericDecl>(funcDecl->parentDecl)) + { + return true; + } + } + + return false; +} + +DeclRef<Decl> Linkage::specializeWithArgTypes( + Expr* funcExpr, + List<Type*> argTypes, + DiagnosticSink* sink) +{ + SemanticsVisitor visitor(getSemanticsForReflection()); + SemanticsVisitor::ExprLocalScope scope; + visitor = visitor.withSink(sink).withExprLocalScope(&scope); + + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + if (auto declRefFuncExpr = as<DeclRefExpr>(funcExpr)) + { + if (isFuncGeneric(declRefFuncExpr->declRef) && !isSpecialized(declRefFuncExpr->declRef)) + { + if (auto genericDeclRef = getGenericParentDeclRef( + getCurrentASTBuilder(), + &visitor, + declRefFuncExpr->declRef)) + { + auto genericDeclRefExpr = getCurrentASTBuilder()->create<DeclRefExpr>(); + genericDeclRefExpr->declRef = genericDeclRef; + funcExpr = genericDeclRefExpr; + } + } + } + + List<Expr*> argExprs; + for (SlangInt aa = 0; aa < argTypes.getCount(); ++aa) + { + auto argType = argTypes[aa]; + + // Create an 'empty' expr with the given type. Ideally, the expression itself should not + // matter only its checked type. + // + auto argExpr = getCurrentASTBuilder()->create<VarExpr>(); + argExpr->type = argType; + argExpr->type.isLeftValue = true; + argExprs.add(argExpr); + } + + // Construct invoke expr. + auto invokeExpr = getCurrentASTBuilder()->create<InvokeExpr>(); + invokeExpr->functionExpr = funcExpr; + invokeExpr->arguments = argExprs; + + auto checkedInvokeExpr = visitor.CheckInvokeExprWithCheckedOperands(invokeExpr); + + return as<DeclRefExpr>(as<InvokeExpr>(checkedInvokeExpr)->functionExpr)->declRef; +} + + +DeclRef<Decl> Linkage::specializeGeneric( + DeclRef<Decl> declRef, + List<Expr*> argExprs, + DiagnosticSink* sink) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + SLANG_ASSERT(declRef); + + SemanticsVisitor visitor(getSemanticsForReflection()); + visitor = visitor.withSink(sink); + + auto genericDeclRef = getGenericParentDeclRef(getASTBuilder(), &visitor, declRef); + + DeclRefExpr* declRefExpr = getASTBuilder()->create<DeclRefExpr>(); + declRefExpr->declRef = genericDeclRef; + + GenericAppExpr* genericAppExpr = getASTBuilder()->create<GenericAppExpr>(); + genericAppExpr->functionExpr = declRefExpr; + genericAppExpr->arguments = argExprs; + + auto specializedDeclRef = + as<DeclRefExpr>(visitor.checkGenericAppWithCheckedArgs(genericAppExpr))->declRef; + + return specializedDeclRef; +} + +SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL Linkage::getTypeLayout( + slang::TypeReflection* inType, + SlangInt targetIndex, + slang::LayoutRules rules, + ISlangBlob** outDiagnostics) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto type = asInternal(inType); + + if (targetIndex < 0 || targetIndex >= targets.getCount()) + return nullptr; + + auto target = targets[targetIndex]; + + // TODO: We need a way to pass through the layout rules + // that the user requested (e.g., constant buffers vs. + // structured buffer rules). Right now the API only + // exposes a single case, so this isn't a big deal. + // + SLANG_UNUSED(rules); + + auto typeLayout = target->getTypeLayout(type, rules); + + // TODO: We currently don't have a path for capturing + // errors that occur during layout (e.g., types that + // are invalid because of target-specific layout constraints). + // + SLANG_UNUSED(outDiagnostics); + + return asExternal(typeLayout); +} + +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::getContainerType( + slang::TypeReflection* inType, + slang::ContainerType containerType, + ISlangBlob** outDiagnostics) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto type = asInternal(inType); + + Type* containerTypeReflection = nullptr; + ContainerTypeKey key = {inType, containerType}; + if (!m_containerTypes.tryGetValue(key, containerTypeReflection)) + { + switch (containerType) + { + case slang::ContainerType::ConstantBuffer: + { + SemanticsVisitor visitor(getSemanticsForReflection()); + auto layoutType = getASTBuilder()->getDefaultLayoutType(); + Type* cbType = visitor.getConstantBufferType(type, layoutType); + containerTypeReflection = cbType; + } + break; + case slang::ContainerType::ParameterBlock: + { + ParameterBlockType* pbType = getASTBuilder()->getParameterBlockType(type); + containerTypeReflection = pbType; + } + break; + case slang::ContainerType::StructuredBuffer: + { + HLSLStructuredBufferType* sbType = getASTBuilder()->getStructuredBufferType(type); + containerTypeReflection = sbType; + } + break; + case slang::ContainerType::UnsizedArray: + { + ArrayExpressionType* arrType = getASTBuilder()->getArrayType(type, nullptr); + containerTypeReflection = arrType; + } + break; + default: + containerTypeReflection = type; + break; + } + + m_containerTypes.add(key, containerTypeReflection); + } + + SLANG_UNUSED(outDiagnostics); + + return asExternal(containerTypeReflection); +} + +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::getDynamicType() +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + return asExternal(getASTBuilder()->getSharedASTBuilder()->getDynamicType()); +} + +SLANG_NO_THROW SlangResult SLANG_MCALL +Linkage::getTypeRTTIMangledName(slang::TypeReflection* type, ISlangBlob** outNameBlob) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto internalType = asInternal(type); + if (auto declRefType = as<DeclRefType>(internalType)) + { + auto name = getMangledName(m_astBuilder, declRefType->getDeclRef()); + Slang::ComPtr<ISlangBlob> blob = Slang::StringUtil::createStringBlob(name); + *outNameBlob = blob.detach(); + return SLANG_OK; + } + return SLANG_FAIL; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::getTypeConformanceWitnessMangledName( + slang::TypeReflection* type, + slang::TypeReflection* interfaceType, + ISlangBlob** outNameBlob) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto subType = asInternal(type); + auto supType = asInternal(interfaceType); + auto name = getMangledNameForConformanceWitness(m_astBuilder, subType, supType); + Slang::ComPtr<ISlangBlob> blob = Slang::StringUtil::createStringBlob(name); + *outNameBlob = blob.detach(); + return SLANG_OK; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::getTypeConformanceWitnessSequentialID( + slang::TypeReflection* type, + slang::TypeReflection* interfaceType, + uint32_t* outId) +{ + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + auto subType = asInternal(type); + auto supType = asInternal(interfaceType); + + if (!subType || !supType) + return SLANG_FAIL; + + auto name = getMangledNameForConformanceWitness(m_astBuilder, subType, supType); + auto interfaceName = getMangledTypeName(m_astBuilder, supType); + uint32_t resultIndex = 0; + if (mapMangledNameToRTTIObjectIndex.tryGetValue(name, resultIndex)) + { + if (outId) + *outId = resultIndex; + return SLANG_OK; + } + auto idAllocator = mapInterfaceMangledNameToSequentialIDCounters.tryGetValue(interfaceName); + if (!idAllocator) + { + mapInterfaceMangledNameToSequentialIDCounters[interfaceName] = 0; + idAllocator = mapInterfaceMangledNameToSequentialIDCounters.tryGetValue(interfaceName); + } + resultIndex = (*idAllocator); + ++(*idAllocator); + mapMangledNameToRTTIObjectIndex[name] = resultIndex; + if (outId) + *outId = resultIndex; + return SLANG_OK; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::getDynamicObjectRTTIBytes( + slang::TypeReflection* type, + slang::TypeReflection* interfaceType, + uint32_t* outBuffer, + uint32_t bufferSize) +{ + // Slang RTTI header format: + // byte 0-7: pointer to RTTI struct describing the type. (not used for now, set to 1 for valid + // types, and 0 to represent null). + // byte 8-11: 32-bit sequential ID of the type conformance witness. + // byte 12-15: unused. + + if (bufferSize < 16) + return SLANG_E_BUFFER_TOO_SMALL; + + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + SLANG_RETURN_ON_FAIL(getTypeConformanceWitnessSequentialID(type, interfaceType, outBuffer + 2)); + + // Make the RTTI part non zero. + outBuffer[0] = 1; + + return SLANG_OK; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::createTypeConformanceComponentType( + slang::TypeReflection* type, + slang::TypeReflection* interfaceType, + slang::ITypeConformance** outConformanceComponentType, + SlangInt conformanceIdOverride, + ISlangBlob** outDiagnostics) +{ + if (outConformanceComponentType == nullptr) + return SLANG_E_INVALID_ARG; + + SLANG_AST_BUILDER_RAII(getASTBuilder()); + + RefPtr<TypeConformance> result; + DiagnosticSink sink; + applySettingsToDiagnosticSink(&sink, &sink, m_optionSet); + + try + { + SemanticsVisitor visitor(getSemanticsForReflection()); + visitor = visitor.withSink(&sink); + + auto witness = visitor.isSubtype( + (Slang::Type*)type, + (Slang::Type*)interfaceType, + IsSubTypeOptions::None); + if (auto subtypeWitness = as<SubtypeWitness>(witness)) + { + result = new TypeConformance(this, subtypeWitness, conformanceIdOverride, &sink); + } + } + catch (...) + { + } + sink.getBlobIfNeeded(outDiagnostics); + bool success = (result != nullptr); + *outConformanceComponentType = result.detach(); + return success ? SLANG_OK : SLANG_FAIL; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL +Linkage::createCompileRequest(SlangCompileRequest** outCompileRequest) +{ + auto compileRequest = new EndToEndCompileRequest(this); + compileRequest->addRef(); + *outCompileRequest = asExternal(compileRequest); + return SLANG_OK; +} + +SLANG_NO_THROW SlangInt SLANG_MCALL Linkage::getLoadedModuleCount() +{ + return loadedModulesList.getCount(); +} + +SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::getLoadedModule(SlangInt index) +{ + if (index >= 0 && index < loadedModulesList.getCount()) + return loadedModulesList[index].get(); + return nullptr; +} + +void Linkage::buildHash(DigestBuilder<SHA1>& builder, SlangInt targetIndex) +{ + // Add the Slang compiler version to the hash + auto version = String(getBuildTagString()); + builder.append(version); + + // Add compiler options, including search path, preprocessor includes, etc. + m_optionSet.buildHash(builder); + + auto addTargetDigest = [&](TargetRequest* targetReq) + { + targetReq->getOptionSet().buildHash(builder); + + const PassThroughMode passThroughMode = + getDownstreamCompilerRequiredForTarget(targetReq->getTarget()); + const SourceLanguage sourceLanguage = + getDefaultSourceLanguageForDownstreamCompiler(passThroughMode); + + // Add prelude for the given downstream compiler. + ComPtr<ISlangBlob> prelude; + getGlobalSession()->getLanguagePrelude( + (SlangSourceLanguage)sourceLanguage, + prelude.writeRef()); + if (prelude) + { + builder.append(prelude); + } + + // TODO: Downstream compilers (specifically dxc) can currently #include additional + // dependencies. This is currently the case for NVAPI headers included in the prelude. These + // dependencies are currently not picked up by the shader cache which is a significant + // issue. This can only be fixed by running the preprocessor in the slang compiler so dxc + // (or any other downstream compiler for that matter) isn't resolving any includes + // implicitly. + + // Add the downstream compiler version (if it exists) to the hash + auto downstreamCompiler = + getSessionImpl()->getOrLoadDownstreamCompiler(passThroughMode, nullptr); + if (downstreamCompiler) + { + ComPtr<ISlangBlob> versionString; + if (SLANG_SUCCEEDED(downstreamCompiler->getVersionString(versionString.writeRef()))) + { + builder.append(versionString); + } + } + }; + + // Add the target specified by targetIndex + if (targetIndex == -1) + { + // -1 means all targets. + for (auto targetReq : targets) + { + addTargetDigest(targetReq); + } + } + else + { + auto targetReq = targets[targetIndex]; + addTargetDigest(targetReq); + } +} + +SlangResult Linkage::addSearchPath(char const* path) +{ + m_optionSet.add(CompilerOptionName::Include, String(path)); + return SLANG_OK; +} + +SlangResult Linkage::addPreprocessorDefine(char const* name, char const* value) +{ + CompilerOptionValue val; + val.kind = CompilerOptionValueKind::String; + val.stringValue = name; + val.stringValue2 = value; + m_optionSet.add(CompilerOptionName::MacroDefine, val); + return SLANG_OK; +} + +SlangResult Linkage::setMatrixLayoutMode(SlangMatrixLayoutMode mode) +{ + m_optionSet.setMatrixLayoutMode((MatrixLayoutMode)mode); + return SLANG_OK; +} + +SlangResult Linkage::loadFile(String const& path, PathInfo& outPathInfo, ISlangBlob** outBlob) +{ + outPathInfo.type = PathInfo::Type::Unknown; + + SLANG_RETURN_ON_FAIL(m_fileSystemExt->loadFile(path.getBuffer(), outBlob)); + + ComPtr<ISlangBlob> uniqueIdentity; + // Get the unique identity + if (SLANG_FAILED( + m_fileSystemExt->getFileUniqueIdentity(path.getBuffer(), uniqueIdentity.writeRef()))) + { + // We didn't get a unique identity, so go with just a found path + outPathInfo.type = PathInfo::Type::FoundPath; + outPathInfo.foundPath = path; + } + else + { + outPathInfo = PathInfo::makeNormal(path, StringUtil::getString(uniqueIdentity)); + } + return SLANG_OK; +} + +Expr* Linkage::parseTermString(String typeStr, Scope* scope) +{ + // Create a SourceManager on the stack, so any allocations for 'SourceFile'/'SourceView' etc + // will be cleaned up + SourceManager localSourceManager; + localSourceManager.initialize(getSourceManager(), nullptr); + + Slang::SourceFile* srcFile = + localSourceManager.createSourceFileWithString(PathInfo::makeTypeParse(), typeStr); + + // We'll use a temporary diagnostic sink + DiagnosticSink sink(&localSourceManager, nullptr); + + // RAII type to make make sure current SourceManager is restored after parse. + // Use RAII - to make sure everything is reset even if an exception is thrown. + struct ScopeReplaceSourceManager + { + ScopeReplaceSourceManager(Linkage* linkage, SourceManager* replaceManager) + : m_linkage(linkage), m_originalSourceManager(linkage->getSourceManager()) + { + linkage->setSourceManager(replaceManager); + } + + ~ScopeReplaceSourceManager() { m_linkage->setSourceManager(m_originalSourceManager); } + + private: + Linkage* m_linkage; + SourceManager* m_originalSourceManager; + }; + + // We need to temporarily replace the SourceManager for this CompileRequest + ScopeReplaceSourceManager scopeReplaceSourceManager(this, &localSourceManager); + + SourceLanguage sourceLanguage = SourceLanguage::Slang; + SlangLanguageVersion languageVersion = m_optionSet.getLanguageVersion(); + + auto tokens = preprocessSource( + srcFile, + &sink, + nullptr, + Dictionary<String, String>(), + this, + sourceLanguage, + languageVersion); + + if (sourceLanguage == SourceLanguage::Unknown) + sourceLanguage = SourceLanguage::Slang; + + return parseTermFromSourceFile( + getASTBuilder(), + tokens, + &sink, + scope, + getNamePool(), + sourceLanguage); +} + +UInt Linkage::addTarget(CodeGenTarget target) +{ + RefPtr<TargetRequest> targetReq = new TargetRequest(this, target); + + Index result = targets.getCount(); + targets.add(targetReq); + return UInt(result); +} + +void Linkage::loadParsedModule( + RefPtr<FrontEndCompileRequest> compileRequest, + RefPtr<TranslationUnitRequest> translationUnit, + Name* name, + const PathInfo& pathInfo) +{ + // Note: we add the loaded module to our name->module listing + // before doing semantic checking, so that if it tries to + // recursively `import` itself, we can detect it. + // + RefPtr<Module> loadedModule = translationUnit->getModule(); + + // Get a path + String mostUniqueIdentity = pathInfo.getMostUniqueIdentity(); + SLANG_ASSERT(mostUniqueIdentity.getLength() > 0); + + mapPathToLoadedModule.add(mostUniqueIdentity, loadedModule); + mapNameToLoadedModules.add(name, loadedModule); + + auto sink = translationUnit->compileRequest->getSink(); + + int errorCountBefore = sink->getErrorCount(); + int errorCountAfter; + try + { + compileRequest->checkAllTranslationUnits(); + } + catch (...) + { + mapPathToLoadedModule.remove(mostUniqueIdentity); + mapNameToLoadedModules.remove(name); + throw; + } + errorCountAfter = sink->getErrorCount(); + if (isInLanguageServer()) + { + // Don't generate IR as language server. + // This means that we currently cannot report errors that are detected during IR passes. + // Ideally we want to run those passes, but that is too risky for what it is worth right + // now. + } + else + { + if (errorCountAfter != errorCountBefore) + { + // There must have been an error in the loaded module. + // Remove from maps if there were errors during semantic checking + mapPathToLoadedModule.remove(mostUniqueIdentity); + mapNameToLoadedModules.remove(name); + } + else + { + // If we didn't run into any errors, then try to generate + // IR code for the imported module. + if (errorCountAfter == 0) + { + loadedModule->setIRModule( + generateIRForTranslationUnit(getASTBuilder(), translationUnit)); + } + } + } + loadedModulesList.add(loadedModule); +} + +RefPtr<Module> Linkage::findOrLoadSerializedModuleForModuleLibrary( + ISlangBlob* blobHoldingSerializedData, + ModuleChunk const* moduleChunk, + RIFF::ListChunk const* libraryChunk, + DiagnosticSink* sink) +{ + RefPtr<Module> resultModule; + + // We will attempt things in a few different steps, trying to + // decode as little of the serialized module as necessary at + // each step, so that we don't waste time on the heavyweight + // stuff when we didn't need to. + // + // The first step is to simply decode the module name, and + // see if we have a already loaded a matching module. + + auto moduleName = getNamePool()->getName(moduleChunk->getName()); + if (mapNameToLoadedModules.tryGetValue(moduleName, resultModule)) + return resultModule; + + // It is possible that the module has been loaded, but somehow + // under a different name, so next we decode the list of file + // paths that the module depends on, and then rely on the assumption + // that the first of those paths represents the file for the module + // itself to detect if we've already loaded a module from that + // path. + // + // Note: While this is a distasteful assumption to make, it is + // one that gets made in several parts of the compiler codebase + // already. It isn't something that can be fixed in just one + // place at this point. + + auto fileDependenciesList = moduleChunk->getFileDependencies(); + auto firstFileDependencyChunk = fileDependenciesList.getFirst(); + if (!firstFileDependencyChunk) + return nullptr; + + auto modulePathInfo = PathInfo::makePath(firstFileDependencyChunk->getValue()); + if (mapPathToLoadedModule.tryGetValue(modulePathInfo.getMostUniqueIdentity(), resultModule)) + return resultModule; + + // If we failed to find a previously-loaded module, then we + // will go ahead and load the module from the serialized form. + // + PathInfo filePathInfo; + return loadSerializedModule( + moduleName, + modulePathInfo, + blobHoldingSerializedData, + moduleChunk, + libraryChunk, + SourceLoc(), + sink); +} + +RefPtr<Module> Linkage::loadSerializedModule( + Name* moduleName, + const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, + ModuleChunk const* moduleChunk, + RIFF::ListChunk const* containerChunk, + SourceLoc const& requestingLoc, + DiagnosticSink* sink) +{ + auto astBuilder = getASTBuilder(); + SLANG_AST_BUILDER_RAII(astBuilder); + + auto module = RefPtr(new Module(this, astBuilder)); + module->setName(moduleName); + + // Just as if we were processing an `import` declaration in + // source code, we will track the fact that this serialized + // modlue is (effectively) being imported, so that we can + // diagnose anything troublesome, like an attempt at a + // recursive import. + // + ModuleBeingImportedRAII moduleBeingImported(this, module, moduleName, requestingLoc); + + // We will register the module in our data structures to + // track loaded modules, and then remove it in the case + // where there is some kind of failure. + // + String mostUniqueIdentity = moduleFilePathInfo.getMostUniqueIdentity(); + SLANG_ASSERT(mostUniqueIdentity.getLength() > 0); + + mapPathToLoadedModule.add(mostUniqueIdentity, module); + mapNameToLoadedModules.add(moduleName, module); + try + { + if (SLANG_FAILED(loadSerializedModuleContents( + module, + moduleFilePathInfo, + blobHoldingSerializedData, + moduleChunk, + containerChunk, + sink))) + { + mapPathToLoadedModule.remove(mostUniqueIdentity); + mapNameToLoadedModules.remove(moduleName); + return nullptr; + } + + loadedModulesList.add(module); + return module; + } + catch (...) + { + mapPathToLoadedModule.remove(mostUniqueIdentity); + mapNameToLoadedModules.remove(moduleName); + throw; + } +} + +RefPtr<Module> Linkage::loadBinaryModuleImpl( + Name* moduleName, + const PathInfo& moduleFilePathInfo, + ISlangBlob* moduleFileContents, + SourceLoc const& requestingLoc, + DiagnosticSink* sink) +{ + auto astBuilder = getASTBuilder(); + SLANG_AST_BUILDER_RAII(astBuilder); + + // We start by reading the content of the file as + // an in-memory RIFF container. + // + auto rootChunk = RIFF::RootChunk::getFromBlob(moduleFileContents); + if (!rootChunk) + { + return nullptr; + } + + auto moduleChunk = ModuleChunk::find(rootChunk); + if (!moduleChunk) + { + return nullptr; + } + + // Next, we attempt to check if the binary module is up to + // date with the compilation options in use as well as + // the contents of all the files its compilation depended + // on (as determined by its hash). + // + String mostUniqueIdentity = moduleFilePathInfo.getMostUniqueIdentity(); + SLANG_ASSERT(mostUniqueIdentity.getLength() > 0); + if (m_optionSet.getBoolOption(CompilerOptionName::UseUpToDateBinaryModule)) + { + if (!isBinaryModuleUpToDate(moduleFilePathInfo.foundPath, moduleChunk)) + { + return nullptr; + } + } + + // If everything seems reasonable, then we will go ahead and load + // the module more completely from that serialized representation. + // + RefPtr<Module> module = loadSerializedModule( + moduleName, + moduleFilePathInfo, + moduleFileContents, + moduleChunk, + rootChunk, + requestingLoc, + sink); + + return module; +} + +void Linkage::_diagnoseErrorInImportedModule(DiagnosticSink* sink) +{ + for (auto info = m_modulesBeingImported; info; info = info->next) + { + sink->diagnose(info->importLoc, Diagnostics::errorInImportedModule, info->name); + } + if (!isInLanguageServer()) + { + sink->diagnose(SourceLoc(), Diagnostics::complationCeased); + } +} + +RefPtr<Module> Linkage::loadModuleImpl( + Name* moduleName, + const PathInfo& modulePathInfo, + ISlangBlob* moduleBlob, + SourceLoc const& requestingLoc, + DiagnosticSink* sink, + const LoadedModuleDictionary* additionalLoadedModules, + ModuleBlobType blobType) +{ + switch (blobType) + { + case ModuleBlobType::IR: + return loadBinaryModuleImpl(moduleName, modulePathInfo, moduleBlob, requestingLoc, sink); + + case ModuleBlobType::Source: + return loadSourceModuleImpl( + moduleName, + modulePathInfo, + moduleBlob, + requestingLoc, + sink, + additionalLoadedModules); + + default: + SLANG_UNEXPECTED("unknown module blob type"); + UNREACHABLE_RETURN(nullptr); + } +} + +RefPtr<Module> Linkage::loadSourceModuleImpl( + Name* name, + const PathInfo& filePathInfo, + ISlangBlob* sourceBlob, + SourceLoc const& srcLoc, + DiagnosticSink* sink, + const LoadedModuleDictionary* additionalLoadedModules) +{ + RefPtr<FrontEndCompileRequest> frontEndReq = new FrontEndCompileRequest(this, nullptr, sink); + + frontEndReq->additionalLoadedModules = additionalLoadedModules; + + RefPtr<TranslationUnitRequest> translationUnit = new TranslationUnitRequest(frontEndReq); + translationUnit->compileRequest = frontEndReq; + translationUnit->setModuleName(name); + Stage impliedStage; + translationUnit->sourceLanguage = SourceLanguage::Slang; + + // If we are loading from a file with apparaent glsl extension, + // set the source language to GLSL to enable GLSL compatibility mode. + if ((SourceLanguage)findSourceLanguageFromPath(filePathInfo.getName(), impliedStage) == + SourceLanguage::GLSL) + { + translationUnit->sourceLanguage = SourceLanguage::GLSL; + } + + frontEndReq->addTranslationUnit(translationUnit); + + auto module = translationUnit->getModule(); + + ModuleBeingImportedRAII moduleBeingImported(this, module, name, srcLoc); + + // Create an artifact for the source + auto sourceArtifact = ArtifactUtil::createArtifact( + ArtifactDesc::make(ArtifactKind::Source, ArtifactPayload::Slang, ArtifactStyle::Unknown)); + + if (sourceBlob) + { + // If the user has already provided a source blob, use that. + sourceArtifact->addRepresentation( + new SourceBlobWithPathInfoArtifactRepresentation(filePathInfo, sourceBlob)); + } + else if ( + filePathInfo.type == PathInfo::Type::Normal || + filePathInfo.type == PathInfo::Type::FoundPath) + { + // Create with the 'friendly' name + // We create that it was loaded from the file system + sourceArtifact->addRepresentation(new ExtFileArtifactRepresentation( + filePathInfo.foundPath.getUnownedSlice(), + getFileSystemExt())); + } + else + { + return nullptr; + } + + translationUnit->addSourceArtifact(sourceArtifact); + + if (SLANG_FAILED(translationUnit->requireSourceFiles())) + { + // Some problem accessing source files + return nullptr; + } + int errorCountBefore = sink->getErrorCount(); + frontEndReq->parseTranslationUnit(translationUnit); + int errorCountAfter = sink->getErrorCount(); + + if (errorCountAfter != errorCountBefore && !isInLanguageServer()) + { + _diagnoseErrorInImportedModule(sink); + // Something went wrong during the parsing, so we should bail out. + return nullptr; + } + + try + { + loadParsedModule(frontEndReq, translationUnit, name, filePathInfo); + } + catch (const Slang::AbortCompilationException&) + { + // Something is fatally wrong, we should return nullptr. + module = nullptr; + } + errorCountAfter = sink->getErrorCount(); + + if (errorCountAfter != errorCountBefore && !isInLanguageServer()) + { + // If something is fatally wrong, we want to report + // the diagnostic even if we are in language server + // and processing a different module. + _diagnoseErrorInImportedModule(sink); + // Something went wrong during the parsing, so we should bail out. + return nullptr; + } + + if (!module) + return nullptr; + + module->setPathInfo(filePathInfo); + return module; +} + +bool Linkage::isBeingImported(Module* module) +{ + for (auto ii = m_modulesBeingImported; ii; ii = ii->next) + { + if (module == ii->module) + return true; + } + return false; +} + +// Derive a file name for the module, by taking the given +// identifier, replacing all occurrences of `_` with `-`, +// and then appending `.slang`. +// +// For example, `foo_bar` becomes `foo-bar.slang`. +String getFileNameFromModuleName(Name* name, bool translateUnderScore) +{ + String fileName; + if (!getText(name).getUnownedSlice().endsWithCaseInsensitive(".slang")) + { + StringBuilder sb; + for (auto c : getText(name)) + { + if (translateUnderScore && c == '_') + c = '-'; + + sb.append(c); + } + sb.append(".slang"); + fileName = sb.produceString(); + } + else + { + fileName = getText(name); + } + return fileName; +} + +RefPtr<Module> Linkage::findOrImportModule( + Name* moduleName, + SourceLoc const& requestingLoc, + DiagnosticSink* sink, + const LoadedModuleDictionary* loadedModules) +{ + // Have we already loaded a module matching this name? + // + RefPtr<LoadedModule> previouslyLoadedModule; + if (mapNameToLoadedModules.tryGetValue(moduleName, previouslyLoadedModule)) + { + // If the map shows a null module having been loaded, + // then that means there was a prior load attempt, + // but it failed, so we won't bother trying again. + // + if (!previouslyLoadedModule) + return nullptr; + + // If state shows us that the module is already being + // imported deeper on the call stack, then we've + // hit a recursive case, and that is an error. + // + if (isBeingImported(previouslyLoadedModule)) + { + // We seem to be in the middle of loading this module + sink->diagnose(requestingLoc, Diagnostics::recursiveModuleImport, moduleName); + return nullptr; + } + + return previouslyLoadedModule; + } + + // If the user is providing an additional list of loaded modules, we find + // if the module being imported is in that list. This allows a translation + // unit to use previously checked translation units in the same + // FrontEndCompileRequest. + { + Module* previouslyLoadedLocalModule = nullptr; + if (loadedModules && loadedModules->tryGetValue(moduleName, previouslyLoadedLocalModule)) + { + return previouslyLoadedLocalModule; + } + } + + // If the name being requested matches the name of a built-in module, + // then we will special-case the process by loading that builtin + // module directly. + // + // TODO: right now this logic is only considering the built-in `glsl` + // module, but it should probably be generalized so that we can more + // easily support having multiple built-in modules rather than just + // putting everything into `core`. + // + if (moduleName == getSessionImpl()->glslModuleName) + { + // This is a builtin glsl module, just load it from embedded definition. + auto glslModule = getSessionImpl()->getBuiltinModule(slang::BuiltinModuleName::GLSL); + if (!glslModule) + { + // Note: the way this logic is currently written, if the built-in + // `glsl` module fails to load, then we will *not* fall back to + // searching for a user-defined module in a file like `glsl.slang`. + // + // It is unclear if this should be the default behavior or not. + // Should built-in modules be prioritized over user modules? + // Should built-in modules shadow user modules, even when the + // built-in module fails to load, for some reason? + // + sink->diagnose(requestingLoc, Diagnostics::glslModuleNotAvailable, moduleName); + } + return glslModule; + } + + // We are going to use a loop to search for a suitable file to + // load the module from, to account for a few key choices: + // + // * We can both load modules from a source `.slang` file, + // or from a binary `.slang-module` file. + // + // * For a variety of reasons, the `import` logic has historically + // translated underscores in a module name into dashes (so that + // `import my_module` will look for `my-module.slang`), and we + // try to support both that convention as well as a convention + // that preserves underscores. + // + // To try to keep this logic as orthogonal as possible, we first + // construct lists of the options we want to iterate over, and + // then do the actual loop later. + + ShortList<ModuleBlobType, 2> typesToTry; + if (isInLanguageServer()) + { + // When in language server, we always prefer to use source module if it is available. + typesToTry.add(ModuleBlobType::Source); + typesToTry.add(ModuleBlobType::IR); + } + else + { + // Look for a precompiled module first, if not exist, load from source. + typesToTry.add(ModuleBlobType::IR); + typesToTry.add(ModuleBlobType::Source); + } + + // We will always search for a file name that directly matches the + // module name as written first, and then search for one with + // underscores replaced by dashes. The latter is the original + // behavior that `import` provided, but it seems safest to prefer + // the exact name spelled in the user's code when there might + // actually be ambiguity. + // + auto defaultSourceFileName = getFileNameFromModuleName(moduleName, false); + auto alternativeSourceFileName = getFileNameFromModuleName(moduleName, true); + String sourceFileNamesToTry[] = {defaultSourceFileName, alternativeSourceFileName}; + + // We are going to look for the candidate file using the same + // logic that would be used for a preprocessor `#include`, + // so we set up the necessary state. + // + IncludeSystem includeSystem(&getSearchDirectories(), getFileSystemExt(), getSourceManager()); + + // Just like with a `#include`, the search will take into + // account the path to the file where the request to import + // this module came from (e.g. the source file with the + // `import` declaration), if such a path is available. + // + PathInfo requestingPathInfo = + getSourceManager()->getPathInfo(requestingLoc, SourceLocType::Actual); + + for (auto type : typesToTry) + { + for (auto sourceFileName : sourceFileNamesToTry) + { + // The `sourceFileName` will have the `.slang` extension, + // so if we are looking for a binary module, we need + // to change the extension we will look for. + // + String fileName; + switch (type) + { + case ModuleBlobType::Source: + fileName = sourceFileName; + break; + + case ModuleBlobType::IR: + fileName = Path::replaceExt(sourceFileName, "slang-module"); + break; + } + + // We now search for a file matching the desired name, + // using the same logic as for a `#include`. + // + // TODO: We might want to consider how to handle the case + // of an `import` with a relative path a little specially, + // since it could in theory be possible for two `.slang` + // files with the same base name to exist in different + // directories in a project, and we'd want file-relative + // `import`s to work for each, without having either one + // be able to "claim" the bare identifier of the base + // name for itself. + // + PathInfo filePathInfo; + if (SLANG_FAILED( + includeSystem.findFile(fileName, requestingPathInfo.foundPath, filePathInfo))) + { + // If we failed to find the file at this step, we + // will continue the search for our other options. + // + continue; + } + + // We will *again* search for a previously loaded module. + // + // It is possible that the same file will have been loaded + // as a module under two different module names. The easiest + // way for this to happen is if there are `import` declarations + // using both the underscore and dash conventions (e.g., both + // `import "my-module.slang"` and `import my_module`). + // + // This case may also arise if one file `import`s a module using + // just an identifier for its name, but another `import`s it + // using a path (e.g., `import "subdir/file.slang"`). + // + // No matter how the situation arises, we only want to have one + // copy of the "same" module loaded at a given time, so we + // will re-use the existing module if we find one here. + // + if (mapPathToLoadedModule.tryGetValue( + filePathInfo.getMostUniqueIdentity(), + previouslyLoadedModule)) + { + // TODO: If we find a previously-loaded module at this step, + // then we should probably register that module under the + // given `moduleName` in the map of loaded modules, so + // that subsequent `import`s using the same form will find it. + // + return previouslyLoadedModule; + } + + // Now we try to load the content of the file. + // + // If for some reason we could find a file at the + // given path, but for some reason couldn't *open* + // and *read* it, then we continue the search + // using whatever other candidate file names are left. + // + ComPtr<ISlangBlob> fileContents; + if (SLANG_FAILED(includeSystem.loadFile(filePathInfo, fileContents))) + { + continue; + } + + // If we found a real file and were able to load its contents, + // then we'll go ahead and try to load a module from it, + // whether by compiling it or decoding the binary. + // + auto module = loadModuleImpl( + moduleName, + filePathInfo, + fileContents, + requestingLoc, + sink, + loadedModules, + type); + + // If the attempt to load the module from the given path + // was successful, we go ahead and use it, without trying + // out any other options. + // + if (module) + return module; + } + } + + // If we tried out all of our candidate file names + // and failed with each of them, then we diagnose + // an error based on the original *source* file + // name. + // + // TODO: this should really be an error message + // that clearly states something like "no file + // suitable for module `whatever` was found + // and loaded. + // + // Ideally that error message would include whatever + // of the candidate file names from the loop above + // got furthest along in the process (or just a + // list of the file names that were tried, if + // nothing was even found via the include system). + // + sink->diagnose(requestingLoc, Diagnostics::cannotOpenFile, defaultSourceFileName); + + // If the attempt to import the module failed, then + // we will stick a null pointer into the map of loaded + // modules, so that subsequent attempts to load a module + // with this name will return null without having to + // go through all the above steps yet again. + // + mapNameToLoadedModules[moduleName] = nullptr; + return nullptr; +} + +SourceFile* Linkage::loadSourceFile(String pathFrom, String path) +{ + IncludeSystem includeSystem(&getSearchDirectories(), getFileSystemExt(), getSourceManager()); + ComPtr<slang::IBlob> blob; + PathInfo pathInfo; + SLANG_RETURN_NULL_ON_FAIL(includeSystem.findFile(path, pathFrom, pathInfo)); + SourceFile* sourceFile = nullptr; + SLANG_RETURN_NULL_ON_FAIL(includeSystem.loadFile(pathInfo, blob, sourceFile)); + return sourceFile; +} + +// Check if a serialized module is up-to-date with current compiler options and source files. +bool Linkage::isBinaryModuleUpToDate(String fromPath, RIFF::ListChunk const* baseChunk) +{ + auto moduleChunk = ModuleChunk::find(baseChunk); + if (!moduleChunk) + return false; + + SHA1::Digest existingDigest = moduleChunk->getDigest(); + + DigestBuilder<SHA1> digestBuilder; + auto version = String(getBuildTagString()); + digestBuilder.append(version); + m_optionSet.buildHash(digestBuilder); + + // Find the canonical path of the directory containing the module source file. + String moduleSrcPath = ""; + + auto dependencyChunks = moduleChunk->getFileDependencies(); + if (auto firstDependencyChunk = dependencyChunks.getFirst()) + { + moduleSrcPath = firstDependencyChunk->getValue(); + + IncludeSystem includeSystem( + &getSearchDirectories(), + getFileSystemExt(), + getSourceManager()); + PathInfo modulePathInfo; + if (SLANG_SUCCEEDED(includeSystem.findFile(moduleSrcPath, fromPath, modulePathInfo))) + { + moduleSrcPath = modulePathInfo.foundPath; + Path::getCanonical(moduleSrcPath, moduleSrcPath); + } + } + + for (auto dependencyChunk : dependencyChunks) + { + auto file = dependencyChunk->getValue(); + auto sourceFile = loadSourceFile(fromPath, file); + if (!sourceFile) + { + // If we cannot find the source file from `fromPath`, + // try again from the module's source file path. + if (dependencyChunks.getFirst()) + sourceFile = loadSourceFile(moduleSrcPath, file); + } + if (!sourceFile) + return false; + digestBuilder.append(sourceFile->getDigest()); + } + return digestBuilder.finalize() == existingDigest; +} + +SLANG_NO_THROW bool SLANG_MCALL +Linkage::isBinaryModuleUpToDate(const char* modulePath, slang::IBlob* binaryModuleBlob) +{ + auto rootChunk = RIFF::RootChunk::getFromBlob(binaryModuleBlob); + if (!rootChunk) + return false; + return isBinaryModuleUpToDate(modulePath, rootChunk); +} + +SourceFile* Linkage::findFile(Name* name, SourceLoc loc, IncludeSystem& outIncludeSystem) +{ + auto impl = [&](bool translateUnderScore) -> SourceFile* + { + auto fileName = getFileNameFromModuleName(name, translateUnderScore); + + // Next, try to find the file of the given name, + // using our ordinary include-handling logic. + + auto& searchDirs = getSearchDirectories(); + outIncludeSystem = IncludeSystem(&searchDirs, getFileSystemExt(), getSourceManager()); + + // Get the original path info + PathInfo pathIncludedFromInfo = getSourceManager()->getPathInfo(loc, SourceLocType::Actual); + PathInfo filePathInfo; + + ComPtr<ISlangBlob> fileContents; + + // We have to load via the found path - as that is how file was originally loaded + if (SLANG_FAILED( + outIncludeSystem.findFile(fileName, pathIncludedFromInfo.foundPath, filePathInfo))) + { + return nullptr; + } + // Otherwise, try to load it. + SourceFile* sourceFile; + if (SLANG_FAILED(outIncludeSystem.loadFile(filePathInfo, fileContents, sourceFile))) + { + return nullptr; + } + return sourceFile; + }; + if (auto rs = impl(false)) + return rs; + return impl(true); +} + +Linkage::IncludeResult Linkage::findAndIncludeFile( + Module* module, + TranslationUnitRequest* translationUnit, + Name* name, + SourceLoc const& loc, + DiagnosticSink* sink) +{ + IncludeResult result; + result.fileDecl = nullptr; + result.isNew = false; + + IncludeSystem includeSystem; + auto sourceFile = findFile(name, loc, includeSystem); + if (!sourceFile) + { + sink->diagnose(loc, Diagnostics::cannotOpenFile, getText(name)); + return result; + } + + // If the file has already been included, don't need to do anything further. + if (auto existingFileDecl = module->getIncludedSourceFileMap().tryGetValue(sourceFile)) + { + result.fileDecl = *existingFileDecl; + result.isNew = false; + return result; + } + + if (isInLanguageServer()) + { + // HACK: When in language server mode, we will always load the currently opend file as a + // fresh module even if some previously opened file already references the current file via + // `import` or `include`. see comments in `WorkspaceVersion::getOrLoadModule()` for the + // reason behind this. An undesired outcome of this decision is that we could endup + // including the currently opened file itself via chain of `__include`s because the + // currently opened file will not have a true unique file system identity that allows it to + // be deduplicated correct. Therefore we insert a hack logic here to detect re-inclusion by + // just the file path. We can clean up this hack by making the language server truly support + // incremental checking so we can reuse the previously loaded module instead of needing to + // always start with a fresh copy. + // + for (auto file : translationUnit->getSourceFiles()) + { + if (file->getPathInfo().hasFoundPath() && + Path::equals(file->getPathInfo().foundPath, sourceFile->getPathInfo().foundPath)) + return result; + } + } + + module->addFileDependency(sourceFile); + + // Create a transparent FileDecl to hold all children from the included file. + auto fileDecl = module->getASTBuilder()->create<FileDecl>(); + fileDecl->nameAndLoc.name = name; + fileDecl->parentDecl = module->getModuleDecl(); + module->getIncludedSourceFileMap().add(sourceFile, fileDecl); + + FrontEndPreprocessorHandler preprocessorHandler( + module, + module->getASTBuilder(), + sink, + translationUnit); + auto combinedPreprocessorDefinitions = translationUnit->getCombinedPreprocessorDefinitions(); + SourceLanguage sourceLanguage = translationUnit->sourceLanguage; + SlangLanguageVersion slangLanguageVersion = module->getModuleDecl()->languageVersion; + auto tokens = preprocessSource( + sourceFile, + sink, + &includeSystem, + combinedPreprocessorDefinitions, + this, + sourceLanguage, + slangLanguageVersion, + &preprocessorHandler); + + if (sourceLanguage == SourceLanguage::Unknown) + sourceLanguage = translationUnit->sourceLanguage; + + if (slangLanguageVersion != module->getModuleDecl()->languageVersion) + { + sink->diagnose( + tokens.begin()->getLoc(), + Diagnostics::languageVersionDiffersFromIncludingModule); + } + + auto outerScope = module->getModuleDecl()->ownedScope; + parseSourceFile( + module->getASTBuilder(), + translationUnit, + sourceLanguage, + tokens, + sink, + outerScope, + fileDecl); + + module->getModuleDecl()->addMember(fileDecl); + + result.fileDecl = fileDecl; + result.isNew = true; + return result; +} + +void Linkage::setFileSystem(ISlangFileSystem* inFileSystem) +{ + // Set the fileSystem + m_fileSystem = inFileSystem; + + // Release what's there + m_fileSystemExt.setNull(); + + // If nullptr passed in set up default + if (inFileSystem == nullptr) + { + m_fileSystemExt = new Slang::CacheFileSystem(Slang::OSFileSystem::getExtSingleton()); + } + else + { + if (auto cacheFileSystem = as<CacheFileSystem>(inFileSystem)) + { + m_fileSystemExt = cacheFileSystem; + } + else + { + if (m_requireCacheFileSystem) + { + m_fileSystemExt = new Slang::CacheFileSystem(inFileSystem); + } + else + { + // See if we have the full ISlangFileSystemExt interface, if we do just use it + inFileSystem->queryInterface(SLANG_IID_PPV_ARGS(m_fileSystemExt.writeRef())); + + // If not wrap with CacheFileSystem that emulates ISlangFileSystemExt from the + // ISlangFileSystem interface + if (!m_fileSystemExt) + { + // Construct a wrapper to emulate the extended interface behavior + m_fileSystemExt = new Slang::CacheFileSystem(m_fileSystem); + } + } + } + } + + // If requires a cache file system, check that it does have one + SLANG_ASSERT(m_requireCacheFileSystem == false || as<CacheFileSystem>(m_fileSystemExt)); + + // Set the file system used on the source manager + getSourceManager()->setFileSystemExt(m_fileSystemExt); +} + +SlangResult Linkage::loadSerializedModuleContents( + Module* module, + const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, + ModuleChunk const* moduleChunk, + RIFF::ListChunk const* containerChunk, + DiagnosticSink* sink) +{ + // At this point we've dealt with basically all of + // the formalities, and we just need to get down + // to the real work of decoding the information + // in the `moduleChunk`. + + // + // TODO(tfoley): The fact that a separate `containerChunk` is getting + // passed in here is entirely byproduct of the support for "module libraries" + // that can (in principle) contain multiple serialized modules. When + // things are serialized in the "container" representation used for + // a module library, there is a single `DebugChunk` as a child of + // the container, with all of the `ModuleChunk`s sharing that debug info. + // + // In contrast, the more typical kind of serialized module that the compiler + // produces serializes a single `ModuleChunk`, and the `DebugChunk` is + // one of its direct children. Thus there are currently two different + // locations where debug information might be found. + // + // Prior to the change where we navigate the serialized RIFF hierarchy + // in memory without copying it, this issue was addressed by having + // the subroutine that looked for a `DebugChunk` start at the `ModuleChunk` + // and work its way up through the hierarchy using parent pointers that + // were created as part of RIFF loading. When navigating the RIFF in-place + // we don't have such parent pointers. + // + // As a short-term solution, we should deprecate and remove the support + // for "module libraries" so that the code doesn't have to handle two + // different layouts. + // + // In the longer term, we should be making some conscious design decisions + // around how we want to organize the top-level structure of our serialized + // intermediate/output formats, since there's quite a mix of different + // approaches currently in use. + // + + auto sourceManager = getSourceManager(); + RefPtr<SerialSourceLocReader> sourceLocReader; + if (auto debugChunk = DebugChunk::find(moduleChunk, containerChunk)) + { + SLANG_RETURN_ON_FAIL( + readSourceLocationsFromDebugChunk(debugChunk, sourceManager, sourceLocReader)); + } + + auto astChunk = moduleChunk->findAST(); + if (!astChunk) + return SLANG_FAIL; + + auto irChunk = moduleChunk->findIR(); + if (!irChunk) + return SLANG_FAIL; + + auto astBuilder = getASTBuilder(); + auto session = getSessionImpl(); + + // For the purposes of any modules referenced + // by the module we're about to decode, we will + // construct a source location that represents + // the module itself (if possible). + // + // TODO(tfoley): This logic seems like overkill, given + // that many (most? all?) control-flow paths that can + // reach this routine will have already found a `SourceFile` + // to represent the module, as part of even getting the + // `moduleFilePathInfo` to pass in + // + // The approach here is more or less exactly copied + // from what the old `SerialContainerUtil::read` function + // used to do, with the hopes that it will as many tests + // passing as possible. + // + // Down the line somebody should scrutinize all of this + // kind of logic in the compiler codebase, because there + // is something that feels unclean about how paths are being handled. + // + SourceLoc serializedModuleLoc; + { + auto sourceFile = + sourceManager->findSourceFileByPathRecursively(moduleFilePathInfo.foundPath); + if (!sourceFile) + { + sourceFile = sourceManager->createSourceFileWithString(moduleFilePathInfo, String()); + sourceManager->addSourceFile(moduleFilePathInfo.getMostUniqueIdentity(), sourceFile); + } + auto sourceView = + sourceManager->createSourceView(sourceFile, &moduleFilePathInfo, SourceLoc()); + serializedModuleLoc = sourceView->getRange().begin; + } + + auto moduleDecl = readSerializedModuleAST( + this, + astBuilder, + sink, + blobHoldingSerializedData, + astChunk, + sourceLocReader, + serializedModuleLoc); + if (!moduleDecl) + return SLANG_FAIL; + module->setModuleDecl(moduleDecl); + + RefPtr<IRModule> irModule; + SLANG_RETURN_ON_FAIL(readSerializedModuleIR(irChunk, session, sourceLocReader, irModule)); + module->setIRModule(irModule); + + // The handling of file dependencies is complicated, because of + // the way that the encoding logic tried to make all of the + // paths be relative to the primary source file for the module. + // + // We end up needing to undo some amount of that work here. + // + + module->clearFileDependency(); + String moduleSourcePath = moduleFilePathInfo.foundPath; + bool isFirst = true; + for (auto depenencyFileChunk : moduleChunk->getFileDependencies()) + { + auto encodedDependencyFilePath = depenencyFileChunk->getValue(); + + auto sourceFile = loadSourceFile(moduleFilePathInfo.foundPath, encodedDependencyFilePath); + if (isFirst) + { + // The first file is the source for the main module file. + // We store the module path as the basis for finding the remaining + // dependent files. + if (sourceFile) + moduleSourcePath = sourceFile->getPathInfo().foundPath; + isFirst = false; + } + // If we cannot find the dependent file directly, try to find + // it relative to the module source path. + if (!sourceFile) + { + sourceFile = loadSourceFile(moduleSourcePath, encodedDependencyFilePath); + } + if (sourceFile) + { + module->addFileDependency(sourceFile); + } + } + module->setPathInfo(moduleFilePathInfo); + module->setDigest(moduleChunk->getDigest()); + module->_collectShaderParams(); + module->_discoverEntryPoints(sink, targets); + + // Hook up fileDecl's scope to module's scope. + for (auto fileDecl : moduleDecl->getDirectMemberDeclsOfType<FileDecl>()) + { + addSiblingScopeForContainerDecl(m_astBuilder, moduleDecl->ownedScope, fileDecl); + } + + return SLANG_OK; +} + +void Linkage::setRequireCacheFileSystem(bool requireCacheFileSystem) +{ + if (requireCacheFileSystem == m_requireCacheFileSystem) + { + return; + } + + ComPtr<ISlangFileSystem> scopeFileSystem(m_fileSystem); + m_requireCacheFileSystem = requireCacheFileSystem; + + setFileSystem(scopeFileSystem); +} + +RefPtr<Module> findOrImportModule( + Linkage* linkage, + Name* name, + SourceLoc const& loc, + DiagnosticSink* sink, + const LoadedModuleDictionary* loadedModules) +{ + return linkage->findOrImportModule(name, loc, sink, loadedModules); +} + +Type* checkProperType(Linkage* linkage, TypeExp typeExp, DiagnosticSink* sink); + + +} // namespace Slang |
