@yowasp/clang 0.0.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.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ YoWASP Clang/LLD package
2
+ ========================
3
+
4
+ This package provides a complete [Clang/LLD][] toolchain built for [WebAssembly][] and targeting WebAssembly as well. See the [overview of the YoWASP project][yowasp] for details.
5
+
6
+ At the moment, this package only provides an API allowing to run Clang and LLD in a virtual filesystem; no binaries are provided.
7
+
8
+ [llvm/clang]: https://llvm.org/
9
+ [webassembly]: https://webassembly.org/
10
+ [yowasp]: https://yowasp.github.io/
11
+
12
+
13
+ API reference
14
+ -------------
15
+
16
+ This package provides one function:
17
+
18
+ - `runLLVM`
19
+
20
+ For more detail, see the documentation for [the JavaScript YoWASP runtime](https://github.com/YoWASP/runtime-js#api-reference).
21
+
22
+
23
+ Versioning
24
+ ----------
25
+
26
+ The version of this package is derived from the upstream LLVM package version in the `X.Y.Z-S-M` or `X.Y.Z-M` format, where the symbols are:
27
+
28
+ 1. `X`: LLVM major version
29
+ 2. `Y`: LLVM minor version
30
+ 3. `Z`: LLVM patch version
31
+ 4. `S`: `gitK` for unreleased LLVM snapshots, `rcN` for LLVM release candidates, not present for LLVM releases
32
+ 5. `M`: package build version; disambiguates different builds produced from the same LLVM source tree
33
+
34
+ With this scheme, there is a direct correspondence between upstream versions and [SemVer][semver] NPM package versions.
35
+
36
+ [semver]: https://semver.org/
37
+
38
+
39
+ License
40
+ -------
41
+
42
+ This package is covered by the [MIT license](LICENSE.txt), which is the same as the [Boolector license](https://github.com/Boolector/boolector/blob/master/COPYING).
package/lib/api.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ export type Tree = {
2
+ [name: string]: Tree | string | Uint8Array
3
+ };
4
+
5
+ export type InputStream =
6
+ (byteLength: number) => Uint8Array | null;
7
+
8
+ export type OutputStream =
9
+ (bytes: Uint8Array | null) => void;
10
+
11
+ export type ProgressCallback =
12
+ (event: { source: object, totalLength: number, doneLength: number }) => void;
13
+
14
+ export type RunOptions = {
15
+ stdin?: InputStream | null;
16
+ stdout?: OutputStream | null;
17
+ stderr?: OutputStream | null;
18
+ decodeASCII?: boolean;
19
+ synchronously?: boolean;
20
+ fetchProgress?: ProgressCallback;
21
+ };
22
+
23
+ export type Command =
24
+ (args?: string[], files?: Tree, options?: RunOptions) => Promise<Tree> | Tree | undefined;
25
+
26
+ export class Exit extends Error {
27
+ code: number;
28
+ files: Tree;
29
+ }
30
+
31
+ //--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------
32
+
33
+ export const runLLVM: Command;
34
+
35
+ export const commands: {
36
+ 'addr2line': Command,
37
+ 'size': Command,
38
+ 'objdump': Command,
39
+ 'objcopy': Command,
40
+ 'strip': Command,
41
+ 'c++filt': Command,
42
+ 'ar': Command,
43
+ 'ranlib': Command,
44
+ 'wasm-ld': Command,
45
+ 'clang': Command,
46
+ 'clang++': Command,
47
+ };
package/lib/api.js ADDED
@@ -0,0 +1,217 @@
1
+ import { Application } from '@yowasp/runtime';
2
+ import * as resources from '../gen/llvm-resources.js';
3
+ import { instantiate } from '../gen/llvm.js';
4
+
5
+ import { Exit } from '@yowasp/runtime';
6
+
7
+ const llvm = new Application(resources, instantiate, 'yowasp-llvm');
8
+ const runLLVM = llvm.run.bind(llvm);
9
+
10
+ function subcommand(command, subcommandName) {
11
+ return function (args, files, options) {
12
+ if (args === undefined || args == null)
13
+ return command(args, files, options); // preload resources
14
+ return command([subcommandName, ...args], files, options);
15
+ }
16
+ }
17
+
18
+ // horrific ??? code. [insert standard disclaimer here]
19
+ // 'it cant hurt me if im not looking at it'
20
+ function runClang(args, files, options = {}) {
21
+ if (args === undefined || args === null)
22
+ return runLLVM(args, files, options); // preload resources
23
+
24
+ // We pattern-match output of `-###` plus `args` to understand which subprocesses to run.
25
+ // If `args` contains `-###` this is an explicit user request to display these subprocesess,
26
+ // and we should not interpret the output in any way.
27
+ if (args.includes("-###"))
28
+ return runLLVM(args, files, options);
29
+
30
+ // All of these options have more priority than `-###`, and we shouldn't interfere with them.
31
+ if (args.includes(`--version`) ||
32
+ args.includes(`-help`) ||
33
+ args.includes(`--help`) ||
34
+ args.includes(`--help-hidden`))
35
+ return runLLVM(args, files, options);
36
+
37
+ function writeStderr(output) {
38
+ if (options.stderr === undefined) {
39
+ console.log(output);
40
+ } else {
41
+ options.stderr(new TextEncoder().encode(output));
42
+ options.stderr(null);
43
+ }
44
+ }
45
+
46
+ /** See the `clang/lib/Driver/Job.cpp` file, `Command::Print()` subroutine, as well as
47
+ * the `llvm/lib/Support/Program.cpp` file, `sys::printArg()` subroutine.
48
+ * @param {string} line
49
+ * @return {string[]} */
50
+ function unquoteClangArgs(line) {
51
+ return Array.from(line.matchAll(/ (?:([^ "]+)|"((?:[^"\\$]|\\["\\$])+)")/g), (match) => {
52
+ if (match[1] !== undefined) {
53
+ return match[1];
54
+ } else if (match[2] !== undefined) {
55
+ return match[2].replaceAll(/\\["$\\]/g, (m) => m[1]);
56
+ }
57
+ });
58
+ }
59
+
60
+ let gen = (function* () {
61
+ const [arg0, ...argsRest] = args;
62
+
63
+ /** @type {Uint8Array[]} Output of `-###` */
64
+ const outputSubarrays = [];
65
+ function captureOutput(bytes) {
66
+ if (bytes !== null)
67
+ outputSubarrays.push(new Uint8Array(bytes));
68
+ }
69
+
70
+ /** @type {Exit | undefined} Outcome of running `-###` */
71
+ let hash3Error = undefined;
72
+ try {
73
+ yield runLLVM([arg0, "-###", ...argsRest], files, {
74
+ stdout: captureOutput,
75
+ stderr: captureOutput,
76
+ synchronously: options.synchronously,
77
+ });
78
+ } catch (err) {
79
+ hash3Error = err;
80
+ }
81
+
82
+ const outputArray = new Uint8Array(outputSubarrays.reduce((a, b) => a + b.length, 0));
83
+ let outputLength = 0;
84
+ for (const outputSubarray of outputSubarrays) {
85
+ outputArray.subarray(outputLength, outputLength + outputSubarray.length).set(outputSubarray);
86
+ outputLength += outputSubarray.length;
87
+ }
88
+ const output = new TextDecoder().decode(outputArray);
89
+
90
+ if (hash3Error !== undefined) {
91
+ // Something definitely went wrong, and the output contains no commands to execute,
92
+ // but probably has a human-readable explanation. We had to squish it all to stderr
93
+ // though, even though some of it might've been printed to stdout originally.
94
+ writeStderr(output);
95
+ throw hash3Error;
96
+ }
97
+
98
+ // horrific in-band signaling code. please do not hold me to account for writing this
99
+ let state = 0;
100
+ /** @type {string[][]} */
101
+ const commands = [];
102
+ for (const line of output.split("\n")) {
103
+ if (state === 0) { // header
104
+ if (!(line.startsWith("clang") ||
105
+ line.startsWith("Target:") ||
106
+ line.startsWith("Thread model:") ||
107
+ line.startsWith("InstalledDir:") ||
108
+ line.startsWith("Build config:"))) {
109
+ state = 1;
110
+ }
111
+ }
112
+ if (state === 1) { // command lines
113
+ if (line === " (in-process)") {
114
+ // Ignore; indicates clang would ordinarily invoke itself as a library for
115
+ // the following command line. Since we do not have an ordinarily working
116
+ // compiler driver (but rather a compiler driver²), all ordinarily in-process
117
+ // invocations have to be performed with a separate `runLLVM` call.
118
+ } else if (line.startsWith(' "')) {
119
+ commands.push(unquoteClangArgs(line));
120
+ } else if (line === "") {
121
+ state = 2; // final command; success!
122
+ } else {
123
+ state = 3; // unknown input; error!
124
+ }
125
+ continue;
126
+ }
127
+ if (state === 2) { // success
128
+ state = 3;
129
+ }
130
+ if (state === 3) { // error
131
+ break;
132
+ }
133
+ }
134
+ if (state !== 2) { // no valid `-###` command list in the output
135
+ // Who knows what went wrong? Could have been an odd combination of options, could
136
+ // have been an error (with a zero exit code, other exit codes are handled above).
137
+ // We can't interpret the output of `-###` if there is even any, so just display it.
138
+ writeStderr(output);
139
+ } else { // valid `-###` command list recognized
140
+ // Verbose? Print `-###` output. This will differ slightly from a desktop compiler,
141
+ // but is more in the spirit of the `-v` option.
142
+ if (args.includes('-v'))
143
+ writeStderr(output);
144
+ // Run the command list.
145
+ for (const command of commands) {
146
+ if (command[0] === "") {
147
+ // Clang would normally run this command in-process, which is indicated by
148
+ // an empty argument in the command list, followed by clang's argv[0] for
149
+ // this command. This distinction doesn't matter for us.
150
+ command.shift();
151
+ }
152
+ // If this command line fails, the `Exit` exception will bubble up its exit code
153
+ // and output tree.
154
+ try {
155
+ files = yield runLLVM(command, files, options);
156
+ } catch (err) {
157
+ if (err instanceof Exit)
158
+ delete err.files.tmp;
159
+ throw err;
160
+ }
161
+ }
162
+ }
163
+ delete files.tmp;
164
+ return files;
165
+ })();
166
+
167
+ let promise, resolve, reject;
168
+ function runNext(value) {
169
+ try {
170
+ let done;
171
+ do {
172
+ ({ value, done } = gen.next(value));
173
+ } while (!(value instanceof Promise) && !done);
174
+ if (done) {
175
+ if (resolve) resolve(value);
176
+ else return value;
177
+ }
178
+ if (!promise) promise = new Promise((_resolve, _reject) =>
179
+ (resolve = _resolve, reject = _reject));
180
+ value.then(
181
+ nextVal => done ? resolve() : runNext(nextVal),
182
+ error => { // give the coroutine a first chance to handle the error.
183
+ // we have SEH at home!
184
+ try { ({ value, done } = gen.throw(error)); }
185
+ catch (e) { reject(e); }
186
+ });
187
+ }
188
+ catch (e) {
189
+ if (reject) reject(e);
190
+ else throw e;
191
+ }
192
+ }
193
+ const maybeSyncReturn = runNext(null);
194
+ return promise || maybeSyncReturn;
195
+ }
196
+
197
+ export { runLLVM, runClang };
198
+ export const commands = {
199
+ // LLVM tools
200
+ 'addr2line': subcommand(runLLVM, 'addr2line'), // actually `symbolizer`
201
+ 'ar': subcommand(runLLVM, 'ar'),
202
+ 'c++filt': subcommand(runLLVM, 'c++filt'),
203
+ 'dwarfdump': subcommand(runLLVM, 'dwarfdump'),
204
+ 'nm': subcommand(runLLVM, 'nm'),
205
+ 'objcopy': subcommand(runLLVM, 'objcopy'),
206
+ 'objdump': subcommand(runLLVM, 'objdump'),
207
+ 'readobj': subcommand(runLLVM, 'readobj'),
208
+ 'ranlib': subcommand(runLLVM, 'ranlib'), // actually `ar`
209
+ 'size': subcommand(runLLVM, 'size'),
210
+ 'strip': subcommand(runLLVM, 'strip'), // actually `objcopy`
211
+ 'symbolizer': subcommand(runLLVM, 'symbolizer'),
212
+ // Compiler and linker
213
+ 'wasm-ld': subcommand(runLLVM, 'wasm-ld'),
214
+ 'clang': subcommand(runClang, 'clang'),
215
+ 'clang++': subcommand(runClang, 'clang++'),
216
+ };
217
+ export const version = VERSION;
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@yowasp/clang",
3
+ "version": "__VERSION__",
4
+ "description": "LLVM/Clang/LLD toolchain targeting WebAssembly",
5
+ "author": "Catherine <whitequark@whitequark.org>",
6
+ "license": "ISC",
7
+ "homepage": "https://yowasp.org/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/YoWASP/clang.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/YoWASP/clang/issues"
14
+ },
15
+ "type": "module",
16
+ "files": [
17
+ "lib/api.d.ts",
18
+ "gen/bundle.js",
19
+ "gen/*-resources.tar",
20
+ "gen/*.wasm"
21
+ ],
22
+ "exports": {
23
+ "types": "./lib/api.d.ts",
24
+ "default": "./gen/bundle.js"
25
+ },
26
+ "types": "./lib/api.d.ts",
27
+ "devDependencies": {
28
+ "@bytecodealliance/jco": "1.15.1",
29
+ "@yowasp/runtime": "11.0.67",
30
+ "esbuild": "^0.25.11"
31
+ },
32
+ "scripts": {
33
+ "transpile": "jco new ../wasi-prefix/usr/bin/llvm.wasm --wasi-command --output llvm.wasm && jco transpile llvm.wasm --instantiation async --no-typescript --no-namespaced-exports --map 'wasi:io/*=runtime#io' --map 'wasi:cli/*=runtime#cli' --map 'wasi:clocks/*=runtime#*' --map 'wasi:filesystem/*=runtime#fs' --map 'wasi:random/*=runtime#random' --out-dir gen/",
34
+ "pack": "yowasp-pack-resources gen/llvm-resources.js gen ../wasi-prefix/usr usr",
35
+ "build": "esbuild --bundle lib/api.js --outfile=gen/bundle.js --format=esm --platform=node",
36
+ "all": "npm run transpile && npm run pack && npm run build"
37
+ }
38
+ }
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@yowasp/clang",
3
+ "version": "0.0.0",
4
+ "description": "LLVM/Clang/LLD toolchain targeting WebAssembly",
5
+ "author": "Catherine <whitequark@whitequark.org>",
6
+ "license": "ISC",
7
+ "homepage": "https://yowasp.org/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/YoWASP/clang.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/YoWASP/clang/issues"
14
+ }
15
+ }
package/prepare.py ADDED
@@ -0,0 +1,34 @@
1
+ import re
2
+ import json
3
+ import subprocess
4
+
5
+
6
+ llvm_version_raw = subprocess.check_output([
7
+ "git", "-C", "../llvm-src", "describe", "--tags", "HEAD"
8
+ ], encoding="utf-8").strip()
9
+ print(llvm_version_raw)
10
+
11
+ git_rev_list_raw = subprocess.check_output([
12
+ "git", "rev-list", "HEAD"
13
+ ], encoding="utf-8").split()
14
+
15
+ llvm_version = re.match(r"^llvmorg-(\d+)(?:\.(\d+)\.(\d+)(-rc\d+)?|-init-(.+)-g)?", llvm_version_raw)
16
+ llvm_major = int(llvm_version[1])
17
+ llvm_minor = int(llvm_version[2] or "0")
18
+ llvm_patch = int(llvm_version[3] or "0")
19
+ if not llvm_version[5]:
20
+ llvm_suffix = llvm_version[4] or ""
21
+ else:
22
+ llvm_suffix = f"-git{llvm_version[5]}"
23
+
24
+ distance = len(git_rev_list_raw) - 1
25
+
26
+ version = f"{llvm_major}.{llvm_minor}.{llvm_patch}{llvm_suffix}-{distance}"
27
+ print(f"version {version}")
28
+
29
+ with open("package-in.json", "rt") as f:
30
+ package_json = json.load(f)
31
+ package_json["version"] = version
32
+ package_json["scripts"]["build"] += f" --define:VERSION=\\\"{version}\\\""
33
+ with open("package.json", "wt") as f:
34
+ json.dump(package_json, f, indent=2)
@@ -0,0 +1,17 @@
1
+ const { runClang, commands } = await import('@yowasp/clang');
2
+
3
+ await commands.clang(["--version"]);
4
+
5
+ const { meowC } = await runClang(["clang", "test.c", "-o", "meowC"],
6
+ {"test.c": `#include <stdio.h>\nint main() { puts("meow"); }`});
7
+ const { meowCXX } = await runClang(["clang++", "test.cc", "-o", "meowCXX"],
8
+ {"test.cc": `#include <iostream>\nint main() { std::cout << "meow++" << std::endl; }`});
9
+
10
+ const { WASI } = await import('node:wasi');
11
+ for (const meow of [meowC, meowCXX]) {
12
+ const meowdule = await WebAssembly.compile(meow);
13
+ const wasi = new WASI({ version: 'preview1' });
14
+ const instance = await WebAssembly.instantiate(meowdule,
15
+ {wasi_snapshot_preview1: wasi.wasiImport});
16
+ wasi.start(instance);
17
+ }