Skip to content

Commit 25b897a

Browse files
authored
Merge pull request #1777 from clasp-developers/trampolines
Adds a facility for giving bytecodes native "trampolines" so that they are visible by name in external backtraces (e.g. perf, gdb). Supersedes #1765. drmeister has done the work here, just filing a PR so I can review it.
2 parents a35d167 + 0fe6410 commit 25b897a

48 files changed

Lines changed: 17278 additions & 198 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apptainer/amber-x86.def

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
BootStrap: docker
2+
From: ubuntu:26.04
3+
4+
%files
5+
$HOME/Downloads/ambertools26.tar.bz2 /downloads/ambertools26.tar.bz2
6+
$HOME/Downloads/rosetta.tar.bz2 /downloads/rosetta.tar.bz2
7+
8+
9+
%post
10+
set -eo pipefail
11+
export DEBIAN_FRONTEND=noninteractive
12+
export DEBCONF_NONINTERACTIVE_SEEN=true
13+
export TZ=America/New_York
14+
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
15+
echo $TZ > /etc/timezone
16+
17+
apt upgrade
18+
apt update
19+
20+
apt install -y nodejs npm git curl
21+
npm install -g @anthropic-ai/claude-code
22+
useradd -m user
23+
export PATH="/root/.npm/_npx:$PATH"
24+
25+
apt -y install tcsh make \
26+
gcc gfortran g++ \
27+
flex bison patch bc \
28+
libbz2-dev libzip-dev \
29+
xorg-dev wget cmake \
30+
mpich openssh-client linux-perf \
31+
libtool-bin libvterm-dev bzip2 gdb gdbserver
32+
33+
cd /opt
34+
bunzip2 -c /downloads/rosetta.tar.bz2 | tar xvf - --strip-components=1
35+
bunzip2 -c /downloads/ambertools26.tar.bz2 | tar xvf -
36+
mkdir -p /opt/ambertools26_src/build
37+
cd /opt/ambertools26_src/build
38+
mkdir /opt/ambertools26
39+
ln -s /opt/ambertools26 /opt/amber
40+
cmake /opt/ambertools26_src \
41+
-DCMAKE_INSTALL_PREFIX=/opt/ambertools26 \
42+
-DCOMPILER=GNU \
43+
-DBUILD_GUI=FALSE \
44+
-DMPI=FALSE -DCUDA=FALSE -DINSTALL_TESTS=FALSE \
45+
-DDOWNLOAD_MINICONDA=FALSE \
46+
-DBUILD_PYTHON=FALSE \
47+
2>&1 | tee cmake.log
48+
make -j20 install 2>&1 | tee /tmp/make.log
49+
50+
51+
52+
apt install --yes binutils libboost-all-dev clang-18 libclang-cpp18-dev libclang-18-dev libgmp-dev libfmt-dev libunwind-dev llvm-18 llvm-18-dev ninja-build sbcl jupyterlab emacs openssh-client openssh-server libnetcdf-dev expat gocryptfs nodejs npm strace emacs libelf-dev wget
53+
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh
54+
bash Miniconda3-latest-Linux-aarch64.sh -b -p /home/cando/miniconda3
55+
rm -rf Miniconda3-latest-Linux-aarch64.sh
56+
export PATH="/home/cando/miniconda3/bin:${PATH}"
57+
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
58+
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
59+
conda install -y -c conda-forge jupyterlab sidecar # ambertools not available
60+
jupyter-lab build
61+
# cd /mnt
62+
# ./koga --reproducible-build --extensions=cando --build-mode=bytecode-faso --llvm-config="/usr/bin/llvm-config-18" --build-path=build-apptainer/
63+
# ninja -C build-apptainer
64+
# ninja -C build-apptainer install
65+
# mkdir -p /home/cando/
66+
# chmod ugo+rwx /home/cando/
67+
# tar -xvf /mnt/systems.tar -C /home/cando
68+
# whoami
69+
# ls /home/
70+
# chmod -R ugo+rw /home/cando/
71+
72+
%environment
73+
export AMBERHOME=/opt/amber
74+
export ROSETTA_HOME=/opt/rosetta
75+
export PATH=${AMBERHOME}/bin:/root/.npm/_npx:${PATH}
76+
export LD_LIBRARY_PATH=${AMBERHOME}/lib:${LD_LIBRARY_PATH}
77+
export ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
78+
79+
# export LD_LIBRARY_PATH=/usr/local/lib
80+
# export XDG_CACHE_HOME=/home/cando/.cache
81+

include/clasp/core/clasp_gmpxx.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ THE SOFTWARE.
2828

2929
/* Define a C++ GMP wrapper */
3030

31+
/* One one of the clusters I was building on (I'm going to guess the arm64)
32+
I got a lot of these warnings so I'm adding this #pragma to silence them
33+
*/
34+
#ifdef __aarch64__
35+
#pragma clang diagnostic push
36+
#pragma clang diagnostic ignored "-Wdeprecated-literal-operator"
37+
#endif
3138
#include <gmpxx.h>
3239

40+
#ifdef __arch64__
41+
#pragma clang diagnostic pop
42+
#endif
43+
3344
typedef mpz_class Bignum;

include/clasp/core/commandLineOptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ struct CommandLineOptions {
7777
bool _NoRc;
7878
bool _PauseForDebugger;
7979
bool _GenerateTrampolines;
80+
std::vector<std::string> _ExtensionArguments;
8081

8182
bool validStartupTypeOption(const std::string& arg);
8283
void printVersion();

include/clasp/core/lisp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ class Lisp {
279279
std::atomic<T_sp> _AllObjectFiles;
280280
std::atomic<T_sp> _AllCodeBlocks;
281281
std::atomic<T_sp> _AllBytecodeModules;
282+
// Every GFBytecodeSimpleFun ever made (atomic-pushed list of cons cells).
283+
// Walked by arena_post_load_regenerate_trampolines after a snapshot load
284+
// so the dispatch trampoline for each generic function gets re-attached.
285+
std::atomic<T_sp> _AllGFBytecodeFuns;
282286
SimpleFun_sp _UnboundCellFunctionEntryPoint;
283287
T_sp _TerminalIO;
284288
List_sp _ActiveThreads;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* sampling_profiler.h — CPU-time sampling profiler.
3+
*
4+
* At rate `N` Hz, an ITIMER_PROF timer delivers SIGPROF to an arbitrary
5+
* running thread. The handler walks the frame-pointer chain via the
6+
* ucontext registers and appends a sample (timestamp, thread id, depth,
7+
* optional bytecode-VM pc, variable-length PC array) to a per-process
8+
* bump-allocated ring.
9+
*
10+
* Separate from src/core/profiler.cc's RangePush/RangePop instrumentation.
11+
* That profiler measures user-annotated regions; this one periodically
12+
* snapshots whatever code is running.
13+
*
14+
* See Phase 4 / Phase 5 for post-mortem symbolication and flame-graph
15+
* output — this header covers the recording side only.
16+
* Phase 4 is Symbolication
17+
* Phase 5 is collapsed-stacks aggregation - see sampling_profiler.cc
18+
*/
19+
#pragma once
20+
21+
#include <cstdint>
22+
#include <cstddef>
23+
#include <string>
24+
#include <vector>
25+
26+
namespace core {
27+
28+
// Per-sample header (variable-length record). A SampleHeader is followed
29+
// immediately in the ring buffer by `depth` × uint64_t native PCs.
30+
struct SampleHeader {
31+
uint64_t timestamp_ns; // CLOCK_MONOTONIC at signal delivery
32+
uint64_t vm_pc; // bytecode VM's _pc at sample time, or 0
33+
uint32_t thread_id; // Linux tid / macOS port id (truncated)
34+
uint32_t depth; // number of trailing PCs (0 if walk failed)
35+
};
36+
37+
// Aggregated symbolicated sample: one entry per unique (thread_id, frames)
38+
// group. `frames` is outermost-first (index 0 is the root, last is the
39+
// leaf). `sample_count` is the number of raw samples that collapsed into
40+
// this entry.
41+
struct SymbolicatedSample {
42+
uint32_t thread_id;
43+
size_t sample_count;
44+
std::vector<std::string> frames;
45+
core::T_sp encode();
46+
};
47+
48+
// Start the profiler.
49+
// rate_hz : sampling rate in Hz (e.g. 97). Clamped to [1, 10000].
50+
// max_depth : per-sample stack-depth cap. Clamped to [1, 8192].
51+
// buffer_bytes : ring buffer size (0 = default 256 MiB).
52+
// Returns true on success. Fails if the profiler is already running or the
53+
// OS timer/signal setup fails.
54+
bool sampling_profiler_start(unsigned rate_hz,
55+
unsigned max_depth,
56+
size_t buffer_bytes);
57+
58+
// Stop sampling. The buffer is preserved; call
59+
// sampling_profiler_save / sampling_profiler_reset to drain / clear.
60+
void sampling_profiler_stop();
61+
62+
// True while a profile session is active.
63+
bool sampling_profiler_running();
64+
65+
// Discard all captured samples and reset the bump pointer.
66+
void sampling_profiler_reset();
67+
68+
// Drop the ring buffer contents to `path` as collapsed-stacks format
69+
// (one stack per line, semicolon-separated, trailing ' <count>'), ready
70+
// to feed Brendan Gregg's flamegraph.pl. Symbolicates on the fly using
71+
// the arena side table, ObjectFile lookup, bytecode-module scan, and
72+
// dladdr. Returns true on success, false on I/O error.
73+
bool sampling_profiler_save(const char* path);
74+
75+
// Return one entry per recorded sample. Each inner vector holds the
76+
// symbolicated frame names for that sample, outermost-first (index 0
77+
// is the root, last index is the leaf). Prints a warning and returns
78+
// an empty vector if the profiler is still running.
79+
std::vector<SymbolicatedSample> sampling_profiler_symbolicated_samples();
80+
81+
// Populate the calling thread's stack bounds for later frame-walking.
82+
// Must be called from a non-signal context.
83+
void sampling_profiler_register_current_thread();
84+
85+
// Register an executable memory range with the profiler's return-address
86+
// validator. Call this when new executable pages are allocated (JIT, arena)
87+
// so the frame-pointer walker recognizes return addresses in them.
88+
// Lock-free, safe to call from any thread while the profiler is running.
89+
void sampling_profiler_add_executable_range(uintptr_t lo, uintptr_t hi);
90+
91+
// Diagnostics.
92+
size_t sampling_profiler_samples_recorded();
93+
size_t sampling_profiler_samples_dropped();
94+
size_t sampling_profiler_bytes_used();
95+
96+
} // namespace core

include/clasp/llvmo/code.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class ObjectFile_O : public LibraryBase_O {
8484
size_t _Size;
8585
size_t _ObjectId;
8686
JITDylib_sp _TheJITDylib;
87+
// If true, this ObjectFile is transient arena-init scaffolding (shared
88+
// trampoline / stub template) that must not be serialized into snapshots.
89+
// The ObjectFile is still registered in _AllObjectFiles normally — LLVM's
90+
// link layer plugin looks it up by name during materialization, so it must
91+
// stay findable at runtime. The snapshot save walker checks this flag and
92+
// skips any ObjectFile with it set.
93+
bool _TransientSkipSnapshot = false;
8794
//
8895
// Code data
8996
void* _TextSectionStart;

include/clasp/llvmo/jit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ THE SOFTWARE.
8989
#include <clasp/llvmo/debugInfoExpose.fwd.h>
9090
#include <clasp/llvmo/translators.h>
9191
#include <clasp/llvmo/insertPoint.fwd.h>
92+
#include <clasp/llvmo/llvmoExpose.h>
9293
#include <clasp/llvmo/debugLoc.fwd.h>
9394
#include <clasp/llvmo/llvmoPackage.h>
9495

include/clasp/llvmo/llvmoExpose.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3736,4 +3736,9 @@ llvm::raw_pwrite_stream* llvm_stream(core::T_sp stream, llvm::SmallString<1024>&
37363736

37373737
core::T_sp llvm_sys__lookup_jit_symbol_info(void* ptr);
37383738

3739+
JITDylib_sp loadModule(Module_sp module, size_t startupID, const std::string& libname);
3740+
3741+
3742+
3743+
37393744
}; // namespace llvmo

include/clasp/llvmo/llvmoPackage.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ bool llvm_sys__load_bc(core::Pathname_sp filename, bool verbose, bool print, cor
6565

6666
ClaspJIT_sp llvm_sys__clasp_jit();
6767

68+
6869
void initialize_llvm();
6970
void initialize_ClaspJIT();
7071

71-
core::Pointer_mv cmp__compile_trampoline(core::T_sp name);
72-
7372
}; // namespace llvmo
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#pragma once
2+
3+
/*
4+
File: trampolineWork.h
5+
*/
6+
7+
/*
8+
Copyright (c) 2014, Christian E. Schafmeister
9+
10+
CLASP is free software; you can redistribute it and/or
11+
modify it under the terms of the GNU Library General Public
12+
License as published by the Free Software Foundation; either
13+
version 2 of the License, or (at your option) any later version.
14+
15+
See directory 'clasp/licenses' for full details.
16+
17+
The above copyright notice and this permission notice shall be included in
18+
all copies or substantial portions of the Software.
19+
20+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26+
THE SOFTWARE.
27+
*/
28+
/* -^- */
29+
30+
#include <clasp/core/common.h>
31+
32+
namespace llvmo {
33+
34+
35+
core::Pointer_mv cmp__compile_trampoline(core::T_sp name);
36+
37+
// Per-generic-function trampoline. Returns the address of an arena slot that
38+
// tail-calls GFBytecodeEntryPoint::entry_point_n. Each GF gets a unique
39+
// address so flame charts and backtraces show its name instead of all GFs
40+
// sharing the static entry_point_n symbol.
41+
core::Pointer_sp cmp__compile_gf_trampoline(core::T_sp name);
42+
43+
}; // namespace llvmo

0 commit comments

Comments
 (0)