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
|
//! Build-time codegen: scan Lua's `lua.h` and `lauxlib.h` for every
//! `LUA_API` / `LUALIB_API` function declaration, and emit a C source
//! file that takes the address of each into a single `__attribute__
//! ((used))` array.
//!
//! Result: the linker can't drop those functions even under LTO +
//! gc-sections, because they're reachable from the surviving array.
//! Combined with `rdynamic = true` on the executable, every Lua API
//! function ends up in the dynamic symbol table for C rocks (luv.so
//! etc.) to resolve at `dlopen` time.
//!
//! Invoked from `build.zig`:
//!
//! gen-lua-anchor <lua-src-dir> <out-file>
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const arena = init.arena.allocator();
const io = init.io;
var args = init.minimal.args.iterate();
defer args.deinit();
_ = args.next();
const src_dir = args.next() orelse return error.MissingSrcDir;
const out_path = args.next() orelse return error.MissingOutPath;
var names: std.array_list.Managed([]const u8) = .init(arena);
try collectFrom(arena, io, src_dir, "lua.h", &names);
try collectFrom(arena, io, src_dir, "lauxlib.h", &names);
// Deduplicate via a string set.
var seen: std.StringHashMapUnmanaged(void) = .empty;
try seen.ensureTotalCapacity(arena, @intCast(names.items.len * 2));
var unique: std.array_list.Managed([]const u8) = .init(arena);
for (names.items) |n| {
const gop = seen.getOrPutAssumeCapacity(n);
if (!gop.found_existing) try unique.append(n);
}
std.mem.sort([]const u8, unique.items, {}, lessThanString);
var out_file = try std.Io.Dir.cwd().createFile(io, out_path, .{});
defer out_file.close(io);
var buf: [4096]u8 = undefined;
var writer = out_file.writer(io, &buf);
const w = &writer.interface;
try w.writeAll(
\\/* Auto-generated by build/gen_lua_anchor.zig. Do not edit.
\\ *
\\ * Holds a reference to every Lua public API function so the
\\ * linker can't drop them under LTO + gc-sections. C rocks
\\ * loaded at runtime (luv.so etc.) resolve these symbols
\\ * against the host process's symbol table; combined with
\\ * rdynamic on the executable, this keeps the whole API
\\ * surface exported.
\\ */
\\
\\#include "lua.h"
\\#include "lauxlib.h"
\\
\\__attribute__((used, visibility("default")))
\\const void *const _panto_lua_api_anchor[] = {
\\
);
for (unique.items) |n| {
try w.print(" (const void *)&{s},\n", .{n});
}
try w.writeAll("};\n");
try w.flush();
}
fn lessThanString(_: void, a: []const u8, b: []const u8) bool {
return std.mem.lessThan(u8, a, b);
}
fn collectFrom(
arena: std.mem.Allocator,
io: std.Io,
src_dir: []const u8,
filename: []const u8,
out: *std.array_list.Managed([]const u8),
) !void {
const path = try std.fs.path.join(arena, &.{ src_dir, filename });
const contents = try std.Io.Dir.cwd().readFileAlloc(io, path, arena, .unlimited);
// Walk line by line. When we hit a line starting with `LUA_API`,
// accumulate continuation lines until we see `);`. From the joined
// string, extract the function name \u2014 either parenthesized
// (`(lua_foo)`) or bare (`lua_foo(...)`).
var line_it = std.mem.splitScalar(u8, contents, '\n');
while (line_it.next()) |first_line| {
const trimmed = std.mem.trimStart(u8, first_line, " \t");
if (!std.mem.startsWith(u8, trimmed, "LUA_API") and
!std.mem.startsWith(u8, trimmed, "LUALIB_API")) continue;
var joined: std.array_list.Managed(u8) = .init(arena);
try joined.appendSlice(first_line);
while (std.mem.indexOf(u8, joined.items, ");") == null) {
const next_line = line_it.next() orelse break;
try joined.append(' ');
try joined.appendSlice(next_line);
}
if (extractName(joined.items)) |name| {
try out.append(try arena.dupe(u8, name));
}
}
}
/// Return the function-name portion of a Lua API declaration. Handles
/// both `LUA_API <ret> (lua_foo) (...)` and `LUA_API <ret> lua_foo(...)`.
fn extractName(decl: []const u8) ?[]const u8 {
// Find a `(name)` token first.
var i: usize = 0;
while (i + 1 < decl.len) : (i += 1) {
if (decl[i] != '(') continue;
const end = std.mem.indexOfScalar(u8, decl[i + 1 ..], ')') orelse return null;
const candidate = decl[i + 1 .. i + 1 + end];
if (isApiName(candidate)) return candidate;
// Skip this candidate; try further on.
}
// Fall back: bare-name pattern (no parens around the name).
var j: usize = 0;
while (j < decl.len) : (j += 1) {
if (j + 4 > decl.len) break;
if (!std.mem.eql(u8, decl[j .. j + 4], "lua_") and
!std.mem.eql(u8, decl[j .. j + 5], "luaL_")) continue;
var end = j;
while (end < decl.len and (std.ascii.isAlphanumeric(decl[end]) or decl[end] == '_')) end += 1;
return decl[j..end];
}
return null;
}
fn isApiName(s: []const u8) bool {
if (s.len < 4) return false;
const ok_prefix = std.mem.startsWith(u8, s, "lua_") or std.mem.startsWith(u8, s, "luaL_");
if (!ok_prefix) return false;
for (s) |ch| {
if (!std.ascii.isAlphanumeric(ch) and ch != '_') return false;
}
return true;
}
|