Skip to content

Commit 0340de6

Browse files
tz12323Sunrisepeak
andauthored
feat(openmp): add OpenMP compatibility package and tests (#529)
* feat(openmp): add OpenMP compatibility package and tests * feat(openmp): enhance compatibility header and fallback for Apple Clang * fix(openmp): build the real LLVM runtime and test parallel execution * fix(openmp): run quoted CMake commands through a Windows batch file * fix(openmp): install the DLL linker import library with Visual Studio --------- Co-authored-by: sunrisepeak <speakshen@163.com>
1 parent cfb808b commit 0340de6

7 files changed

Lines changed: 222 additions & 2 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Add compat.openmp
2+
3+
LLVM libomp 22.1.8 is a separable CMake project inside llvm-project.
4+
The descriptor downloads the full official llvm-project-22.1.8.src.tar.xz,
5+
whose SHA-256 was checked twice; license Apache-2.0 WITH LLVM-exception.
6+
The install hook builds upstream openmp/ and installs the generated omp.h
7+
and real runtime. It writes the C anchor only after installation succeeds.
8+
9+
Unix uses static libomp.a, which implements both Clang and GCC OpenMP ABIs
10+
without a C++ standard-library ABI dependency. The hook uses the system C/C++
11+
SDK explicitly because installation precedes mcpp consumer toolchain selection.
12+
Windows uses the upstream-supported DLL/import library with Visual Studio 2022;
13+
the DLL is declared as a runtime artifact directory for consumer deployment.
14+
The Visual Studio generator can omit the archive for upstream's source-less
15+
ompimp target. In that case the hook installs the import library produced by
16+
the DLL link under the expected libomp.lib name; DLL and imports are from the
17+
same build. Windows CMake commands run through a batch file, preserving quoted
18+
SDK paths and propagating failures with the build log tail.
19+
CMake and Python are declared build dependencies. Offloading and OMPT tools
20+
are disabled; there is no misleading sources-only feature for these builds.
21+
22+
No mirror upload was performed. Plain-string GLOBAL URLs provide CN fallback.
23+
The example redirects compat to this checkout. It enables OpenMP with normal
24+
GCC/Clang flags (Apple Clang uses -Xclang -fopenmp), links the package runtime,
25+
launches two threads, and checks thread identity plus a reduction sum. The
26+
header never defines _OPENMP: a missing compiler flag is a compile error.
27+
28+
Local validation with CI-pinned mcpp 2026.10.1.2 and gcc 16.1.0:
29+
`mcpp test -p openmp`: `test result ok. 1 passed; 0 failed`.
30+
An independent upstream CMake build also succeeded. Cross-platform behavior
31+
is validated by the PR CI matrix rather than inferred from a SIMD-only test.

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ One descriptor per common shape:
4242
| Generated config header | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) |
4343
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) |
4444
| C++23 module shipped by upstream | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) |
45-
| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) |
45+
| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.openmp`](pkgs/c/compat.openmp.lua) |
4646

4747
Every other shape, and why each descriptor is written the way it is, is in
4848
**[Descriptor examples by shape](docs/descriptor-examples.md)**.

‎README.zh-CN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
4141
| 生成 config 头 | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) |
4242
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) |
4343
| 上游自带 C++23 module | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) |
44-
| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) |
44+
| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.openmp`](pkgs/c/compat.openmp.lua) |
4545

4646
其余形态,以及每个描述符为何这样写,见 **[描述符示例总览(按形态)](docs/zh/descriptor-examples.md)**。
4747

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ members = [
8282
"tests/examples/mysql-connector-cpp",
8383
"tests/examples/nlohmann.json",
8484
"tests/examples/openblas",
85+
"tests/examples/openmp",
8586
"tests/examples/openssl",
8687
"tests/examples/recastnavigation",
8788
"tests/examples/recastnavigation-features",

‎pkgs/c/compat.openmp.lua‎

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
-- LLVM OpenMP runtime built from its pinned release sources.
2+
-- libomp implements the Clang and (on Unix) GCC OpenMP ABIs. Consumers enable
3+
-- OpenMP compilation themselves; this package supplies the header and runtime.
4+
-- Unix uses a static C-ABI runtime; Windows ships the upstream DLL/import lib.
5+
package = {
6+
spec = "1",
7+
namespace = "compat",
8+
name = "openmp",
9+
description = "LLVM OpenMP runtime and omp.h, built from source",
10+
licenses = {"Apache-2.0 WITH LLVM-exception"},
11+
repo = "https://github.com/llvm/llvm-project/tree/main/openmp",
12+
type = "package",
13+
xpm = {
14+
linux = {
15+
deps = { "xim:cmake@latest", "xim:python@3.13.12" },
16+
["22.1.8"] = {
17+
url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz",
18+
sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888",
19+
},
20+
},
21+
macosx = {
22+
deps = { "xim:cmake@latest", "xim:python@3.13.12" },
23+
["22.1.8"] = {
24+
url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz",
25+
sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888",
26+
},
27+
},
28+
windows = {
29+
deps = { "xim:cmake@latest", "xim:python@3.13.12" },
30+
["22.1.8"] = {
31+
url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz",
32+
sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888",
33+
},
34+
},
35+
},
36+
mcpp = {
37+
language = "c++23",
38+
import_std = false,
39+
c_standard = "c11",
40+
include_dirs = {"mcpp_openmp/include"},
41+
-- Written last by install(), after CMake has installed the runtime.
42+
sources = {"mcpp_openmp_anchor.c"},
43+
targets = {["openmp"] = {kind = "lib"}},
44+
linux = {ldflags = {"-Lmcpp_openmp/lib", "-lomp", "-lpthread", "-ldl"}},
45+
macosx = {ldflags = {"-Lmcpp_openmp/lib", "-lomp"}},
46+
windows = {
47+
ldflags = {"-Lmcpp_openmp/lib", "-llibomp"},
48+
runtime = {library_dirs = {"mcpp_openmp/bin"}},
49+
},
50+
deps = {},
51+
},
52+
}
53+
54+
import("xim.libxpkg.pkginfo")
55+
import("xim.libxpkg.log")
56+
57+
local function quote(value)
58+
value = tostring(value)
59+
if os.host() == "windows" then
60+
return '"' .. value:gsub('"', '\\"') .. '"'
61+
end
62+
return "'" .. value:gsub("'", "'\\''") .. "'"
63+
end
64+
65+
local function tool(dep, name)
66+
local info = pkginfo.build_dep(dep)
67+
if info then
68+
for _, candidate in ipairs({
69+
path.join(info.bin or info.path or "", name),
70+
path.join(info.path or "", "CMake.app", "Contents", "bin", name),
71+
}) do
72+
if os.isfile(candidate) then return candidate end
73+
end
74+
end
75+
return name
76+
end
77+
78+
local function run(command, logfile)
79+
command = command .. " >> " .. quote(logfile) .. " 2>&1"
80+
if os.host() == "windows" then
81+
-- xlings cannot reliably invoke a quoted .exe directly on Windows.
82+
-- A batch file preserves CMake's arguments and reports its exit code.
83+
local bat = path.join(pkginfo.install_dir(), "mcpp_openmp_run.bat")
84+
io.writefile(bat, "@echo off\r\n" .. command
85+
.. "\r\nif errorlevel 1 exit /b 1\r\nexit /b 0\r\n")
86+
command = "cmd /c " .. quote(bat)
87+
end
88+
local ok, result = pcall(os.exec, command)
89+
if ok and result then return true end
90+
local lines = {}
91+
if os.isfile(logfile) then
92+
for line in (io.readfile(logfile) .. "\n"):gmatch("(.-)\n") do
93+
lines[#lines + 1] = line
94+
end
95+
end
96+
local tail = table.concat(lines, "\n", math.max(1, #lines - 59), #lines)
97+
log.error("compat.openmp: CMake failed; see %s\n%s", logfile, tail)
98+
return false
99+
end
100+
101+
function install()
102+
local prefix = pkginfo.install_dir()
103+
local root = "llvm-project-" .. pkginfo.version() .. ".src"
104+
if not os.isdir(root) then
105+
log.error("compat.openmp: missing extracted source %s", root)
106+
return false
107+
end
108+
os.tryrm(prefix)
109+
os.mv(root, prefix)
110+
local build = path.join(prefix, "mcpp_openmp_build")
111+
local output = path.join(prefix, "mcpp_openmp")
112+
local logfile = path.join(prefix, "mcpp_openmp_build.log")
113+
local windows = os.host() == "windows"
114+
local cmake = tool("xim:cmake", windows and "cmake.exe" or "cmake")
115+
local python = tool("xim:python", windows and "python.exe" or "python3")
116+
local options = windows and ' -G "Visual Studio 17 2022" -A x64 -DLIBOMP_ENABLE_SHARED=ON'
117+
or ' -G "Unix Makefiles" -DLIBOMP_ENABLE_SHARED=OFF'
118+
if not windows then
119+
-- install() runs before consumer toolchain resolution. Build the C-ABI
120+
-- runtime with the system SDK; libomp has no C++ standard-library ABI.
121+
options = options .. " -DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++"
122+
.. " -DCMAKE_ASM_COMPILER=/usr/bin/cc -DCMAKE_MAKE_PROGRAM=/usr/bin/make"
123+
end
124+
local configure = quote(cmake) .. " -S " .. quote(path.join(prefix, "openmp"))
125+
.. " -B " .. quote(build) .. " -DCMAKE_BUILD_TYPE=Release"
126+
.. " -DCMAKE_INSTALL_PREFIX=" .. quote(output)
127+
.. " -DPython3_EXECUTABLE=" .. quote(python)
128+
.. " -DOPENMP_ENABLE_LIBOMPTARGET=OFF -DOPENMP_ENABLE_OMPT_TOOLS=OFF"
129+
.. " -DLIBOMP_OMPT_SUPPORT=OFF -DLIBOMP_USE_ITT_NOTIFY=OFF" .. options
130+
if not run(configure, logfile) then return false end
131+
if not run(quote(cmake) .. " --build " .. quote(build) .. " --config Release --parallel 4", logfile) then return false end
132+
if windows then
133+
-- The Visual Studio generator can omit the archive of upstream's
134+
-- source-less ompimp target. The DLL link already produced an import
135+
-- library for these exact exports; give install() its expected name.
136+
local expected = path.join(build, "runtime", "src", "Release", "libomp.lib")
137+
local linked = path.join(build, "runtime", "src", "Release", "libomp.dll.lib")
138+
if not os.isfile(expected) and os.isfile(linked) then
139+
os.cp(linked, expected)
140+
end
141+
end
142+
if not run(quote(cmake) .. " --install " .. quote(build) .. " --config Release", logfile) then return false end
143+
local library = path.join(output, "lib", windows and "libomp.lib" or "libomp.a")
144+
if not os.isfile(library) or not os.isfile(path.join(output, "include", "omp.h")) then
145+
log.error("compat.openmp: missing installed header/library; see %s", logfile)
146+
return false
147+
end
148+
io.writefile(path.join(prefix, "mcpp_openmp_anchor.c"), "int mcpp_compat_openmp_anchor(void) { return 0; }\n")
149+
return true
150+
end

‎tests/examples/openmp/mcpp.toml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# OpenMP consumer test: the package supplies the compiler-facing omp.h entry
2+
# point, while this member enables the selected compiler's OpenMP runtime.
3+
[package]
4+
name = "openmp-tests"
5+
version = "0.1.0"
6+
7+
[dependencies.compat]
8+
openmp = "22.1.8"
9+
10+
[target.'cfg(linux)'.build]
11+
cxxflags = ["-fopenmp"]
12+
13+
[target.'cfg(macos)'.build]
14+
cxxflags = ["-Xclang", "-fopenmp"]
15+
16+
[target.'cfg(windows)'.build]
17+
cxxflags = ["-fopenmp"]
18+
19+
[indices]
20+
compat = { path = "../../.." }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <omp.h>
2+
3+
#ifndef _OPENMP
4+
#error "The compiler must enable OpenMP; a header cannot supply _OPENMP."
5+
#endif
6+
7+
int main() {
8+
omp_set_dynamic(0);
9+
int sum = 0;
10+
int threads = 0;
11+
#pragma omp parallel num_threads(2) reduction(+:sum)
12+
{
13+
#pragma omp single
14+
threads = omp_get_num_threads();
15+
sum += omp_get_thread_num() + 1;
16+
}
17+
return threads == 2 && sum == 3 ? 0 : 1;
18+
}

0 commit comments

Comments
 (0)