@amritk/nish-x86_64-linux 0.10.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/INSTALL.md +468 -0
- package/LICENSE +21 -0
- package/bin/nish +0 -0
- package/package.json +31 -0
- package/runtime/nish.d.ts +290 -0
- package/runtime/nish.h +340 -0
- package/runtime/nish.mjs +143 -0
- package/runtime/runtime.c +1184 -0
- package/runtime/runtime_os.c +351 -0
- package/runtime/runtime_parallel.c +156 -0
- package/runtime/runtime_wasm.c +99 -0
- package/runtime/shim.mjs +672 -0
- package/scripts/build.sh +279 -0
- package/std/README.md +185 -0
- package/std/json.ts +402 -0
- package/std/pair.ts +28 -0
- package/std/testing.ts +347 -0
- package/std/text.ts +193 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the Nish global surface.
|
|
3
|
+
*
|
|
4
|
+
* Every Nish program is legal TypeScript syntax — the compiler parses it
|
|
5
|
+
* with the official TypeScript parser and nothing else. This file is what
|
|
6
|
+
* makes it legal TypeScript *semantics* too: reference it from a `tsconfig.json`
|
|
7
|
+
* (or with `/// <reference path="..." />`) and `tsc --noEmit`, your editor and
|
|
8
|
+
* your language server all accept `Result<T, E>`, `Ok(...)`, `i32` and the rest
|
|
9
|
+
* without a red squiggle.
|
|
10
|
+
*
|
|
11
|
+
* { "include": ["src/**\/*.ts", "node_modules/nish/runtime/nish.d.ts"] }
|
|
12
|
+
*
|
|
13
|
+
* **`nish` is still the authority.** TypeScript's structural checker is
|
|
14
|
+
* weaker than this compiler's in the places where Nish is deliberately
|
|
15
|
+
* stricter, and it cannot see the flow rules at all:
|
|
16
|
+
*
|
|
17
|
+
* - the integer and float widths are aliases of `number` here, so `tsc`
|
|
18
|
+
* lets an `i32` and an `f64` mix where `nish` refuses;
|
|
19
|
+
* - `tsc` does not know that a `Result` may not be dropped, that
|
|
20
|
+
* `orReturn()` returns early, or that the enclosing function has to
|
|
21
|
+
* return a `Result` of its own for it to be legal at all;
|
|
22
|
+
* - everything Phase 0 forbids (`any`, `throw`, `try`, prototypes,
|
|
23
|
+
* arrow functions, ...) is ordinary TypeScript and passes `tsc` happily.
|
|
24
|
+
*
|
|
25
|
+
* So a program that `tsc` accepts may still be rejected by `nish`; the
|
|
26
|
+
* reverse should never happen, and a case where it does is a bug in this file.
|
|
27
|
+
* `docs/LANGUAGE.md` is the normative description.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// ---- Numeric widths (docs/LANGUAGE.md -> Types) ------------------------------
|
|
31
|
+
//
|
|
32
|
+
// Nish treats these as distinct types that never mix implicitly. There is
|
|
33
|
+
// no way to say that in TypeScript without branding them, and a brand would
|
|
34
|
+
// break the literal syntax the language relies on (`let x: i32 = 5`), so they
|
|
35
|
+
// are aliases and the width check is left to `nish`.
|
|
36
|
+
|
|
37
|
+
type i32 = number;
|
|
38
|
+
type i64 = number;
|
|
39
|
+
type u8 = number;
|
|
40
|
+
type u16 = number;
|
|
41
|
+
type u32 = number;
|
|
42
|
+
type u64 = number;
|
|
43
|
+
type f32 = number;
|
|
44
|
+
type f64 = number;
|
|
45
|
+
|
|
46
|
+
// ---- Result (docs/LANGUAGE.md -> Result and error handling) ------------------
|
|
47
|
+
//
|
|
48
|
+
// Modelled as the tagged union TypeScript would use anyway, intersected with
|
|
49
|
+
// the method surface. That is what lets `if (r.ok)` and `if (r.isOk())` narrow
|
|
50
|
+
// `r` in `tsc` for the same reason and in the same places they narrow in
|
|
51
|
+
// `nish`.
|
|
52
|
+
|
|
53
|
+
/** The success arm of a `Result`, as the discriminant proves it. */
|
|
54
|
+
type ResultOk<T> = { readonly ok: true; readonly value: T };
|
|
55
|
+
/** The failure arm. */
|
|
56
|
+
type ResultErr<E> = { readonly ok: false; readonly error: E };
|
|
57
|
+
|
|
58
|
+
interface ResultMethods<T, E> {
|
|
59
|
+
/** True when this is the success arm; narrows `r` where it is a condition. */
|
|
60
|
+
isOk(): this is ResultOk<T> & ResultMethods<T, E>;
|
|
61
|
+
/** True when this is the failure arm; narrows `r` the other way. */
|
|
62
|
+
isErr(): this is ResultErr<E> & ResultMethods<T, E>;
|
|
63
|
+
/**
|
|
64
|
+
* The success payload, or an early `return Err(error)` from the enclosing
|
|
65
|
+
* function — Rust's `?`. `nish` additionally requires that function to
|
|
66
|
+
* return a `Result` whose error arm accepts `E`; `tsc` cannot check that.
|
|
67
|
+
*/
|
|
68
|
+
orReturn(): T;
|
|
69
|
+
/** The success payload, or `fallback` when this is the failure arm. */
|
|
70
|
+
unwrapOr(fallback: T): T;
|
|
71
|
+
/** The success payload, or `message` on stderr and exit 1. */
|
|
72
|
+
expect(message: string): T;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The outcome of an operation that can fail. `T` may be `void` for an
|
|
77
|
+
* operation with nothing to hand back; `E` may not be.
|
|
78
|
+
*/
|
|
79
|
+
type Result<T, E> = (ResultOk<T> | ResultErr<E>) & ResultMethods<T, E>;
|
|
80
|
+
|
|
81
|
+
/** The success value. Its `Result` type comes from the context, as `null`'s does. */
|
|
82
|
+
declare function Ok<T, E>(value: T): Result<T, E>;
|
|
83
|
+
declare function Ok<E>(): Result<void, E>;
|
|
84
|
+
/** The failure value, likewise. */
|
|
85
|
+
declare function Err<T, E>(error: E): Result<T, E>;
|
|
86
|
+
|
|
87
|
+
// ---- console and process -----------------------------------------------------
|
|
88
|
+
//
|
|
89
|
+
// Declared here with *Nish's* signatures — one argument, no format string,
|
|
90
|
+
// statement position — rather than borrowed from `lib.dom` or `@types/node`,
|
|
91
|
+
// which describe something much wider. `Console` is an interface so that a
|
|
92
|
+
// project which does pull `lib.dom` in merges with it instead of colliding;
|
|
93
|
+
// `Process` is not that lucky, so a project that needs `@types/node` for other
|
|
94
|
+
// reasons should drop this file's `process` rather than fight it.
|
|
95
|
+
|
|
96
|
+
interface Console {
|
|
97
|
+
/** `x` and a newline to stdout. Statement position; exactly one argument. */
|
|
98
|
+
log(x: string | number | boolean): void;
|
|
99
|
+
/** The same, on stderr. */
|
|
100
|
+
error(x: string | number | boolean): void;
|
|
101
|
+
}
|
|
102
|
+
declare var console: Console;
|
|
103
|
+
|
|
104
|
+
interface Process {
|
|
105
|
+
/**
|
|
106
|
+
* Terminate with `code`. Statement position, and a terminator: `never` is
|
|
107
|
+
* how TypeScript spells that, so a function ending in `process.exit(c)`
|
|
108
|
+
* satisfies its return type in `tsc` as it does in `nish`.
|
|
109
|
+
*/
|
|
110
|
+
exit(code: i32): never;
|
|
111
|
+
/** The command line; `argv[0]` is the program path, as in C. Read-only. */
|
|
112
|
+
readonly argv: string[];
|
|
113
|
+
/** The operating system the program runs on: `"linux"`, `"darwin"`, or `"unknown"`. */
|
|
114
|
+
readonly platform: string;
|
|
115
|
+
/** The architecture: `"x64"`, `"arm64"`, or `"unknown"`. */
|
|
116
|
+
readonly arch: string;
|
|
117
|
+
}
|
|
118
|
+
declare var process: Process;
|
|
119
|
+
|
|
120
|
+
// ---- Numeric conversions (there is no cast in Nish) ----------------------
|
|
121
|
+
|
|
122
|
+
declare function toI32(x: number | boolean): i32;
|
|
123
|
+
declare function toI64(x: number | boolean): i64;
|
|
124
|
+
declare function toU8(x: number | boolean): u8;
|
|
125
|
+
declare function toU16(x: number | boolean): u16;
|
|
126
|
+
declare function toU32(x: number | boolean): u32;
|
|
127
|
+
declare function toU64(x: number | boolean): u64;
|
|
128
|
+
declare function toF32(x: number | boolean): f32;
|
|
129
|
+
declare function toF64(x: number | boolean): f64;
|
|
130
|
+
/** Reinterpret the 64 bits of an `f64`, never convert the value. */
|
|
131
|
+
declare function f64ToBits(x: f64): i64;
|
|
132
|
+
declare function bitsToF64(bits: i64): f64;
|
|
133
|
+
|
|
134
|
+
// ---- Streams and files (globals: Nish has no package resolution) ---------
|
|
135
|
+
|
|
136
|
+
/** `s` to stdout with no trailing newline and no conversion. */
|
|
137
|
+
declare function write(s: string): void;
|
|
138
|
+
/** `s` to stderr, likewise. */
|
|
139
|
+
declare function writeError(s: string): void;
|
|
140
|
+
/**
|
|
141
|
+
* `message` and a newline to stderr, then exit 1. Terminates control flow, so
|
|
142
|
+
* it is `never`: that is what lets `tsc` agree that a function ending in a
|
|
143
|
+
* `panic` returns, and that `x` is not null after `if (x === null) { panic(...); }`
|
|
144
|
+
* — the guard-then-panic shape `self/` uses everywhere in place of an assert.
|
|
145
|
+
*/
|
|
146
|
+
declare function panic(message: string): never;
|
|
147
|
+
/**
|
|
148
|
+
* The whole file as a string; a path that cannot be read as one — missing, a
|
|
149
|
+
* directory, a parent that cannot be searched — prints `nish: cannot read
|
|
150
|
+
* <path>` and exits 1.
|
|
151
|
+
*/
|
|
152
|
+
declare function readFileSync(path: string): string;
|
|
153
|
+
/** The same read, answering `null` for every path the other exits over. */
|
|
154
|
+
declare function readFileSyncOrNull(path: string): string | null;
|
|
155
|
+
declare function writeFileSync(path: string, data: string): void;
|
|
156
|
+
declare function appendFileSync(path: string, data: string): void;
|
|
157
|
+
/** One directory, not recursive; whether a directory is there afterwards. */
|
|
158
|
+
declare function mkdirSync(path: string): boolean;
|
|
159
|
+
/** Whether a directory is at `path` right now. One `stat`, and never an exit. */
|
|
160
|
+
declare function isDirectorySync(path: string): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* The directory's entries, sorted ascending by bytes and without `.` or `..`,
|
|
163
|
+
* or `null` when it cannot be read. An empty directory is an empty array, so
|
|
164
|
+
* the null check is about the directory and not about its contents.
|
|
165
|
+
*/
|
|
166
|
+
declare function readdirSync(path: string): string[] | null;
|
|
167
|
+
/**
|
|
168
|
+
* `path` with every symbolic link resolved, as an absolute normalised path —
|
|
169
|
+
* or `null` when it does not resolve, a path that does not exist included.
|
|
170
|
+
*/
|
|
171
|
+
declare function realpathSync(path: string): string | null;
|
|
172
|
+
/** Run `argv[0]` through `PATH` and wait: the exit status, `128 + n` for a signal, `-1` for a failure. */
|
|
173
|
+
declare function spawnSync(argv: string[]): number;
|
|
174
|
+
/**
|
|
175
|
+
* The same run with each non-empty path receiving that stream, created or
|
|
176
|
+
* truncated; an empty string leaves that stream inherited.
|
|
177
|
+
*/
|
|
178
|
+
declare function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
179
|
+
/** One environment variable, or `null` when it is unset (an empty value is a set variable). */
|
|
180
|
+
declare function getenv(name: string): string | null;
|
|
181
|
+
/**
|
|
182
|
+
* A monotonic clock in nanoseconds, for timing a region of a program. The
|
|
183
|
+
* origin is arbitrary, so only the difference between two reads is meaningful.
|
|
184
|
+
*/
|
|
185
|
+
declare function monotonicNanos(): i64;
|
|
186
|
+
|
|
187
|
+
// ---- The builtin modules (`nish:`) ---------------------------------------------
|
|
188
|
+
//
|
|
189
|
+
// The same builtins, importable. `nish:` resolves to no file — the import
|
|
190
|
+
// renames a builtin rather than introducing one, and the compiler emits the
|
|
191
|
+
// same IR for either spelling — but `tsc` still has to be told the modules
|
|
192
|
+
// exist, or an editor reports every one of these imports as unresolved. The
|
|
193
|
+
// signatures are deliberately the globals' own, repeated rather than aliased:
|
|
194
|
+
// `export { readFileSync }` inside a `declare module` would re-export the
|
|
195
|
+
// global and lose the doc comment an editor shows at the call site.
|
|
196
|
+
//
|
|
197
|
+
// `docs/LANGUAGE.md` -> Builtins -> Builtin modules is the normative list.
|
|
198
|
+
|
|
199
|
+
declare module "nish:fs" {
|
|
200
|
+
export function readFileSync(path: string): string;
|
|
201
|
+
export function readFileSyncOrNull(path: string): string | null;
|
|
202
|
+
export function writeFileSync(path: string, data: string): void;
|
|
203
|
+
export function appendFileSync(path: string, data: string): void;
|
|
204
|
+
/** `true` when the directory was created, `false` when it already existed. */
|
|
205
|
+
export function mkdirSync(path: string): boolean;
|
|
206
|
+
export function isDirectorySync(path: string): boolean;
|
|
207
|
+
/**
|
|
208
|
+
* The directory's entries, sorted ascending by bytes and without `.` or
|
|
209
|
+
* `..`, or `null` when it cannot be read.
|
|
210
|
+
*/
|
|
211
|
+
export function readdirSync(path: string): string[] | null;
|
|
212
|
+
export function realpathSync(path: string): string | null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare module "nish:process" {
|
|
216
|
+
/** The global `process.exit`: a terminator, which `never` is how `tsc` spells it. */
|
|
217
|
+
export function exit(code: i32): never;
|
|
218
|
+
export function getenv(name: string): string | null;
|
|
219
|
+
export function spawnSync(argv: string[]): number;
|
|
220
|
+
/**
|
|
221
|
+
* The same run with each non-empty path receiving that stream, created or
|
|
222
|
+
* truncated; an empty string leaves that stream inherited.
|
|
223
|
+
*/
|
|
224
|
+
export function spawnSyncTo(argv: string[], stdoutPath: string, stderrPath: string): number;
|
|
225
|
+
/**
|
|
226
|
+
* A monotonic clock in nanoseconds. The origin is arbitrary, so only the
|
|
227
|
+
* difference between two reads is meaningful.
|
|
228
|
+
*/
|
|
229
|
+
export function monotonicNanos(): i64;
|
|
230
|
+
/** The command line; `argv[0]` is the program path, as in C. Read-only. */
|
|
231
|
+
export const argv: readonly string[];
|
|
232
|
+
/** The operating system the program runs on: `"linux"`, `"darwin"`, or `"unknown"`. */
|
|
233
|
+
export const platform: string;
|
|
234
|
+
/** The architecture: `"x64"`, `"arm64"`, or `"unknown"`. */
|
|
235
|
+
export const arch: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
declare module "nish:io" {
|
|
239
|
+
/** `s` to stdout with no trailing newline and no conversion. */
|
|
240
|
+
export function write(s: string): void;
|
|
241
|
+
/** The same, on stderr. */
|
|
242
|
+
export function writeError(s: string): void;
|
|
243
|
+
/** `message` to stderr, then exit 1. A terminator, as `process.exit` is. */
|
|
244
|
+
export function panic(message: string): never;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---- Arena (docs/LANGUAGE.md -> Arena) ---------------------------------------
|
|
248
|
+
|
|
249
|
+
declare const Arena: {
|
|
250
|
+
/** The current bump address. */
|
|
251
|
+
mark(): i64;
|
|
252
|
+
/** Free everything allocated since `m`. */
|
|
253
|
+
release(m: i64): void;
|
|
254
|
+
/** Recycle everything in O(1), keeping the newest chunk. */
|
|
255
|
+
reset(): void;
|
|
256
|
+
/** Bytes bumped in the current chunk. */
|
|
257
|
+
used(): i64;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* `CPtr` (WP27 S2): the address a `declare function` hands back, opaque and
|
|
262
|
+
* eight bytes wide. `docs/LANGUAGE.md` has the rules — it may be written in a
|
|
263
|
+
* `declare function` signature and on a local, compared with `null` or with
|
|
264
|
+
* another `CPtr`, and passed back to C, and that is all.
|
|
265
|
+
*
|
|
266
|
+
* An empty interface with a private brand rather than `unknown` or a type
|
|
267
|
+
* alias: `tsc` has to refuse the arithmetic and the dereference `nish` refuses,
|
|
268
|
+
* and it has to refuse assigning any other value to one. The brand is what makes
|
|
269
|
+
* it nominal, so a `{}` does not satisfy it; the name is never written, so the
|
|
270
|
+
* declaration costs a reader nothing.
|
|
271
|
+
*/
|
|
272
|
+
declare interface CPtr {
|
|
273
|
+
readonly __nishForeignPointer: unique symbol;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---- What this file cannot say ----------------------------------------------
|
|
277
|
+
//
|
|
278
|
+
// The typed-array aliases (`Int32Array`, `Float32Array`, `Float64Array`,
|
|
279
|
+
// `BigInt64Array`) are *not* declared here. In Nish each one names the
|
|
280
|
+
// element-typed array itself — `Int32Array` is `i32[]` — but redeclaring them
|
|
281
|
+
// would collide with `lib.es5.d.ts` and break every other type in the standard
|
|
282
|
+
// library. So `tsc` reads them as the JavaScript views it knows, which accept
|
|
283
|
+
// indexing and `.length` but not an array literal; write `i32[]` where you
|
|
284
|
+
// want both compilers to agree.
|
|
285
|
+
//
|
|
286
|
+
// `a.pop()` is `T` in Nish — an empty array panics, because there is no
|
|
287
|
+
// `undefined` to answer with — and `T | undefined` in `lib.es5.d.ts`. Narrowing
|
|
288
|
+
// the standard `Array<T>` would need a global augmentation that changed the
|
|
289
|
+
// method for every array in the project, including a host's, so this one is
|
|
290
|
+
// left as it is: `tsc` asks for a null check that `nish` does not need.
|
package/runtime/nish.h
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/* Nish runtime ABI, public C header.
|
|
2
|
+
*
|
|
3
|
+
* Include this from C drivers, N-API shims, or any other host that links the C
|
|
4
|
+
* runtime next to compiled Nish modules. Everything here is a contract shared
|
|
5
|
+
* with `self/runtime.ts` (the IR side) and the implementation, which is
|
|
6
|
+
* two translation units: `runtime/runtime.c` holds the core every program
|
|
7
|
+
* touches — the arena, strings, arrays, number formatting, the panics — and
|
|
8
|
+
* `runtime/runtime_os.c` holds everything that wraps a system call: the file
|
|
9
|
+
* functions, the directory and subprocess calls, `nish_getenv`, the monotonic
|
|
10
|
+
* clock, `nish_platform` / `nish_arch`. They are separate so that each carries
|
|
11
|
+
* its own measured code-size ceiling (docs/wp7-runtime.md, "Runtime additions
|
|
12
|
+
* and budget"); a host links both, which `scripts/build.sh` does for you when
|
|
13
|
+
* handed either. `tests/run.js` checks that every runtime function declared in
|
|
14
|
+
* runtime.ts has a prototype in this file.
|
|
15
|
+
*
|
|
16
|
+
* Headers generated by `nish --emit-header` include this one, so keep
|
|
17
|
+
* it C11-clean under -Wall -Wextra -Werror and usable from C++.
|
|
18
|
+
*/
|
|
19
|
+
#ifndef NISH_H
|
|
20
|
+
#define NISH_H
|
|
21
|
+
|
|
22
|
+
#include <stdbool.h>
|
|
23
|
+
#include <stddef.h>
|
|
24
|
+
#include <stdint.h>
|
|
25
|
+
|
|
26
|
+
#ifdef __cplusplus
|
|
27
|
+
extern "C" {
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
/* Bind a C declaration to an Nish symbol whose name C cannot spell (a
|
|
31
|
+
* keyword such as `double`): `int32_t double_(int32_t n) NISH_SYMBOL("double");`
|
|
32
|
+
* Generated headers use it; __USER_LABEL_PREFIX__ supplies the `_` Mach-O adds. */
|
|
33
|
+
#define NISH_STRINGIFY_(x) #x
|
|
34
|
+
#define NISH_STRINGIFY(x) NISH_STRINGIFY_(x)
|
|
35
|
+
#define NISH_SYMBOL(name) __asm__(NISH_STRINGIFY(__USER_LABEL_PREFIX__) name)
|
|
36
|
+
|
|
37
|
+
/* Thread-local storage for the arena and the RNG seed (WP20 T0). A host that
|
|
38
|
+
* includes this header must be compiled with the same `-DNISH_THREADS` the
|
|
39
|
+
* runtime was, because the storage class is part of the ABI: modules compiled
|
|
40
|
+
* with `nish --threads` reference `@nish_arena` as a `thread_local` global and
|
|
41
|
+
* ELF refuses to link that against a non-TLS definition. `scripts/build.sh
|
|
42
|
+
* --threads` passes the macro to every input, which is how `nish --threads
|
|
43
|
+
* --link` keeps the two halves in step. The same definition is in
|
|
44
|
+
* runtime/runtime.c and runtime/runtime_wasm.c. */
|
|
45
|
+
#ifdef NISH_THREADS
|
|
46
|
+
#define NISH_TLS _Thread_local
|
|
47
|
+
#else
|
|
48
|
+
#define NISH_TLS
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
/* ---- Strings ------------------------------------------------------------
|
|
52
|
+
* An Nish `string` is a pointer to this header: { u64 len, bytes[len], 0 }.
|
|
53
|
+
* `len` is the UTF-8 byte length (`s.length` in Nish). The bytes are
|
|
54
|
+
* NUL-terminated so `s->data` is a valid C string, but they may contain
|
|
55
|
+
* embedded NULs, so prefer `len` over strlen. Strings are immutable: a
|
|
56
|
+
* pointer can be shared freely and never needs copying.
|
|
57
|
+
*
|
|
58
|
+
* Literals live in the module's constant data; every other string is
|
|
59
|
+
* allocated in the arena and stays valid until `nish_reset_arena` or
|
|
60
|
+
* `nish_free_arena`. Never free a string yourself. */
|
|
61
|
+
typedef struct nish_str {
|
|
62
|
+
uint64_t len;
|
|
63
|
+
char data[];
|
|
64
|
+
} nish_str;
|
|
65
|
+
|
|
66
|
+
/* ---- Arena --------------------------------------------------------------
|
|
67
|
+
* One bump allocator per process, or one per thread under `-DNISH_THREADS`
|
|
68
|
+
* (WP20 T0; the layout and every function below are the same either way).
|
|
69
|
+
* Compiled modules read this struct directly
|
|
70
|
+
* (the fast path is inlined into the IR), so its layout is ABI:
|
|
71
|
+
* %struct.nish_arena = type { i8*, i64, i64, i8* }
|
|
72
|
+
* Fields: current chunk buffer, bump offset, chunk capacity, chunk list.
|
|
73
|
+
* `off` and `cap` are `uint64_t` and not `size_t` because that `i64` is what
|
|
74
|
+
* the inlined fast path bumps and compares on every target: under wasm32 a
|
|
75
|
+
* `size_t` pair would put them at bytes 4 and 8 while compiled code reads 8
|
|
76
|
+
* and 16. runtime.c and runtime_wasm.c static-assert these offsets. */
|
|
77
|
+
struct nish_arena {
|
|
78
|
+
char *buf;
|
|
79
|
+
uint64_t off;
|
|
80
|
+
uint64_t cap;
|
|
81
|
+
void *chunks;
|
|
82
|
+
};
|
|
83
|
+
extern NISH_TLS struct nish_arena nish_arena;
|
|
84
|
+
|
|
85
|
+
/* Bump allocation: 8-byte rounded and aligned, uninitialised. */
|
|
86
|
+
void *nish_alloc_struct(uint64_t size);
|
|
87
|
+
/* Slow path: push a new chunk (at least 64 KB) and bump from it. Called by
|
|
88
|
+
* the inlined fast path when the current chunk is full; rarely by hosts. */
|
|
89
|
+
void *nish_arena_grow(uint64_t size);
|
|
90
|
+
/* Recycle everything in O(1): keeps the newest chunk, frees the rest. Every
|
|
91
|
+
* arena string and object becomes invalid. Call it between batches. */
|
|
92
|
+
void nish_reset_arena(void);
|
|
93
|
+
/* Release every chunk. The arena is lazy, so it can be used again afterwards. */
|
|
94
|
+
void nish_free_arena(void);
|
|
95
|
+
/* Arena scopes (WP6). A mark is the current bump address (`buf + off`), or 0
|
|
96
|
+
* while the arena is empty; `nish_arena_release(mark)` frees everything
|
|
97
|
+
* allocated since that mark (chunks pushed after it are freed, a mark of 0
|
|
98
|
+
* behaves like `nish_reset_arena`). Releasing while an object, string or
|
|
99
|
+
* array allocated after the mark is still referenced is undefined behaviour.
|
|
100
|
+
* Compiled functions whose allocations provably die with them bracket their
|
|
101
|
+
* body with these two calls; `Arena.mark/release/used` expose them. */
|
|
102
|
+
uint64_t nish_arena_mark(void);
|
|
103
|
+
void nish_arena_release(uint64_t mark);
|
|
104
|
+
/* Call-site reclaim (WP9): release back to `mark` but keep the newest block,
|
|
105
|
+
* moving it down to `mark` and answering its new address. Only a string may be
|
|
106
|
+
* kept, because a string is one flat block with no interior pointers; `p` must
|
|
107
|
+
* be the last allocation the arena handed out. Anything the guards cannot
|
|
108
|
+
* prove (a `p` outside the current chunk, a stale or newer mark, no room below
|
|
109
|
+
* it) leaves the arena untouched and answers `p`. Compiled callers use it to
|
|
110
|
+
* reclaim the temporaries a string-returning callee left behind. */
|
|
111
|
+
void *nish_arena_keep(uint64_t mark, void *p);
|
|
112
|
+
/* Bytes bumped in the current chunk (`Arena.used()`); a steady-state loop keeps it flat. */
|
|
113
|
+
uint64_t nish_arena_used(void);
|
|
114
|
+
|
|
115
|
+
/* ---- String operations ------------------------------------------------- */
|
|
116
|
+
/* Copy `len` bytes into a new arena string (`bytes` need not be terminated). */
|
|
117
|
+
nish_str *nish_str_new(const char *bytes, uint64_t len);
|
|
118
|
+
/* `a + b` */
|
|
119
|
+
nish_str *nish_str_concat(const nish_str *a, const nish_str *b);
|
|
120
|
+
/* `a === b`: same bytes (pointer equality is a fast path, not a requirement). */
|
|
121
|
+
bool nish_str_eq(const nish_str *a, const nish_str *b);
|
|
122
|
+
/* `s.length`: the byte length. Compiled code loads the header directly. */
|
|
123
|
+
uint64_t nish_str_len(const nish_str *s);
|
|
124
|
+
/* Whether `sub` occurs at byte offset `at` (negative: never); `startsWith` / `endsWith`. */
|
|
125
|
+
bool nish_str_at(const nish_str *s, int64_t at, const nish_str *sub);
|
|
126
|
+
/* `s.indexOf(sub)`: the first byte offset where `sub` occurs, or -1. An empty
|
|
127
|
+
needle answers 0 and one longer than `s` answers -1, as in JavaScript. */
|
|
128
|
+
int64_t nish_str_index_of(const nish_str *s, const nish_str *sub);
|
|
129
|
+
/* `console.log(s)`: one write(2) of the bytes plus a newline to stdout. */
|
|
130
|
+
void nish_print(const nish_str *s);
|
|
131
|
+
/* `console.error` and the newline-free `write` / `writeError`: fd 1 or 2. */
|
|
132
|
+
void nish_write(const nish_str *s, int32_t fd, bool newline);
|
|
133
|
+
/* Number to string, as `${n}` does: decimal for integers, shortest round-trip (JS Number#toString) for f64. */
|
|
134
|
+
nish_str *nish_str_from_i32(int32_t v);
|
|
135
|
+
nish_str *nish_str_from_f64(double v);
|
|
136
|
+
nish_str *nish_str_from_i64(int64_t v);
|
|
137
|
+
/* Unsigned decimal (WP15). u8/u16/u32 are zero-extended by the caller, so one
|
|
138
|
+
symbol serves every unsigned width. */
|
|
139
|
+
nish_str *nish_str_from_u64(uint64_t v);
|
|
140
|
+
|
|
141
|
+
/* Process and file I/O (WP7). `nish_exit` never returns; the file functions
|
|
142
|
+
* print a message to stderr and exit(1) on a fatal error. Everything from here
|
|
143
|
+
* to the end of the clock section below is implemented in runtime_os.c, with
|
|
144
|
+
* `nish_random` and `nish_argv_init` the exceptions: neither asks the operating
|
|
145
|
+
* system anything (the clock only seeds the first, and the entry point hands the
|
|
146
|
+
* second its arguments), so both stay with the core. `nish_random` keeps one seed
|
|
147
|
+
* word, per process or — under `-DNISH_THREADS` — per thread. */
|
|
148
|
+
double nish_random(void);
|
|
149
|
+
void nish_exit(int32_t code);
|
|
150
|
+
nish_str *nish_read_file(const nish_str *path);
|
|
151
|
+
/* As above, but NULL instead of exiting when the file cannot be read. */
|
|
152
|
+
nish_str *nish_read_file_or_null(const nish_str *path);
|
|
153
|
+
void nish_write_file(const nish_str *path, const nish_str *data);
|
|
154
|
+
void nish_append_file(const nish_str *path, const nish_str *data);
|
|
155
|
+
|
|
156
|
+
/* ---- Arrays (WP4) -------------------------------------------------------
|
|
157
|
+
* An Nish `T[]` (also spelled `Int32Array` / `Float64Array` /
|
|
158
|
+
* `BigInt64Array` for i32 / f64 / i64 elements) is a pointer to this header:
|
|
159
|
+
* %struct.nish_array = type { i64 len, i64 cap, i8* data }
|
|
160
|
+
* `data` holds `cap` elements of one fixed size (int32_t 4, double 8,
|
|
161
|
+
* int64_t 8, bool 1, pointers 8), 8-byte aligned when the runtime allocated
|
|
162
|
+
* it.
|
|
163
|
+
*
|
|
164
|
+
* WP15 section 2a: when the element type is an Nish `interface` — a record,
|
|
165
|
+
* fields and nothing else — `data` holds the records *themselves*, end to end,
|
|
166
|
+
* at exactly the stride and alignment clang gives the matching C struct. A
|
|
167
|
+
* `Point[]` is therefore a `struct Point *` and `((struct Point *)a->data)[i]`
|
|
168
|
+
* is element `i`, which is what lets a C host walk one with no marshalling.
|
|
169
|
+
* A `class` element, and a `T | null` element of any kind, is still one
|
|
170
|
+
* pointer per slot. `--emit-header` names the element type above each
|
|
171
|
+
* prototype and says which of the two it is.
|
|
172
|
+
*
|
|
173
|
+
* A generated header spells a parameter the callee only reads as
|
|
174
|
+
* `const nish_array *` and one it writes through (`a[i] = v`, `push`) as
|
|
175
|
+
* `nish_array *`; the element type is in the comment above each prototype.
|
|
176
|
+
*
|
|
177
|
+
* Passing a host buffer: build the header yourself (`nish_array a = { n, n,
|
|
178
|
+
* (char *)buf }`) and pass `&a`. The callee treats it like any array, so a
|
|
179
|
+
* `push` that grows it copies the elements into the arena and leaves your
|
|
180
|
+
* buffer behind; the callee must not retain the pointer beyond the call.
|
|
181
|
+
* A returned array lives in the arena (valid until the next reset/release):
|
|
182
|
+
* copy `len` elements out of `data` before recycling. */
|
|
183
|
+
typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array;
|
|
184
|
+
/* A fresh arena array of `len` uninitialised elements (`len == cap`), for a
|
|
185
|
+
* host that wants the runtime to own the storage (the wasm loader does).
|
|
186
|
+
* `elem_size` is `sizeof` one element, so for a record element type it is
|
|
187
|
+
* `sizeof(struct Point)` and the block is a C array of them. */
|
|
188
|
+
nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
189
|
+
/* The cold paths compiled code calls: `push` when len == cap, a failed bounds
|
|
190
|
+
* check. `nish_array_grow` moves `cap * elem_size` bytes into a fresh arena
|
|
191
|
+
* block and writes `data`/`cap`, so for a record element type it relocates the
|
|
192
|
+
* elements themselves — which is why the checker refuses to let a program hold
|
|
193
|
+
* a pointer to one across a `push` (WP15 section 2a). */
|
|
194
|
+
void nish_array_grow(nish_array *a, uint64_t elem_size);
|
|
195
|
+
void nish_panic_index(uint64_t idx, uint64_t len);
|
|
196
|
+
/* The failed range check of `s.slice(start, end)` (WP15 section 4): prints the
|
|
197
|
+
half-open interval that was asked for and the byte length it left, then
|
|
198
|
+
exits 1. Signed, although the check itself compares unsigned, so that an
|
|
199
|
+
offset that went negative reads as `-1` and not as 2^64 - 1. `substring`
|
|
200
|
+
clamps instead and never reaches this. */
|
|
201
|
+
void nish_panic_slice(int64_t start, int64_t end, int64_t len);
|
|
202
|
+
|
|
203
|
+
/* `process.argv` (WP7): a `string[]` (elements are `nish_str *`) that the entry
|
|
204
|
+
* wrapper `main` builds once from argc/argv before calling the program; index 0
|
|
205
|
+
* is the executable path. Allocated with malloc, so arena resets never touch
|
|
206
|
+
* it. Hosts that call Nish code without a `main` never need it: a program
|
|
207
|
+
* without an entry point cannot read `process.argv` (compile error). */
|
|
208
|
+
extern nish_array *nish_argv;
|
|
209
|
+
void nish_argv_init(int32_t argc, char **argv);
|
|
210
|
+
|
|
211
|
+
/* ---- Directories and subprocesses (WP14 D4) -----------------------------
|
|
212
|
+
* The calls a self-hosted driver needs to find its inputs and link its own
|
|
213
|
+
* output. Every one of them answers a value instead of exiting, exactly as
|
|
214
|
+
* `nish_read_file_or_null` does: Nish has no exceptions, so the caller
|
|
215
|
+
* owns the diagnostic. */
|
|
216
|
+
/* `mkdirSync(path)`: create one directory, NOT recursive (mode 0777 & ~umask,
|
|
217
|
+
* like Node's `fs.mkdirSync(p)` with no options). True when a directory
|
|
218
|
+
* exists at `path` afterwards, whether this call created it or it was already
|
|
219
|
+
* there; false for every other failure, a plain file at `path` included. */
|
|
220
|
+
bool nish_mkdir(const nish_str *path);
|
|
221
|
+
/* `isDirectorySync(path)` (WP14 §7a): true when a directory exists at `path`
|
|
222
|
+
as this call runs, false for everything else — a missing path, a plain file,
|
|
223
|
+
a parent that cannot be searched. One `stat`, no allocation, no exit; it is
|
|
224
|
+
the `stat` half of `nish_mkdir`, which calls it. */
|
|
225
|
+
bool nish_is_dir(const nish_str *path);
|
|
226
|
+
/* `readdirSync(path)`: the entries of directory `path` as a `string[]` (the
|
|
227
|
+
* elements are `nish_str *`), **sorted ascending by bytes** (`strcmp`), with
|
|
228
|
+
* `.` and `..` dropped and every other dotfile kept. The runtime sorts because
|
|
229
|
+
* the language has no `sort` for the caller to reach for, and because a listing
|
|
230
|
+
* in the file system's own order differs between two machines running the same
|
|
231
|
+
* program. NULL when the directory cannot be read at all — the language's
|
|
232
|
+
* `string[] | null` — where a directory that exists and is empty answers an
|
|
233
|
+
* empty array, which is a different answer. The array and its strings live in
|
|
234
|
+
* the arena, so copy what you keep before the next reset or release. NULL
|
|
235
|
+
* under WASI: `fd_readdir` lists a preopened directory rather than a path, so
|
|
236
|
+
* porting this there is a different contract and not a translation. */
|
|
237
|
+
nish_array *nish_readdir(const nish_str *path);
|
|
238
|
+
/* `spawnSync(argv)`: run element 0 of `argv` (searched on `PATH`) with `argv`
|
|
239
|
+
* as its argument vector, wait for it, and answer its exit status, or
|
|
240
|
+
* `128 + n` when signal `n` killed it. -1 when `argv` is empty, when the
|
|
241
|
+
* child cannot be started or waited for, and always under WASI, which has no
|
|
242
|
+
* processes. The elements are `nish_str *`; the child receives their bytes,
|
|
243
|
+
* so an argument containing a NUL is truncated at it. */
|
|
244
|
+
int32_t nish_spawn(const nish_array *argv);
|
|
245
|
+
/* `spawnSyncTo(argv, stdoutPath, stderrPath)`: exactly `nish_spawn` — the same
|
|
246
|
+
* `PATH` search, the same wait, the same status, `128 + n` and -1 — except that
|
|
247
|
+
* each non-empty path receives that stream, created or truncated at 0644 as
|
|
248
|
+
* `nish_write_file` would leave it. An **empty** string leaves that stream
|
|
249
|
+
* inherited, so one call can capture stdout and let stderr through to the
|
|
250
|
+
* terminal. The child opens the files, so a failed open is a child that could
|
|
251
|
+
* not start and answers -1 rather than leaving this process redirected. The two
|
|
252
|
+
* paths must differ: each is opened separately with its own offset, so naming
|
|
253
|
+
* one file twice makes the streams overwrite each other instead of
|
|
254
|
+
* interleaving; capture them apart and concatenate to merge them. One `static`
|
|
255
|
+
* implementation in runtime_os.c backs both spawn builtins, which is what keeps
|
|
256
|
+
* the argument vector, the wait and the signal convention written once. */
|
|
257
|
+
int32_t nish_spawn_to(const nish_array *argv, const nish_str *out, const nish_str *err);
|
|
258
|
+
|
|
259
|
+
/* ---- The environment (WP19 R1) ------------------------------------------
|
|
260
|
+
* `getenv(name)`: the value of environment variable `name`, copied into the
|
|
261
|
+
* arena (so a later `setenv` cannot change a string the program still holds),
|
|
262
|
+
* or NULL when it is unset — the language's `string | null`. An empty value
|
|
263
|
+
* is a set variable and answers a zero-length string, not NULL. `name` is
|
|
264
|
+
* read and never retained; a NUL inside it truncates the lookup, as it does
|
|
265
|
+
* for every other path-like argument here. */
|
|
266
|
+
nish_str *nish_getenv(const nish_str *name);
|
|
267
|
+
|
|
268
|
+
/* ---- Symlinks (WP19 §5a item 4) -----------------------------------------
|
|
269
|
+
* `realpathSync(path)`: `path` with every symbolic link resolved and every
|
|
270
|
+
* `.`, `..` and repeated separator removed, as an absolute path copied into
|
|
271
|
+
* the arena — or NULL when it does not resolve, which the language reads as
|
|
272
|
+
* `string | null`. A path that does not exist is the ordinary NULL case and
|
|
273
|
+
* not an error: the caller is asking whether it resolves, and every component
|
|
274
|
+
* but the last must exist for POSIX `realpath` to answer at all.
|
|
275
|
+
*
|
|
276
|
+
* `path` is read and never retained. The answer is a fresh arena string, so
|
|
277
|
+
* it outlives the call the way every other string here does. */
|
|
278
|
+
nish_str *nish_realpath(const nish_str *path);
|
|
279
|
+
|
|
280
|
+
/* ---- What machine this is (WP14 §7a) ------------------------------------
|
|
281
|
+
* `process.platform` and `process.arch`, spelled as Node spells them:
|
|
282
|
+
* "linux" or "darwin", "x64" or "arm64", and "unknown" for anything this
|
|
283
|
+
* compiler has no target triple for, a WASI build included. Both answer a
|
|
284
|
+
* pointer into this library's own constant data, settled when it was compiled
|
|
285
|
+
* (a cross build compiles it for the target), so the strings survive every
|
|
286
|
+
* arena reset, never change, and must not be freed. */
|
|
287
|
+
const nish_str *nish_platform(void);
|
|
288
|
+
const nish_str *nish_arch(void);
|
|
289
|
+
|
|
290
|
+
/* ---- The clock ----------------------------------------------------------
|
|
291
|
+
* `monotonicNanos()`: `CLOCK_MONOTONIC` in nanoseconds, which is what timing a
|
|
292
|
+
* run needs. Deliberately not the wall clock: a wall clock corrected mid-run
|
|
293
|
+
* can go backwards and make an elapsed time negative. The origin is arbitrary
|
|
294
|
+
* — only the difference between two reads means anything, and no reading is
|
|
295
|
+
* comparable across processes or machines — and an i64 of nanoseconds holds 292
|
|
296
|
+
* years of difference. Compiled code calls this as a *writing* function on
|
|
297
|
+
* purpose: `readnone` would let LLVM fold the two reads around a measured
|
|
298
|
+
* region into one and measure zero. */
|
|
299
|
+
int64_t nish_monotonic_nanos(void);
|
|
300
|
+
|
|
301
|
+
/* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
|
|
302
|
+
* (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
|
|
303
|
+
* (the whole string, trimmed; blank is 0; `0x` hex accepted, as in JS),
|
|
304
|
+
* mode 2 is `parseInt` (base 10 via strtoll, 0 without digits) as a double
|
|
305
|
+
* that the compiler saturates into an i32 with `llvm.fptosi.sat`. */
|
|
306
|
+
double nish_parse_number(const nish_str *s, int32_t mode);
|
|
307
|
+
|
|
308
|
+
/* Checked integer division (Rust semantics): the failed-check path. */
|
|
309
|
+
void nish_panic_div(bool by_zero);
|
|
310
|
+
|
|
311
|
+
/* ---- Parallel work (WP20 T1 / wp29 stage P1), runtime/runtime_parallel.c ----
|
|
312
|
+
*
|
|
313
|
+
* One region of work, divided. `nish_parallel_range` calls `body(lo, hi, ctx)`
|
|
314
|
+
* once per chunk of a partition of `[0, len)`: contiguous chunks, at most one
|
|
315
|
+
* element apart in size, at most `nish_cpu_count()` of them and never more than
|
|
316
|
+
* `len / grain`, with chunk 0 running on the calling thread. It returns when
|
|
317
|
+
* every chunk has run.
|
|
318
|
+
*
|
|
319
|
+
* Under `-DNISH_THREADS` the other chunks run on their own threads, each with
|
|
320
|
+
* its own arena, which it frees before exiting. Without the macro -- and on
|
|
321
|
+
* WASI, which has no threads -- the whole range runs on the calling thread, so
|
|
322
|
+
* the entry point exists either way and the flag decides only whether the work
|
|
323
|
+
* is divided. A body that itself calls this runs its range on one thread: the
|
|
324
|
+
* nesting guard is thread-local and there is no scheduler here.
|
|
325
|
+
*
|
|
326
|
+
* Two preconditions the caller owns, because this file cannot check them and
|
|
327
|
+
* the language surface above it will:
|
|
328
|
+
* - chunks run concurrently, so a body may read what the caller owns and may
|
|
329
|
+
* write only through storage the caller lent it and no two chunks share;
|
|
330
|
+
* - a body's result may not point into the arena, because a worker's arena is
|
|
331
|
+
* freed when its thread exits while chunk 0's is the caller's own. */
|
|
332
|
+
typedef void (*nish_par_body)(int64_t lo, int64_t hi, void *ctx);
|
|
333
|
+
int64_t nish_cpu_count(void);
|
|
334
|
+
void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t grain);
|
|
335
|
+
|
|
336
|
+
#ifdef __cplusplus
|
|
337
|
+
}
|
|
338
|
+
#endif
|
|
339
|
+
|
|
340
|
+
#endif /* NISH_H */
|