This commit is contained in:
agra
2026-05-23 15:41:12 +03:00
parent 4c6c29b299
commit 49b39ba07a
4 changed files with 153 additions and 12 deletions

View File

@@ -275,6 +275,11 @@ pub const LLVMEmitter = struct {
// Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5).
self.emitObjcSelectorInit();
// Pass 2.6: On macOS, chdir to the .app bundle's Resources dir at
// startup so relative asset paths work when Finder/`open`
// launches the binary with CWD=/. Non-bundled binaries no-op.
self.emitMacosBundleChdir();
// Pass 3: Verify typeSizeBytes matches LLVM's ABI sizes
self.verifySizes();
}
@@ -387,6 +392,104 @@ pub const LLVMEmitter = struct {
}
}
/// On macOS, emit a startup helper that chdir's to the .app bundle's
/// `Contents/Resources` directory when the executable lives inside a
/// `.app/Contents/MacOS/` path. Lets relative asset paths like
/// `assets/foo.png` resolve correctly when Finder/`open` launches the
/// binary with CWD=/.
///
/// Bundled binary: strstr finds the marker, chdir succeeds.
/// CLI binary / `sx run`: strstr returns null, the function no-ops.
///
/// The call is injected at the very start of `main()` (matching the
/// pattern used for the Obj-C selector init) rather than registered
/// via `@llvm.global_ctors`, so the ORC JIT path runs it too without
/// special handling.
fn emitMacosBundleChdir(self: *LLVMEmitter) void {
if (!self.target_config.is_aot) return;
if (!self.target_config.isMacOS()) return;
const ptr_ty = self.cached_ptr;
const i32_ty = self.cached_i32;
const i8_ty = self.cached_i8;
const void_ty = self.cached_void;
// Declare libc externs (re-use if already declared).
var ns_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
const ns_ty = c.LLVMFunctionType(i32_ty, &ns_params, 2, 0);
var ns_fn = c.LLVMGetNamedFunction(self.llvm_module, "_NSGetExecutablePath");
if (ns_fn == null) ns_fn = c.LLVMAddFunction(self.llvm_module, "_NSGetExecutablePath", ns_ty);
var chdir_params: [1]c.LLVMTypeRef = .{ptr_ty};
const chdir_ty = c.LLVMFunctionType(i32_ty, &chdir_params, 1, 0);
var chdir_fn = c.LLVMGetNamedFunction(self.llvm_module, "chdir");
if (chdir_fn == null) chdir_fn = c.LLVMAddFunction(self.llvm_module, "chdir", chdir_ty);
var ss_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
const ss_ty = c.LLVMFunctionType(ptr_ty, &ss_params, 2, 0);
var ss_fn = c.LLVMGetNamedFunction(self.llvm_module, "strstr");
if (ss_fn == null) ss_fn = c.LLVMAddFunction(self.llvm_module, "strstr", ss_ty);
var sc_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
const sc_ty = c.LLVMFunctionType(ptr_ty, &sc_params, 2, 0);
var sc_fn = c.LLVMGetNamedFunction(self.llvm_module, "strcpy");
if (sc_fn == null) sc_fn = c.LLVMAddFunction(self.llvm_module, "strcpy", sc_ty);
var no_params: [0]c.LLVMTypeRef = .{};
const ctor_ty = c.LLVMFunctionType(void_ty, &no_params, 0, 0);
const ctor = c.LLVMAddFunction(self.llvm_module, "__sx_macos_bundle_chdir", ctor_ty);
c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage);
const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry");
const found_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "found");
const done_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "done");
c.LLVMPositionBuilderAtEnd(self.builder, entry_bb);
const buf_ty = c.LLVMArrayType2(i8_ty, 1024);
const buf = c.LLVMBuildAlloca(self.builder, buf_ty, "buf");
const bufsize = c.LLVMBuildAlloca(self.builder, i32_ty, "bufsize");
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, 1024, 0), bufsize);
var ns_args: [2]c.LLVMValueRef = .{ buf, bufsize };
_ = c.LLVMBuildCall2(self.builder, ns_ty, ns_fn, &ns_args, 2, "");
const needle = self.emitCStringGlobal("/Contents/MacOS/", "__sx_macos_chdir_needle");
const replacement = self.emitCStringGlobal("/Contents/Resources", "__sx_macos_chdir_replacement");
var ss_args: [2]c.LLVMValueRef = .{ buf, needle };
const p = c.LLVMBuildCall2(self.builder, ss_ty, ss_fn, &ss_args, 2, "p");
const is_null = c.LLVMBuildIsNull(self.builder, p, "is_null");
_ = c.LLVMBuildCondBr(self.builder, is_null, done_bb, found_bb);
c.LLVMPositionBuilderAtEnd(self.builder, found_bb);
var sc_args: [2]c.LLVMValueRef = .{ p, replacement };
_ = c.LLVMBuildCall2(self.builder, sc_ty, sc_fn, &sc_args, 2, "");
var chdir_args: [1]c.LLVMValueRef = .{buf};
_ = c.LLVMBuildCall2(self.builder, chdir_ty, chdir_fn, &chdir_args, 1, "");
_ = c.LLVMBuildBr(self.builder, done_bb);
c.LLVMPositionBuilderAtEnd(self.builder, done_bb);
_ = c.LLVMBuildRetVoid(self.builder);
// Inject a call at the very start of main(). Matches the
// emitObjcSelectorInit pattern so the ORC JIT path picks it up
// without needing `@llvm.global_ctors` plumbing.
const main_fn = c.LLVMGetNamedFunction(self.llvm_module, "main");
if (main_fn != null) {
const main_entry = c.LLVMGetEntryBasicBlock(main_fn);
const first_inst = c.LLVMGetFirstInstruction(main_entry);
if (first_inst != null) {
c.LLVMPositionBuilderBefore(self.builder, first_inst);
} else {
c.LLVMPositionBuilderAtEnd(self.builder, main_entry);
}
var no_args: [0]c.LLVMValueRef = .{};
_ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, "");
}
}
/// Return `{cls_slot, mid_slot}` global pair for the
/// `(name, sig)` literal — created on first lookup, shared across
/// later `#jni_call` sites with the same literal pair. Both