skip to content
==> src/link.c <==
1/* The cluster float mode is one flag, --denormal-fp-math-f32=preserve-sign:
2 * opt stamps it on every function ("denormal-fp-math-f32"="preserve-sign,
3 * preserve-sign"), so the optimiser folds f32 constants with flushing and
4 * the NVPTX backend selects the .ftz form of every f32 instruction that has
5 * one. f32 subnormal inputs and results become sign-preserving zero, as Apple
6 * GPUs always do, so CUDA and Metal members compute the same bits. f64 is
7 * untouched (PTX has no f64 ftz). llc also gets --fp-contract=off: rustc never
8 * contracts, but clang's C may emit llvm.fmuladd in a library compiled without
9 * -ffp-contract=off, and strict contraction lowers that to a multiply and an add. */
10#include "link.h"
11
12#include <dirent.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19#define DENORM "--denormal-fp-math-f32=preserve-sign"
20
21static char *sdup(const char *s) {
22    size_t n = strlen(s) + 1;
23    char *d = malloc(n);
24    if (!d) abort();
25    memcpy(d, s, n);
26    return d;
27}
28static char *fmt2(const char *a, const char *b) {
29    size_t n = strlen(a) + strlen(b) + 1;
30    char *d = malloc(n);
31    if (!d) abort();
32    snprintf(d, n, "%s%s", a, b);
33    return d;
34}
35
36typedef struct {
37    char **v;
38    size_t n, cap;
39} argv_buf;
40static void push(argv_buf *a, char *s) {
41    if (a->n + 2 > a->cap) {
42        a->cap = a->cap ? 2 * a->cap : 16;
43        a->v = realloc(a->v, a->cap * sizeof *a->v);
44        if (!a->v) abort();
45    }
46    a->v[a->n++] = s;
47    a->v[a->n] = NULL;
48}
49
50char *mesh_ptx_link_side(const mesh_ptx_link *l, const char *suffix) {
51    size_t n = strlen(l->output);
52    char *base = sdup(l->output);
53    if (n >= 4 && strcmp(base + n - 4, ".ptx") == 0) base[n - 4] = 0;
54    char *r = fmt2(base, suffix);
55    free(base);
56    return r;
57}
58
59char **mesh_ptx_link_argv_link(const mesh_ptx_link *l) {
60    argv_buf a = {0};
61    push(&a, sdup(l->llvm_link));
62    push(&a, sdup("--ignore-non-bitcode"));
63    for (size_t i = 0; i < l->ninputs; i++) push(&a, sdup(l->inputs[i]));
64    push(&a, sdup("-o"));
65    push(&a, mesh_ptx_link_side(l, ".o"));
66    return a.v;
67}
68
69char **mesh_ptx_link_argv_opt(const mesh_ptx_link *l) {
70    argv_buf a = {0};
71    char *syms = mesh_ptx_link_side(l, ".symbols.txt");
72    char passes[64];
73    snprintf(passes, sizeof passes, "--passes=default<O%d>,internalize,globaldce", l->opt);
74    push(&a, sdup(l->opt_tool));
75    push(&a, mesh_ptx_link_side(l, ".o"));
76    push(&a, sdup("-o"));
77    push(&a, mesh_ptx_link_side(l, ".optimized.o"));
78    push(&a, sdup(DENORM));
79    push(&a, fmt2("--internalize-public-api-file=", syms));
80    push(&a, sdup(passes));
81    if (!l->debug) push(&a, sdup("--strip-debug"));
82    free(syms);
83    return a.v;
84}
85
86char **mesh_ptx_link_argv_llc(const mesh_ptx_link *l) {
87    argv_buf a = {0};
88    push(&a, sdup(l->llc));
89    push(&a, sdup(DENORM));
90    push(&a, sdup("--fp-contract=off"));
91    push(&a, sdup("--mcpu"));
92    push(&a, sdup(l->cpu));
93    push(&a, mesh_ptx_link_side(l, ".optimized.o"));
94    push(&a, sdup("-o"));
95    push(&a, sdup(l->output));
96    return a.v;
97}
98
99void mesh_ptx_link_free_argv(char **argv) {
100    if (!argv) return;
101    for (char **p = argv; *p; p++) free(*p);
102    free(argv);
103}
104
105char *mesh_ptx_link_symbols(const mesh_ptx_link *l) {
106    static const char *const def[] = {"mesh_main", "MESH_RT_ABI"};
107    const char *const *e = l->exports ? l->exports : def;
108    size_t n = l->exports ? l->nexports : 2, len = 1;
109    for (size_t i = 0; i < n; i++) len += strlen(e[i]) + 1;
110    char *s = malloc(len), *p = s;
111    if (!s) abort();
112    for (size_t i = 0; i < n; i++) p += sprintf(p, "%s\n", e[i]);
113    *p = 0;
114    return s;
115}
116
117static bool executable(const char *path) {
118    struct stat st;
119    return stat(path, &st) == 0 && S_ISREG(st.st_mode) && access(path, X_OK) == 0;
120}
121
122static char *on_path(const char *name) {
123    const char *path = getenv("PATH");
124    if (!path) path = "/usr/bin:/bin";
125    while (*path) {
126        const char *c = strchr(path, ':');
127        size_t n = c ? (size_t)(c - path) : strlen(path);
128        char buf[4096];
129        if (n > 0 && n + strlen(name) + 2 < sizeof buf) {
130            snprintf(buf, sizeof buf, "%.*s/%s", (int)n, path, name);
131            if (executable(buf)) return sdup(buf);
132        }
133        path += n + (c ? 1 : 0);
134        if (!c) break;
135    }
136    return NULL;
137}
138
139char *mesh_ptx_link_find_tool(const char *name, const char *env) {
140    const char *e = env ? getenv(env) : NULL;
141    if (e && *e) return sdup(e);
142    char *p = on_path(name);
143    if (p) return p;
144    for (int v = 21; v >= 15; v--) {
145        char n[128];
146        snprintf(n, sizeof n, "%s-%d", name, v);
147        if ((p = on_path(n))) return p;
148    }
149    /* The newest /usr/lib/llvm-N/bin. */
150    int best = -1;
151    DIR *d = opendir("/usr/lib");
152    if (d) {
153        struct dirent *de;
154        while ((de = readdir(d))) {
155            int v;
156            char tail;
157            if (sscanf(de->d_name, "llvm-%d%c", &v, &tail) == 1 && v > best) {
158                char buf[512];
159                snprintf(buf, sizeof buf, "/usr/lib/llvm-%d/bin/%s", v, name);
160                if (executable(buf)) best = v;
161            }
162        }
163        closedir(d);
164    }
165    if (best >= 0) {
166        char buf[512];
167        snprintf(buf, sizeof buf, "/usr/lib/llvm-%d/bin/%s", best, name);
168        return sdup(buf);
169    }
170    return NULL;
171}
172
173bool mesh_ptx_link_valid_arch(const char *a) {
174    if (strncmp(a, "sm_", 3) != 0) return false;
175    size_t i = 3, d = 0;
176    while (a[i] >= '0' && a[i] <= '9') i++, d++;
177    if (d < 2 || d > 3) return false;
178    if (a[i] == 'a' || a[i] == 'f') i++;
179    return a[i] == 0;
180}