Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 43 additions & 15 deletions cmake/CompilerFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,28 @@ function(set_default_compile_options target)
add_supported_cxx_linker_flags(
${target}
PRIVATE
# Don't assume that symbols will be resolved at runtime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes such as this should be commented in the PR description. It is not obvious to the reviewer why you removed this flag. Presumably there was a good reason.

"-Wl,--no-undefined"
# No reason not to do this? Useful when using split debug info
"-Wl,--build-id"
)
# Workaround for slang-llvm's own classes needing RTTI for UBSan's type checks but LLVM not
# supporting RTTI, meaning slang-llvm built with RTTI will fail to link due to undefined
# LLVM typeinfo symbols
if(${target} STREQUAL slang-llvm)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
add_supported_cxx_linker_flags(
${target}
PRIVATE
"-Wl,-undefined,suppress"
)
endif()
else()
Comment on lines +168 to +176
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure where the WAR is supposed to be applied to.
Is it intentional to not set any option for slang-llvm on platforms other than Darwin?

Shouldn't the logic more be like,

if (${target} STREQUAL slang-llvm AND SLANG_ENABLE_ASN AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
   suppress warning
else
...

add_supported_cxx_linker_flags(
${target}
PRIVATE
# Don't assume that symbols will be resolved at runtime
"-Wl,--no-undefined"
)
endif()
endif()

set_target_properties(
Expand Down Expand Up @@ -214,19 +231,30 @@ function(set_default_compile_options target)
)

if(SLANG_ENABLE_ASAN)
add_supported_cxx_flags(
${target}
PRIVATE
/fsanitize=address
-fsanitize=address
)
add_supported_cxx_linker_flags(
${target}
BEFORE
PUBLIC
/INCREMENTAL:NO
-fsanitize=address
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Clang defaults to statically linking the sanitizer runtime (except on macOS/Darwin),
# which is not compatible with -Wl,--no-undefined, so we need to use dynamic linking
# instead (-shared-libsan).
target_compile_options(
${target}
PRIVATE -fsanitize=address,undefined -shared-libsan
)
target_link_options(
${target}
PUBLIC -fsanitize=address,undefined -shared-libsan
)
Comment on lines +234 to +245
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to mention, the downside of -shared-libsan is the .so's are stored somewhere that is basically guaranteed to not be in LD_LIBRARY_PATH (e.g., /usr/lib/llvm-20/lib/clang/20/lib/linux for LLVM 20 on Ubuntu), so that directory has to be added to LD_LIBRARY_PATH before running CMake. But I've also had to add build/<CMake config, e.g., Debug>/lib to LD_LIBRARY_PATH for a test to find libslang-glslang.so.

elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(
${target}
PRIVATE -fsanitize=address,undefined
)
target_link_options(${target} PUBLIC -fsanitize=address,undefined)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${target} PRIVATE /fsanitize=address)
target_link_options(${target} PRIVATE /INCREMENTAL:NO)
else()
message(WARNING "Not enabling sanitizers: unsupported C++ compiler")
endif()
endif()

if(SLANG_ENABLE_COVERAGE)
Expand Down
6 changes: 0 additions & 6 deletions cmake/LLVM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,6 @@ function(fetch_or_build_slang_llvm)
target_compile_options(slang-llvm PRIVATE -wd4244 /Zc:preprocessor)
endif()

if(NOT LLVM_ENABLE_RTTI)
# Make sure that we don't disable rtti if this library wasn't compiled with
# support
add_supported_cxx_flags(slang-llvm PRIVATE -fno-rtti /GR-)
endif()

# TODO: Put a check here that libslang-llvm.so doesn't have a 'NEEDED'
# directive for libLLVM-21.so, it's almost certainly going to break at
# runtime in surprising ways when linked alongside Mesa (or anything else
Expand Down
126 changes: 103 additions & 23 deletions source/compiler-core/slang-downstream-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,30 @@ SlangResult CommandLineDownstreamCompiler::compile(
if (!isVersionCompatible(inOptions))
{
// Not possible to compile with this version of the interface.
// TODO: Fix other `IDownstreamCompiler::compile` implementations not always writing to
// `outArtifact` when returning early.
const auto targetDesc = ArtifactDescUtil::makeDescForCompileTarget(SLANG_TARGET_UNKNOWN);
auto artifact = ArtifactUtil::createArtifact(targetDesc);
*outArtifact = artifact.detach();
return SLANG_E_NOT_IMPLEMENTED;
}

CompileOptions options = getCompatibleVersion(&inOptions);
const auto targetDesc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);

// Copy the command line options
CommandLine cmdLine(m_cmdLine);
// Create the result artifact
auto resultArtifact = ArtifactUtil::createArtifact(targetDesc);

// Work out the ArtifactDesc
const auto targetDesc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);
// Create the diagnostics
auto diagnostics = ArtifactDiagnostics::create();
ArtifactUtil::addAssociated(resultArtifact, diagnostics);

// If Slang was built with Clang or GCC and sanitizers were enabled, the same `-fsanitize=...`
// flag must be passed when linking an executable or shared library with a Slang library, or
// linking will fail. We can't instrument C++ generated from Slang code with sanitizers as they
// might report errors that we can't fix, so we separate compilation and linking into two
// commands to enable sanitizers only during linking.
bool shouldSeparateCompileAndLink = options.targetType != SLANG_OBJECT_CODE;

auto helper = DefaultArtifactHelper::getSingleton();

Expand All @@ -95,8 +109,13 @@ SlangResult CommandLineDownstreamCompiler::compile(
{
// We could use the path to the source, or use the source name/paths as defined on the
// artifact For now we just go with a lock file based on "slang-generated".
SLANG_RETURN_ON_FAIL(
helper->createLockFile(asCharSlice(toSlice("slang-generated")), lockFile.writeRef()));
if (SLANG_FAILED(helper->createLockFile(
asCharSlice(toSlice("slang-generated")),
lockFile.writeRef())))
{
*outArtifact = resultArtifact.detach();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know this part of slangc very well (yet), but I'd just like to double-check that this is intentional.

Usually, a failure should rollback whatever changes was done by the function. In this case, it feels a bit odd that we're returning a failure but still also returning the artifact.

See also https://en.wikipedia.org/wiki/Exception_safety

Addendum. I see from above that apparently this is the intention. But I do wonder if the call site shouldn't try to use the artifact in case this function failed, unless it's for some sort of diagnostics (= error output). I this case, I would appreciate a code comment.

return SLANG_FAIL;
}

auto lockArtifact = Artifact::create(
ArtifactDesc::make(ArtifactKind::Base, ArtifactPayload::Lock, ArtifactStyle::None));
Expand All @@ -110,17 +129,81 @@ SlangResult CommandLineDownstreamCompiler::compile(
options.modulePath = SliceUtil::asTerminatedCharSlice(modulePath);
}

// Compile stage: compile source to object code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this newly added code just as an ASAN fix? (I presume we do. The PR description would be the place to explain why. Not at all obvious.)

ComPtr<IArtifact> objectArtifact;

if (shouldSeparateCompileAndLink)
{
CompileOptions compileOptions = options;
compileOptions.targetType = SLANG_OBJECT_CODE;

CommandLine compileCmdLine(m_cmdLine);
if (SLANG_FAILED(calcArgs(compileOptions, compileCmdLine)))
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

List<ComPtr<IArtifact>> compileArtifacts;
if (SLANG_FAILED(calcCompileProducts(
compileOptions,
DownstreamProductFlag::All,
lockFile,
compileArtifacts)))
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

// There should only be one object file (.o/.obj) as artifact
SLANG_ASSERT(compileArtifacts.getCount() == 1);
SLANG_ASSERT(compileArtifacts[0]->getDesc().kind == ArtifactKind::ObjectCode);
objectArtifact = compileArtifacts[0];

ExecuteResult compileResult;
if (SLANG_FAILED(ProcessUtil::execute(compileCmdLine, compileResult)) ||
SLANG_FAILED(parseOutput(compileResult, diagnostics)))
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

// If compilation failed, return the diagnostics
if (compileResult.resultCode != 0 || !objectArtifact->exists())
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}
}

// Link stage (or single-stage compile for object code targets)
CommandLine cmdLine(m_cmdLine);

if (shouldSeparateCompileAndLink)
{
// Pass compiled object to linker
options.sourceArtifacts = makeSlice(objectArtifact.readRef(), 1);
}

// Append command line args to the end of cmdLine using the target specific function for the
// specified options
SLANG_RETURN_ON_FAIL(calcArgs(options, cmdLine));
if (SLANG_FAILED(calcArgs(options, cmdLine)))
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

// The 'productArtifact' is the main product produced from the compilation - the
// executable/sharedlibrary/object etc
ComPtr<IArtifact> productArtifact;
{
List<ComPtr<IArtifact>> artifacts;
SLANG_RETURN_ON_FAIL(
calcCompileProducts(options, DownstreamProductFlag::All, lockFile, artifacts));
if (SLANG_FAILED(
calcCompileProducts(options, DownstreamProductFlag::All, lockFile, artifacts)))
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

for (IArtifact* artifact : artifacts)
{
Expand All @@ -139,6 +222,7 @@ SlangResult CommandLineDownstreamCompiler::compile(
// Somethings gone wrong if we don't find the main artifact
if (!productArtifact)
{
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

Expand All @@ -152,7 +236,13 @@ SlangResult CommandLineDownstreamCompiler::compile(
}
#endif

SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes));
if (SLANG_FAILED(ProcessUtil::execute(cmdLine, exeRes)) ||
SLANG_FAILED(parseOutput(exeRes, diagnostics)))
{
// If the process failed or the output parsing failed, return the diagnostics
*outArtifact = resultArtifact.detach();
return SLANG_FAIL;
}

#if 0
{
Expand Down Expand Up @@ -209,23 +299,13 @@ SlangResult CommandLineDownstreamCompiler::compile(
}
}

// Create the result artifact
auto artifact = ArtifactUtil::createArtifact(targetDesc);

// Createa the diagnostics
auto diagnostics = ArtifactDiagnostics::create();

SLANG_RETURN_ON_FAIL(parseOutput(exeRes, diagnostics));

ArtifactUtil::addAssociated(artifact, diagnostics);

// Find the rep from the 'main' artifact, we'll just use the same representation on the output
// artifact. Sharing is desirable, because the rep owns the file.
if (auto fileRep = productArtifact
? findRepresentation<IOSFileArtifactRepresentation>(productArtifact)
: nullptr)
{
artifact->addRepresentation(fileRep);
resultArtifact->addRepresentation(fileRep);
}

// Add the artifact list if there is anything in it
Expand All @@ -242,10 +322,10 @@ SlangResult CommandLineDownstreamCompiler::compile(

artifactContainer->setChildren(slice.data, slice.count);

artifact->addAssociated(artifactContainer);
resultArtifact->addAssociated(artifactContainer);
}

*outArtifact = artifact.detach();
*outArtifact = resultArtifact.detach();
return SLANG_OK;
}

Expand Down
62 changes: 47 additions & 15 deletions source/compiler-core/slang-gcc-compiler-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,7 @@ static SlangResult _parseGCCFamilyLine(

SliceAllocator allocator;

diagnostics->reset();
diagnostics->setRaw(SliceUtil::asCharSlice(exeRes.standardError));
diagnostics->appendRaw(SliceUtil::asCharSlice(exeRes.standardError));

// We hold in workDiagnostics so as it is more convenient to append to the last with a
// continuation also means we don't hold the allocations of building up continuations, just the
Expand Down Expand Up @@ -620,12 +619,34 @@ static SlangResult _parseGCCFamilyLine(
{
// Don't link, just produce object file
cmdLine.addArg("-c");

if (PlatformUtil::isFamily(PlatformFamily::Unix, platformKind))
{
// Position independent
cmdLine.addArg("-fPIC");
}
Comment on lines +623 to +627
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why this is needed.
If needed, can we apply to all platform without making a divergence based on the target platform?

Copy link
Contributor Author

@ncelikNV ncelikNV Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed to fix this kind of link error:

/usr/bin/ld: /tmp/slang-generated-lhMVPe.o: warning: relocation against `globalCounter' in read-only section `.text'
/usr/bin/ld: /tmp/slang-generated-lhMVPe.o: relocation R_X86_64_PC32 against symbol `intGlobal' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: bad value
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

Objects linked into shared libraries must be compiled with -fPIC.

Note that with this change, position-independent code will be enabled for all objects (there's no way to tell just from SLANG_OBJECT_CODE whether we ultimately want to link a shared library or an executable or if we really just want to compile an object), but there doesn't seem to be a reason not to.

If needed, can we apply to all platform without making a divergence based on the target platform?

Right.

break;
}
default:
break;
}

#if defined(__has_feature)
#if __has_feature(address_sanitizer)
if (options.targetType != SLANG_OBJECT_CODE)
{
#if SLANG_CLANG || SLANG_GCC
// Assume ASan was enabled through the SLANG_ENABLE_ASAN CMake option, meaning UBSan was
// enabled as well.
cmdLine.addArg("-fsanitize=address,undefined");
#endif
Comment on lines +634 to +642
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should really assume that ASan being enabled + the compiler being either Clang or GCC means that UBSan is also enabled.

#if SLANG_CLANG
cmdLine.addArg("-shared-libsan");
#endif
}
#endif
#endif

// Add defines
for (const auto& define : options.defines)
{
Expand Down Expand Up @@ -678,14 +699,18 @@ static SlangResult _parseGCCFamilyLine(
cmdLine.addArg(fileRep->getPath());
}

// Add the library paths

if (options.libraryPaths.count && (options.targetType == SLANG_HOST_EXECUTABLE))
// Add linker-specific options only when not compiling to object code
if (options.targetType != SLANG_OBJECT_CODE)
{
if (PlatformUtil::isFamily(PlatformFamily::Apple, platformKind))
cmdLine.addArg("-Wl,-rpath,@loader_path,-rpath,@loader_path/../lib");
else
cmdLine.addArg("-Wl,-rpath,$ORIGIN,-rpath,$ORIGIN/../lib");
// Add the library paths

if (options.libraryPaths.count && (options.targetType == SLANG_HOST_EXECUTABLE))
{
if (PlatformUtil::isFamily(PlatformFamily::Apple, platformKind))
cmdLine.addArg("-Wl,-rpath,@loader_path,-rpath,@loader_path/../lib");
else
cmdLine.addArg("-Wl,-rpath,$ORIGIN,-rpath,$ORIGIN/../lib");
}
}

StringSlicePool libPathPool(StringSlicePool::Style::Default);
Expand All @@ -711,13 +736,17 @@ static SlangResult _parseGCCFamilyLine(
const UnownedStringSlice path(fileRep->getPath());
libPathPool.add(Path::getParentDirectory(path));

cmdLine.addPrefixPathArg(
"-l",
ArtifactDescUtil::getBaseNameFromPath(artifact->getDesc(), path));
if (options.targetType != SLANG_OBJECT_CODE)
{
cmdLine.addPrefixPathArg(
"-l",
ArtifactDescUtil::getBaseNameFromPath(artifact->getDesc(), path));
}
}
}

if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP &&
options.targetType != SLANG_OBJECT_CODE &&
!PlatformUtil::isFamily(PlatformFamily::Windows, platformKind))
{
// Make STD libs available
Expand All @@ -728,9 +757,12 @@ static SlangResult _parseGCCFamilyLine(

for (const auto& libPath : libPathPool.getAdded())
{
// Note that any escaping of the path is handled in the ProcessUtil::
cmdLine.addArg("-L");
cmdLine.addArg(libPath);
if (options.targetType != SLANG_OBJECT_CODE)
{
// Note that any escaping of the path is handled in the ProcessUtil::
cmdLine.addArg("-L");
cmdLine.addArg(libPath);
}
cmdLine.addArg("-F");
cmdLine.addArg(libPath);
}
Expand Down
Loading
Loading