forked from factor/factor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathc_api.zig
More file actions
642 lines (530 loc) · 22.5 KB
/
Copy pathc_api.zig
File metadata and controls
642 lines (530 loc) · 22.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
// c_api.zig - C API functions exported for Factor compiled code
// These functions are called by Factor code via dlsym relocations
//
// Factor's compiled code expects these functions to be available at runtime.
// They're called via dlsym lookups from the code heap.
const std = @import("std");
const bignum = @import("bignum.zig");
const code_blocks = @import("code_blocks.zig");
const builtin = @import("builtin");
const contexts = @import("contexts.zig");
const image = @import("image.zig");
const inline_cache = @import("inline_cache.zig");
const layouts = @import("layouts.zig");
const fixnum = @import("fixnum.zig");
const vm_mod = @import("vm.zig");
const Cell = layouts.Cell;
const Context = contexts.Context;
const FactorVM = vm_mod.FactorVM;
const Fixnum = layouts.Fixnum;
const VMAssemblyFields = vm_mod.VMAssemblyFields;
const PTHREAD_CANCEL_ASYNCHRONOUS = 0; // macOS value (2 is DEFERRED)
pub extern "c" fn pthread_setcanceltype(ty: c_int, old_ty: ?*c_int) c_int;
// Fixnum range (60 bits on 64-bit systems, accounting for 4-bit tag)
const fixnum_min: Fixnum = std.math.minInt(Fixnum) >> @intCast(layouts.tag_bits);
const fixnum_max: Fixnum = std.math.maxInt(Fixnum) >> @intCast(layouts.tag_bits);
// Force the linker to retain all exported functions that Factor resolves via dlsym.
// Without this, Release builds eliminate these "unused" functions via dead code elimination,
// causing dlsym failures at runtime. The exported symbol table is referenced from main.zig.
// Global VM pointer for C callbacks
var global_vm: ?*FactorVM = null;
// Global Io interface for file operations (set once in main)
pub var global_io: std.Io = undefined;
pub var global_io_initialized: bool = false;
// ============================================================================
// Stdin Handling (Unix)
// ============================================================================
/// Pipe for stdin data: stdin_thread reads from fd 0, writes to stdin_write; Factor reads from stdin_read
pub export var stdin_read: c_int = -1;
pub export var stdin_write: c_int = -1;
/// Pipe for control: Factor writes 'X' to control_write to request a read; stdin_thread reads from control_read
pub export var control_read: c_int = -1;
pub export var control_write: c_int = -1;
/// Pipe for size: stdin_thread writes byte count to size_write after reading; Factor reads from size_read
pub export var size_read: c_int = -1;
pub export var size_write: c_int = -1;
/// Whether the stdin thread has been initialized
pub export var stdin_thread_initialized_p: bool = false;
// Stdin thread handle
var stdin_thread: ?std.Thread = null;
// Native pthread handle for sending signals
var stdin_pthread: std.c.pthread_t = undefined;
const SpinMutex = @import("mutex.zig").SpinMutex;
var stdin_mutex: SpinMutex = .{};
// Flag to signal stdin thread to stop
var stdin_loop_running: std.atomic.Value(bool) = std.atomic.Value(bool).init(true);
// The stdin loop function that runs in a background thread.
fn stdinLoop() void {
// Set up signal mask: block all signals except SIGUSR2, SIGTTIN, SIGTERM, SIGQUIT
var mask: std.c.sigset_t = undefined;
_ = std.c.sigfillset(&mask);
_ = std.c.sigdelset(&mask, .USR2);
_ = std.c.sigdelset(&mask, .TTIN);
_ = std.c.sigdelset(&mask, .TERM);
_ = std.c.sigdelset(&mask, .QUIT);
var old_mask = std.posix.sigemptyset();
_ = std.c.pthread_sigmask(std.c.SIG.SETMASK, &mask, &old_mask);
// Enable async cancellation so pthread_cancel can interrupt blocking reads
_ = std.c.pthread_setcancelstate(.ENABLE, null);
_ = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, null);
var buf: [4096]u8 = undefined;
while (stdin_loop_running.load(.acquire)) {
// Wait for control signal ('X') from Factor
var control_buf: [1]u8 = undefined;
const ctrl_result = std.c.read(@intCast(control_read), &control_buf, control_buf.len);
if (ctrl_result < 0) {
if (std.c._errno().* == @intFromEnum(std.posix.E.INTR)) continue;
break;
}
const ctrl_bytes: usize = @intCast(ctrl_result);
if (ctrl_bytes == 0) break;
if (control_buf[0] != 'X') break;
// Read from actual stdin (fd 0)
while (stdin_loop_running.load(.acquire)) {
stdin_mutex.lock();
stdin_mutex.unlock();
const read_result = std.c.read(0, &buf, buf.len);
if (read_result < 0) {
if (std.c._errno().* == @intFromEnum(std.posix.E.INTR)) continue;
stdin_loop_running.store(false, .release);
break;
}
const bytes_read: usize = @intCast(read_result);
// Write the byte count to size_write (as a native integer)
var size_val: isize = @intCast(bytes_read);
const size_bytes: [*]const u8 = @ptrCast(&size_val);
if (std.c.write(@intCast(size_write), size_bytes, @sizeOf(isize)) < 0) {
stdin_loop_running.store(false, .release);
break;
}
// Write the data to stdin_write
if (bytes_read > 0) {
if (std.c.write(@intCast(stdin_write), buf[0..bytes_read].ptr, bytes_read) < 0) {
stdin_loop_running.store(false, .release);
break;
}
}
break; // Go back to waiting for next control signal
}
}
// Close our ends of the pipes
_ = std.c.close(@intCast(stdin_write));
_ = std.c.close(@intCast(control_read));
}
fn safePipe() ?[2]c_int {
var filedes: [2]c_int = undefined;
if (std.c.pipe(&filedes) < 0) {
return null;
}
const F_SETFD: c_int = 2;
const FD_CLOEXEC: c_int = 1;
if (std.c.fcntl(filedes[0], F_SETFD, FD_CLOEXEC) < 0) {
_ = std.c.close(filedes[0]);
_ = std.c.close(filedes[1]);
return null;
}
if (std.c.fcntl(filedes[1], F_SETFD, FD_CLOEXEC) < 0) {
_ = std.c.close(filedes[0]);
_ = std.c.close(filedes[1]);
return null;
}
return filedes;
}
/// Initialize stdin handling pipes
pub export fn open_console() callconv(.c) void {
if (stdin_thread_initialized_p) return;
const control_pipe = safePipe() orelse return;
const size_pipe = safePipe() orelse {
_ = std.c.close(control_pipe[0]);
_ = std.c.close(control_pipe[1]);
return;
};
const stdin_pipe = safePipe() orelse {
_ = std.c.close(control_pipe[0]);
_ = std.c.close(control_pipe[1]);
_ = std.c.close(size_pipe[0]);
_ = std.c.close(size_pipe[1]);
return;
};
control_read = control_pipe[0];
control_write = control_pipe[1];
size_read = size_pipe[0];
size_write = size_pipe[1];
stdin_read = stdin_pipe[0];
stdin_write = stdin_pipe[1];
// Start the stdin thread
stdin_loop_running.store(true, .release);
const thread = std.Thread.spawn(.{}, stdinLoop, .{}) catch {
_ = std.c.close(control_pipe[0]);
_ = std.c.close(control_pipe[1]);
_ = std.c.close(size_pipe[0]);
_ = std.c.close(size_pipe[1]);
_ = std.c.close(stdin_pipe[0]);
_ = std.c.close(stdin_pipe[1]);
control_read = -1;
control_write = -1;
size_read = -1;
size_write = -1;
stdin_read = -1;
stdin_write = -1;
return;
};
stdin_thread = thread;
stdin_pthread = thread.getHandle();
stdin_thread_initialized_p = true;
}
/// Close stdin handling pipes and stop the thread
pub export fn close_console() callconv(.c) void {
if (!stdin_thread_initialized_p) return;
// Signal thread to stop
stdin_loop_running.store(false, .release);
// Close control_write to unblock the thread's read on control_read
if (control_write >= 0) {
_ = std.c.close(control_write);
control_write = -1;
}
// Close stdin_write to unblock any read on stdin_read
if (stdin_write >= 0) {
_ = std.c.close(stdin_write);
stdin_write = -1;
}
// Cancel the thread and wait for it to exit
if (stdin_thread != null) {
_ = std.c.pthread_cancel(stdin_pthread);
_ = std.c.pthread_join(stdin_pthread, null);
stdin_thread = null;
}
// Close remaining pipes
if (stdin_read >= 0) _ = std.c.close(stdin_read);
if (size_read >= 0) _ = std.c.close(size_read);
if (size_write >= 0) _ = std.c.close(size_write);
stdin_read = -1;
stdin_write = -1;
control_read = -1;
size_read = -1;
size_write = -1;
stdin_thread_initialized_p = false;
}
/// Lock the console - used by FEP (debugger) to interrupt stdin reads
pub export fn lock_console() callconv(.c) void {
if (!stdin_thread_initialized_p) return;
stdin_mutex.lock();
_ = std.c.pthread_kill(stdin_pthread, .USR2);
}
/// Unlock the console - allows stdin reads to resume
pub export fn unlock_console() callconv(.c) void {
if (!stdin_thread_initialized_p) return;
stdin_mutex.unlock();
}
pub fn setGlobalVM(vm: *FactorVM) void {
global_vm = vm;
}
// ============================================================================
// Context Management
// ============================================================================
/// Called when entering Factor from C (callback entry point)
pub export fn begin_callback(vm_asm: *VMAssemblyFields, quot: Cell) callconv(.c) Cell {
const parent = vm_asm.getVM();
// Root the quotation against GC - initContext allocates, which can trigger GC
var rooted_quot = quot;
parent.data_roots.appendAssumeCapacity(&rooted_quot);
defer _ = parent.data_roots.pop();
// Reset the current context
parent.vm_asm.ctx.reset();
parent.vm_asm.spare_ctx = parent.newContext() catch @panic("OOM");
// Track callback
parent.callback_ids.append(parent.allocator, @intCast(parent.callback_id)) catch @panic("OOM");
parent.callback_id += 1;
parent.initContext(parent.vm_asm.ctx);
return rooted_quot;
}
/// Called when returning from Factor to C (callback exit)
pub export fn end_callback(vm_asm: *VMAssemblyFields) callconv(.c) void {
const parent = vm_asm.getVM();
// Pop callback id
if (parent.callback_ids.items.len > 0) {
_ = parent.callback_ids.pop();
}
// Delete current context and switch to spare
parent.deleteContext();
}
/// Create a new context
pub export fn new_context(vm_asm: *VMAssemblyFields) callconv(.c) ?*Context {
const parent = vm_asm.getVM();
const ctx = parent.newContext() catch return null;
parent.initContext(ctx);
return ctx;
}
/// Delete the current context
pub export fn delete_context(vm_asm: *VMAssemblyFields) callconv(.c) void {
const parent = vm_asm.getVM();
parent.deleteContext();
}
/// Reset the current context
pub export fn reset_context(vm_asm: *VMAssemblyFields) callconv(.c) void {
const parent = vm_asm.getVM();
const ctx = parent.vm_asm.ctx;
const arg1 = ctx.pop();
const arg2 = ctx.pop();
ctx.reset();
ctx.push(arg2);
ctx.push(arg1);
parent.initContext(ctx);
}
// ============================================================================
// JIT and Compilation
// ============================================================================
/// Lazy JIT compile a quotation
pub export fn lazy_jit_compile(quot: Cell, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
const parent = vm_asm.getVM();
const jit_mod = @import("jit.zig");
return jit_mod.lazyJitCompile(parent, quot);
}
// ============================================================================
// Inline Cache
// ============================================================================
/// Handle inline cache miss
pub export fn inline_cache_miss(return_address: Cell, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
const parent = vm_asm.getVM();
return inline_cache.inlineCacheMiss(parent, return_address);
}
// ============================================================================
// Math Operations (overflow handling)
// ============================================================================
/// Handle fixnum addition overflow - promote to bignum.
pub export fn overflow_fixnum_add(x: Fixnum, y: Fixnum, vm_asm: *VMAssemblyFields) callconv(.c) void {
const vm = vm_asm.getVM();
const ux = x >> @intCast(layouts.tag_bits);
const uy = y >> @intCast(layouts.tag_bits);
const sum = ux + uy;
const bn = fixnum.toBignum(vm, sum) catch
@panic("overflow_fixnum_add: out of memory");
vm.replace(layouts.tagBignum(bn));
}
/// Handle fixnum subtraction overflow - promote to bignum.
pub export fn overflow_fixnum_subtract(x: Fixnum, y: Fixnum, vm_asm: *VMAssemblyFields) callconv(.c) void {
const vm = vm_asm.getVM();
const ux = x >> @intCast(layouts.tag_bits);
const uy = y >> @intCast(layouts.tag_bits);
const diff = ux - uy;
const bn = fixnum.toBignum(vm, diff) catch
@panic("overflow_fixnum_subtract: out of memory");
vm.replace(layouts.tagBignum(bn));
}
/// Handle fixnum multiplication overflow - promote to bignum.
pub export fn overflow_fixnum_multiply(x: Fixnum, y: Fixnum, vm_asm: *VMAssemblyFields) callconv(.c) void {
const vm = vm_asm.getVM();
const bx = fixnum.toBignum(vm, x) catch
@panic("overflow_fixnum_multiply: out of memory");
var bx_cell: Cell = layouts.tagBignum(bx);
vm.data_roots.appendAssumeCapacity(&bx_cell);
defer _ = vm.data_roots.pop();
const by = fixnum.toBignum(vm, y) catch
@panic("overflow_fixnum_multiply: out of memory");
var by_cell: Cell = layouts.tagBignum(by);
vm.data_roots.appendAssumeCapacity(&by_cell);
defer _ = vm.data_roots.pop();
const bx_ptr: *const layouts.Bignum = @ptrFromInt(layouts.UNTAG(bx_cell));
const by_ptr: *const layouts.Bignum = @ptrFromInt(layouts.UNTAG(by_cell));
const result = bignum.multiply(vm, bx_ptr, by_ptr) catch
@panic("overflow_fixnum_multiply: out of memory");
vm.replace(layouts.tagBignum(result));
}
// ============================================================================
// Integer Conversion
// ============================================================================
/// Convert signed cell to Factor integer
pub export fn from_signed_cell(integer: Fixnum, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
if (integer >= fixnum_min and integer <= fixnum_max) {
return layouts.tagFixnum(integer);
}
const vm = vm_asm.getVM();
const bn = fixnum.toBignum(vm, integer) catch
@panic("from_signed_cell: out of memory");
return layouts.tagBignum(bn);
}
/// Convert unsigned cell to Factor integer
pub export fn from_unsigned_cell(integer: Cell, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
if (integer <= @as(Cell, @intCast(fixnum_max))) {
return layouts.tagFixnum(@intCast(integer));
}
const vm = vm_asm.getVM();
const signed_val: Fixnum = @bitCast(integer);
if (signed_val >= 0) {
const bn = fixnum.toBignum(vm, signed_val) catch
@panic("from_unsigned_cell: out of memory");
return layouts.tagBignum(bn);
}
// Value has high bit set (> i64 max). Need a 2-digit bignum.
const bn = bignum.allocBignum(vm, 2, false) catch
@panic("from_unsigned_cell: out of memory");
bn.setDigit(0, integer & bignum.DIGIT_MASK);
bn.setDigit(1, integer >> bignum.DIGIT_BITS);
return layouts.tagBignum(bn);
}
/// Convert signed 64-bit to Factor integer
pub export fn from_signed_8(n: i64, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
return from_signed_cell(@intCast(n), vm_asm);
}
/// Convert unsigned 64-bit to Factor integer
pub export fn from_unsigned_8(n: u64, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
return from_unsigned_cell(n, vm_asm);
}
/// Convert signed 32-bit to Factor integer
pub export fn from_signed_4(n: i32, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
return from_signed_cell(n, vm_asm);
}
/// Convert unsigned 32-bit to Factor integer
pub export fn from_unsigned_4(n: u32, vm_asm: *VMAssemblyFields) callconv(.c) Cell {
return from_unsigned_cell(n, vm_asm);
}
// ============================================================================
// Utility Functions
// ============================================================================
// Use std.c._errno() which is already platform-independent
/// Get errno
pub export fn err_no() callconv(.c) c_int {
return std.c._errno().*;
}
/// Set errno
pub export fn set_err_no(err: c_int) callconv(.c) void {
std.c._errno().* = err;
}
/// memcpy wrapper - used by set-callstack inline code
pub export fn factor_memcpy(dst: ?*anyopaque, src: ?*const anyopaque, len: usize) callconv(.c) ?*anyopaque {
const d_ptr = dst orelse return dst;
const s_ptr = src orelse return dst;
const d: [*]u8 = @ptrCast(d_ptr);
const s: [*]const u8 = @ptrCast(s_ptr);
@memcpy(d[0..len], s[0..len]);
return d_ptr;
}
// ============================================================================
// GC Functions
// ============================================================================
/// Trigger minor GC
pub export fn minor_gc(vm_asm: *VMAssemblyFields) callconv(.c) void {
const parent = vm_asm.getVM();
parent.minorGc();
}
/// Trigger full GC
pub export fn full_gc(vm_asm: *VMAssemblyFields) callconv(.c) void {
const parent = vm_asm.getVM();
parent.fullGc();
}
// ============================================================================
// Undefined Symbol Handler
// ============================================================================
/// Called when Factor code tries to call an FFI function that wasn't found.
/// Uses the return address to find which symbol was being called by looking
/// up the code block and scanning its relocation table for RT_DLSYM entries.
pub export fn undefined_symbol() callconv(.c) void {
const vm = global_vm orelse {
std.debug.print("\nFATAL: undefined_symbol called with no global VM\n", .{});
std.process.abort();
};
const ctx = vm.vm_asm.ctx;
const frame = ctx.callstack_top;
// Return address is at frame + FRAME_RETURN_ADDRESS (8 on arm64 where [frame]
// is the saved FP chain, 0 on x86). Reading *(frame) gave the FP chain on arm64.
const return_address: Cell = @as(*const Cell, @ptrFromInt(frame + contexts.FRAME_RETURN_ADDRESS)).*;
// Find the code block and relocation entry
var symbol: Cell = layouts.false_object;
var library: Cell = layouts.false_object;
if (vm.code) |code| {
if (code.codeBlockForAddress(return_address)) |block| {
const dlsym_result = findDlsymRelocation(block, return_address);
symbol = dlsym_result.symbol;
library = dlsym_result.library;
}
}
if (symbol == layouts.false_object) {
std.debug.print("\nFATAL: Cannot find RT_DLSYM at return address 0x{x}\n", .{return_address});
std.debug.print("Entering low-level debugger (factorbug)...\n", .{});
vm.factorbug();
std.process.abort();
}
// Print what symbol is missing
std.debug.print("\nUndefined symbol at 0x{x}: ", .{return_address});
if (layouts.hasTag(symbol, .byte_array)) {
const ba: *const layouts.ByteArray = @ptrFromInt(layouts.UNTAG(symbol));
const cap = layouts.untagFixnum(ba.capacity);
if (cap > 0 and cap < 256) {
const name = ba.data()[0..@intCast(cap)];
std.debug.print("{s}\n", .{name});
} else {
std.debug.print("(capacity={d})\n", .{cap});
}
} else {
std.debug.print("(symbol=0x{x})\n", .{symbol});
}
// Raise a Factor-level error that can be caught
vm.generalError(.undefined_symbol, symbol, library);
}
// Result of finding RT_DLSYM relocation
const DlsymResult = struct {
symbol: Cell,
library: Cell,
};
// Find the RT_DLSYM relocation at or near the given return address
fn findDlsymRelocation(block: *code_blocks.CodeBlock, return_address: Cell) DlsymResult {
var result = DlsymResult{
.symbol = layouts.false_object,
.library = layouts.false_object,
};
const reloc_cell = block.relocation;
if (reloc_cell == 0 or reloc_cell == layouts.false_object) return result;
if (!layouts.hasTag(reloc_cell, .byte_array)) return result;
const reloc_array: *layouts.ByteArray = @ptrFromInt(layouts.UNTAG(reloc_cell));
const capacity = layouts.untagFixnumUnsigned(reloc_array.capacity);
const entry_count = capacity / @sizeOf(code_blocks.RelocationEntry);
// Get parameters array
const params_cell = block.parameters;
if (params_cell == 0 or params_cell == layouts.false_object) return result;
if (!layouts.hasTag(params_cell, .array)) return result;
const params: *layouts.Array = @ptrFromInt(layouts.UNTAG(params_cell));
const params_capacity = layouts.untagFixnumUnsigned(params.capacity);
const entry_point = block.entryPoint();
const entries: [*]const code_blocks.RelocationEntry = @ptrCast(@alignCast(reloc_array.data()));
// Scan relocations for the RT_DLSYM entry of THIS call site. On x86 the reloc
// (call immediate) sits before the return address; on arm64 the symbol pointer
// lives in the trailing literal pool, AFTER the call site, so pointer >=
// return_address. Match the dlsym reloc nearest the return address either way.
var best_offset: Cell = 0;
var best_dist: Cell = std.math.maxInt(Cell);
var best_param_index: Cell = 0;
var found = false;
var param_index: Cell = 0;
for (0..entry_count) |i| {
const entry = entries[i];
const rel_type = entry.getType();
const offset = entry.getOffset();
const param_count = entry.numberOfParameters();
if (rel_type == .dlsym) {
const pointer = entry_point + offset;
if (builtin.cpu.arch == .aarch64) {
const dist = if (pointer >= return_address) pointer - return_address else return_address - pointer;
if (dist < best_dist) {
best_dist = dist;
best_param_index = param_index;
found = true;
}
} else {
if (pointer < return_address and offset > best_offset) {
best_offset = offset;
best_param_index = param_index;
found = true;
}
}
}
param_index += param_count;
}
if (!found) return result;
// Parameters for RT_DLSYM are at [param_index] = symbol, [param_index+1] = library
if (best_param_index + 1 >= params_capacity) return result;
const params_data = params.data();
result.symbol = params_data[best_param_index];
result.library = params_data[best_param_index + 1];
return result;
}
// Tests