mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks. - Preserve compiler import boundaries and protected fixtures while cleaning up lint findings. - Standardize source formatting and validate import, fixture, and embedded-source protections.
384 lines
11 KiB
TypeScript
384 lines
11 KiB
TypeScript
import {
|
|
BOOL,
|
|
F64,
|
|
STRING,
|
|
UNDEFINED_T,
|
|
VOID,
|
|
arrayOf,
|
|
mapOf,
|
|
type IrClassDef,
|
|
type IrExpr,
|
|
type IrFunction,
|
|
type IrModule,
|
|
type IrStmt,
|
|
type IrType,
|
|
} from "../../packages/compiler/src/ir/ir.js";
|
|
import { boolLit, numLit, strLit, varRef } from "../../packages/compiler/src/ir/build.js";
|
|
import { IR_VERSION } from "../../packages/compiler/src/ir/serialize.js";
|
|
|
|
export interface EmitterInputCase {
|
|
name: string;
|
|
module: IrModule;
|
|
sources: { file: string; text: string }[];
|
|
}
|
|
|
|
const loc = { file: "native emitter/日本.ts", start: 0, end: 1 };
|
|
const record = (shapeId: string): IrType => ({ kind: "record", shapeId });
|
|
const object = (className: string): IrType => ({ kind: "object", className });
|
|
const union = (unionId: string): IrType => ({ kind: "union", unionId });
|
|
|
|
function module(): IrModule {
|
|
return {
|
|
irVersion: IR_VERSION,
|
|
sourceFile: loc.file,
|
|
entry: "main",
|
|
functions: [{ name: "main", params: [], locals: [], returnType: VOID, body: [], loc }],
|
|
};
|
|
}
|
|
|
|
function effect(expr: IrExpr): IrStmt {
|
|
return { kind: "exprStmt", expr, loc: expr.loc };
|
|
}
|
|
|
|
function method(cls: string, member: string): IrFunction {
|
|
return {
|
|
name: `%${cls}.${member}`,
|
|
params: [{ localId: "this", name: "this", type: object(cls) }],
|
|
locals: [{ id: "this", name: "this", type: object(cls), mutable: false }],
|
|
returnType: VOID,
|
|
body: [],
|
|
loc,
|
|
};
|
|
}
|
|
|
|
/** Small, hand-built inputs cover code-generation branches that ordinary
|
|
* TypeScript sources cannot request independently (unused layouts, exact
|
|
* library ABI metadata, module compression and literal interning). The
|
|
* execution cases in the harness separately check emitted program behavior. */
|
|
export function emitterInputCases(): EmitterInputCase[] {
|
|
const cases: EmitterInputCase[] = [];
|
|
const add = (
|
|
name: string,
|
|
mod: IrModule,
|
|
sources: EmitterInputCase["sources"] = [],
|
|
): EmitterInputCase => {
|
|
const item = { name, module: mod, sources };
|
|
cases.push(item);
|
|
return item;
|
|
};
|
|
add("empty executable", module());
|
|
|
|
const strings = module();
|
|
strings.functions[0]!.body = [
|
|
effect(strLit('quote" slash\\ newline\n tab\t nul\u0000 end', loc)),
|
|
effect(strLit("日本 π 😀 \ud800", loc)),
|
|
effect(strLit("trigraph??/comment*/", loc)),
|
|
effect(strLit("repeat", loc)),
|
|
effect(strLit("repeat", loc)),
|
|
];
|
|
add("literal escaping and interning", strings);
|
|
|
|
const numbers = module();
|
|
numbers.functions[0]!.body = [
|
|
-0,
|
|
0,
|
|
1,
|
|
-1,
|
|
Number.MAX_VALUE,
|
|
Number.MIN_VALUE,
|
|
Infinity,
|
|
-Infinity,
|
|
].map((value) => effect(numLit(value, loc)));
|
|
numbers.functions[0]!.body.push(
|
|
effect({ kind: "bin", op: "/", left: numLit(0, loc), right: numLit(0, loc), type: F64, loc }),
|
|
);
|
|
add("floating point constants", numbers);
|
|
|
|
const source = module();
|
|
source.functions[0]!.body = [effect(strLit("line two", { ...loc, start: 12, end: 20 }))];
|
|
add("source locations with multibyte text", source, [
|
|
{ file: loc.file, text: "// 日本\nconsole.log('line two');\n" },
|
|
]);
|
|
|
|
const records = module();
|
|
records.records = [
|
|
{
|
|
id: "scalar",
|
|
fields: [
|
|
{ name: "flag", type: BOOL },
|
|
{ name: "value", type: F64 },
|
|
],
|
|
},
|
|
{
|
|
id: "references",
|
|
fields: [
|
|
{ name: "list", type: arrayOf(STRING) },
|
|
{ name: "text", type: STRING },
|
|
],
|
|
},
|
|
{
|
|
id: "tuple",
|
|
tuple: true,
|
|
fields: [
|
|
{ name: "0", type: STRING },
|
|
{ name: "1", type: F64 },
|
|
],
|
|
},
|
|
];
|
|
add("scalar, reference and tuple layouts", records);
|
|
|
|
const recursive = module();
|
|
recursive.records = [
|
|
{
|
|
id: "node",
|
|
fields: [
|
|
{ name: "children", type: arrayOf(record("node")) },
|
|
{ name: "text", type: STRING },
|
|
],
|
|
},
|
|
];
|
|
add("recursive record trace and teardown", recursive);
|
|
|
|
const mutual = module();
|
|
mutual.records = [
|
|
{ id: "left", fields: [{ name: "next", type: union("rightMaybe") }] },
|
|
{ id: "right", fields: [{ name: "next", type: union("leftMaybe") }] },
|
|
];
|
|
mutual.unions = [
|
|
{ id: "rightMaybe", arms: [record("right"), UNDEFINED_T] },
|
|
{ id: "leftMaybe", arms: [record("left"), UNDEFINED_T] },
|
|
];
|
|
add("mutually recursive optional records", mutual);
|
|
|
|
const overflow = module();
|
|
overflow.records = [
|
|
{
|
|
id: "dictionary",
|
|
fields: [{ name: "label", type: STRING }],
|
|
indexValue: record("dictionary"),
|
|
},
|
|
];
|
|
add("recursive overflow maps", overflow);
|
|
|
|
const cls = (name: string, base?: string): IrClassDef => ({
|
|
name,
|
|
...(base === undefined ? {} : { base }),
|
|
fields: [{ name: "label", type: STRING }],
|
|
methods: ["visit"],
|
|
loc,
|
|
});
|
|
const classes = module();
|
|
classes.classes = [cls("Root"), cls("Left", "Root"), cls("Right", "Root"), cls("Leaf", "Left")];
|
|
for (const c of classes.classes) classes.functions.push(method(c.name, "visit"));
|
|
add("class forest and virtual dispatch tables", classes);
|
|
|
|
const abstract = module();
|
|
abstract.classes = [
|
|
{ ...cls("Abstract"), abstract: true, abstractMethods: ["visit"] },
|
|
cls("Concrete", "Abstract"),
|
|
];
|
|
abstract.functions.push(method("Concrete", "visit"));
|
|
add("abstract method ABI from descendant", abstract);
|
|
|
|
const globals = module();
|
|
globals.globals = [
|
|
{ id: "%g.text", name: "text", type: STRING, mutable: true },
|
|
{ id: "%g.values", name: "values", type: arrayOf(F64), mutable: true },
|
|
];
|
|
globals.functions[0]!.body = [
|
|
{ kind: "assign", localId: "%g.text", value: strLit("global", loc), loc },
|
|
{
|
|
kind: "assign",
|
|
localId: "%g.values",
|
|
value: { kind: "arrayLit", elems: [numLit(1, loc)], type: arrayOf(F64), loc },
|
|
loc,
|
|
},
|
|
];
|
|
add("global initialization and shutdown ownership", globals);
|
|
|
|
const constantTable = module();
|
|
constantTable.globals = [{ id: "%g.table", name: "table", type: arrayOf(F64), mutable: false }];
|
|
constantTable.functions[0]!.body = [
|
|
{
|
|
kind: "assign",
|
|
localId: "%g.table",
|
|
value: {
|
|
kind: "arrayLit",
|
|
elems: [1, 4, 9, 16].map((n) => numLit(n, loc)),
|
|
type: arrayOf(F64),
|
|
loc,
|
|
},
|
|
loc,
|
|
},
|
|
effect({
|
|
kind: "arrayGet",
|
|
arr: varRef("%g.table", arrayOf(F64), loc),
|
|
index: numLit(2, loc),
|
|
type: F64,
|
|
loc,
|
|
}),
|
|
];
|
|
add("constant numeric array storage", constantTable);
|
|
|
|
const labels = module();
|
|
labels.functions[0]!.body = [
|
|
{
|
|
kind: "block",
|
|
labels: ["outer"],
|
|
body: [
|
|
{
|
|
kind: "switch",
|
|
disc: boolLit(true, loc),
|
|
labels: ["choose"],
|
|
cases: [
|
|
{ test: boolLit(false, loc), body: [effect(strLit("skipped", loc))] },
|
|
{ test: null, body: [{ kind: "break", label: "outer", loc }] },
|
|
],
|
|
loc,
|
|
},
|
|
],
|
|
loc,
|
|
},
|
|
];
|
|
add("switch and block jump labels", labels);
|
|
|
|
const closure = module();
|
|
const closureType: IrType = { kind: "func", params: [], ret: STRING };
|
|
closure.functions.push({
|
|
name: "reader",
|
|
params: [],
|
|
locals: [{ id: "message", name: "message", type: STRING, mutable: true, boxed: true }],
|
|
returnType: STRING,
|
|
captures: [{ localId: "message", name: "message", type: STRING }],
|
|
body: [{ kind: "return", value: varRef("message", STRING, loc), loc }],
|
|
loc,
|
|
});
|
|
closure.functions[0]!.locals = [
|
|
{ id: "message", name: "message", type: STRING, mutable: true, boxed: true },
|
|
];
|
|
closure.functions[0]!.body = [
|
|
{ kind: "varDecl", localId: "message", init: strLit("capture", loc), loc },
|
|
effect({ kind: "closure", fnName: "reader", captures: ["message"], type: closureType, loc }),
|
|
];
|
|
add("closure capture boxes", closure);
|
|
|
|
const unions = module();
|
|
unions.unions = [{ id: "scalar", arms: [BOOL, F64, STRING, UNDEFINED_T] }];
|
|
unions.functions[0]!.body = [
|
|
effect({
|
|
kind: "unionWrap",
|
|
unionId: "scalar",
|
|
tag: 2,
|
|
value: strLit("payload", loc),
|
|
type: union("scalar"),
|
|
loc,
|
|
}),
|
|
effect({
|
|
kind: "unionWrap",
|
|
unionId: "scalar",
|
|
tag: 3,
|
|
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
|
|
type: union("scalar"),
|
|
loc,
|
|
}),
|
|
];
|
|
add("union scalar payloads and immortal units", unions);
|
|
|
|
const dictionary = module();
|
|
const mapType = mapOf(STRING, F64);
|
|
dictionary.functions[0]!.body = [effect({ kind: "mapNew", type: mapType, loc })];
|
|
add("typed map construction", dictionary);
|
|
|
|
const shortNpm = module();
|
|
shortNpm.embedded = {
|
|
modules: [
|
|
{ key: "/node_modules/tiny/index.js", format: "esm", source: "export const n = 1;" },
|
|
{ key: "/node_modules/tiny/config.json", format: "json", source: '{"name":"π"}' },
|
|
{ key: "/node_modules/tiny/empty.js", format: "cjs", source: "", esm: "export default {};" },
|
|
],
|
|
edges: [
|
|
{
|
|
from: "/node_modules/tiny/index.js",
|
|
specifier: "./config.json",
|
|
to: "/node_modules/tiny/config.json",
|
|
kind: "import",
|
|
},
|
|
{ from: "/node_modules/tiny/index.js", specifier: "node:path", to: "node:path", kind: "any" },
|
|
],
|
|
};
|
|
add("uncompressed npm, JSON and CJS facade tables", shortNpm);
|
|
|
|
const compressed = module();
|
|
compressed.embedded = {
|
|
modules: [
|
|
{
|
|
key: "/node_modules/large/index.js",
|
|
format: "cjs",
|
|
source: "exports.value = 'repeat π';\n".repeat(2000),
|
|
esm: "export const value = 'repeat π';\n".repeat(2000),
|
|
},
|
|
],
|
|
edges: [
|
|
{
|
|
from: "/app/main.js",
|
|
specifier: "large",
|
|
to: "/node_modules/large/index.js",
|
|
kind: "require",
|
|
},
|
|
{
|
|
from: "/app/main.js",
|
|
specifier: "large",
|
|
to: "/node_modules/large/index.js",
|
|
kind: "import",
|
|
},
|
|
],
|
|
};
|
|
add("level-nine module and facade compression", compressed);
|
|
|
|
const chunked = module();
|
|
// A deterministic byte distribution prevents this source from being a
|
|
// single repeated run while forcing several source-literal chunks.
|
|
let state = 1;
|
|
let text = "";
|
|
for (let i = 0; i < 12_000; i++) {
|
|
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
|
|
text += String.fromCharCode(32 + (state % 95));
|
|
}
|
|
chunked.embedded = {
|
|
modules: [
|
|
{ key: "/node_modules/random/data.json", format: "json", source: JSON.stringify(text) },
|
|
],
|
|
edges: [],
|
|
};
|
|
add("large source literal chunk boundaries", chunked);
|
|
|
|
const library = module();
|
|
library.lib = {
|
|
profileName: "native-test",
|
|
prefix: "native_",
|
|
initSymbol: "native_init",
|
|
sinkRegisterSymbol: "native_sink",
|
|
collectSymbol: "native_collect",
|
|
resultResetSymbol: "native_reset",
|
|
threadInstances: false,
|
|
exports: [],
|
|
trapOverlays: [],
|
|
identity: {
|
|
buildIdSymbol: "native_build_id",
|
|
abiVersionSymbol: "native_abi",
|
|
buildId: "0123456789abcdef",
|
|
abiVersion: 7,
|
|
},
|
|
};
|
|
add("library entry points and identity constants", library);
|
|
|
|
const threaded = module();
|
|
threaded.lib = { ...library.lib, threadInstances: true };
|
|
threaded.globals = [{ id: "%g.counter", name: "counter", type: F64, mutable: true }];
|
|
threaded.functions[0]!.body = [
|
|
{ kind: "assign", localId: "%g.counter", value: numLit(0, loc), loc },
|
|
];
|
|
add("thread-local library globals", threaded);
|
|
return cases;
|
|
}
|