schedule.ha (10102B)
1 // License: GPL-3.0 2 // (c) 2021-2022 Alexey Yerin <yyp@disroot.org> 3 // (c) 2021-2022 Drew DeVault <sir@cmpwn.com> 4 // (c) 2021 Ember Sawady <ecs@d2evs.net> 5 // (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz> 6 // (c) 2022 Jon Eskin <eskinjp@gmail.com> 7 use encoding::hex; 8 use fmt; 9 use fs; 10 use hare::ast; 11 use hare::module; 12 use hare::unparse; 13 use hash::fnv; 14 use hash; 15 use os; 16 use path; 17 use shlex; 18 use strings; 19 20 fn getenv(var: str) []str = { 21 match (os::getenv(var)) { 22 case let val: str => 23 match (shlex::split(val)) { 24 case let fields: []str => 25 return fields; 26 case => void; 27 }; 28 case => void; 29 }; 30 31 return []; 32 }; 33 34 // (executable name, executable variable, flags variable) 35 type tool = (str, str, str); 36 37 let ld_tool: tool = ("", "LD", "LDLINKFLAGS"); 38 let as_tool: tool = ("", "AS", "ASFLAGS"); 39 let ar_tool: tool = ("", "AR", "ARFLAGS"); 40 let qbe_tool: tool = ("qbe", "QBE", "QBEFLAGS"); 41 42 fn getcmd(tool: *tool, args: str...) []str = { 43 let execargs: []str = []; 44 45 let vals = getenv(tool.1); 46 defer free(vals); 47 if (len(vals) == 0) { 48 append(execargs, tool.0); 49 } else { 50 append(execargs, vals...); 51 }; 52 53 let vals = getenv(tool.2); 54 defer free(vals); 55 append(execargs, vals...); 56 57 append(execargs, args...); 58 59 return execargs; 60 }; 61 62 fn ident_hash(ident: ast::ident) u32 = { 63 let hash = fnv::fnv32(); 64 for (let i = 0z; i < len(ident); i += 1) { 65 hash::write(&hash, strings::toutf8(ident[i])); 66 hash::write(&hash, [0]); 67 }; 68 return fnv::sum32(&hash); 69 }; 70 71 fn sched_module(plan: *plan, ident: ast::ident, link: *[]*task) *task = { 72 let hash = ident_hash(ident); 73 let bucket = &plan.modmap[hash % len(plan.modmap)]; 74 for (let i = 0z; i < len(bucket); i += 1) { 75 if (bucket[i].hash == hash 76 && ast::ident_eq(bucket[i].ident, ident)) { 77 return bucket[i].task; 78 }; 79 }; 80 81 let ver = match (module::lookup(plan.context, ident)) { 82 case let err: module::error => 83 let ident = unparse::identstr(ident); 84 progress_clear(plan); 85 fmt::fatalf("Error resolving {}: {}", ident, 86 module::strerror(err)); 87 case let ver: module::version => 88 yield ver; 89 }; 90 91 let depends: []*task = []; 92 defer free(depends); 93 for (let i = 0z; i < len(ver.depends); i += 1) { 94 const dep = ver.depends[i]; 95 let obj = sched_module(plan, dep, link); 96 append(depends, obj); 97 }; 98 99 let obj = sched_hare_object(plan, ver, ident, void, depends...); 100 append(bucket, modcache { 101 hash = hash, 102 task = obj, 103 ident = ident, 104 version = ver, 105 }); 106 append(link, obj); 107 return obj; 108 }; 109 110 // Schedules a task which compiles objects into an executable. 111 fn sched_ld(plan: *plan, output: str, depend: *task...) *task = { 112 let task = alloc(task { 113 status = status::SCHEDULED, 114 output = output, 115 depend = alloc(depend...), 116 cmd = getcmd(&ld_tool, 117 "-T", plan.script, 118 "-o", output), 119 module = void, 120 }); 121 122 if (len(plan.libdir) != 0) { 123 for (let i = 0z; i < len(plan.libdir); i += 1) { 124 append(task.cmd, strings::concat("-L", plan.libdir[i])); 125 }; 126 }; 127 128 // Using --gc-sections will not work when using cc as the linker 129 if (len(plan.libs) == 0 && task.cmd[0] == plan.target.ld_cmd) { 130 append(task.cmd, "--gc-sections"); 131 }; 132 133 let archives: []str = []; 134 defer free(archives); 135 136 for (let i = 0z; i < len(depend); i += 1) { 137 if (strings::hassuffix(depend[i].output, ".a")) { 138 append(archives, depend[i].output); 139 } else { 140 append(task.cmd, depend[i].output); 141 }; 142 }; 143 append(task.cmd, archives...); 144 for (let i = 0z; i < len(plan.libs); i += 1) { 145 append(task.cmd, strings::concat("-l", plan.libs[i])); 146 }; 147 append(plan.scheduled, task); 148 return task; 149 }; 150 151 // Schedules a task which merges objects into an archive. 152 fn sched_ar(plan: *plan, output: str, depend: *task...) *task = { 153 let task = alloc(task { 154 status = status::SCHEDULED, 155 output = output, 156 depend = alloc(depend...), 157 cmd = getcmd(&ar_tool, "-c", output), 158 module = void, 159 }); 160 161 // POSIX specifies `ar -r [-cuv] <archive> <file>` 162 // Add -r here so it is always before any ARFLAGS 163 insert(task.cmd[1], "-r"); 164 165 for (let i = 0z; i < len(depend); i += 1) { 166 assert(strings::hassuffix(depend[i].output, ".o")); 167 append(task.cmd, depend[i].output); 168 }; 169 append(plan.scheduled, task); 170 return task; 171 }; 172 173 // Schedules a task which compiles assembly into an object. 174 fn sched_as(plan: *plan, output: str, input: str, depend: *task...) *task = { 175 let task = alloc(task { 176 status = status::SCHEDULED, 177 output = output, 178 depend = alloc(depend...), 179 cmd = getcmd(&as_tool, "-g", "-o", output), 180 module = void, 181 }); 182 183 append(task.cmd, input); 184 185 append(plan.scheduled, task); 186 return task; 187 }; 188 189 // Schedules a task which compiles an SSA file into assembly. 190 fn sched_qbe(plan: *plan, output: str, depend: *task) *task = { 191 let task = alloc(task { 192 status = status::SCHEDULED, 193 output = output, 194 depend = alloc([depend]), 195 cmd = getcmd(&qbe_tool, 196 "-t", plan.target.qbe_target, 197 "-o", output, 198 depend.output), 199 module = void, 200 }); 201 append(plan.scheduled, task); 202 return task; 203 }; 204 205 // Schedules tasks which compiles a Hare module into an object or archive. 206 fn sched_hare_object( 207 plan: *plan, 208 ver: module::version, 209 namespace: ast::ident, 210 output: (void | str), 211 depend: *task... 212 ) *task = { 213 // XXX: Do we care to support assembly-only modules? 214 let mixed = false; 215 for (let i = 0z; i < len(ver.inputs); i += 1) { 216 if (strings::hassuffix(ver.inputs[i].path, ".s")) { 217 mixed = true; 218 break; 219 }; 220 }; 221 222 const ns = unparse::identstr(namespace); 223 const displayed_ns = if (len(ns) == 0) "(root)" else ns; 224 if (len(ns) > plan.progress.maxwidth) 225 plan.progress.maxwidth = len(ns); 226 227 let ssa = mkfile(plan, ns, "ssa"); 228 let harec = alloc(task { 229 status = status::SCHEDULED, 230 output = ssa, 231 depend = alloc(depend...), 232 cmd = alloc([ 233 os::tryenv("HAREC", "harec"), "-o", ssa, 234 ]), 235 module = strings::dup(ns), 236 }); 237 238 let libc = false; 239 for (let i = 0z; i < len(plan.context.tags); i += 1) { 240 if (plan.context.tags[i].mode == module::tag_mode::INCLUSIVE 241 && plan.context.tags[i].name == "test") { 242 const opaths = plan.context.paths; 243 plan.context.paths = ["."]; 244 const ver = module::lookup(plan.context, namespace); 245 if (ver is module::version) { 246 append(harec.cmd, "-T"); 247 }; 248 plan.context.paths = opaths; 249 } else if (plan.context.tags[i].mode == module::tag_mode::INCLUSIVE 250 && plan.context.tags[i].name == "libc") { 251 libc = true; 252 }; 253 }; 254 255 if (len(ns) != 0 || libc) { 256 append(harec.cmd, ["-N", ns]...); 257 }; 258 259 let current = false; 260 let output = if (output is str) { 261 static let buf = path::buffer{...}; 262 path::set(&buf, output as str)!; 263 // TODO: Should we use the cache here? 264 const ext = match (path::peek_ext(&buf)) { 265 case let s: str => yield s; 266 case void => yield ""; 267 }; 268 const expected = if (mixed) "a" else "o"; 269 if (ext != expected) { 270 fmt::errorfln("Warning: Expected output file extension {}, found {}", 271 expected, output)!; 272 }; 273 yield strings::dup(output as str); 274 } else if (len(namespace) != 0) { 275 let buf = path::init(plan.context.cache)!; 276 path::push(&buf, namespace...)!; 277 const path = path::string(&buf); 278 match (os::mkdirs(path, 0o755)) { 279 case void => void; 280 case let err: fs::error => 281 progress_clear(plan); 282 fmt::fatalf("Error: mkdirs {}: {}", path, 283 fs::strerror(err)); 284 }; 285 286 let version = hex::encodestr(ver.hash); 287 let td = fmt::asprintf("{}.td", version); 288 defer free(td); 289 let name = fmt::asprintf("{}.{}", version, 290 if (mixed) "a" else "o"); 291 defer free(name); 292 path::push(&buf, td)!; 293 294 append(plan.environ, ( 295 fmt::asprintf("HARE_TD_{}", ns), 296 strings::dup(path::string(&buf)), 297 )); 298 299 // TODO: Keep this around and append new versions, rather than 300 // overwriting with just the latest 301 let manifest = match (module::manifest_load( 302 plan.context, namespace)) { 303 case let err: module::error => 304 progress_clear(plan); 305 fmt::fatalf("Error reading cache entry for {}: {}", 306 displayed_ns, module::strerror(err)); 307 case let m: module::manifest => 308 yield m; 309 }; 310 defer module::manifest_finish(&manifest); 311 current = module::current(&manifest, &ver); 312 313 append(harec.cmd, ["-t", strings::dup(path::string(&buf))]...); 314 yield strings::dup(path::push(&buf, "..", name)!); 315 } else { 316 // XXX: This is probably kind of dumb 317 // It would be better to apply any defines which affect this 318 // namespace instead 319 for (let i = 0z; i < len(plan.context.defines); i += 1) { 320 append(harec.cmd, ["-D", plan.context.defines[i]]...); 321 }; 322 323 yield mkfile(plan, ns, "o"); // TODO: Should exes go in the cache? 324 }; 325 326 let hare_inputs = 0z; 327 for (let i = 0z; i < len(ver.inputs); i += 1) { 328 let path = ver.inputs[i].path; 329 if (strings::hassuffix(path, ".ha")) { 330 append(harec.cmd, path); 331 hare_inputs += 1; 332 }; 333 }; 334 if (hare_inputs == 0) { 335 progress_clear(plan); 336 fmt::fatalf("Error: Module {} has no Hare input files", 337 displayed_ns); 338 }; 339 340 if (current) { 341 harec.status = status::COMPLETE; 342 harec.output = output; 343 append(plan.complete, harec); 344 return harec; 345 } else { 346 append(plan.scheduled, harec); 347 }; 348 349 let s = mkfile(plan, ns, "s"); 350 let qbe = sched_qbe(plan, s, harec); 351 let hare_obj = sched_as(plan, 352 if (mixed) mkfile(plan, ns, "o") else output, 353 s, qbe); 354 if (!mixed) { 355 return hare_obj; 356 }; 357 358 let objs: []*task = alloc([hare_obj]); 359 defer free(objs); 360 for (let i = 0z; i < len(ver.inputs); i += 1) { 361 // XXX: All of our assembly files don't depend on anything else, 362 // but that may not be generally true. We may have to address 363 // this at some point. 364 let path = ver.inputs[i].path; 365 if (!strings::hassuffix(path, ".s")) { 366 continue; 367 }; 368 append(objs, sched_as(plan, mkfile(plan, ns, "o"), path)); 369 }; 370 return sched_ar(plan, output, objs...); 371 }; 372 373 // Schedules tasks which compiles hare sources into an executable. 374 fn sched_hare_exe( 375 plan: *plan, 376 ver: module::version, 377 output: str, 378 depend: *task... 379 ) *task = { 380 let obj = sched_hare_object(plan, ver, [], void, depend...); 381 // TODO: We should be able to use partial variadic application 382 let link: []*task = alloc([], len(depend)); 383 defer free(link); 384 append(link, obj); 385 append(link, depend...); 386 return sched_ld(plan, strings::dup(output), link...); 387 };