~sircmpwn/hare

hare/cmd/hare/schedule.ha -rw-r--r-- 8.1 KiB
b801af0cBor Grošelj Simić regex: simplify charclass name -> func mapping a day ago
                                                                                
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
// License: GPL-3.0
// (c) 2021-2022 Alexey Yerin <yyp@disroot.org>
// (c) 2021-2022 Drew DeVault <sir@cmpwn.com>
// (c) 2021 Eyal Sawady <ecs@d2evs.net>
// (c) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz>
// (c) 2022 Jon Eskin <eskinjp@gmail.com>
use encoding::hex;
use fmt;
use fs;
use hare::ast;
use hare::module;
use hare::unparse;
use hash::fnv;
use hash;
use os;
use path;
use strings;
use strio;

fn ident_hash(ident: ast::ident) u32 = {
	let hash = fnv::fnv32();
	for (let i = 0z; i < len(ident); i += 1) {
		hash::write(&hash, strings::toutf8(ident[i]));
		hash::write(&hash, [0]);
	};
	return fnv::sum32(&hash);
};

fn sched_module(plan: *plan, ident: ast::ident, link: *[]*task) *task = {
	let hash = ident_hash(ident);
	let bucket = &plan.modmap[hash % len(plan.modmap)];
	for (let i = 0z; i < len(bucket); i += 1) {
		if (bucket[i].hash == hash
				&& ast::ident_eq(bucket[i].ident, ident)) {
			return bucket[i].task;
		};
	};

	let ver = match (module::lookup(plan.context, ident)) {
	case let err: module::error =>
		let ident = unparse::identstr(ident);
		fmt::fatalf("Error resolving {}: {}", ident,
			module::strerror(err));
	case let ver: module::version =>
		yield ver;
	};

	let depends: []*task = [];
	defer free(depends);
	for (let i = 0z; i < len(ver.depends); i += 1) {
		const dep = ver.depends[i];
		let obj = sched_module(plan, dep, link);
		append(depends, obj);
	};

	let obj = sched_hare_object(plan, ver, ident, depends...);
	append(bucket, modcache {
		hash = hash,
		task = obj,
		ident = ident,
		version = ver,
	});
	append(link, obj);
	return obj;
};

// Schedules a task which compiles objects into an executable.
fn sched_ld(plan: *plan, output: str, depend: *task...) *task = {
	let task = alloc(task {
		status = status::SCHEDULED,
		output = output,
		depend = alloc(depend...),
		cmd = alloc([
			// C compiler is used as linker if we -l something
			os::tryenv("LD",
				if (len(plan.libs) > 0) "cc" else "ld"),
			"-T", plan.script,
			"-o", output,
		]),
		module = void,
	});

	// Using --gc-sections will not work when using cc as the linker
	if (len(plan.libs) == 0) {
		append(task.cmd, "--gc-sections");
	};

	let archives: []str = [];
	defer free(archives);

	for (let i = 0z; i < len(depend); i += 1) {
		if (strings::hassuffix(depend[i].output, ".a")) {
			append(archives, depend[i].output);
		} else {
			append(task.cmd, depend[i].output);
		};
	};
	append(task.cmd, archives...);
	for (let i = 0z; i < len(plan.libs); i += 1) {
		append(task.cmd, strings::concat("-l", plan.libs[i]));
	};
	append(plan.scheduled, task);
	return task;
};

// Schedules a task which merges objects into an archive.
fn sched_ar(plan: *plan, output: str, depend: *task...) *task = {
	let task = alloc(task {
		status = status::SCHEDULED,
		output = output,
		depend = alloc(depend...),
		cmd = alloc([
			os::tryenv("AR", "ar"), "-csr", output,
		]),
		module = void,
	});
	for (let i = 0z; i < len(depend); i += 1) {
		assert(strings::hassuffix(depend[i].output, ".o"));
		append(task.cmd, depend[i].output);
	};
	append(plan.scheduled, task);
	return task;
};

// Schedules a task which compiles assembly into an object.
fn sched_as(plan: *plan, output: str, input: str, depend: *task...) *task = {
	let task = alloc(task {
		status = status::SCHEDULED,
		output = output,
		depend = alloc(depend...),
		cmd = alloc([
			os::tryenv("AS", "as"), "-g", "-o", output, input,
		]),
		module = void,
	});
	append(plan.scheduled, task);
	return task;
};

// Schedules a task which compiles an SSA file into assembly.
fn sched_qbe(plan: *plan, output: str, depend: *task) *task = {
	let task = alloc(task {
		status = status::SCHEDULED,
		output = output,
		depend = alloc([depend]),
		cmd = alloc([
			os::tryenv("QBE", "qbe"), "-o", output, depend.output,
		]),
		module = void,
	});
	append(plan.scheduled, task);
	return task;
};

// Schedules tasks which compiles a Hare module into an object or archive.
fn sched_hare_object(
	plan: *plan,
	ver: module::version,
	namespace: ast::ident,
	depend: *task...
) *task = {
	// XXX: Do we care to support assembly-only modules?
	let mixed = false;
	for (let i = 0z; i < len(ver.inputs); i += 1) {
		if (strings::hassuffix(ver.inputs[i].path, ".s")) {
			mixed = true;
			break;
		};
	};

	const ns = unparse::identstr(namespace);
	const displayed_ns = if (len(ns) == 0) "(root)" else ns;
	if (len(ns) > plan.progress.maxwidth)
		plan.progress.maxwidth = len(ns);

	let ssa = mkfile(plan, ns, "ssa");
	let harec = alloc(task {
		status = status::SCHEDULED,
		output = ssa,
		depend = alloc(depend...),
		cmd = alloc([
			os::tryenv("HAREC", "harec"), "-o", ssa,
		]),
		module = strings::dup(ns),
	});

	for (let i = 0z; i < len(plan.context.tags); i += 1) {
		if (plan.context.tags[i].mode == module::tag_mode::INCLUSIVE
				&& plan.context.tags[i].name == "test") {
			const opaths = plan.context.paths;
			plan.context.paths = ["."];
			const ver = module::lookup(plan.context, namespace);
			if (ver is module::version) {
				append(harec.cmd, "-T");
			};
			plan.context.paths = opaths;
			break;
		};
	};

	let current = false;
	let output = if (len(namespace) != 0) {
		let version = hex::encodestr(ver.hash);
		let env = module::identuscore(namespace);
		defer free(env);

		append(harec.cmd, ["-N", ns]...);
		append(plan.environ, (
			fmt::asprintf("HARE_VERSION_{}", env), version,
		));

		// TODO: Keep this around and append new versions, rather than
		// overwriting with just the latest
		let manifest = match (module::manifest_load(
				plan.context, namespace)) {
		case let err: module::error =>
			fmt::fatalf("Error reading cache entry for {}: {}",
				displayed_ns, module::strerror(err));
		case let m: module::manifest =>
			yield m;
		};
		defer module::manifest_finish(&manifest);
		current = module::current(&manifest, &ver);

		let name = fmt::asprintf("{}.{}", version,
			if (mixed) "a" else "o");
		defer free(name);

		let td = fmt::asprintf("{}.td", version);
		defer free(td);

		let buf = path::init();
		path::set(&buf, plan.context.cache)!;
		path::add(&buf, namespace...)!;
		const path = path::string(&buf);
		match (os::mkdirs(path, 0o755)) {
		case void => void;
		case let err: fs::error =>
			fmt::fatalf("Error: mkdirs {}: {}", path,
				fs::strerror(err));
		};
		append(harec.cmd, ["-t", path::join(path, td)]...);
		yield path::join(path, name);
	} else {
		// XXX: This is probably kind of dumb
		// It would be better to apply any defines which affect this
		// namespace instead
		for (let i = 0z; i < len(plan.context.defines); i += 1) {
			append(harec.cmd, ["-D", plan.context.defines[i]]...);
		};

		yield mkfile(plan, ns, "o"); // TODO: Should exes go in the cache?
	};

	let hare_inputs = 0z;
	for (let i = 0z; i < len(ver.inputs); i += 1) {
		let path = ver.inputs[i].path;
		if (strings::hassuffix(path, ".ha")) {
			append(harec.cmd, path);
			hare_inputs += 1;
		};
	};
	if (hare_inputs == 0) {
		fmt::fatalf("Error: Module {} has no Hare input files",
			displayed_ns);
	};

	if (current) {
		harec.status = status::COMPLETE;
		harec.output = output;
		append(plan.complete, harec);
		return harec;
	} else {
		append(plan.scheduled, harec);
	};

	let s = mkfile(plan, ns, "s");
	let qbe = sched_qbe(plan, s, harec);
	let hare_obj = sched_as(plan,
		if (mixed) mkfile(plan, ns, "o") else output,
		s, qbe);
	if (!mixed) {
		return hare_obj;
	};

	let objs: []*task = alloc([hare_obj]);
	defer free(objs);
	for (let i = 0z; i < len(ver.inputs); i += 1) {
		// XXX: All of our assembly files don't depend on anything else,
		// but that may not be generally true. We may have to address
		// this at some point.
		let path = ver.inputs[i].path;
		if (!strings::hassuffix(path, ".s")) {
			continue;
		};
		append(objs, sched_as(plan, mkfile(plan, ns, "o"), path));
	};
	return sched_ar(plan, output, objs...);
};

// Schedules tasks which compiles hare sources into an executable.
fn sched_hare_exe(
	plan: *plan,
	ver: module::version,
	output: str,
	depend: *task...
) *task = {
	let obj = sched_hare_object(plan, ver, [], depend...);
	// TODO: We should be able to use partial variadic application
	let link: []*task = alloc([], len(depend));
	defer free(link);
	append(link, obj);
	append(link, depend...);
	return sched_ld(plan, strings::dup(output), link...);
};