Skip to content

Commit 344acb6

Browse files
committed
Add cpuinfo tests and fix x86 detection
1 parent 667d7dc commit 344acb6

3 files changed

Lines changed: 355 additions & 0 deletions

File tree

src/detect.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ DetectedCpuInfo detect_from_proc_cpuinfo() {
166166
}
167167
}
168168
}
169+
170+
// ssse3 implies sse3; on Linux sse3 is reported as "pni" in /proc/cpuinfo,
171+
// so add sse3 when ssse3 is present (matching archspec Python behavior)
172+
if (info.features.count("ssse3")) {
173+
info.features.insert("sse3");
174+
}
169175
} else if (arch == ARCH_AARCH64) {
170176
// Get vendor from CPU implementer
171177
if (data.count("CPU implementer")) {

src/microarchitecture.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,13 @@ std::vector<std::string> Microarchitecture::ancestors() const {
6666
std::vector<std::string> result;
6767
const auto& db = MicroarchitectureDatabase::instance();
6868

69+
// First add all direct parents (matching Python's breadth-first approach)
6970
for (const auto& parent_name : parent_names_) {
7071
result.push_back(parent_name);
72+
}
73+
74+
// Then add each parent's ancestors
75+
for (const auto& parent_name : parent_names_) {
7176
const auto* parent = db.get(parent_name);
7277
if (parent) {
7378
auto parent_ancestors = parent->ancestors();

tests/test_fake_cpuinfo.cpp

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
// This file is a part of Julia. License is MIT: https://julialang.org/license
2+
//
3+
// Test CPU detection using fake /proc/cpuinfo files from archspec test data
4+
5+
#include "test_common.hpp"
6+
#include <archspec/archspec.hpp>
7+
#include <fstream>
8+
#include <sstream>
9+
#include <filesystem>
10+
#include <map>
11+
#include <set>
12+
13+
using namespace archspec;
14+
15+
namespace fs = std::filesystem;
16+
17+
// Parse cpuinfo content (similar to detect_from_proc_cpuinfo but takes string input)
18+
DetectedCpuInfo parse_cpuinfo_content(const std::string& content, const std::string& arch) {
19+
DetectedCpuInfo info;
20+
21+
std::map<std::string, std::string> data;
22+
std::istringstream stream(content);
23+
std::string line;
24+
25+
while (std::getline(stream, line)) {
26+
size_t colon = line.find(':');
27+
if (colon == std::string::npos) {
28+
if (!data.empty()) break;
29+
continue;
30+
}
31+
32+
// Trim key
33+
std::string key = line.substr(0, colon);
34+
size_t start = key.find_first_not_of(" \t");
35+
size_t end = key.find_last_not_of(" \t");
36+
if (start != std::string::npos) {
37+
key = key.substr(start, end - start + 1);
38+
}
39+
40+
// Trim value
41+
std::string value = line.substr(colon + 1);
42+
start = value.find_first_not_of(" \t");
43+
end = value.find_last_not_of(" \t\r\n");
44+
if (start != std::string::npos) {
45+
value = value.substr(start, end - start + 1);
46+
} else {
47+
value = "";
48+
}
49+
50+
data[key] = value;
51+
}
52+
53+
if (arch == "x86_64" || arch == "i686" || arch == "i386") {
54+
info.vendor = data.count("vendor_id") ? data["vendor_id"] : "generic";
55+
56+
// Parse flags
57+
if (data.count("flags")) {
58+
std::istringstream flags_stream(data["flags"]);
59+
std::string flag;
60+
while (flags_stream >> flag) {
61+
info.features.insert(flag);
62+
}
63+
}
64+
65+
// ssse3 implies sse3 (Linux reports sse3 as "pni")
66+
if (info.features.count("ssse3")) {
67+
info.features.insert("sse3");
68+
}
69+
} else if (arch == "aarch64") {
70+
// Get vendor from CPU implementer
71+
if (data.count("CPU implementer")) {
72+
const auto& db = MicroarchitectureDatabase::instance();
73+
const auto& vendors = db.arm_vendors();
74+
auto it = vendors.find(data["CPU implementer"]);
75+
if (it != vendors.end()) {
76+
info.vendor = it->second;
77+
} else {
78+
info.vendor = data["CPU implementer"];
79+
}
80+
} else {
81+
info.vendor = "generic";
82+
}
83+
84+
// Parse features
85+
if (data.count("Features")) {
86+
std::istringstream feat_stream(data["Features"]);
87+
std::string feat;
88+
while (feat_stream >> feat) {
89+
info.features.insert(feat);
90+
}
91+
}
92+
93+
info.cpu_part = data.count("CPU part") ? data["CPU part"] : "";
94+
} else if (arch == "ppc64le" || arch == "ppc64") {
95+
// Parse POWER generation
96+
if (data.count("cpu")) {
97+
std::string cpu_str = data["cpu"];
98+
size_t pos = cpu_str.find("POWER");
99+
if (pos != std::string::npos) {
100+
pos += 5; // Skip "POWER"
101+
std::string gen_str;
102+
while (pos < cpu_str.size() && std::isdigit(cpu_str[pos])) {
103+
gen_str += cpu_str[pos++];
104+
}
105+
if (!gen_str.empty()) {
106+
info.generation = std::stoi(gen_str);
107+
}
108+
}
109+
}
110+
}
111+
112+
return info;
113+
}
114+
115+
// Detect host from cpuinfo content
116+
std::string detect_from_content(const std::string& content, const std::string& arch) {
117+
DetectedCpuInfo info = parse_cpuinfo_content(content, arch);
118+
auto candidates = compatible_microarchitectures(info);
119+
120+
if (candidates.empty()) {
121+
return arch;
122+
}
123+
124+
// Sorting criteria: more ancestors and more features is better
125+
auto sorting_fn = [](const Microarchitecture* a, const Microarchitecture* b) {
126+
size_t a_depth = a->ancestors().size();
127+
size_t b_depth = b->ancestors().size();
128+
if (a_depth != b_depth) return a_depth < b_depth;
129+
return a->features().size() < b->features().size();
130+
};
131+
132+
// Find best generic candidate
133+
std::vector<const Microarchitecture*> generic_candidates;
134+
for (const auto* c : candidates) {
135+
if (c->vendor() == "generic") {
136+
generic_candidates.push_back(c);
137+
}
138+
}
139+
140+
const Microarchitecture* best_generic = nullptr;
141+
if (!generic_candidates.empty()) {
142+
best_generic = *std::max_element(generic_candidates.begin(), generic_candidates.end(), sorting_fn);
143+
}
144+
145+
// Filter by CPU part for AArch64
146+
if (!info.cpu_part.empty()) {
147+
std::vector<const Microarchitecture*> cpu_part_matches;
148+
for (const auto* c : candidates) {
149+
if (c->cpu_part() == info.cpu_part) {
150+
cpu_part_matches.push_back(c);
151+
}
152+
}
153+
if (!cpu_part_matches.empty()) {
154+
candidates = cpu_part_matches;
155+
}
156+
}
157+
158+
// Filter candidates to be descendants of best generic
159+
if (best_generic) {
160+
std::vector<const Microarchitecture*> filtered;
161+
for (const auto* c : candidates) {
162+
if (*c > *best_generic) {
163+
filtered.push_back(c);
164+
}
165+
}
166+
if (!filtered.empty()) {
167+
candidates = filtered;
168+
}
169+
}
170+
171+
if (candidates.empty()) {
172+
return best_generic ? best_generic->name() : arch;
173+
}
174+
175+
const Microarchitecture* best = *std::max_element(candidates.begin(), candidates.end(), sorting_fn);
176+
return best->name();
177+
}
178+
179+
// Read file content
180+
std::string read_file_content(const std::string& path) {
181+
std::ifstream file(path);
182+
if (!file.is_open()) return "";
183+
std::stringstream buffer;
184+
buffer << file.rdbuf();
185+
return buffer.str();
186+
}
187+
188+
// Extract expected target from filename (e.g., "linux-ubuntu20.04-zen3" -> "zen3")
189+
std::string extract_expected_target(const std::string& filename) {
190+
size_t last_dash = filename.rfind('-');
191+
if (last_dash != std::string::npos) {
192+
return filename.substr(last_dash + 1);
193+
}
194+
return "";
195+
}
196+
197+
// Extract architecture from filename
198+
std::string extract_arch_from_filename(const std::string& filename) {
199+
if (filename.find("linux-") == 0 || filename.find("bgq-") == 0) {
200+
// Check for power
201+
if (filename.find("power") != std::string::npos) {
202+
if (filename.find("le") != std::string::npos) return "ppc64le";
203+
return "ppc64";
204+
}
205+
// Check for ARM
206+
if (filename.find("cortex") != std::string::npos ||
207+
filename.find("neoverse") != std::string::npos ||
208+
filename.find("thunderx") != std::string::npos ||
209+
filename.find("a64fx") != std::string::npos ||
210+
filename.find("-m1") != std::string::npos ||
211+
filename.find("-m2") != std::string::npos ||
212+
filename.find("-m3") != std::string::npos ||
213+
filename.find("-m4") != std::string::npos) {
214+
return "aarch64";
215+
}
216+
// Default to x86_64 for Linux
217+
return "x86_64";
218+
}
219+
if (filename.find("darwin-") == 0) {
220+
// macOS - skip these as they use sysctl not cpuinfo
221+
return "skip";
222+
}
223+
return "x86_64";
224+
}
225+
226+
TEST(fake_cpuinfo_zen3) {
227+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-ubuntu20.04-zen3");
228+
ASSERT(!content.empty());
229+
230+
std::string detected = detect_from_content(content, "x86_64");
231+
std::cout << "(detected: " << detected << ", expected: zen3) ";
232+
ASSERT_EQ(detected, "zen3");
233+
TEST_PASS();
234+
}
235+
236+
TEST(fake_cpuinfo_haswell) {
237+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rhel7-haswell");
238+
ASSERT(!content.empty());
239+
240+
std::string detected = detect_from_content(content, "x86_64");
241+
std::cout << "(detected: " << detected << ", expected: haswell) ";
242+
ASSERT_EQ(detected, "haswell");
243+
TEST_PASS();
244+
}
245+
246+
TEST(fake_cpuinfo_broadwell) {
247+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rhel7-broadwell");
248+
ASSERT(!content.empty());
249+
250+
std::string detected = detect_from_content(content, "x86_64");
251+
std::cout << "(detected: " << detected << ", expected: broadwell) ";
252+
ASSERT_EQ(detected, "broadwell");
253+
TEST_PASS();
254+
}
255+
256+
TEST(fake_cpuinfo_cascadelake) {
257+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-centos7-cascadelake");
258+
ASSERT(!content.empty());
259+
260+
std::string detected = detect_from_content(content, "x86_64");
261+
std::cout << "(detected: " << detected << ", expected: cascadelake) ";
262+
ASSERT_EQ(detected, "cascadelake");
263+
TEST_PASS();
264+
}
265+
266+
TEST(fake_cpuinfo_skylake_avx512) {
267+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rhel7-skylake_avx512");
268+
ASSERT(!content.empty());
269+
270+
std::string detected = detect_from_content(content, "x86_64");
271+
std::cout << "(detected: " << detected << ", expected: skylake_avx512) ";
272+
ASSERT_EQ(detected, "skylake_avx512");
273+
TEST_PASS();
274+
}
275+
276+
TEST(fake_cpuinfo_piledriver) {
277+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rhel6-piledriver");
278+
ASSERT(!content.empty());
279+
280+
std::string detected = detect_from_content(content, "x86_64");
281+
std::cout << "(detected: " << detected << ", expected: piledriver) ";
282+
ASSERT_EQ(detected, "piledriver");
283+
TEST_PASS();
284+
}
285+
286+
TEST(fake_cpuinfo_zen4) {
287+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rocky8.5-zen4");
288+
ASSERT(!content.empty());
289+
290+
std::string detected = detect_from_content(content, "x86_64");
291+
std::cout << "(detected: " << detected << ", expected: zen4) ";
292+
ASSERT_EQ(detected, "zen4");
293+
TEST_PASS();
294+
}
295+
296+
// Note: ARM and Power tests would require architecture simulation support
297+
// since compatible_microarchitectures() uses get_machine() internally.
298+
// These are left as placeholders for future cross-architecture testing support.
299+
300+
TEST(fake_cpuinfo_zen) {
301+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rhel7-zen");
302+
ASSERT(!content.empty());
303+
304+
std::string detected = detect_from_content(content, "x86_64");
305+
std::cout << "(detected: " << detected << ", expected: zen) ";
306+
ASSERT_EQ(detected, "zen");
307+
TEST_PASS();
308+
}
309+
310+
TEST(fake_cpuinfo_zen5) {
311+
std::string content = read_file_content("extern/archspec/archspec/json/tests/targets/linux-rocky9-zen5");
312+
ASSERT(!content.empty());
313+
314+
std::string detected = detect_from_content(content, "x86_64");
315+
std::cout << "(detected: " << detected << ", expected: zen5) ";
316+
ASSERT_EQ(detected, "zen5");
317+
TEST_PASS();
318+
}
319+
320+
int main() {
321+
std::cout << "=== archspec_cpp Fake cpuinfo Tests ===" << std::endl;
322+
std::cout << std::endl;
323+
324+
// x86_64 tests (Intel)
325+
RUN_TEST(fake_cpuinfo_haswell);
326+
RUN_TEST(fake_cpuinfo_broadwell);
327+
RUN_TEST(fake_cpuinfo_skylake_avx512);
328+
RUN_TEST(fake_cpuinfo_cascadelake);
329+
330+
// x86_64 tests (AMD)
331+
RUN_TEST(fake_cpuinfo_piledriver);
332+
RUN_TEST(fake_cpuinfo_zen);
333+
RUN_TEST(fake_cpuinfo_zen3);
334+
RUN_TEST(fake_cpuinfo_zen4);
335+
RUN_TEST(fake_cpuinfo_zen5);
336+
337+
std::cout << std::endl;
338+
std::cout << "=== Results ===" << std::endl;
339+
std::cout << "Passed: " << g_tests_passed << std::endl;
340+
std::cout << "Failed: " << g_tests_failed << std::endl;
341+
342+
return g_tests_failed > 0 ? 1 : 0;
343+
}
344+

0 commit comments

Comments
 (0)