From b83906d4b45e6be6a488cafe84b217880e9eb285 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 22:05:02 +0000 Subject: [PATCH 01/15] feat: add macos-intel ci --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5cd499..4530f70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: os: - ubuntu-latest - macos-latest + - macos-15-intel features: - "rayon cranelift external_f64 external_f32" experimental: @@ -140,7 +141,7 @@ jobs: - name: Rust version run: rustc -Vv - name: Install LLVM and Clang (LLVM >= 19.1 & macos) - if : matrix.llvm != '' && matrix.os == 'macos-latest' && (matrix.llvm[0] >= '19.1') + if : matrix.llvm != '' && (matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel') && (matrix.llvm[0] >= '19.1') run: | brew install llvm@${{ matrix.llvm[3] }} echo "LLVM_PATH=$(brew --prefix llvm@${{ matrix.llvm[3] }})" >> $GITHUB_ENV @@ -161,7 +162,7 @@ jobs: echo "LLVM_PATH=/usr/lib/llvm-${{ matrix.llvm[3] }}" >> $GITHUB_ENV - name: Install LLVM and Clang (LLVM < 19.1) uses: KyleMayes/install-llvm-action@v2 - if : matrix.llvm != '' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' || matrix.os == 'ubuntu-24.04-arm') && (matrix.llvm[0] < '19.1') + if : matrix.llvm != '' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel' || matrix.os == 'ubuntu-24.04-arm') && (matrix.llvm[0] < '19.1') with: version: ${{ matrix.llvm[0] }} - name: Set LLVM_SYS_XXX_DIR and CC, CXX environment variables @@ -182,7 +183,7 @@ jobs: uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - name: Workaround missing libcurses.tbd on macOS - if : matrix.os == 'macos-latest' && matrix.llvm != '' + if : (matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel') && matrix.llvm != '' run: | SDK_PATH=$(xcrun --sdk macosx --show-sdk-path) if [ ! -f "$SDK_PATH/usr/lib/libcurses.tbd" ] && [ -f "$SDK_PATH/usr/lib/libncurses.tbd" ]; then From 86fa059ac2ca2534e6399c29071eb00c13ef0056 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 22:13:35 +0000 Subject: [PATCH 02/15] fix(ci): use brew for all LLVM versions on macos-15-intel KyleMayes/install-llvm-action@v2 doesn't support all LLVM versions on macOS Intel x64 (unsupported for 17.0, 18.1; segfault for 15.0). Switch to Homebrew for all LLVM < 19.1 on macos-15-intel, matching the approach already used for LLVM >= 19.1. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4530f70..068dc60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,9 +162,15 @@ jobs: echo "LLVM_PATH=/usr/lib/llvm-${{ matrix.llvm[3] }}" >> $GITHUB_ENV - name: Install LLVM and Clang (LLVM < 19.1) uses: KyleMayes/install-llvm-action@v2 - if : matrix.llvm != '' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel' || matrix.os == 'ubuntu-24.04-arm') && (matrix.llvm[0] < '19.1') + if : matrix.llvm != '' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' || matrix.os == 'ubuntu-24.04-arm') && (matrix.llvm[0] < '19.1') with: version: ${{ matrix.llvm[0] }} + - name: Install LLVM and Clang (macos-15-intel, LLVM < 19.1) + if : matrix.llvm != '' && matrix.os == 'macos-15-intel' && (matrix.llvm[0] < '19.1') + run: | + LLVM_MAJOR=$(echo '${{ matrix.llvm[0] }}' | cut -d. -f1) + brew install llvm@${LLVM_MAJOR} + echo "LLVM_PATH=$(brew --prefix llvm@${LLVM_MAJOR})" >> $GITHUB_ENV - name: Set LLVM_SYS_XXX_DIR and CC, CXX environment variables if : matrix.llvm != '' run: | From ccaf2d2fdc510e638c2f4dc5969b39881ca7f4fe Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 22:24:37 +0000 Subject: [PATCH 03/15] fix(ci): tolerate brew link failures on macos-15-intel Homebrew install may fail during the post-install link step due to conflicts (e.g. python@3.12 as a dependency of llvm@16). The formula is still installed and usable even if linking fails. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 068dc60..08d509a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,7 @@ jobs: - name: Install LLVM and Clang (LLVM >= 19.1 & macos) if : matrix.llvm != '' && (matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel') && (matrix.llvm[0] >= '19.1') run: | - brew install llvm@${{ matrix.llvm[3] }} + brew install llvm@${{ matrix.llvm[3] }} || true echo "LLVM_PATH=$(brew --prefix llvm@${{ matrix.llvm[3] }})" >> $GITHUB_ENV - name: Install LLVM and Clang (LLVM >= 19.1 & ubuntu) if : matrix.llvm != '' && (matrix.os == 'ubuntu-latest'|| matrix.os == 'ubuntu-24.04-arm') && (matrix.llvm[0] >= '19.1') @@ -169,7 +169,7 @@ jobs: if : matrix.llvm != '' && matrix.os == 'macos-15-intel' && (matrix.llvm[0] < '19.1') run: | LLVM_MAJOR=$(echo '${{ matrix.llvm[0] }}' | cut -d. -f1) - brew install llvm@${LLVM_MAJOR} + brew install llvm@${LLVM_MAJOR} || true echo "LLVM_PATH=$(brew --prefix llvm@${LLVM_MAJOR})" >> $GITHUB_ENV - name: Set LLVM_SYS_XXX_DIR and CC, CXX environment variables if : matrix.llvm != '' From aeb8c1696909411c8d91f86c73a979100d203d7c Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 22:58:52 +0000 Subject: [PATCH 04/15] fix: support x86_64 MachO relocations for Intel macOS The generic x86 relocation handler (handle_relocation_generic_x86) works with the object crate's abstracted RelocationKind interface, which is format-agnostic. This enables JIT compilation on Intel macOS where ARCH=x86_64 and the format is MachO. --- diffsl/src/execution/relocations.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index 1e03a62..f658519 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -359,9 +359,7 @@ fn relocation(rela: &Relocation, s: *const u8, p: *mut u8) -> Result<()> { r_pcrel, r_length, } => match ARCH { - // TODO: require x86_64 for intel macOS - //"x86" => handle_relocation_generic_x86(rela, s, p), - //"x86_64" => handle_relocation_generic_x86(rela, s, p), + "x86" | "x86_64" => handle_relocation_generic_x86(rela, s, p), "aarch64" => handle_relocation_macho_aarch64(s, a, p, r_type, r_pcrel, r_length), _ => Err(anyhow!( "Unsupported architecture {} for MachO relocations", From 1af619e082349e45f626cf90c5d4d856c9009f1b Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 23:16:34 +0000 Subject: [PATCH 05/15] fix: handle Section relocation targets on x86_64 MachO On Intel macOS, LLVM generates section-targeted relocations (not just symbol-targeted ones). Fix is_jump_table_entry to only classify external symbols as jump entries, and extend handle_relocation to resolve Section targets via their loaded section address. --- diffsl/src/execution/relocations.rs | 60 +++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index f658519..a2402ea 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -125,10 +125,16 @@ pub(crate) fn relocation_target_section<'file, 'data>( } pub(crate) fn is_jump_table_entry(file: &File<'_>, rela: &Relocation) -> bool { - // any relocation that is not a local symbol and with a size smaller than the + // any relocation for an external symbol (no section) with a size smaller than the // architecture's pointer size is a jump table entry - relocation_target_section(file, rela).is_none() - && (rela.size() < u8::try_from(std::mem::size_of::() * 8).unwrap()) + match rela.target() { + RelocationTarget::Symbol(symbol_index) => { + let symbol = file.symbol_by_index(symbol_index).unwrap(); + symbol.section_index().is_none() + && (rela.size() < u8::try_from(std::mem::size_of::() * 8).unwrap()) + } + _ => false, + } } fn handle_relocation_generic_x86(rela: &Relocation, s: *const u8, p: *mut u8) -> Result<()> { @@ -398,29 +404,35 @@ pub(crate) fn handle_relocation( p: *mut u8, mapped_sections: &HashMap, ) -> Result<()> { - let symbol_index = match rela.target() { - RelocationTarget::Symbol(s) => s, - _ => Err(anyhow!( - "Only relocation targets that are symbols are supported" - ))?, - }; - let symbol = file.symbol_by_index(symbol_index).unwrap(); - let s = match symbol.section_index() { - Some(section_index) => { - let section = file.section_by_index(section_index).unwrap(); - let section_name = section.name().expect("Could not get section name"); - let section_ptr = mapped_sections[section_name].as_ptr(); - let offset = symbol_offset(file, &symbol, §ion)?; - unsafe { section_ptr.offset(offset) } + let s = match rela.target() { + RelocationTarget::Symbol(symbol_index) => { + let symbol = file.symbol_by_index(symbol_index).unwrap(); + match symbol.section_index() { + Some(section_index) => { + let section = file.section_by_index(section_index).unwrap(); + let section_name = section.name().expect("Could not get section name"); + let section_ptr = mapped_sections[section_name].as_ptr(); + let offset = symbol_offset(file, &symbol, §ion)?; + unsafe { section_ptr.offset(offset) } + } + None => { + // must be an external function call generate the jump table entry + // return an Err if the function is not found + function_resolver(symbol.name().unwrap()).ok_or(anyhow!( + "Could not resolve function {}", + symbol.name().unwrap() + ))? + } + } } - None => { - // must be an external function call generate the jump table entry - // return an Err if the function is not found - function_resolver(symbol.name().unwrap()).ok_or(anyhow!( - "Could not resolve function {}", - symbol.name().unwrap() - ))? + RelocationTarget::Section(section_index) => { + let section = file.section_by_index(section_index).unwrap(); + let section_name = section.name().unwrap(); + mapped_sections[section_name].as_ptr() } + _ => Err(anyhow!( + "Only relocation targets that are symbols or sections are supported" + ))?, }; relocation(rela, s, p) } From 0e1e1c9c80e7917a58bb9dd99b81b86a51ccea7d Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Tue, 30 Jun 2026 23:42:52 +0000 Subject: [PATCH 06/15] fix: map sections targeted by Section-relocations on x86_64 MachO On Intel macOS, LLVM generates relocations targeting sections directly (via RelocationTarget::Section). Extend the section mapping scan to discover these sections so they're available in mapped_sections when handle_relocation resolves them. --- diffsl/src/execution/object/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/diffsl/src/execution/object/mod.rs b/diffsl/src/execution/object/mod.rs index e247ec7..49a6d6c 100644 --- a/diffsl/src/execution/object/mod.rs +++ b/diffsl/src/execution/object/mod.rs @@ -9,7 +9,7 @@ use super::{ symbol_offset, JumpTableEntry, }, }; -use object::{Object, ObjectSection, ObjectSymbol, SectionKind}; +use object::{Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionKind}; pub struct ObjectModule { object_buffer: Vec, @@ -56,6 +56,14 @@ impl CodegenModuleLink for ObjectModule { } let section_name = section.name().expect("Could not get section name"); acc.insert(section_name, section.index()); + } else if let RelocationTarget::Section(section_index) = rela.target() { + // on x86_64 MachO, some relocations target sections directly + let section = file.section_by_index(section_index).unwrap(); + if section.index() == text_sec.index() { + return acc; + } + let section_name = section.name().unwrap(); + acc.insert(section_name, section.index()); } if is_jump_table_entry(&file, &rela) { // this is a jump table entry From 3d1931693e27eb71aa56ccb80e74df247874f8b5 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 00:16:28 +0000 Subject: [PATCH 07/15] debug: add eprintln for section-based relocations --- diffsl/src/execution/relocations.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index a2402ea..b50425c 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -428,7 +428,17 @@ pub(crate) fn handle_relocation( RelocationTarget::Section(section_index) => { let section = file.section_by_index(section_index).unwrap(); let section_name = section.name().unwrap(); - mapped_sections[section_name].as_ptr() + let s = mapped_sections[section_name].as_ptr(); + eprintln!( + "section reloc: target={}, kind={:?}, addend={:#x}, size={}, s={:p}, p={:p}", + section_name, + rela.kind(), + rela.addend(), + rela.size(), + s, + p + ); + s } _ => Err(anyhow!( "Only relocation targets that are symbols or sections are supported" From c8572dc34edc3c99f6c12fae7d5eb09bbe02d536 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 00:39:28 +0000 Subject: [PATCH 08/15] fix: use correct addend handling for section-based relocations on x86_64 Section-based relocations on x86_64 MachO have the addend already encoded as the PC-relative displacement in file space. Since all sections are mapped contiguously in a single mmap, the relative positions are preserved, so the addend needs no adjustment for Relative/PltRelative kinds. For Absolute, compute section_ptr + addend. This fixes the wrong values produced by the generic handler which incorrectly applied S - P to already-correct PC-relative addends. --- diffsl/src/execution/relocations.rs | 57 +++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index b50425c..b08438b 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -137,6 +137,50 @@ pub(crate) fn is_jump_table_entry(file: &File<'_>, rela: &Relocation) -> bool { } } +fn handle_section_relocation(rela: &Relocation, section_ptr: *const u8, p: *mut u8) -> Result<()> { + let a = rela.addend(); + let size = rela.size(); + let val = match rela.kind() { + // Absolute: addend is the offset within the section. + // S + A where S = section_ptr + RelocationKind::Absolute => i64::try_from(section_ptr as usize).unwrap() + a, + // Relative / PltRelative: the addend is already the PC-relative + // displacement in file space. Since all sections are mapped + // contiguously in a single mmap, the relative positions are + // preserved, and no adjustment is needed. + RelocationKind::Relative | RelocationKind::PltRelative => a, + _ => { + return Err(anyhow!( + "Unsupported relocation type {:?} for section-based x86 relocation", + rela.kind() + )) + } + }; + match size { + 16 => unsafe { + (p as *mut i16).write_unaligned(i16::try_from(val).map_err(|_| { + anyhow!( + "x86 section relocation overflow for {:?} {:?}-bit relocation: value {val:#x} does not fit in i16", + rela.kind(), + size + ) + })?) + }, + 32 => unsafe { + (p as *mut i32).write_unaligned(i32::try_from(val).map_err(|_| { + anyhow!( + "x86 section relocation overflow for {:?} {:?}-bit relocation: value {val:#x} does not fit in i32", + rela.kind(), + size + ) + })?) + }, + 64 => unsafe { (p as *mut i64).write_unaligned(val) }, + _ => return Err(anyhow!("Unsupported relocation size {:?}", size)), + } + Ok(()) +} + fn handle_relocation_generic_x86(rela: &Relocation, s: *const u8, p: *mut u8) -> Result<()> { let a = rela.addend(); let size = rela.size(); @@ -428,17 +472,8 @@ pub(crate) fn handle_relocation( RelocationTarget::Section(section_index) => { let section = file.section_by_index(section_index).unwrap(); let section_name = section.name().unwrap(); - let s = mapped_sections[section_name].as_ptr(); - eprintln!( - "section reloc: target={}, kind={:?}, addend={:#x}, size={}, s={:p}, p={:p}", - section_name, - rela.kind(), - rela.addend(), - rela.size(), - s, - p - ); - s + let section_ptr = mapped_sections[section_name].as_ptr(); + return handle_section_relocation(rela, section_ptr, p); } _ => Err(anyhow!( "Only relocation targets that are symbols or sections are supported" From 437185316dfa612c1512382abddd7cd7ebd9429c Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 07:17:36 +0000 Subject: [PATCH 09/15] fix: add Got, GotRelative, GotBaseRelative, GotBaseOffset relocation kinds These are generated by LLVM on x86_64 macOS when using the PIC relocation model. Treat Got/GotBaseOffset like Absolute (S+A) and GotRelative/GotBaseRelative like Relative (S+A-P, or addend-as-is for section-based). --- diffsl/src/execution/relocations.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index b08438b..ac1b82f 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -141,14 +141,19 @@ fn handle_section_relocation(rela: &Relocation, section_ptr: *const u8, p: *mut let a = rela.addend(); let size = rela.size(); let val = match rela.kind() { - // Absolute: addend is the offset within the section. + // Absolute / Got / GotBaseOffset: addend is the offset within the section. // S + A where S = section_ptr - RelocationKind::Absolute => i64::try_from(section_ptr as usize).unwrap() + a, - // Relative / PltRelative: the addend is already the PC-relative - // displacement in file space. Since all sections are mapped - // contiguously in a single mmap, the relative positions are + RelocationKind::Absolute | RelocationKind::Got | RelocationKind::GotBaseOffset => { + i64::try_from(section_ptr as usize).unwrap() + a + } + // Relative / PltRelative / GotRelative / GotBaseRelative: the addend is + // already the PC-relative displacement in file space. Since all sections + // are mapped contiguously in a single mmap, the relative positions are // preserved, and no adjustment is needed. - RelocationKind::Relative | RelocationKind::PltRelative => a, + RelocationKind::Relative + | RelocationKind::PltRelative + | RelocationKind::GotRelative + | RelocationKind::GotBaseRelative => a, _ => { return Err(anyhow!( "Unsupported relocation type {:?} for section-based x86 relocation", @@ -186,9 +191,16 @@ fn handle_relocation_generic_x86(rela: &Relocation, s: *const u8, p: *mut u8) -> let size = rela.size(); let val = match rela.kind() { // S + A - RelocationKind::Absolute => i64::try_from(s as usize).unwrap() + a, + RelocationKind::Absolute + | RelocationKind::Got + | RelocationKind::GotBaseOffset => { + i64::try_from(s as usize).unwrap() + a + } // S + A - P - RelocationKind::Relative | RelocationKind::PltRelative => { + RelocationKind::Relative + | RelocationKind::PltRelative + | RelocationKind::GotRelative + | RelocationKind::GotBaseRelative => { i64::try_from(s as usize).unwrap() + a - i64::try_from(p as usize).unwrap() } _ => { From fec6694c36fce1450099790dc35e3ae5b247bc64 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 08:08:18 +0000 Subject: [PATCH 10/15] Revert "fix: add Got, GotRelative, GotBaseRelative, GotBaseOffset relocation kinds" This reverts commit 437185316dfa612c1512382abddd7cd7ebd9429c. --- diffsl/src/execution/relocations.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index ac1b82f..b08438b 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -141,19 +141,14 @@ fn handle_section_relocation(rela: &Relocation, section_ptr: *const u8, p: *mut let a = rela.addend(); let size = rela.size(); let val = match rela.kind() { - // Absolute / Got / GotBaseOffset: addend is the offset within the section. + // Absolute: addend is the offset within the section. // S + A where S = section_ptr - RelocationKind::Absolute | RelocationKind::Got | RelocationKind::GotBaseOffset => { - i64::try_from(section_ptr as usize).unwrap() + a - } - // Relative / PltRelative / GotRelative / GotBaseRelative: the addend is - // already the PC-relative displacement in file space. Since all sections - // are mapped contiguously in a single mmap, the relative positions are + RelocationKind::Absolute => i64::try_from(section_ptr as usize).unwrap() + a, + // Relative / PltRelative: the addend is already the PC-relative + // displacement in file space. Since all sections are mapped + // contiguously in a single mmap, the relative positions are // preserved, and no adjustment is needed. - RelocationKind::Relative - | RelocationKind::PltRelative - | RelocationKind::GotRelative - | RelocationKind::GotBaseRelative => a, + RelocationKind::Relative | RelocationKind::PltRelative => a, _ => { return Err(anyhow!( "Unsupported relocation type {:?} for section-based x86 relocation", @@ -191,16 +186,9 @@ fn handle_relocation_generic_x86(rela: &Relocation, s: *const u8, p: *mut u8) -> let size = rela.size(); let val = match rela.kind() { // S + A - RelocationKind::Absolute - | RelocationKind::Got - | RelocationKind::GotBaseOffset => { - i64::try_from(s as usize).unwrap() + a - } + RelocationKind::Absolute => i64::try_from(s as usize).unwrap() + a, // S + A - P - RelocationKind::Relative - | RelocationKind::PltRelative - | RelocationKind::GotRelative - | RelocationKind::GotBaseRelative => { + RelocationKind::Relative | RelocationKind::PltRelative => { i64::try_from(s as usize).unwrap() + a - i64::try_from(p as usize).unwrap() } _ => { From e0b5781127584502230e02cb8d94d5f75516d233 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 08:11:22 +0000 Subject: [PATCH 11/15] fix: correct PC-relative displacement for MachO x86_64 jump table entries The generic handler's S+A-P formula is wrong for MachO x86_64 because the implicit addend already contains the file-space PC-relative displacement. Compute the correct displacement as: A + (jump_ptr - mmap_base) where mmap_base = text_runtime - text_file_address. This prevents SIGSEGV when JIT code calls external functions via jump table entries on Intel macOS. --- diffsl/src/execution/object/mod.rs | 2 +- diffsl/src/execution/relocations.rs | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/diffsl/src/execution/object/mod.rs b/diffsl/src/execution/object/mod.rs index 49a6d6c..cfd7f7c 100644 --- a/diffsl/src/execution/object/mod.rs +++ b/diffsl/src/execution/object/mod.rs @@ -132,7 +132,7 @@ impl CodegenModuleLink for ObjectModule { let patch_ptr = unsafe { text_ptr.offset(offset as isize) }; if is_jump_table_entry(&file, &rela) { let jumptable_entry = &mut jumptable.as_mut().unwrap()[jumptable_idx]; - handle_jump_entry(&file, &rela, patch_ptr, jumptable_entry)?; + handle_jump_entry(&file, &rela, patch_ptr, jumptable_entry, &mapped_sections, &text_sec)?; jumptable_idx += 1; } else { handle_relocation(&file, &rela, patch_ptr, &mapped_sections)?; diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index b08438b..f54cda9 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -487,6 +487,8 @@ pub(crate) fn handle_jump_entry( rela: &Relocation, p: *mut u8, jumptable_entry: &mut JumpTableEntry, + mapped_sections: &HashMap, + text_sec: &Section<'_, '_>, ) -> Result<()> { let symbol_index = match rela.target() { RelocationTarget::Symbol(s) => s, @@ -501,5 +503,26 @@ pub(crate) fn handle_jump_entry( .ok_or(anyhow!("Could not resolve function {}", symbol_name))?; *jumptable_entry = JumpTableEntry::new(addr); let s = jumptable_entry.jump_ptr(); - relocation(rela, s, p) + match file.format() { + BinaryFormat::MachO => { + // For MachO x86_64, the addend A already contains the file-space + // PC-relative displacement: target_file - (p_file + instr_len). + // Since target_file = 0 for external symbols, A = -(p_file + instr_len). + // The correct runtime displacement is: s - (p_runtime + instr_len). + // This equals: A + (s - mmap_base), where mmap_base = text_runtime - text_file. + let text_runtime = mapped_sections[text_sec.name().unwrap()].as_ptr(); + let mmap_base = text_runtime as i64 - text_sec.address() as i64; + let a = rela.addend(); + let val = a + (s as i64 - mmap_base); + let size = rela.size(); + match size { + 32 => unsafe { (p as *mut i32).write_unaligned(i32::try_from(val).map_err(|_| { + anyhow!("x86 jump entry relocation overflow: val {val:#x} does not fit in i32") + })?) }, + _ => return Err(anyhow!("Unsupported relocation size {:?} for jump entry", size)), + } + } + _ => relocation(rela, s, p), + } + Ok(()) } From f8ce328f5fc5dd3de9644f6b5847e368e514c03a Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 08:43:57 +0000 Subject: [PATCH 12/15] style: rustfmt --- diffsl/src/execution/object/mod.rs | 9 ++++++++- diffsl/src/execution/relocations.rs | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/diffsl/src/execution/object/mod.rs b/diffsl/src/execution/object/mod.rs index cfd7f7c..5bd4d41 100644 --- a/diffsl/src/execution/object/mod.rs +++ b/diffsl/src/execution/object/mod.rs @@ -132,7 +132,14 @@ impl CodegenModuleLink for ObjectModule { let patch_ptr = unsafe { text_ptr.offset(offset as isize) }; if is_jump_table_entry(&file, &rela) { let jumptable_entry = &mut jumptable.as_mut().unwrap()[jumptable_idx]; - handle_jump_entry(&file, &rela, patch_ptr, jumptable_entry, &mapped_sections, &text_sec)?; + handle_jump_entry( + &file, + &rela, + patch_ptr, + jumptable_entry, + &mapped_sections, + &text_sec, + )?; jumptable_idx += 1; } else { handle_relocation(&file, &rela, patch_ptr, &mapped_sections)?; diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index f54cda9..9e017f5 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -516,10 +516,19 @@ pub(crate) fn handle_jump_entry( let val = a + (s as i64 - mmap_base); let size = rela.size(); match size { - 32 => unsafe { (p as *mut i32).write_unaligned(i32::try_from(val).map_err(|_| { - anyhow!("x86 jump entry relocation overflow: val {val:#x} does not fit in i32") - })?) }, - _ => return Err(anyhow!("Unsupported relocation size {:?} for jump entry", size)), + 32 => unsafe { + (p as *mut i32).write_unaligned(i32::try_from(val).map_err(|_| { + anyhow!( + "x86 jump entry relocation overflow: val {val:#x} does not fit in i32" + ) + })?) + }, + _ => { + return Err(anyhow!( + "Unsupported relocation size {:?} for jump entry", + size + )) + } } } _ => relocation(rela, s, p), From 4ada508e04a1cc7f94ae825e09a1271b952352bc Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 09:17:28 +0000 Subject: [PATCH 13/15] fix: resolve match arm type mismatch in handle_jump_entry --- diffsl/src/execution/relocations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index 9e017f5..2cc6881 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -530,8 +530,8 @@ pub(crate) fn handle_jump_entry( )) } } + Ok(()) } _ => relocation(rela, s, p), } - Ok(()) } From f2884b5b213a1cdb47c0f91ba608994a8a0c74a3 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 09:37:48 +0000 Subject: [PATCH 14/15] fix: correct Absolute formula for section-based relocations on MachO The addend for X86_64_RELOC_UNSIGNED is the file-absolute address of the target. The correct runtime address is: addend + (section_runtime - section_file_addr) Not section_runtime + addend, which doubly adds section_file_addr. --- diffsl/src/execution/relocations.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index 2cc6881..17fca77 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -137,13 +137,21 @@ pub(crate) fn is_jump_table_entry(file: &File<'_>, rela: &Relocation) -> bool { } } -fn handle_section_relocation(rela: &Relocation, section_ptr: *const u8, p: *mut u8) -> Result<()> { +fn handle_section_relocation( + rela: &Relocation, + section_ptr: *const u8, + section_file_addr: u64, + p: *mut u8, +) -> Result<()> { let a = rela.addend(); let size = rela.size(); let val = match rela.kind() { - // Absolute: addend is the offset within the section. - // S + A where S = section_ptr - RelocationKind::Absolute => i64::try_from(section_ptr as usize).unwrap() + a, + // Absolute: the addend is the file-absolute address of the target. + // The correct runtime address is: addend + (section_runtime - section_file_addr) + RelocationKind::Absolute => { + let section_runtime = i64::try_from(section_ptr as usize).unwrap(); + a + (section_runtime - section_file_addr as i64) + } // Relative / PltRelative: the addend is already the PC-relative // displacement in file space. Since all sections are mapped // contiguously in a single mmap, the relative positions are @@ -473,7 +481,7 @@ pub(crate) fn handle_relocation( let section = file.section_by_index(section_index).unwrap(); let section_name = section.name().unwrap(); let section_ptr = mapped_sections[section_name].as_ptr(); - return handle_section_relocation(rela, section_ptr, p); + return handle_section_relocation(rela, section_ptr, section.address(), p); } _ => Err(anyhow!( "Only relocation targets that are symbols or sections are supported" From e92b2117f04b333110a7461a35984dc86508443a Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 1 Jul 2026 09:45:53 +0000 Subject: [PATCH 15/15] fix: restrict MachO jump entry fix to x86_64 only Aarch64 uses 26-bit branch displacements, not 32-bit. Delegate back to the aarch64-specific relocation handler for non-x86_64. --- diffsl/src/execution/relocations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diffsl/src/execution/relocations.rs b/diffsl/src/execution/relocations.rs index 17fca77..414a75e 100644 --- a/diffsl/src/execution/relocations.rs +++ b/diffsl/src/execution/relocations.rs @@ -511,8 +511,8 @@ pub(crate) fn handle_jump_entry( .ok_or(anyhow!("Could not resolve function {}", symbol_name))?; *jumptable_entry = JumpTableEntry::new(addr); let s = jumptable_entry.jump_ptr(); - match file.format() { - BinaryFormat::MachO => { + match (file.format(), ARCH) { + (BinaryFormat::MachO, "x86_64") | (BinaryFormat::MachO, "x86") => { // For MachO x86_64, the addend A already contains the file-space // PC-relative displacement: target_file - (p_file + instr_len). // Since target_file = 0 for external symbols, A = -(p_file + instr_len).