==> 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}