Skip to content

Commit 5c86cd8

Browse files
authored
Merge pull request #265 from plasma-umass/lief_port
macOS port: LIEF-based binary parsing and causal profiling
2 parents 00a15ec + 288b4ff commit 5c86cd8

29 files changed

Lines changed: 2241 additions & 456 deletions

.github/workflows/ci.yml

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,50 +35,49 @@ jobs:
3535
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
3636
make -j$(nproc)
3737
38-
- name: Build toy benchmark
38+
- name: Build benchmarks
3939
run: |
4040
cd build
4141
cmake .. -DBUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
42-
make -j$(nproc) toy
42+
make -j$(nproc) toy lock_test
4343
4444
- name: Verify build artifacts
4545
run: |
4646
test -f build/libcoz/libcoz.so
4747
test -x build/benchmarks/toy/toy
48+
test -x build/benchmarks/lock_test/lock_test
4849
echo "Build artifacts verified"
4950
5051
- name: Run toy benchmark with coz
5152
run: |
5253
cd build
5354
# Run coz with the toy benchmark for a short duration
54-
timeout 30 ../coz run -o profile.coz --- ./benchmarks/toy/toy || true
55+
timeout 30 ../coz run -o profile.jsonl --- ./benchmarks/toy/toy || true
5556
56-
# Check that profile was created
57-
if [ ! -f profile.coz ]; then
58-
echo "ERROR: profile.coz was not created"
57+
if [ ! -f profile.jsonl ]; then
58+
echo "ERROR: profile.jsonl was not created"
5959
exit 1
6060
fi
6161
6262
echo "Profile created successfully"
6363
64-
- name: Validate profile output
64+
- name: Validate toy profile output
6565
run: |
6666
cd build
6767
68-
# Check profile has content
69-
if [ ! -s profile.coz ]; then
70-
echo "ERROR: profile.coz is empty"
68+
if [ ! -s profile.jsonl ]; then
69+
echo "ERROR: profile.jsonl is empty"
7170
exit 1
7271
fi
7372
7473
echo "=== Profile Contents ==="
75-
cat profile.coz
74+
cat profile.jsonl
7675
echo ""
7776
7877
# Check for expected profile format (should contain experiment lines)
79-
EXPERIMENT_COUNT=$(grep -c 'experiment' profile.coz || echo 0)
78+
EXPERIMENT_COUNT=$(grep -c 'experiment' profile.jsonl || echo 0)
8079
echo "=== Profile Summary ==="
81-
echo " Total lines: $(wc -l < profile.coz)"
80+
echo " Total lines: $(wc -l < profile.jsonl)"
8281
echo " Experiments: $EXPERIMENT_COUNT"
8382
8483
if [ "$EXPERIMENT_COUNT" -eq 0 ]; then
@@ -87,8 +86,7 @@ jobs:
8786
fi
8887
8988
# Validate profile references toy.cpp line 18 (the loop in function a())
90-
# This is the critical line that should show optimization potential
91-
if grep -q "toy.cpp:18" profile.coz; then
89+
if grep -q "toy.cpp:18" profile.jsonl; then
9290
echo " Found experiments for toy.cpp:18 (loop in function a())"
9391
else
9492
echo "ERROR: toy.cpp:18 not found in profile - expected loop in a() to be profiled"
@@ -98,44 +96,27 @@ jobs:
9896
echo ""
9997
echo "Profile validation PASSED"
10098
101-
- name: Validate optimization potential
99+
- name: Validate toy optimization potential
102100
run: |
103101
cd build
104102
105-
# Validate that toy.cpp:18 shows expected optimization behavior:
106-
# - Linear speedup potential up to ~50% (because a() takes 2x as long as b())
107-
# - Diminishing returns beyond 50% (speeding up a() more doesn't help since b() becomes bottleneck)
108-
109103
python3 << 'VALIDATION_SCRIPT'
110104
import sys
111-
import re
105+
import json
112106
113-
# Parse profile.coz
114-
# Format: experiment\tselected=path:line\tspeedup=X.XX\tduration=N\tselected-samples=N
115-
# Followed by: throughput-point\tname=path:line\tdelta=N
116107
experiments = []
117108
current_exp = None
118109
119-
with open('profile.coz', 'r') as f:
110+
with open('profile.jsonl', 'r') as f:
120111
for line in f:
121112
line = line.strip()
122-
if line.startswith('experiment'):
123-
# Parse key=value pairs
124-
parts = line.split('\t')
125-
exp = {}
126-
for part in parts[1:]:
127-
if '=' in part:
128-
key, val = part.split('=', 1)
129-
exp[key] = val
130-
current_exp = exp
131-
elif line.startswith('throughput-point') and current_exp:
132-
# Parse throughput point and attach to current experiment
133-
parts = line.split('\t')
134-
for part in parts[1:]:
135-
if '=' in part:
136-
key, val = part.split('=', 1)
137-
if key == 'delta':
138-
current_exp['delta'] = float(val)
113+
if not line:
114+
continue
115+
obj = json.loads(line)
116+
if obj.get('type') == 'experiment':
117+
current_exp = obj
118+
elif obj.get('type') == 'throughput_point' and current_exp:
119+
current_exp['delta'] = float(obj['delta'])
139120
experiments.append(current_exp)
140121
current_exp = None
141122
@@ -151,7 +132,7 @@ jobs:
151132
# Group by speedup percentage and calculate average delta
152133
speedup_deltas = {}
153134
for e in line18_exps:
154-
s = int(float(e['speedup']) * 100) # Convert decimal (0.40) to percentage (40)
135+
s = int(float(e['speedup']) * 100)
155136
if s not in speedup_deltas:
156137
speedup_deltas[s] = []
157138
speedup_deltas[s].append(e['delta'])
@@ -162,7 +143,6 @@ jobs:
162143
avg_delta = sum(deltas) / len(deltas)
163144
print(f" {speedup}%: avg_delta={avg_delta:.2f} (n={len(deltas)})")
164145
165-
# Validation: Check that we have data for multiple speedup levels
166146
if len(speedup_deltas) < 3:
167147
print(f"WARNING: Only {len(speedup_deltas)} speedup levels - need more data for accurate validation")
168148
else:
@@ -172,9 +152,80 @@ jobs:
172152
print("\nProfile validation COMPLETE")
173153
VALIDATION_SCRIPT
174154
175-
- name: Upload profile artifact
155+
- name: Run lock_test benchmark with coz
156+
run: |
157+
cd build
158+
timeout 60 ../coz run -o lock_test_profile.jsonl --- ./benchmarks/lock_test/lock_test || true
159+
160+
if [ ! -f lock_test_profile.jsonl ]; then
161+
echo "ERROR: lock_test_profile.jsonl was not created"
162+
exit 1
163+
fi
164+
165+
echo "Lock test profile created successfully"
166+
167+
- name: Validate lock_test profile
168+
run: |
169+
cd build
170+
171+
if [ ! -s lock_test_profile.jsonl ]; then
172+
echo "ERROR: lock_test_profile.jsonl is empty"
173+
exit 1
174+
fi
175+
176+
echo "=== Lock Test Profile Contents ==="
177+
cat lock_test_profile.jsonl
178+
echo ""
179+
180+
python3 << 'VALIDATION_SCRIPT'
181+
import sys
182+
import json
183+
184+
experiments = []
185+
current_exp = None
186+
187+
with open('lock_test_profile.jsonl', 'r') as f:
188+
for line in f:
189+
line = line.strip()
190+
if not line:
191+
continue
192+
obj = json.loads(line)
193+
if obj.get('type') == 'experiment':
194+
current_exp = obj
195+
elif obj.get('type') == 'throughput_point' and current_exp:
196+
current_exp['delta'] = float(obj['delta'])
197+
experiments.append(current_exp)
198+
current_exp = None
199+
200+
# Check for experiments on critical_work (line 12) and local_work (line 20)
201+
line12_exps = [e for e in experiments if 'lock_test.cpp:12' in e['selected']]
202+
line20_exps = [e for e in experiments if 'lock_test.cpp:20' in e['selected']]
203+
204+
print(f"Experiments for line 12 (critical_work): {len(line12_exps)}")
205+
print(f"Experiments for line 20 (local_work): {len(line20_exps)}")
206+
207+
if not line12_exps:
208+
print("ERROR: No experiments found for lock_test.cpp:12 (critical section)")
209+
sys.exit(1)
210+
211+
# Check samples to verify critical_work dominates
212+
with open('lock_test_profile.jsonl', 'r') as f:
213+
for line in f:
214+
obj = json.loads(line.strip())
215+
if obj.get('type') == 'samples':
216+
loc = obj['location']
217+
count = obj['count']
218+
print(f" Samples: {loc} = {count}")
219+
220+
print("\nLock contention benchmark validation: PASSED")
221+
print(" - lock_test.cpp:12 (critical section) identified as profiling target")
222+
VALIDATION_SCRIPT
223+
224+
- name: Upload profile artifacts
176225
uses: actions/upload-artifact@v4
177226
with:
178-
name: coz-profile
179-
path: build/profile.coz
227+
name: coz-profiles
228+
path: |
229+
build/profile.jsonl
230+
build/lock_test_profile.jsonl
180231
retention-days: 7

CLAUDE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,22 @@ coz run --source-scope '/media/psf/Home/git/coz-portage/benchmarks/**' --- ./ben
215215
COZ_FILTER_SYSTEM=1 coz run --- ./benchmarks/toy/toy
216216
```
217217

218+
**Verbose output:**
219+
220+
Use `--verbose` (or `-v`) to see what libraries and source files coz is processing:
221+
222+
```bash
223+
coz run --verbose --- ./myapp
224+
```
225+
226+
This prints:
227+
- Bootstrap messages
228+
- MAIN executable path resolution
229+
- Source files found in DWARF debug info
230+
- Libraries being profiled
231+
232+
Useful for debugging when coz isn't finding expected source lines or to verify which files are in scope.
233+
218234
### Adding Progress Points
219235

220236
Include `include/coz.h` and add macros:

CMakeLists.txt

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,48 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake)
1111

1212
find_package(Threads REQUIRED)
1313

14+
# ============== LIEF library for cross-platform binary parsing ==============
15+
option(USE_SYSTEM_LIEF "Link against LIEF discovered via find_package" OFF)
16+
17+
if(USE_SYSTEM_LIEF)
18+
find_package(LIEF REQUIRED)
19+
else()
20+
include(FetchContent)
21+
22+
set(LIEF_GIT_TAG "0.17.0" CACHE STRING
23+
"Git tag/branch/commit for LIEF")
24+
set(LIEF_GIT_REPO "https://github.com/lief-project/LIEF.git" CACHE STRING
25+
"Git repository URL for LIEF")
26+
27+
# Disable LIEF features we don't need to speed up build
28+
set(LIEF_PYTHON_API OFF CACHE BOOL "" FORCE)
29+
set(LIEF_C_API OFF CACHE BOOL "" FORCE)
30+
set(LIEF_EXAMPLES OFF CACHE BOOL "" FORCE)
31+
set(LIEF_TESTS OFF CACHE BOOL "" FORCE)
32+
set(LIEF_DOC OFF CACHE BOOL "" FORCE)
33+
set(LIEF_LOGGING OFF CACHE BOOL "" FORCE)
34+
set(LIEF_ENABLE_JSON OFF CACHE BOOL "" FORCE)
35+
# Only enable formats we need
36+
set(LIEF_ELF ON CACHE BOOL "" FORCE)
37+
set(LIEF_PE OFF CACHE BOOL "" FORCE)
38+
set(LIEF_MACHO ON CACHE BOOL "" FORCE)
39+
set(LIEF_OAT OFF CACHE BOOL "" FORCE)
40+
set(LIEF_DEX OFF CACHE BOOL "" FORCE)
41+
set(LIEF_VDEX OFF CACHE BOOL "" FORCE)
42+
set(LIEF_ART OFF CACHE BOOL "" FORCE)
43+
set(LIEF_COFF OFF CACHE BOOL "" FORCE)
44+
45+
FetchContent_Declare(
46+
LIEF
47+
GIT_REPOSITORY ${LIEF_GIT_REPO}
48+
GIT_TAG ${LIEF_GIT_TAG}
49+
)
50+
51+
FetchContent_MakeAvailable(LIEF)
52+
message(STATUS "Fetched LIEF ${LIEF_GIT_TAG}")
53+
endif()
54+
55+
# ============== libelfin for DWARF parsing ==============
1456
option(USE_SYSTEM_LIBELFIN "Link against libelfin discovered via pkg-config" OFF)
1557

1658
if(USE_SYSTEM_LIBELFIN)
@@ -25,8 +67,8 @@ else()
2567
# Fetch libelfin from GitHub
2668
include(FetchContent)
2769

28-
set(LIBELFIN_GIT_TAG "8779775beea1ee5444ba53d14e354dbcf42a811d" CACHE STRING
29-
"Git tag/branch/commit for libelfin (default: includes pre-built to_string.cc)")
70+
set(LIBELFIN_GIT_TAG "origin/improved_dwarf5_support" CACHE STRING
71+
"Git tag/branch/commit for libelfin (default: DWARF5 support with pre-built to_string.cc)")
3072
set(LIBELFIN_GIT_REPO "https://github.com/plasma-umass/libelfin.git" CACHE STRING
3173
"Git repository URL for libelfin")
3274

@@ -86,8 +128,8 @@ option(INSTALL_COZ "Enable installation of coz. (Projects embedding coz may want
86128
if(INSTALL_COZ)
87129
include(GNUInstallDirs)
88130
include(CMakePackageConfigHelpers)
89-
install(PROGRAMS coz DESTINATION bin)
90-
install(FILES LICENSE.md DESTINATION licenses)
131+
install(PROGRAMS coz DESTINATION ${CMAKE_INSTALL_BINDIR})
132+
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DOCDIR})
91133

92134
# Install the viewer web application
93135
install(DIRECTORY viewer/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
add_executable(lock_test lock_test.cpp)
2+
target_link_libraries(lock_test PRIVATE pthread coz-instrumentation)
3+
4+
add_coz_run_target(run_lock_test COMMAND $<TARGET_FILE:lock_test>)

benchmarks/lock_test/lock_test.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <coz.h>
2+
#include <pthread.h>
3+
#include <stdio.h>
4+
5+
// Shared mutex — the bottleneck
6+
static pthread_mutex_t the_lock = PTHREAD_MUTEX_INITIALIZER;
7+
8+
// Work inside the critical section (the bottleneck)
9+
static volatile unsigned long long shared_counter;
10+
11+
void critical_work() {
12+
for (volatile int i = 0; i < 5000000; i++) {
13+
shared_counter++; // line 14 — should be the bottleneck
14+
}
15+
}
16+
17+
// Work outside the critical section (not the bottleneck)
18+
void local_work() {
19+
volatile unsigned long long x = 0;
20+
for (volatile int i = 0; i < 1000000; i++) {
21+
x++; // line 22 — should show ~0% impact
22+
}
23+
}
24+
25+
struct thread_arg {
26+
int iterations;
27+
};
28+
29+
void* worker(void* arg) {
30+
int iters = ((thread_arg*)arg)->iterations;
31+
for (int i = 0; i < iters; i++) {
32+
local_work();
33+
34+
pthread_mutex_lock(&the_lock);
35+
critical_work();
36+
pthread_mutex_unlock(&the_lock);
37+
38+
COZ_PROGRESS;
39+
}
40+
return nullptr;
41+
}
42+
43+
int main() {
44+
const int NUM_THREADS = 4;
45+
const int ITERS_PER_THREAD = 500;
46+
47+
printf("Lock contention test: %d threads, %d iterations each\n", NUM_THREADS, ITERS_PER_THREAD);
48+
49+
pthread_t threads[NUM_THREADS];
50+
thread_arg args[NUM_THREADS];
51+
52+
for (int i = 0; i < NUM_THREADS; i++) {
53+
args[i].iterations = ITERS_PER_THREAD;
54+
pthread_create(&threads[i], nullptr, worker, &args[i]);
55+
}
56+
57+
for (int i = 0; i < NUM_THREADS; i++) {
58+
pthread_join(threads[i], nullptr);
59+
}
60+
61+
printf("Done.\n");
62+
}

benchmarks/toy/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
add_executable(toy toy.cpp)
2-
target_link_libraries(toy PRIVATE pthread)
2+
target_link_libraries(toy PRIVATE pthread coz-instrumentation)
33

44
add_coz_run_target(run_toy COMMAND $<TARGET_FILE:toy>)

0 commit comments

Comments
 (0)