Files
scriptc/scripts/gen-dyn-methods.mjs
T

102 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/* Writes packages/runtime/src/scr_dyn_methods.h: the method-name ids the
* checked-dynamic dispatch in scr_dyn_invoke.c compares (DYN_M_<name>), the
* names, and an open-addressed hash table mapping a name to its id. The ids
* are the DYN_M_ names scr_dyn_invoke.c uses, so the header follows the
* dispatch code.
*
* node scripts/gen-dyn-methods.mjs write the header
* node scripts/gen-dyn-methods.mjs --check fail when it is out of date */
import { readFileSync, writeFileSync } from "node:fs";
const source = new URL("../packages/runtime/src/scr_dyn_invoke.c", import.meta.url);
const target = new URL("../packages/runtime/src/scr_dyn_methods.h", import.meta.url);
const SLOTS = 256;
const used = new Set();
for (const match of readFileSync(source, "utf8").matchAll(/\bDYN_M_([A-Za-z][A-Za-z0-9]*)\b/g))
if (match[1] !== "NONE" && match[1] !== "COUNT") used.add(match[1]);
const names = [...used].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
if (names.length >= SLOTS / 2) throw new Error("too many method names for the hash table");
// The slot dyn_method_of in the header computes.
const slotOf = (name) =>
(name.length * 7 + name.charCodeAt(0) * 31 + name.charCodeAt(name.length - 1)) & (SLOTS - 1);
const slots = new Array(SLOTS).fill(0);
names.forEach((name, i) => {
let slot = slotOf(name);
while (slots[slot] !== 0) slot = (slot + 1) & (SLOTS - 1);
slots[slot] = i + 1;
});
const rows = (values, width) => {
const out = [];
for (let i = 0; i < values.length; i += width)
out.push(` ${values.slice(i, i + width).join(", ")},`);
return out;
};
const text = [
"/* Generated by scripts/gen-dyn-methods.mjs from the DYN_M_ ids scr_dyn_invoke.c",
" * uses. Do not edit by hand. */",
"#ifndef SCR_DYN_METHODS_H",
"#define SCR_DYN_METHODS_H",
"",
"#include <stddef.h>",
"#include <string.h>",
"",
"enum {",
" DYN_M_NONE,",
...names.map((name) => ` DYN_M_${name},`),
" DYN_M_COUNT",
"};",
"",
"static const char *const dyn_method_names[DYN_M_COUNT] = {",
" NULL,",
...names.map((name) => ` ${JSON.stringify(name)},`),
"};",
"",
"static const unsigned char dyn_method_lengths[DYN_M_COUNT] = {",
...rows([0, ...names.map((name) => name.length)], 16),
"};",
"",
`/* Each name's id at its slot (or the next free one); 0 ends a probe. */`,
`static const unsigned char dyn_method_slots[${SLOTS}] = {`,
...rows(slots, 16),
"};",
"",
`#define DYN_METHOD_MAX_LENGTH ${Math.max(...names.map((name) => name.length))}`,
"",
"/* The id of a method name, compared by content; DYN_M_NONE for any other",
" * name. */",
"static inline int dyn_method_of(const char *m) {",
" size_t length = strlen(m);",
" if (length == 0 || length > DYN_METHOD_MAX_LENGTH) return DYN_M_NONE;",
" unsigned slot = (unsigned)(length * 7 + (unsigned char)m[0] * 31 +",
` (unsigned char)m[length - 1]) & ${SLOTS - 1};`,
" for (;;) {",
" unsigned id = dyn_method_slots[slot];",
" if (id == 0) return DYN_M_NONE;",
" if (dyn_method_lengths[id] == length && memcmp(m, dyn_method_names[id], length) == 0) return (int)id;",
` slot = (slot + 1) & ${SLOTS - 1};`,
" }",
"}",
"",
"#endif",
"",
].join("\n");
if (process.argv.includes("--check")) {
let current = "";
try {
current = readFileSync(target, "utf8");
} catch {}
if (current !== text) {
console.error(
"packages/runtime/src/scr_dyn_methods.h is out of date: run node scripts/gen-dyn-methods.mjs",
);
process.exit(1);
}
} else {
writeFileSync(target, text);
}