romdevtools 0.56.1 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ // sh-elf-gcc — WASM toolchain wrappers for Dreamcast (SH-4) C builds.
2
+ //
3
+ // Pipeline (mirrors mips-elf-gcc/gcc.js — gcc-the-driver can't fork/exec under
4
+ // emscripten, so we orchestrate cc1 → as → ld → objcopy through callMain):
5
+ // runCc1sh({source, headers, options}) → SH assembly (.s)
6
+ // runShAs({source, includes}) → .o ELF object
7
+ // runShLd({objects, linkScript, ...}) → linked .elf (+ map)
8
+ // runShObjcopy({elf}) → raw .bin
9
+ //
10
+ // The Dreamcast SH-4 is little-endian, m4-single-only FP. Single endianness — no
11
+ // EL/EB split like MIPS. The staged tool stems keep the createMips* EXPORT_NAMEs
12
+ // the build script reused, but the glue filenames are sh-elf-*.
13
+
14
+ import { fileURLToPath } from "node:url";
15
+ import { existsSync } from "node:fs";
16
+ import path from "node:path";
17
+
18
+ import { runIsolated, textFile, binaryFile, getOutputBytes, getOutputText } from "../_worker/run.js";
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // The WASM ships in romdev-toolchain-sh-gcc; fall back to the in-tree dev copy.
24
+ function resolveShGlue(file) {
25
+ try {
26
+ const u = import.meta.resolve("romdev-toolchain-sh-gcc");
27
+ const p = path.join(path.dirname(fileURLToPath(u)), "wasm", file);
28
+ if (existsSync(p)) return p;
29
+ } catch { /* not resolvable — fall through */ }
30
+ const local = path.join(__dirname, "wasm", file);
31
+ if (existsSync(local)) return local;
32
+ throw new Error(`sh-elf-gcc WASM (${file}) not found — build it with scripts/build-sh-wasm-tools.sh`);
33
+ }
34
+ const _glue = {};
35
+ const shGlue = (file) => (_glue[file] ??= resolveShGlue(file));
36
+
37
+ // SH-4 little-endian, m4-single-only FP. cc1 wants -ml; as wants -little --isa=sh4.
38
+ const CC1_ARCH = ["-ml", "-m4-single-only"];
39
+ const AS_ARCH = ["-little", "--isa=sh4"];
40
+
41
+ // ── cc1 — SH gcc C frontend, source → assembly ───────────────────────
42
+ export async function runCc1sh(args) {
43
+ const { source, options = [] } = args;
44
+ const headers = args.headers ?? {};
45
+ const inputFiles = [textFile("/work/main.c", source)];
46
+ for (const [name, content] of Object.entries(headers)) inputFiles.push(textFile("/work/" + name, content));
47
+ const argv = [
48
+ ...CC1_ARCH,
49
+ "-iquote", "/work", "-I", "/work",
50
+ ...options,
51
+ "/work/main.c", "-o", "/work/main.s",
52
+ ];
53
+ const r = await runIsolated({
54
+ gluePath: shGlue("cc1.mjs"),
55
+ argv, inputFiles,
56
+ outputFiles: [{ vfsPath: "/work/main.s", encoding: "utf8" }],
57
+ });
58
+ return { log: r.log, exitCode: r.exitCode, asmSource: getOutputText(r, "/work/main.s") || null,
59
+ ...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
60
+ }
61
+
62
+ // ── sh-elf-as — GNU assembler, .s → .o ───────────────────────────────
63
+ export async function runShAs(args) {
64
+ const { source, options = [] } = args;
65
+ const includes = args.includes ?? {};
66
+ const binaryIncludes = args.binaryIncludes ?? {};
67
+ const inputFiles = [textFile("/work/main.s", source)];
68
+ for (const [name, content] of Object.entries(includes)) inputFiles.push(textFile("/work/" + name, content));
69
+ for (const [name, bytes] of Object.entries(binaryIncludes)) inputFiles.push(binaryFile("/work/" + name, bytes));
70
+ const argv = [...AS_ARCH, "-I", "/work", ...options, "/work/main.s", "-o", "/work/main.o"];
71
+ const r = await runIsolated({
72
+ gluePath: shGlue("sh-elf-as.mjs"),
73
+ argv, inputFiles,
74
+ outputFiles: [{ vfsPath: "/work/main.o", encoding: "base64" }],
75
+ });
76
+ return { log: r.log, exitCode: r.exitCode, object: getOutputBytes(r, "/work/main.o"),
77
+ ...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
78
+ }
79
+
80
+ // ── sh-elf-ld — GNU linker, .o + linker script → .elf ────────────────
81
+ export async function runShLd(args) {
82
+ const { objects, linkScript, libraries = [], libraryPaths = [], options = [] } = args;
83
+ const archives = args.archives ?? {};
84
+ const inputFiles = [textFile("/work/link.ld", linkScript)];
85
+ for (const [name, bytes] of Object.entries(objects)) inputFiles.push(binaryFile("/work/" + name, bytes));
86
+ for (const [name, bytes] of Object.entries(archives)) inputFiles.push(binaryFile("/work/" + name, bytes));
87
+ const argv = [
88
+ "-EL",
89
+ "-T", "/work/link.ld",
90
+ "-o", "/work/main.elf",
91
+ "-Map=/work/main.map",
92
+ ...libraryPaths.flatMap((p) => ["-L", p]),
93
+ ...Object.keys(objects).map((n) => "/work/" + n),
94
+ ...libraries.map((l) => `-l${l}`),
95
+ ...options,
96
+ ];
97
+ const r = await runIsolated({
98
+ gluePath: shGlue("sh-elf-ld.mjs"),
99
+ argv, inputFiles,
100
+ outputFiles: [
101
+ { vfsPath: "/work/main.elf", encoding: "base64" },
102
+ { vfsPath: "/work/main.map", encoding: "utf8" },
103
+ ],
104
+ });
105
+ return { log: r.log, exitCode: r.exitCode, elf: getOutputBytes(r, "/work/main.elf"),
106
+ map: getOutputText(r, "/work/main.map") || null,
107
+ ...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
108
+ }
109
+
110
+ // ── sh-elf-objcopy — ELF → raw .bin ──────────────────────────────────
111
+ export async function runShObjcopy(args) {
112
+ const { elf, options = [] } = args;
113
+ const inputFiles = [binaryFile("/work/main.elf", elf)];
114
+ const argv = ["-O", "binary", ...options, "/work/main.elf", "/work/main.bin"];
115
+ const r = await runIsolated({
116
+ gluePath: shGlue("sh-elf-objcopy.mjs"),
117
+ argv, inputFiles,
118
+ outputFiles: [{ vfsPath: "/work/main.bin", encoding: "base64" }],
119
+ });
120
+ return { log: r.log, exitCode: r.exitCode, binary: getOutputBytes(r, "/work/main.bin"),
121
+ ...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
122
+ }