mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
scr_unwind_raise walks the stack with plans cached per return address. The first raise through a return address finds the function's FDE through the .eh_frame_hdr of the object that contains it, runs the CFI up to the call, and finds the call site's landing pad in the LSDA; the plan records the frame's CFA rule, where its callee-saved registers are saved, and the landing pad. Later raises look the plans up, restore the registers, and jump to the first landing pad. Plans are inserted under a lock and read without one. Landing pads continue the unwind with scr_unwind_resume instead of `resume`, so the walk goes on from their frame. A frame whose CFI, pointer encodings or personality the plans do not express, or a return address without an FDE, leaves the unwind to libgcc's forced unwind from the frame the walk started at, as before. Other Linux targets keep libgcc's unwinder; their scr_unwind_resume calls _Unwind_Resume. bench:runtime gains layered-errors, a workload whose errors are thrown below small functions that hold references and caught per entry in a loop, and the corpus gains unwind-landing-pad-chains, which pins landing pads that continue the unwind, including in frames LLVM inlines into a caller whose handler takes the exception.
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
// A configuration loader whose validation errors are thrown at the bottom
|
|
// of a stack of small layers and caught per entry at the top: each layer
|
|
// holds strings and arrays while the error passes through it.
|
|
class ConfigError extends Error {
|
|
readonly key: string;
|
|
constructor(key: string, message: string) {
|
|
super(message);
|
|
this.key = key;
|
|
}
|
|
}
|
|
|
|
let seed = 4242;
|
|
function random(): number {
|
|
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
|
return seed / 4294967296;
|
|
}
|
|
|
|
function fail(key: string, text: string): never {
|
|
throw new ConfigError(key, `invalid value "${text}" for ${key}`);
|
|
}
|
|
function digits(key: string, text: string): number {
|
|
let value = 0;
|
|
for (let i = 0; i < text.length; i++) {
|
|
const c = text.charCodeAt(i);
|
|
if (c < 48 || c > 57) fail(key, text);
|
|
value = value * 10 + (c - 48);
|
|
}
|
|
return value;
|
|
}
|
|
function parseNumber(key: string, text: string): number {
|
|
return text.length === 0 ? fail(key, text) : digits(key, text);
|
|
}
|
|
function checkRange(key: string, text: string, max: number): number {
|
|
const value = parseNumber(key, text);
|
|
return value > max ? fail(key, text) : value;
|
|
}
|
|
function coerce(key: string, raw: string, max: number): number {
|
|
const trimmed = raw.trim();
|
|
const parts = [key, trimmed];
|
|
const value = checkRange(key, trimmed, max);
|
|
return value + parts.length;
|
|
}
|
|
function resolveValue(key: string, raw: string, defaults: Map<string, string>): number {
|
|
const text = raw === "default" ? (defaults.get(key) ?? "") : raw;
|
|
const label = "value:" + key;
|
|
return coerce(key, text, 100000) + label.length;
|
|
}
|
|
function loadEntry(section: string, entry: string, defaults: Map<string, string>): number {
|
|
const eq = entry.indexOf("=");
|
|
const key = section + "." + entry.slice(0, eq);
|
|
const path = [section, key];
|
|
return resolveValue(key, entry.slice(eq + 1), defaults) + path.length;
|
|
}
|
|
function loadEntryWithContext(section: string, entry: string, defaults: Map<string, string>): number {
|
|
const context = `${section}/${entry.length}`;
|
|
return loadEntry(section, entry, defaults) + context.length;
|
|
}
|
|
|
|
function makeEntry(i: number): string {
|
|
const r = random();
|
|
const name = "k" + (i % 97);
|
|
if (r < 0.3) return `${name}=x${i}`;
|
|
if (r < 0.4) return `${name}=`;
|
|
if (r < 0.5) return `${name}=${200000 + i}`;
|
|
if (r < 0.55) return `${name}=default`;
|
|
return `${name}= ${Math.floor(random() * 90000)} `;
|
|
}
|
|
|
|
const rounds = Number(process.argv[2] ?? "1");
|
|
const defaults = new Map<string, string>();
|
|
for (let i = 0; i < 97; i += 2) defaults.set(`net.k${i}`, String(i * 10));
|
|
const sections = ["net", "db", "cache", "auth"];
|
|
const entries: string[] = [];
|
|
for (let i = 0; i < 4000; i++) entries.push(makeEntry(i));
|
|
|
|
let loaded = 0;
|
|
let sum = 0;
|
|
let failed = 0;
|
|
const failedKeys = new Map<string, number>();
|
|
for (let round = 0; round < rounds * 40; round++) {
|
|
for (const section of sections) {
|
|
for (const entry of entries) {
|
|
try {
|
|
sum += loadEntryWithContext(section, entry, defaults);
|
|
loaded++;
|
|
} catch (error) {
|
|
if (!(error instanceof ConfigError)) throw error;
|
|
failed++;
|
|
failedKeys.set(error.key, (failedKeys.get(error.key) ?? 0) + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
console.log(`loaded ${loaded} failed ${failed} sum ${sum} distinct failing keys ${failedKeys.size}`);
|
|
console.log(`net.k0 failures ${failedKeys.get("net.k0") ?? 0}`);
|