diff options
| author | ruki <[email protected]> | 2025-12-18 15:07:54 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-18 15:07:54 +0800 |
| commit | 67019f3310fe67e365c24df2a47806bf3fd947e5 (patch) | |
| tree | 54c91c9642350220003ebaaddedbc2303add1cd7 | |
| parent | a33624639549e6dda946a810f175fc0e9cfc79b3 (diff) | |
| parent | 2ff05c59d4c3aa6b23b8d521d9173c0860109019 (diff) | |
Merge pull request #7145 from xmake-io/clangrt
Improve clang runtime
| -rw-r--r-- | tests/projects/c++/modules/test_base.lua | 7 | ||||
| -rw-r--r-- | tests/projects/c/llvm_compiler_rt/src/main.c | 5 | ||||
| -rw-r--r-- | tests/projects/c/llvm_compiler_rt/test.lua | 29 | ||||
| -rw-r--r-- | tests/projects/c/llvm_compiler_rt/xmake.lua | 3 | ||||
| -rw-r--r-- | xmake/modules/core/tools/clang.lua | 121 | ||||
| -rw-r--r-- | xmake/modules/private/utils/toolchain.lua | 188 | ||||
| -rw-r--r-- | xmake/rules/utils/compiler_runtime/xmake.lua | 2 | ||||
| -rw-r--r-- | xmake/toolchains/clang/load.lua | 3 | ||||
| -rw-r--r-- | xmake/toolchains/llvm/check.lua | 6 | ||||
| -rw-r--r-- | xmake/toolchains/llvm/xmake.lua | 10 |
10 files changed, 291 insertions, 83 deletions
diff --git a/tests/projects/c++/modules/test_base.lua b/tests/projects/c++/modules/test_base.lua index 6abbeecee..466a59e98 100644 --- a/tests/projects/c++/modules/test_base.lua +++ b/tests/projects/c++/modules/test_base.lua @@ -11,7 +11,7 @@ MSVC_MIN_VER = "14.29" function _build(check_outdata) local flags = "" if ci_is_running() then - flags = "-vD" + flags = "-vD" end if check_outdata then local outdata @@ -101,9 +101,12 @@ function build_tests(toolchain_name, opt) if opt.flags then flags = " " .. table.concat(opt.flags, " ") end + if ci_is_running() then + flags = flags .. " -vD" + end os.exec("xmake clean -a") - os.exec("xmake f" .. platform .. "--toolchain=" .. toolchain_name .. runtimes .. "-cD --yes " .. policies .. flags) + os.exec("xmake f" .. platform .. "--toolchain=" .. toolchain_name .. runtimes .. "-c --yes " .. policies .. flags) if opt.build then opt.build() else diff --git a/tests/projects/c/llvm_compiler_rt/src/main.c b/tests/projects/c/llvm_compiler_rt/src/main.c new file mode 100644 index 000000000..e12ade9ad --- /dev/null +++ b/tests/projects/c/llvm_compiler_rt/src/main.c @@ -0,0 +1,5 @@ +int main(int argc, char **argv) { + __int128 a = 123; + __int128 b = 1; + return a / b; +} diff --git a/tests/projects/c/llvm_compiler_rt/test.lua b/tests/projects/c/llvm_compiler_rt/test.lua new file mode 100644 index 000000000..6285ecb1a --- /dev/null +++ b/tests/projects/c/llvm_compiler_rt/test.lua @@ -0,0 +1,29 @@ +import("lib.detect.find_tool") +import("core.tool.toolchain") +import("utils.ci.is_running", {alias = "ci_is_running"}) + +function run_test(toolchain_name) + local flags = "" + if ci_is_running() then + flags = "-vD" + end + + local plat = os.host() + local arch = os.arch() + if is_subhost("msys") then + plat = "mingw" + end + local toolchain_inst = toolchain.load(toolchain_name, {plat = plat, arch = arch}) + if not toolchain_inst or not toolchain_inst:check() then + wprint(toolchain_name .. " not found, skipping tests") + return + end + os.exec("xmake clean -a") + os.exec("xmake f --toolchain=" .. toolchain_name .. " -c --yes " .. flags) + os.run("xmake -r " .. flags) +end + +function main(t) + run_test("llvm") + run_test("clang") +end diff --git a/tests/projects/c/llvm_compiler_rt/xmake.lua b/tests/projects/c/llvm_compiler_rt/xmake.lua new file mode 100644 index 000000000..04267d334 --- /dev/null +++ b/tests/projects/c/llvm_compiler_rt/xmake.lua @@ -0,0 +1,3 @@ +target("foo") + set_kind("binary") + add_files("src/main.c") diff --git a/xmake/modules/core/tools/clang.lua b/xmake/modules/core/tools/clang.lua index cc9a24ca2..6b3098215 100644 --- a/xmake/modules/core/tools/clang.lua +++ b/xmake/modules/core/tools/clang.lua @@ -21,6 +21,7 @@ -- inherit gcc inherit("gcc") import("core.language.language") +import("private.utils.toolchain", {alias = "toolchain_utils"}) -- init it function init(self) @@ -191,35 +192,6 @@ function _has_static_libstdcxx(self) return has_static_libstdcxx end --- get llvm sdk root directory -function _get_llvm_rootdir(self) - local llvm_rootdir = _g._LLVM_ROOTDIR - if llvm_rootdir == nil then - local outdata = try { function() return os.iorunv(self:program(), {"-print-resource-dir"}, {envs = self:runenvs()}) end } - if outdata then - llvm_rootdir = path.normalize(path.join(outdata:trim(), "..", "..", "..")) - if not os.isdir(llvm_rootdir) then - llvm_rootdir = nil - end - end - _g._LLVM_ROOTDIR = llvm_rootdir or false - end - return llvm_rootdir or nil -end - --- get llvm target triple -function _get_llvm_target_triple(self) - local llvm_targettriple = _g._LLVM_TARGETTRIPLE - if llvm_targettriple == nil then - local outdata = try { function() return os.iorunv(self:program(), {"-print-target-triple"}, {envs = self:runenvs()}) end } - if outdata then - llvm_targettriple = outdata:trim() - end - _g._LLVM_TARGETTRIPLE = llvm_targettriple or false - end - return llvm_targettriple or nil -end - -- make the runtime flag -- @see https://github.com/xmake-io/xmake/issues/3546 function nf_runtime(self, runtime, opt) @@ -250,61 +222,66 @@ function nf_runtime(self, runtime, opt) } end end - if not self:is_plat("android") then -- we will set runtimes in android ndk toolchain + -- llvm on windows still doesn't support autolinking of libc++ and compiler-rt builtins + -- @see https://discourse.llvm.org/t/improve-autolinking-of-compiler-rt-and-libc-on-windows-with-lld-link/71392/10 + -- and need manual setting of libc++ headerdirectory + -- @see https://github.com/llvm/llvm-project/issues/79647 + local target = opt.target or opt + local llvm_dirs = toolchain_utils.get_llvm_dirs(self:toolchain()) + -- we will set runtimes in android ndk toolchain + if not self:is_plat("android") then maps = maps or {} - local llvm_rootdir = self:toolchain():sdkdir() - if kind == "cxx" then + if kind == "cxx" or kind == "ld" or kind == "sh" then maps["c++_static"] = "-stdlib=libc++" maps["c++_shared"] = "-stdlib=libc++" maps["stdc++_static"] = "-stdlib=libstdc++" maps["stdc++_shared"] = "-stdlib=libstdc++" - if not llvm_rootdir and self:is_plat("windows") then - -- clang on windows fail to add libc++ includepath when using -stdlib=libc++ so we manually add it + if kind == "cxx" then + -- force the toolchain libc++ headers to prevent clang picking the systems one -- @see https://github.com/llvm/llvm-project/issues/79647 - llvm_rootdir = _get_llvm_rootdir(self) + if llvm_dirs.cxxincludedir then + maps["c++_static"] = table.join(maps["c++_static"], "-cxx-isystem" .. llvm_dirs.cxxincludedir) + maps["c++_shared"] = table.join(maps["c++_shared"], "-cxx-isystem" .. llvm_dirs.cxxincludedir) + end + end + end + + if self:is_plat("windows") and language.sourcekinds()[kind] then + -- on windows force link to compiler_rt builtins + if llvm_dirs.rtdir and llvm_dirs.rtlink then + for name, _ in pairs(maps) do + maps[name] = table.join({"-Xclang", "--dependent-lib=" .. llvm_dirs.rtlink}, maps[name]) + end end - if llvm_rootdir then - maps["c++_static"] = table.join(maps["c++_static"], "-cxx-isystem" .. path.join(llvm_rootdir, "include", "c++", "v1")) - maps["c++_shared"] = table.join(maps["c++_shared"], "-cxx-isystem" .. path.join(llvm_rootdir, "include", "c++", "v1")) + end + if kind == "ld" or kind == "sh" then + if self:is_plat("windows") and llvm_dirs.rtdir then + -- on windows force add compiler_rt link directories + for name, _ in pairs(maps) do + maps[name] = table.join(nf_linkdir(self, llvm_dirs.rtdir), maps[name]) + maps[name] = table.join("-resource-dir=" .. llvm_dirs.resourcedir, maps[name]) + end end - elseif kind == "ld" or kind == "sh" then - local target = opt.target or opt local is_cxx = target and (target.sourcekinds and table.contains(table.wrap(target:sourcekinds()), "cxx")) if is_cxx then - maps["c++_static"] = "-stdlib=libc++" - maps["c++_shared"] = "-stdlib=libc++" - maps["stdc++_static"] = "-stdlib=libstdc++" - maps["stdc++_shared"] = "-stdlib=libstdc++" - if not llvm_rootdir and self:is_plat("windows") then - -- clang on windows fail to add libc++ librarypath when using -stdlib=libc++ so we manually add it - -- @see https://github.com/llvm/llvm-project/issues/79647 - llvm_rootdir = _get_llvm_rootdir(self) - end - if llvm_rootdir then - local libdir = path.absolute(path.join(llvm_rootdir, "lib")) - maps["c++_static"] = table.join(maps["c++_static"], "-L" .. libdir) - maps["c++_shared"] = table.join(maps["c++_shared"], "-L" .. libdir) + if llvm_dirs.libdir then + maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.libdir)) + maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.libdir)) + -- sometimes llvm c++ runtimes are located in c++ subfolder (e.g homebrew llvm) - local cxx_libdir = path.join(libdir, "c++") - if os.isdir(cxx_libdir) then - maps["c++_static"] = table.join(maps["c++_static"], "-L" .. cxx_libdir) - maps["c++_shared"] = table.join(maps["c++_shared"], "-L" .. cxx_libdir) - end - -- sometimes llvm runtimes are located in a target-triple subfolder - local target_triple = _get_llvm_target_triple(self) - local triple_libdir = (target_triple and os.isdir(path.join(libdir, target_triple))) and path.join(libdir, target_triple) - if triple_libdir then - maps["c++_static"] = table.join(maps["c++_static"], "-L" .. triple_libdir) - maps["c++_shared"] = table.join(maps["c++_shared"], "-L" .. triple_libdir) + if llvm_dirs.cxxlibdir then + maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.cxxlibdir)) + maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.cxxlibdir)) end - -- add rpath to avoid the user need to set LD_LIBRARY_PATH by hand - maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, libdir)) - if triple_libdir then - maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, triple_libdir)) - end - if target.is_shared and target:is_shared() and target.filename and self:is_plat("macosx", "iphoneos", "watchos") then - maps["c++_shared"] = table.join(maps["c++_shared"], "-install_name") - maps["c++_shared"] = table.join(maps["c++_shared"], "@rpath/" .. target:filename()) + + -- add rpath to avoid the user need to set (DY)LD_LIBRARY_PATH by hand + if not self:is_plat("windows", "mingw") then + if llvm_dirs.libdir then + maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, llvm_dirs.libdir)) + end + if llvm_dirs.cxxlibdir then + maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, llvm_dirs.cxxlibdir)) + end end end if runtime:endswith("_static") and _has_static_libstdcxx(self) then diff --git a/xmake/modules/private/utils/toolchain.lua b/xmake/modules/private/utils/toolchain.lua index 28cbc23c0..49af8193f 100644 --- a/xmake/modules/private/utils/toolchain.lua +++ b/xmake/modules/private/utils/toolchain.lua @@ -181,3 +181,191 @@ function map_linkflags_for_package(package, targetkind, sourcekinds, name, value return flags end +-- get llvm sdk resource directory +function get_llvm_resourcedir(toolchain) + local memcache = toolchain:memcache() + local cachekey = "get_llvm_resourcedir" + local llvm_resourcedir = memcache:get(cachekey) + if llvm_resourcedir == nil then + local cc = toolchain:tool("cc") + if cc then + local outdata = try { function() return os.iorunv(cc, {"-print-resource-dir"}) end } + if outdata then + llvm_resourcedir = path.normalize(outdata:trim()) + if not os.isdir(llvm_resourcedir) then + llvm_resourcedir = nil + end + end + end + memcache:set(cachekey, llvm_resourcedir or false) + end + return llvm_resourcedir or nil +end + +-- get llvm sdk root directory +function get_llvm_rootdir(toolchain) + local memcache = toolchain:memcache() + local cachekey = "get_llvm_rootdir" + local llvm_rootdir = memcache:get(cachekey) + if llvm_rootdir == nil then + local resourcedir = get_llvm_resourcedir(toolchain) + if resourcedir then + llvm_rootdir = path.normalize(path.join(resourcedir, "..", "..", "..")) + if not os.isdir(llvm_rootdir) then + llvm_rootdir = nil + end + end + memcache:set(cachekey, llvm_rootdir or false) + end + return llvm_rootdir or nil +end + +-- get compiler-rt info +function get_llvm_compiler_rtinfo(toolchain) + local memcache = toolchain:memcache() + local cachekey = "get_llvm_compiler_rtinfo" + local rtinfo = memcache:get(cachekey) + if rtinfo == nil then + local resourcedir = get_llvm_resourcedir(toolchain) + if resourcedir then + local res_libdir = path.join(resourcedir, "lib") + -- when -DLLVM_ENABLE_TARGET_RUNTIME_DIR=OFF rtdir is windows/ and rtlink is clang_rt.builtins_<arch>.lib + -- when ON rtdir is windows/<target-triple> and rtlink is clang_rt.builtins.lib + local target_triple = get_llvm_target_triple(toolchain) + local arch = target_triple and target_triple:split("-")[1] + + local plat + if toolchain:is_plat("windows", "mingw") then + plat = "windows" + elseif toolchain:is_plat("linux") then + plat = "linux" + elseif toolchain:is_plat("macosx", "iphoneos", "watchos", "appletvos", "applexros") then + plat = "darwin" + end + + local tripletdir = target_triple and path.join(res_libdir, "windows", target_triple) + tripletdir = os.isdir(tripletdir) or nil + + local rtdir = tripletdir and path.join(plat, target_triple) or plat + rtinfo = {rtdir = res_libdir, rtlibdir = path.join(res_libdir, rtdir)} + if os.isdir(rtinfo.rtlibdir) and toolchain:is_plat("windows", "mingw") then + local rtlink + if tripletdir then + rtlink = "clang_rt.builtins.lib" + elseif arch then + rtlink = "clang_rt.builtins-" .. arch .. ".lib" + end + if rtlink and os.isfile(path.join(rtinfo.rtlibdir, rtlink)) then + rtinfo.rtlink = path.join(rtdir, rtlink) + end + end + end + memcache:set(cachekey, rtinfo or false) + end + return rtinfo or nil +end + +-- get llvm target triple +function get_llvm_target_triple(toolchain) + local memcache = toolchain:memcache() + local cachekey = "get_llvm_target_triple" + local llvm_targettriple = memcache:get(cachekey) + if llvm_targettriple == nil then + local cc = toolchain:tool("cc") + if cc then + local outdata = try { function() return os.iorunv(cc, {"-print-target-triple"}) end } + if outdata then + llvm_targettriple = outdata:trim() + end + end + memcache:set(cachekey, llvm_targettriple or false) + end + return llvm_targettriple or nil +end + +-- get llvm toolchain dirs +function get_llvm_dirs(toolchain) + local memcache = toolchain:memcache() + local cachekey = "get_llvm_dirs" + local llvm_dirs = memcache:get(cachekey) + if llvm_dirs == nil then + local rootdir = toolchain:sdkdir() + if not rootdir and (toolchain:is_plat("windows") or is_host("windows")) then + rootdir = get_llvm_rootdir(toolchain) + end + + local bindir, libdir, cxxlibdir, includedir, cxxincludedir, resourcedir, rtdir, rtlink, rtlibdir + if rootdir then + bindir = path.join(rootdir, "bin") + bindir = os.isdir(bindir) and bindir or nil + + libdir = path.join(rootdir, "lib") + libdir = os.isdir(libdir) and libdir or nil + + if libdir then + cxxlibdir = path.join(libdir, "c++") + cxxlibdir = os.isdir(cxxlibdir) and cxxlibdir or nil + if not cxxlibdir then + cxxlibdir = path.join(libdir, get_llvm_target_triple(toolchain)) + cxxlibdir = os.isdir(cxxlibdir) and cxxlibdir or nil + end + end + + includedir = path.join(rootdir, "include") + includedir = os.isdir(includedir) and includedir or nil + + if includedir then + cxxincludedir = path.join(includedir, "c++", "v1") + cxxincludedir = os.isdir(cxxincludedir) and cxxincludedir or nil + end + + resourcedir = get_llvm_resourcedir(toolchain) + local rtinfo = get_llvm_compiler_rtinfo(toolchain) + if rtinfo then + rtdir = rtinfo.rtdir + rtlink = rtinfo.rtlink + rtlibdir = rtinfo.rtlibdir + end + end + + llvm_dirs = {rootdir = rootdir, + bindir = bindir, + libdir = libdir, + cxxlibdir = cxxlibdir, + includedir = includedir, + cxxincludedir = cxxincludedir, + resourcedir = resourcedir, + rtdir = rtdir, + rtlibdir = rtlibdir, + rtlink = rtlink } + memcache:set(cachekey, llvm_dirs) + end + return llvm_dirs +end + +-- add runenvs for llvm +function add_llvm_runenvs(toolchain) + local dirs = get_llvm_dirs(toolchain) + if dirs then + if dirs.bindir and (toolchain:is_plat("windows") or is_host("windows")) then + toolchain:add("runenvs", "PATH", dirs.bindir) + end + for _, dir in ipairs({dirs.libdir or false, dirs.cxxlibdir or false, dirs.rtlibdir or false}) do + if dir then + if toolchain:is_plat("windows") or is_host("windows") then + toolchain:add("runenvs", "PATH", dir) + elseif toolchain:is_plat("linux", "bsd") then + toolchain:add("runenvs", "LD_LIBRARY_PATH", dir) + elseif toolchain:is_plat("macosx") then + -- using use DYLD_FALLBACK_LIBRARY_PATH instead of DYLD_LIBRARY_PATH to avoid symbols error when running homebrew llvm (which is linked to system libc++) + -- e.g dyld[5195]: Symbol not found: __ZnwmSt19__type_descriptor_t + -- Referenced from: <378C7CC2-7CD6-3B88-9C66-FE198E30462B> /usr/local/Cellar/llvm/21.1.5/bin/clang-21 + -- Expected as weak-def export from some loaded dylibSymbol not found: __ZnamSt19__type_descriptor_t + toolchain:add("runenvs", "DYLD_FALLBACK_LIBRARY_PATH", dir) + end + end + end + end +end + + diff --git a/xmake/rules/utils/compiler_runtime/xmake.lua b/xmake/rules/utils/compiler_runtime/xmake.lua index 6a59d7ace..756bd55f8 100644 --- a/xmake/rules/utils/compiler_runtime/xmake.lua +++ b/xmake/rules/utils/compiler_runtime/xmake.lua @@ -44,7 +44,7 @@ rule("utils.compiler.runtime") -- enable vs runtime as MD by default if target:is_plat("windows") and not target:get("runtimes") then local vs_runtime_default = target:policy("build.c++.msvc.runtime") - if vs_runtime_default and target:has_tool("cxx", "cl", "clang", "clang_cl") then + if vs_runtime_default and target:has_tool("cxx", "cl", "clang", "clangxx", "clang_cl") then if is_mode("debug") then vs_runtime_default = vs_runtime_default .. "d" end diff --git a/xmake/toolchains/clang/load.lua b/xmake/toolchains/clang/load.lua index 74b04e867..3c50e3be2 100644 --- a/xmake/toolchains/clang/load.lua +++ b/xmake/toolchains/clang/load.lua @@ -21,6 +21,7 @@ import("detect.sdks.find_vstudio") import("detect.sdks.find_mingw") import("core.project.config") +import("private.utils.toolchain", {alias = "toolchain_utils"}) -- add the given vs environment function _add_vsenv(toolchain, name, curenvs) @@ -91,6 +92,8 @@ function main(toolchain, suffix) target = target .. "-linux-gnu" end + toolchain_utils.add_llvm_runenvs(toolchain) + if target then toolchain:add("cxflags", "--target=" .. target) toolchain:add("asflags", "--target=" .. target) diff --git a/xmake/toolchains/llvm/check.lua b/xmake/toolchains/llvm/check.lua index 54901824a..74cc96a7f 100644 --- a/xmake/toolchains/llvm/check.lua +++ b/xmake/toolchains/llvm/check.lua @@ -115,11 +115,13 @@ function main(toolchain) toolchain:config_set("bindir", cross_toolchain.bindir) toolchain:config_set("sdkdir", cross_toolchain.sdkdir) else - raise("llvm toolchain not found!") + wprint("llvm toolchain not found!") + return false end if toolchain:is_plat("cross") and (not toolchain:cross() or toolchain:cross():match("^%s*$")) then - raise("Missing cross target. Use `--cross=name` to specify.") + wprint("Missing cross target. Use `--cross=name` to specify.") + return false end -- attempt to find xcode to pass `-isysroot` on macos diff --git a/xmake/toolchains/llvm/xmake.lua b/xmake/toolchains/llvm/xmake.lua index d4557071c..b8f37bec1 100644 --- a/xmake/toolchains/llvm/xmake.lua +++ b/xmake/toolchains/llvm/xmake.lua @@ -50,12 +50,16 @@ toolchain("llvm") on_check("check") on_load(function (toolchain) + import("private.utils.toolchain", {alias = "toolchain_utils"}) -- add runtimes if toolchain:is_plat("windows") then toolchain:add("runtimes", "MT", "MTd", "MD", "MDd") end + -- add llvm runenvs + toolchain_utils.add_llvm_runenvs(toolchain) + -- add target flags local target if toolchain:is_plat("windows") and not is_host("windows") then @@ -151,11 +155,5 @@ toolchain("llvm") toolchain:add("shflags", "--sysroot=" .. sysroot) end end - - -- add bin search library for loading some dependent .dll files windows - local bindir = toolchain:bindir() - if bindir and is_host("windows") then - toolchain:add("runenvs", "PATH", bindir) - end end) |
