hare

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

commit 0940e717656ce98e942c5ef5c0202e83d9f86fe1
parent a2f63f644453f326e2dd52f43c62ef11ae3abe06
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sat, 13 Mar 2021 17:01:44 -0500

hare: split schedule.ha from plan.ha

Diffstat:
MMakefile | 1+
Mcmd/hare/plan.ha | 223-------------------------------------------------------------------------------
Acmd/hare/schedule.ha | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 230 insertions(+), 223 deletions(-)

diff --git a/Makefile b/Makefile @@ -21,6 +21,7 @@ include stdlib.mk hare_srcs=\ ./cmd/hare/plan.ha \ ./cmd/hare/subcmds.ha \ + ./cmd/hare/schedule.ha \ ./cmd/hare/main.ha $(HARECACHE)/hare.ssa: $(hare_srcs) $(hare_stdlib_deps) diff --git a/cmd/hare/plan.ha b/cmd/hare/plan.ha @@ -1,14 +1,10 @@ -use encoding::hex; use fmt; use hare::ast; use hare::module; -use hash::fnv; -use hash; use os::exec; use os; use path; use strings; -use strio; use temp; type status = enum { @@ -90,225 +86,6 @@ fn plan_finish(plan: *plan) void = { }; }; -fn ident_hash(ident: ast::ident) u32 = { - let hash = fnv::fnv32(); - defer hash::close(hash); - for (let i = 0z; i < len(ident); i += 1) { - hash::write(hash, strings::to_utf8(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); - // TODO: We should not have to dereference the bucket for len or append - // TODO: We should not have to cast the length to u32 - let bucket = &plan.modmap[hash % len(plan.modmap): u32]; - for (let i = 0z; i < len(*bucket); i += 1) { - if (bucket[i].hash == hash) { - return bucket[i].task; - }; - }; - - let ver = match (module::lookup(plan.context, ident)) { - err: module::error => { - let ident = ast::ident_unparse_s(ident); - fmt::fatal("Error resolving {}: {}", - ident, module::errstr(err)); - }, - ver: module::version => ver, - }; - - let depends: []*task = []; - 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 }); - append(*link, obj); - free(depends); - 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 = mkdepends(depend...), - cmd = alloc([ - os::tryenv("LD", "ld"), - "-T", plan.script, - "-o", output, - ]), - }); - for (let i = 0z; i < len(depend); i += 1) { - append(task.cmd, depend[i].output); - }; - 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 = mkdepends(depend...), - cmd = alloc([ - os::tryenv("AR", "ar"), "-csr", output, - ]), - }); - for (let i = 0z; i < len(depend); i += 1) { - assert(strings::has_suffix(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 = mkdepends(depend...), - cmd = alloc([ - os::tryenv("AS", "as"), "-o", output, input, - ]), - }); - 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 = mkdepends(depend), - cmd = alloc([ - os::tryenv("QBE", "qbe"), "-o", output, depend.output, - ]), - }); - append(plan.scheduled, task); - return task; -}; - -export fn ident_to_env(ident: ast::ident) str = { - let buf = strio::dynamic(); - for (let i = 0z; i < len(ident); i += 1) { - fmt::fprintf(buf, "{}{}", ident[i], - if (i + 1 < len(ident)) "_" - else "") as size; - }; - return strio::finish(buf); -}; - -// 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::has_suffix(ver.inputs[i].path, ".s")) { - mixed = true; - break; - }; - }; - - let ssa = mkfile(plan, "ssa"); - let harec = alloc(task { - status = status::SCHEDULED, - output = ssa, - depend = mkdepends(depend...), - cmd = alloc([ - os::tryenv("HAREC", "harec"), "-o", ssa, - ]), - }); - let output = if (len(namespace) != 0) { - // TODO: consult/update cache manifest - let version = hex::encode(ver.hash); - let ns = ast::ident_unparse_s(namespace); - let env = ident_to_env(namespace); - defer free(env); - - append(harec.cmd, "-N", ns); - append(plan.environ, ( - fmt::asprintf("HARE_VERSION_{}", env), version, - )); - - let name = fmt::asprintf("{}.{}", version, - if (mixed) "a" else "o"); - defer free(name); - - let td = fmt::asprintf("{}.td", version); - defer free(td); - - let path = plan.context.cache; - for (let i = 0z; i < len(namespace); i += 1) { - path = path::join(path, namespace[i]); - }; - os::mkdirs(path); - append(harec.cmd, "-t", path::join(path, td)); - path::join(path, name); - } else mkfile(plan, "o"); // TODO: Should exes go in the cache? - - for (let i = 0z; i < len(ver.inputs); i += 1) { - let path = ver.inputs[i].path; - if (strings::has_suffix(path, ".ha")) { - append(harec.cmd, path); - }; - }; - append(plan.scheduled, harec); - - let s = mkfile(plan, "s"); - let qbe = sched_qbe(plan, s, harec); - let hare_obj = sched_as(plan, - if (mixed) mkfile(plan, "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::has_suffix(path, ".s")) { - continue; - }; - append(objs, sched_as(plan, mkfile(plan, "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, ...depend); - return sched_ld(plan, strings::dup(output), link...); -}; - fn plan_execute(plan: *plan, verbose: bool) void = { for (len(plan.scheduled) != 0) { let next: nullable *task = null; diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha @@ -0,0 +1,229 @@ +use encoding::hex; +use fmt; +use hare::ast; +use hare::module; +use hash::fnv; +use hash; +use os; +use path; +use strings; +use strio; + +fn ident_hash(ident: ast::ident) u32 = { + let hash = fnv::fnv32(); + defer hash::close(hash); + for (let i = 0z; i < len(ident); i += 1) { + hash::write(hash, strings::to_utf8(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); + // TODO: We should not have to dereference the bucket for len or append + // TODO: We should not have to cast the length to u32 + let bucket = &plan.modmap[hash % len(plan.modmap): u32]; + for (let i = 0z; i < len(*bucket); i += 1) { + if (bucket[i].hash == hash) { + return bucket[i].task; + }; + }; + + let ver = match (module::lookup(plan.context, ident)) { + err: module::error => { + let ident = ast::ident_unparse_s(ident); + fmt::fatal("Error resolving {}: {}", + ident, module::errstr(err)); + }, + ver: module::version => ver, + }; + + let depends: []*task = []; + 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 }); + append(*link, obj); + free(depends); + 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 = mkdepends(depend...), + cmd = alloc([ + os::tryenv("LD", "ld"), + "-T", plan.script, + "-o", output, + ]), + }); + for (let i = 0z; i < len(depend); i += 1) { + append(task.cmd, depend[i].output); + }; + 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 = mkdepends(depend...), + cmd = alloc([ + os::tryenv("AR", "ar"), "-csr", output, + ]), + }); + for (let i = 0z; i < len(depend); i += 1) { + assert(strings::has_suffix(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 = mkdepends(depend...), + cmd = alloc([ + os::tryenv("AS", "as"), "-o", output, input, + ]), + }); + 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 = mkdepends(depend), + cmd = alloc([ + os::tryenv("QBE", "qbe"), "-o", output, depend.output, + ]), + }); + append(plan.scheduled, task); + return task; +}; + +export fn ident_to_env(ident: ast::ident) str = { + let buf = strio::dynamic(); + for (let i = 0z; i < len(ident); i += 1) { + fmt::fprintf(buf, "{}{}", ident[i], + if (i + 1 < len(ident)) "_" + else "") as size; + }; + return strio::finish(buf); +}; + +// 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::has_suffix(ver.inputs[i].path, ".s")) { + mixed = true; + break; + }; + }; + + let ssa = mkfile(plan, "ssa"); + let harec = alloc(task { + status = status::SCHEDULED, + output = ssa, + depend = mkdepends(depend...), + cmd = alloc([ + os::tryenv("HAREC", "harec"), "-o", ssa, + ]), + }); + let output = if (len(namespace) != 0) { + // TODO: consult/update cache manifest + let version = hex::encode(ver.hash); + let ns = ast::ident_unparse_s(namespace); + let env = ident_to_env(namespace); + defer free(env); + + append(harec.cmd, "-N", ns); + append(plan.environ, ( + fmt::asprintf("HARE_VERSION_{}", env), version, + )); + + let name = fmt::asprintf("{}.{}", version, + if (mixed) "a" else "o"); + defer free(name); + + let td = fmt::asprintf("{}.td", version); + defer free(td); + + let path = plan.context.cache; + for (let i = 0z; i < len(namespace); i += 1) { + path = path::join(path, namespace[i]); + }; + os::mkdirs(path); + append(harec.cmd, "-t", path::join(path, td)); + path::join(path, name); + } else mkfile(plan, "o"); // TODO: Should exes go in the cache? + + for (let i = 0z; i < len(ver.inputs); i += 1) { + let path = ver.inputs[i].path; + if (strings::has_suffix(path, ".ha")) { + append(harec.cmd, path); + }; + }; + append(plan.scheduled, harec); + + let s = mkfile(plan, "s"); + let qbe = sched_qbe(plan, s, harec); + let hare_obj = sched_as(plan, + if (mixed) mkfile(plan, "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::has_suffix(path, ".s")) { + continue; + }; + append(objs, sched_as(plan, mkfile(plan, "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, ...depend); + return sched_ld(plan, strings::dup(output), link...); +};