hare

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

commit e8b4e9c992a28ccf1d8e98baca3afe781d3e072e
parent de82b496b581e60f9de25c414b7c05bd7cdbdf44
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue,  7 Jun 2022 23:19:37 +0200

Mostly implement cross-compiling

The main thing which is still an issue here is that the Hare cache needs
to be broken if the tags don't match up.

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
MMakefile | 22+++++++++-------------
Mcmd/hare/plan.ha | 8+++++++-
Mcmd/hare/schedule.ha | 27+++++++++++++++++----------
Mcmd/hare/subcmds.ha | 41++++++++++++++++++++++-------------------
Acmd/hare/target.ha | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.example.mk | 16++++++++++++++++
Mhare/module/scan.ha | 12++++++++++++
Mrt/+linux/start+aarch64-libc.s | 1-
Atargets.mk | 17+++++++++++++++++
9 files changed, 173 insertions(+), 44 deletions(-)

diff --git a/Makefile b/Makefile @@ -28,7 +28,8 @@ hare_srcs = \ ./cmd/hare/progress.ha \ ./cmd/hare/release.ha \ ./cmd/hare/schedule.ha \ - ./cmd/hare/subcmds.ha + ./cmd/hare/subcmds.ha \ + ./cmd/hare/target.ha harec_srcs = \ ./cmd/harec/main.ha \ @@ -43,21 +44,17 @@ haredoc_srcs = \ ./cmd/haredoc/sort.ha \ ./cmd/haredoc/resolver.ha +include targets.mk + $(HARECACHE)/hare.ssa: $(hare_srcs) $(stdlib_deps_any) $(stdlib_deps_$(PLATFORM)) scripts/version @printf 'HAREC\t%s\n' "$@" @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) \ - -D PLATFORM:str='"'"$(PLATFORM)"'"' \ - -D VERSION:str='"'"$$(./scripts/version)"'"' \ - -D HAREPATH:str='"'"$(HAREPATH)"'"' \ - -o $@ $(hare_srcs) + $(HARE_DEFINES) -o $@ $(hare_srcs) $(TESTCACHE)/hare.ssa: $(hare_srcs) $(testlib_deps_any) $(testlib_deps_$(PLATFORM)) scripts/version @printf 'HAREC\t%s\n' "$@" @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) \ - -D PLATFORM:str='"'"$(PLATFORM)"'"' \ - -D VERSION:str='"'"$$(./scripts/version)"'"' \ - -D HAREPATH:str='"'"$(HAREPATH)"'"' \ - -o $@ $(hare_srcs) + $(HARE_DEFINES) -o $@ $(hare_srcs) $(BINOUT)/hare: $(HARECACHE)/hare.o @mkdir -p $(BINOUT) @@ -74,15 +71,14 @@ $(BINOUT)/hare-tests: $(TESTCACHE)/hare.o $(BINOUT)/harec2: $(BINOUT)/hare $(harec_srcs) @mkdir -p $(BINOUT) @printf 'HARE\t%s\n' "$@" - @env HAREPATH=. HAREC=$(HAREC) QBE=$(QBE) $(BINOUT)/hare build -o $(BINOUT)/harec2 cmd/harec + @env HAREPATH=. HAREC=$(HAREC) QBE=$(QBE) $(BINOUT)/hare build \ + $(HARE_DEFINES) -o $(BINOUT)/harec2 cmd/harec $(BINOUT)/haredoc: $(BINOUT)/hare $(haredoc_srcs) @mkdir -p $(BINOUT) @printf 'HARE\t%s\n' "$@" @env HAREPATH=. HAREC=$(HAREC) QBE=$(QBE) $(BINOUT)/hare build \ - -D PLATFORM:str='"'"$(PLATFORM)"'"' \ - -D HAREPATH:str='"'"$(HAREPATH)"'"' \ - -o $(BINOUT)/haredoc ./cmd/haredoc + $(HARE_DEFINES) -o $(BINOUT)/haredoc ./cmd/haredoc docs/html: $(BINOUT)/haredoc scripts/gen-docs ./scripts/gen-docs diff --git a/cmd/hare/plan.ha b/cmd/hare/plan.ha @@ -50,6 +50,7 @@ type modcache = struct { type plan = struct { context: *module::context, + target: *target, workdir: str, counter: uint, scheduled: []*task, @@ -69,7 +70,11 @@ type plan_progress = struct { maxwidth: size, }; -fn mkplan(ctx: *module::context, libs: []str) plan = { +fn mkplan( + ctx: *module::context, + libs: []str, + target: *target, +) plan = { const rtdir = match (module::lookup(ctx, ["rt"])) { case let err: module::error => fmt::fatal("Error resolving rt:", module::strerror(err)); @@ -99,6 +104,7 @@ fn mkplan(ctx: *module::context, libs: []str) plan = { return plan { context = ctx, + target = target, workdir = os::tryenv("HARE_DEBUG_WORKDIR", temp::dir()), script = path::allocate(&buf), environ = alloc([ diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha @@ -66,14 +66,19 @@ fn sched_module(plan: *plan, ident: ast::ident, link: *[]*task) *task = { // Schedules a task which compiles objects into an executable. fn sched_ld(plan: *plan, output: str, depend: *task...) *task = { + const ld_cmd = os::tryenv("LD", if (len(plan.libs) > 0) { + // C compiler is used as linker if we -l something + yield plan.target.cc_cmd; + } else { + yield plan.target.ld_cmd; + }); + 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"), + ld_cmd, "-T", plan.script, "-o", output, ]), @@ -105,13 +110,12 @@ fn sched_ld(plan: *plan, output: str, depend: *task...) *task = { // Schedules a task which merges objects into an archive. fn sched_ar(plan: *plan, output: str, depend: *task...) *task = { + const ar_cmd = os::tryenv("AR", plan.target.ar_cmd); let task = alloc(task { status = status::SCHEDULED, output = output, depend = alloc(depend...), - cmd = alloc([ - os::tryenv("AR", "ar"), "-csr", output, - ]), + cmd = alloc([ar_cmd, "-csr", output]), module = void, }); for (let i = 0z; i < len(depend); i += 1) { @@ -124,13 +128,12 @@ fn sched_ar(plan: *plan, output: str, depend: *task...) *task = { // Schedules a task which compiles assembly into an object. fn sched_as(plan: *plan, output: str, input: str, depend: *task...) *task = { + const as_cmd = os::tryenv("AS", plan.target.as_cmd); let task = alloc(task { status = status::SCHEDULED, output = output, depend = alloc(depend...), - cmd = alloc([ - os::tryenv("AS", "as"), "-g", "-o", output, input, - ]), + cmd = alloc([as_cmd, "-g", "-o", output, input]), module = void, }); append(plan.scheduled, task); @@ -139,12 +142,16 @@ fn sched_as(plan: *plan, output: str, input: str, depend: *task...) *task = { // Schedules a task which compiles an SSA file into assembly. fn sched_qbe(plan: *plan, output: str, depend: *task) *task = { + const qbe_cmd = os::tryenv("QBE", "qbe"); let task = alloc(task { status = status::SCHEDULED, output = output, depend = alloc([depend]), cmd = alloc([ - os::tryenv("QBE", "qbe"), "-o", output, depend.output, + qbe_cmd, + "-t", plan.target.qbe_target, + "-o", output, + depend.output, ]), module = void, }); diff --git a/cmd/hare/subcmds.ha b/cmd/hare/subcmds.ha @@ -18,16 +18,6 @@ use path; use strings; use unix::tty; -fn default_tags() []module::tag = { - return alloc([module::tag { - name = strings::dup(os::machine()), - mode = module::tag_mode::INCLUSIVE, - }, module::tag { - name = strings::dup(PLATFORM), - mode = module::tag_mode::INCLUSIVE, - }]); -}; - fn addtags(tags: []module::tag, in: str) ([]module::tag | void) = { let in = match (module::parsetags(in)) { case void => @@ -87,7 +77,8 @@ fn build(args: []str) void = { const cmd = getopt::parse(args, help...); defer getopt::finish(&cmd); - let tags = default_tags(); + let target = default_target(); + let tags = module::tags_dup(target.tags); defer module::tags_free(tags); let verbose = false; @@ -113,7 +104,17 @@ fn build(args: []str) void = { case 'o' => output = opt.1; case 't' => - abort("-t option not implemented yet."); // TODO + for (let i = 0z; i < len(targets); i += 1) { + if (targets[i].name == opt.1) { + target = &targets[i]; + module::tags_free(tags); + tags = module::tags_dup(targets[i].tags); + break; + }; + }; + if (target == null) { + fmt::fatal("Unsupported target '{}'", opt.1); + }; case 'T' => tags = match (addtags(tags, opt.1)) { case void => @@ -153,7 +154,7 @@ fn build(args: []str) void = { const ctx = module::context_init(tags, defines, HAREPATH); defer module::context_finish(&ctx); - const plan = mkplan(&ctx, libs); + const plan = mkplan(&ctx, libs, target); defer plan_finish(&plan); const ver = match (module::scan(&ctx, input)) { @@ -286,7 +287,8 @@ fn run(args: []str) void = { const cmd = getopt::parse(args, help...); defer getopt::finish(&cmd); - let tags = default_tags(); + const target = default_target(); + let tags = module::tags_dup(target.tags); defer module::tags_free(tags); let verbose = false; @@ -345,7 +347,7 @@ fn run(args: []str) void = { const ctx = module::context_init(tags, defines, HAREPATH); defer module::context_finish(&ctx); - const plan = mkplan(&ctx, libs); + const plan = mkplan(&ctx, libs, target); defer plan_finish(&plan); const ver = match (module::scan(&ctx, input)) { @@ -396,8 +398,8 @@ fn test(args: []str) void = { const cmd = getopt::parse(args, help...); defer getopt::finish(&cmd); - let tags = default_tags(); - defer module::tags_free(tags); + const target = default_target(); + let tags = module::tags_dup(target.tags); append(tags, module::tag { name = strings::dup("test"), mode = module::tag_mode::INCLUSIVE, @@ -462,7 +464,7 @@ fn test(args: []str) void = { const ctx = module::context_init(tags, defines, HAREPATH); defer module::context_finish(&ctx); - const plan = mkplan(&ctx, libs); + const plan = mkplan(&ctx, libs, target); defer plan_finish(&plan); let depends: []*task = []; @@ -534,7 +536,8 @@ fn version(args: []str) void = { if (verbose) { fmt::errorln()!; fmt::printf("Build tags\t")!; - const tags = default_tags(); + const target = default_target(); + const tags = target.tags; for (let i = 0z; i < len(tags); i += 1) { const tag = tags[i]; const inclusive = (tag.mode & module::tag_mode::INCLUSIVE) == 0; diff --git a/cmd/hare/target.ha b/cmd/hare/target.ha @@ -0,0 +1,73 @@ +use hare::module; +use hare::module::{tag_mode}; + +type target = struct { + name: str, + ar_cmd: str, + as_cmd: str, + cc_cmd: str, + ld_cmd: str, + qbe_target: str, + tags: []module::tag, +}; + +fn default_target() *target = { + for (let i = 0z; i < len(targets); i += 1) { + if (targets[i].tags[0].name == ARCH) { + return &targets[i]; + }; + }; + abort("Build configuration error - unknown default target"); +}; + +// TODO: +// - Source default target information from here +// - Implement cross compiling to other kernels (e.g. Linux => FreeBSD) +// - sysroots +const targets: [_]target = [ + target { + name = "aarch64", + ar_cmd = AARCH64_AR, + as_cmd = AARCH64_AS, + cc_cmd = AARCH64_CC, + ld_cmd = AARCH64_LD, + qbe_target = "arm64", + tags = [module::tag { + name = "aarch64", + mode = tag_mode::INCLUSIVE, + }, module::tag { + name = PLATFORM, + mode = module::tag_mode::INCLUSIVE, + }], + }, + target { + name = "riscv64", + ar_cmd = RISCV64_AR, + as_cmd = RISCV64_AS, + cc_cmd = RISCV64_CC, + ld_cmd = RISCV64_LD, + qbe_target = "rv64", + tags = [module::tag { + name = "riscv64", + mode = tag_mode::INCLUSIVE, + }, module::tag { + name = PLATFORM, + mode = module::tag_mode::INCLUSIVE, + }], + }, + target { + name = "x86_64", + ar_cmd = X86_64_AR, + as_cmd = X86_64_AS, + cc_cmd = X86_64_CC, + ld_cmd = X86_64_LD, + qbe_target = "amd64_sysv", + tags = [module::tag { + name = "x86_64", + mode = tag_mode::INCLUSIVE, + }, module::tag { + name = PLATFORM, + mode = module::tag_mode::INCLUSIVE, + }], + }, +]; diff --git a/config.example.mk b/config.example.mk @@ -30,3 +30,19 @@ SCDOC = scdoc # Where to store build artifacts HARECACHE = .cache BINOUT = .bin + +# Cross-compiling settings +AARCH64_AS=aarch64-as +AARCH64_AR=aarch64-ar +AARCH64_CC=aarch64-cc +AARCH64_LD=aarch64-ld + +RISCV64_AS=riscv64-as +RISCV64_AR=riscv64-ar +RISCV64_CC=riscv64-cc +RISCV64_LD=riscv64-ld + +X86_64_AS=x86_64-as +X86_64_AR=x86_64-ar +X86_64_CC=x86_64-cc +X86_64_LD=x86_64-ld diff --git a/hare/module/scan.ha b/hare/module/scan.ha @@ -443,6 +443,18 @@ export fn tags_free(tags: []tag) void = { free(tags); }; +// Duplicates a set of tags. +export fn tags_dup(tags: []tag) []tag = { + let new: []tag = alloc([], len(tags)); + for (let i = 0z; i < len(tags); i += 1) { + append(new, tag { + name = strings::dup(tags[i].name), + mode = tags[i].mode, + }); + }; + return new; +}; + // Compares two tag sets and tells you if they are compatible. export fn tagcompat(have: []tag, want: []tag) bool = { // XXX: O(n²), lame diff --git a/rt/+linux/start+aarch64-libc.s b/rt/+linux/start+aarch64-libc.s @@ -5,5 +5,4 @@ _start: mov x30, #0 mov x0, sp add sp, x0, #-16 - and sp, sp, #-16 b rt.start_linux diff --git a/targets.mk b/targets.mk @@ -0,0 +1,17 @@ +HARE_DEFINES:=\ + -D PLATFORM:str='"'"$(PLATFORM)"'"' \ + -D ARCH:str='"'"$(ARCH)"'"' \ + -D VERSION:str='"'"$$(./scripts/version)"'"' \ + -D HAREPATH:str='"'"$(HAREPATH)"'"' \ + -D AARCH64_AS:str='"'"$(AARCH64_AS)"'"' \ + -D AARCH64_AR:str='"'"$(AARCH64_AR)"'"' \ + -D AARCH64_CC:str='"'"$(AARCH64_CC)"'"' \ + -D AARCH64_LD:str='"'"$(AARCH64_LD)"'"' \ + -D RISCV64_AS:str='"'"$(RISCV64_AS)"'"' \ + -D RISCV64_AR:str='"'"$(RISCV64_AR)"'"' \ + -D RISCV64_CC:str='"'"$(RISCV64_CC)"'"' \ + -D RISCV64_LD:str='"'"$(RISCV64_LD)"'"' \ + -D X86_64_AS:str='"'"$(X86_64_AS)"'"' \ + -D X86_64_AR:str='"'"$(X86_64_AR)"'"' \ + -D X86_64_CC:str='"'"$(X86_64_CC)"'"' \ + -D X86_64_LD:str='"'"$(X86_64_LD)"'"'