Skip to content
Draft
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
7 changes: 5 additions & 2 deletions tests/projects/c++/modules/test_base.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions tests/projects/c/llvm_compiler_rt/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int main(int argc, char **argv) {
__int128 a = 123;
__int128 b = 1;
return a / b;
}
24 changes: 24 additions & 0 deletions tests/projects/c/llvm_compiler_rt/test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 llvm = toolchain.load("llvm")
if not llvm or not llvm:check() then
wprint("llvm not found, skipping tests")
return
end
os.exec("xmake clean -a")
os.exec("xmake f --toolchain=llvm -c --yes " .. flags)
os.run("xmake -r " .. flags)
end

function main(t)
run_test("llvm")
run_test("clang")
end
3 changes: 3 additions & 0 deletions tests/projects/c/llvm_compiler_rt/xmake.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target("foo")
set_kind("binary")
add_files("src/main.c")
111 changes: 42 additions & 69 deletions xmake/modules/core/tools/clang.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
-- inherit gcc
inherit("gcc")
import("core.language.language")
import("private.utils.toolchain", {alias = "toolchain_utils"})

-- init it
function init(self)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -250,58 +222,59 @@ function nf_runtime(self, runtime, opt)
}
end
end
if not self:is_plat("android") then -- we will set runtimes in android ndk toolchain
local target = opt.target or opt
-- 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 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.cxxinclude then
maps["c++_static"] = table.join(maps["c++_static"], "-cxx-isystem" .. llvm_dirs.cxxinclude)
maps["c++_shared"] = table.join(maps["c++_shared"], "-cxx-isystem" .. llvm_dirs.cxxinclude)
end
end
end

if self:is_plat("windows") and language.sourcekinds()[kind] then
-- on windows force link to compiler_rt builtins
if llvm_dirs.rt 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.rt then
-- on windows force add compiler_rt link directories
for name, _ in pairs(maps) do
maps[name] = table.join(nf_linkdir(self, llvm_dirs.rt), maps[name])
maps[name] = table.join("-resource-dir=" .. llvm_dirs.res, 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.lib then
maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.lib))
maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.lib))

-- 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)
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))
if llvm_dirs.cxxlib then
maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.cxxlib))
maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.cxxlib))
end

-- add rpath to avoid the user need to set DYLD_LIBRARY_PATH by hand
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())
Expand Down
172 changes: 172 additions & 0 deletions xmake/modules/private/utils/toolchain.lua
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,175 @@ function map_linkflags_for_package(package, targetkind, sourcekinds, name, value
return flags
end

-- get llvm sdk resource directory
function _get_llvm_resourcedir(toolchain)
local llvm_resourcedir = _g._LLVM_RESOURCE_DIR
if llvm_resourcedir == nil then
local outdata = try { function() return os.iorunv(toolchain:tool("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
_g._LLVM_RESOURCE_DIR = llvm_resourcedir or false
end
return llvm_resourcedir or nil
end

-- get llvm sdk root directory
function _get_llvm_rootdir(toolchain)
local llvm_rootdir = _g._LLVM_ROOTDIR
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
_g._LLVM_ROOTDIR = llvm_rootdir or false
end
return llvm_rootdir or nil
end

-- find compiler-rt dir
function _get_llvm_compiler_rtdir_and_link(toolchain)
import("lib.detect.find_tool")

local cc = toolchain:tool("cc")
local cc_tool = find_tool(cc, {version = true})
if cc_tool and cc_tool.version then
local resdir = _get_llvm_resourcedir(toolchain)
if resdir then
local res_libdir = path.join(resdir, "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") then
plat = "windows"
elseif toolchain:is_plat("linux") then
plat = "linux"
elseif toolchain:is_plat("macosx", "ios", "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
if os.isdir(path.join(res_libdir, rtdir)) and toolchain:is_plat("windows") then
local rtlink = "clang_rt.builtins" .. (tripletdir and ".lib" or ("-" .. arch .. ".lib"))
if os.isfile(path.join(res_libdir, rtdir, rtlink)) then
return res_libdir, path.join(rtdir, rtlink), path.join(res_libdir, rtdir)
end
end
return res_libdir, nil, path.join(res_libdir, rtdir)
end
end
end

-- get llvm target triple
function _get_llvm_target_triple(toolchain)
local llvm_targettriple = _g._LLVM_TARGETTRIPLE
if llvm_targettriple == nil then
local outdata = try { function() return os.iorunv(toolchain:tool("cc"), {"-print-target-triple"}) end }
if outdata then
llvm_targettriple = outdata:trim()
end
_g._LLVM_TARGETTRIPLE = llvm_targettriple or false
end
return llvm_targettriple or nil
end

-- get llvm toolchain dirs
function get_llvm_dirs(toolchain)
local llvm_dirs = _g.llvm_dirs
if llvm_dirs == nil then
local rootdir = toolchain:sdkdir()
if not rootdir and toolchain:is_plat("windows") then
rootdir = _get_llvm_rootdir(toolchain)
end

local bindir, libdir, cxxlibdir, includedir, cxxincludedir, resdir, rtdir, rtlink
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

resdir = _get_llvm_resourcedir(toolchain)
rtdir, rtlink, rtlib = _get_llvm_compiler_rtdir_and_link(toolchain)
end

llvm_dirs = {root = rootdir,
bin = bindir,
lib = libdir,
cxxlib = cxxlibdir,
include = includedir,
cxxinclude = cxxincludedir,
res = resdir,
rt = rtdir,
rtlib = rtlib,
rtlink = rtlink }
_g.llvm_dirs = llvm_dirs
end
return llvm_dirs
end

-- set runenvs for llvm
function set_llvm_runenvs(toolchain)
local pathname = "PATH"
if toolchain:is_plat("windows") then
local curenvs = os.getenvs()
for k, _ in pairs(curenvs) do
if pathname:lower() == k:lower() then
pathname = k
break
end
end
end

local dirs = get_llvm_dirs(toolchain)
if dirs then
if dirs.bin and toolchain:is_plat("windows") then
toolchain:add("runenvs", pathname, dirs.bin)
end
for _, dir in ipairs({dirs.lib or false, dirs.cxxlib or false, dirs.rtlib or false}) do
if dir then
if toolchain:is_plat("windows") then
toolchain:add("runenvs", pathname, 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
2 changes: 1 addition & 1 deletion xmake/rules/utils/compiler_runtime/xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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_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
Expand Down
Loading
Loading