hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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