Author SHA1 Message Date
ed 7ec778a68e more theme stuff 2026-08-14 21:48:52 -04:00
ed 9ca865d5db update license to zip for now...
not sure what the standalone repo is going to be yet, but it will be relatively permissive since this is prototype/educational setup
2026-08-14 19:43:03 -04:00
ed 764ded4557 initial plugin setup for syntax highlighting in vscode... 2026-08-14 18:57:37 -04:00
ed 67a84d34f3 oops: endregion 2026-08-14 13:41:45 -04:00
ed baaff12f33 Ideating on "RegUse_" patterned structs for describe register allocatins to mips atom proc. 2026-08-14 12:38:00 -04:00
ed b695056b9a finished reviewing normalize_v3s4 for now 2026-08-14 03:45:34 -04:00
ed 3a4d6304dd static analysis: immeidate field awarenss 2026-08-14 01:22:54 -04:00
ed a535d381ed remove encoding masks from gp (unnecessary, hides errors) 2026-08-14 01:22:36 -04:00
ed c447bfa877 fixes to the reg file allocator, exploring... 2026-08-14 00:43:19 -04:00
ed d88e0d0487 remove mask from mips and gte instruction encoders. missing math changes. 2026-08-13 23:39:35 -04:00
ed 9a6eca6047 more review, made a register file allocator (drafted, kinda iffy, want todo comp-time as well). 2026-08-13 23:39:03 -04:00
ed 5c9c61720f Redesign: Not making local var in MipsAtom_Proc_ or MipsAtomComp_Proc_ have sym tied to proc name. Adjusted parser as well base do that. 2026-08-13 21:42:06 -04:00
ed b8e31123e4 editing/reading. 2026-08-13 21:22:29 -04:00
ed ea3e30a11e oops 2026-08-13 20:51:45 -04:00
ed 37f4712237 gutting nosiy comments. Looking into some atom components.. 2026-08-13 19:55:17 -04:00
41 changed files with 2357 additions and 764 deletions
+26
View File
@@ -0,0 +1,26 @@
# Cozy and Windy
Editor theme ported from the Rider scheme of the same name.
It colors the editor surface, C/C++ syntax, and tape-atom DSL keywords
emitted by `local.tape-atom-syntax`. It does not change workbench chrome.
## Install
```powershell
cd C:\projects\Pikuma\ps1\.vscode\cozy-and-windy
npm run package
code --install-extension .\cozy-and-windy-0.1.0.vsix --force
```
Reload the window. Select **Cozy and Windy** as the color theme, or set
`workbench.colorTheme` to `Cozy and Windy` in the PS1 workspace settings.
Keep `local.tape-atom-syntax` installed. This theme colors those token
types; it does not classify them.
## Inspect
Open `hello_camera.atom.c` and run **Developer: Inspect Editor Tokens and Scopes**
on `MipsAtom_`, an atom name, `atom_info`, `R_PrimCursor`, a `gte_*` call,
and a `mac_*` call.
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
{
"name": "cozy-and-windy",
"displayName": "Cozy and Windy",
"description": "Editor theme ported from the Rider Cozy and Windy scheme. Colors C/C++ and tape-atom DSL keywords.",
"publisher": "local",
"version": "0.1.0",
"engines": {
"vscode": "^1.80.0"
},
"categories": [
"Themes"
],
"scripts": {
"package": "npx --yes @vscode/vsce@3.6.1 package --allow-missing-repository --skip-license --out cozy-and-windy-0.1.0.vsix"
},
"contributes": {
"themes": [
{
"label": "Cozy and Windy",
"uiTheme": "vs-dark",
"path": "./themes/cozy-and-windy-color-theme.json"
}
]
}
}
@@ -0,0 +1,122 @@
{
"name": "Cozy and Windy",
"type": "dark",
"semanticHighlighting": true,
"colors": {
"editor.background": "#121212",
"editor.foreground": "#D25A6EFF",
"editor.lineHighlightBackground": "#1c1c1c",
"editor.selectionBackground": "#164371",
"editor.selectionForeground": "#c8c8c8",
"editorLineNumber.foreground": "#43c3c3",
"editorLineNumber.activeForeground": "#00fff4",
"editorIndentGuide.background1": "#181818",
"editorIndentGuide.activeBackground1": "#202020",
"editorRuler.foreground": "#505050",
"editorGutter.background": "#121212",
"editorBracketMatch.background": "#3b514d",
"editor.foldBackground": "#0c0c0c",
"editor.wordHighlightBackground": "#121212",
"editor.wordHighlightStrongBackground": "#303030",
"editorCursor.foreground": "#00fff4",
"editorWhitespace.foreground": "#181818",
"editorLineHighlightBorder": "#1c1c1c",
"editorWidget.background": "#121212",
"editorSuggestWidget.background": "#2c334b",
"editorHoverWidget.background": "#2c334b"
},
"semanticTokenColors": {
"comment": { "foreground": "#868686", "fontStyle": "italic" },
"keyword": { "foreground": "#d8bd5b" },
"string": { "foreground": "#d46a54" },
"number": { "foreground": "#b5cea8" },
"operator": { "foreground": "#00ffff" },
"class": { "foreground": "#54a4d6" },
"struct": { "foreground": "#54a4d6" },
"enum": { "foreground": "#54a4d6" },
"type": { "foreground": "#54a4d6" },
"interface": { "foreground": "#7984ab" },
"function": { "foreground": "#cccab5" },
"method": { "foreground": "#6090a9" },
"variable": { "foreground": "#bc966c" },
"parameter": { "foreground": "#867660", "fontStyle": "underline" },
"property": { "foreground": "#acb8c8" },
"*.static": { "foreground": "#9e95c6" },
"macro": { "foreground": "#5ea852" },
"namespace": { "foreground": "#8e8e8e" },
"typeParameter": { "foreground": "#b8d7a3" },
"enumMember": { "foreground": "#a373b0" },
"label": { "foreground": "#c8c8c8", "fontStyle": "bold" },
"tapeAtomKeyword": { "foreground": "#d8bd5b", "fontStyle": "bold" },
"tapeAtomName": { "foreground": "#cccab5", "fontStyle": "bold" },
"tapeComponentKeyword": { "foreground": "#d68a36", "fontStyle": "bold" },
"tapeComponentName": { "foreground": "#6090a9", "fontStyle": "bold" },
"tapeAnnotation": { "foreground": "#d8bd5b" },
"tapeBindType": { "foreground": "#54a4d6" },
"tapePhase": { "foreground": "#b8d7a3", "fontStyle": "italic" },
"tapeLabel": { "foreground": "#c8c8c8", "fontStyle": "bold" },
"tapeCpuInstruction": { "foreground": "#6090a9" },
"tapeGteInstruction": { "foreground": "#9e95c6" },
"tapeGpuInstruction": { "foreground": "#bf7dac" },
"tapeComponentInstruction": { "foreground": "#8baa5d" },
"tapeGprRegister": { "foreground": "#bc966c" },
"tapeCop2Register": { "foreground": "#9e95c6" },
"tapeDuffleType": { "foreground": "#54a4d6" },
"tapeAttribute": { "foreground": "#73a07c" },
"tapeGprRegister.tapeRead": { "foreground": "#8baa5d", "fontStyle": "italic" },
"tapeGprRegister.tapeWrite": { "foreground": "#d68a36", "fontStyle": "bold" },
"tapeCop2Register.tapeRead": { "foreground": "#a373b0", "fontStyle": "italic" },
"tapeCop2Register.tapeWrite": { "foreground": "#d46a54", "fontStyle": "bold" },
"*.tapeAuto": { "fontStyle": "underline" },
"tapeControlFlow": { "foreground": "#76ff7d", "fontStyle": "bold" },
"tapeDelaySlot": { "foreground": "#ff5647" }
},
"tokenColors": [
{ "scope": ["comment", "comment.block", "comment.line", "comment.block.documentation"], "settings": { "foreground": "#868686", "fontStyle": "italic" } },
{ "scope": ["keyword", "keyword.control", "keyword.other"], "settings": { "foreground": "#d8bd5b" } },
{ "scope": ["string", "string.quoted"], "settings": { "foreground": "#d46a54" } },
{ "scope": ["string.quoted.other"], "settings": { "foreground": "#d69d85" } },
{ "scope": ["constant.numeric"], "settings": { "foreground": "#b5cea8" } },
{ "scope": ["punctuation", "keyword.operator"], "settings": { "foreground": "#00ffff" } },
{ "scope": ["keyword.operator.overload"], "settings": { "foreground": "#00ff64" } },
{ "scope": ["entity.name.type", "entity.name.type.class", "entity.name.type.struct", "entity.name.type.enum"], "settings": { "foreground": "#54a4d6" } },
{ "scope": ["entity.name.type.interface"], "settings": { "foreground": "#7984ab" } },
{ "scope": ["entity.name.function"], "settings": { "foreground": "#cccab5" } },
{ "scope": ["entity.name.function.member"], "settings": { "foreground": "#6090a9" } },
{ "scope": ["variable.other.local"], "settings": { "foreground": "#bc966c" } },
{ "scope": ["variable.parameter"], "settings": { "foreground": "#867660", "fontStyle": "underline" } },
{ "scope": ["variable.other.property"], "settings": { "foreground": "#acb8c8" } },
{ "scope": ["variable.other.constant"], "settings": { "foreground": "#9e95c6" } },
{ "scope": ["variable.other.global", "variable.other.defaultLibrary"], "settings": { "foreground": "#bf7dac" } },
{ "scope": ["support.type", "support.function"], "settings": { "foreground": "#8baa5d" } },
{ "scope": ["meta.preprocessor"], "settings": { "foreground": "#5ea852" } },
{ "scope": ["variable.parameter.preprocessor"], "settings": { "foreground": "#636363" } },
{ "scope": ["keyword.control.directive"], "settings": { "foreground": "#d68a36" } },
{ "scope": ["entity.name.namespace"], "settings": { "foreground": "#8e8e8e" } },
{ "scope": ["entity.name.type.parameter"], "settings": { "foreground": "#b8d7a3" } },
{ "scope": ["variable.other.enummember"], "settings": { "foreground": "#a373b0" } },
{ "scope": ["entity.name.type.concept"], "settings": { "foreground": "#76ff7d" } },
{ "scope": ["entity.name.type.dependent"], "settings": { "foreground": "#448b5a", "fontStyle": "bold" } },
{ "scope": ["entity.name.label"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } },
{ "scope": ["invalid"], "settings": { "foreground": "#ff5647" } },
{ "scope": ["keyword.codetag.todo"], "settings": { "foreground": "#c10000", "fontStyle": "bold italic" } },
{ "scope": ["keyword.control.duffle.atom"], "settings": { "foreground": "#d8bd5b", "fontStyle": "bold" } },
{ "scope": ["entity.name.function.duffle.atom"], "settings": { "foreground": "#cccab5", "fontStyle": "bold" } },
{ "scope": ["keyword.control.duffle.component"], "settings": { "foreground": "#d68a36", "fontStyle": "bold" } },
{ "scope": ["entity.name.function.duffle.component"], "settings": { "foreground": "#6090a9", "fontStyle": "bold" } },
{ "scope": ["support.function.duffle.annotation"], "settings": { "foreground": "#d8bd5b" } },
{ "scope": ["entity.name.type.duffle.bind"], "settings": { "foreground": "#54a4d6" } },
{ "scope": ["entity.name.tag.duffle.phase"], "settings": { "foreground": "#b8d7a3", "fontStyle": "italic" } },
{ "scope": ["entity.name.label.duffle.atom"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } },
{ "scope": ["support.function.duffle.cpu"], "settings": { "foreground": "#6090a9" } },
{ "scope": ["support.function.duffle.gte"], "settings": { "foreground": "#9e95c6" } },
{ "scope": ["support.function.duffle.gpu"], "settings": { "foreground": "#bf7dac" } },
{ "scope": ["support.function.duffle.component"], "settings": { "foreground": "#8baa5d" } },
{ "scope": ["keyword.control.duffle.branch"], "settings": { "foreground": "#76ff7d", "fontStyle": "bold" } },
{ "scope": ["keyword.operator.duffle.delayslot"], "settings": { "foreground": "#ff5647" } },
{ "scope": ["variable.other.constant.duffle.gpr"], "settings": { "foreground": "#bc966c" } },
{ "scope": ["variable.other.constant.duffle.cop2"], "settings": { "foreground": "#9e95c6" } },
{ "scope": ["storage.type.duffle.type"], "settings": { "foreground": "#54a4d6" } },
{ "scope": ["storage.modifier.duffle.attr"], "settings": { "foreground": "#73a07c" } }
]
}
+199
View File
@@ -0,0 +1,199 @@
"use strict";
const { nearestCall } = require("./lexer");
const { mergeIndexes, scanSource } = require("./source-index");
const TOKEN_TYPES = [
"tapeAtomKeyword",
"tapeAtomName",
"tapeComponentKeyword",
"tapeComponentName",
"tapeAnnotation",
"tapeBindType",
"tapePhase",
"tapeLabel",
"tapeCpuInstruction",
"tapeControlFlow",
"tapeGteInstruction",
"tapeGpuInstruction",
"tapeComponentInstruction",
"tapeDelaySlot",
"tapeGprRegister",
"tapeCop2Register",
"tapeDuffleType",
"tapeAttribute",
];
const TOKEN_MODIFIERS = ["declaration", "tapeRead", "tapeWrite", "tapeAuto"];
const TOKEN_TYPE_INDEX = new Map(TOKEN_TYPES.map((name, index) => [name, index]));
const TOKEN_MODIFIER_INDEX = new Map(TOKEN_MODIFIERS.map((name, index) => [name, index]));
const ATOM_KEYWORDS = new Set(["MipsAtom_", "MipsAtom_Proc_"]);
const COMPONENT_KEYWORDS = new Set(["MipsAtomComp_", "MipsAtomComp_Proc_"]);
const ANNOTATIONS = new Set([
"atom_info", "atom_bind", "atom_reads", "atom_writes", "atom_label",
"atom_offset", "atom_reg", "atom_type", "atom_ctx", "atom_phase",
"atom_auto_reg", "phase_auto_reg", "atom_dbg_skip",
]);
const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_"]);
const CONTROL_FLOW_PREFIXES = /^(?:branch_|jump_|call_)/;
const ROLE_TO_TYPE = {
atomName: "tapeAtomName",
componentName: "tapeComponentName",
bindType: "tapeBindType",
duffleType: "tapeDuffleType",
gprRegister: "tapeGprRegister",
cop2Register: "tapeCop2Register",
};
function registerType(name, index) {
const kind = index.registers.get(name);
if (kind === "gpr" || /^R_[A-Za-z0-9_]+$/.test(name)) return "tapeGprRegister";
if (kind === "cop2" || /^(?:C2_|gte_cr_)[A-Za-z0-9_]+$/.test(name)) return "tapeCop2Register";
return null;
}
function instructionType(name, index) {
const domain = index.macros.get(name);
if (domain === "cpu") return "tapeCpuInstruction";
if (domain === "gte") return "tapeGteInstruction";
if (domain === "gpu") return "tapeGpuInstruction";
if (domain === "component") return "tapeComponentInstruction";
if (/^gte_(?!cr_)/.test(name)) return "tapeGteInstruction";
if (/^gp[01]_/.test(name)) return "tapeGpuInstruction";
if (/^mac_gte_/.test(name)) return "tapeGteInstruction";
if (/^mac_gp/.test(name)) return "tapeGpuInstruction";
if (/^mac_/.test(name)) return "tapeCpuInstruction";
return null;
}
function modifierMask(modifiers) {
let mask = 0;
for (const modifier of modifiers) {
const index = TOKEN_MODIFIER_INDEX.get(modifier);
if (index !== undefined) mask |= (1 << index);
}
return mask;
}
function isRegUseAccess(tokens, tokenIndex) {
const prev = tokens[tokenIndex - 1];
if (!prev || prev.text !== ".") return false;
const prevPrev = tokens[tokenIndex - 2];
if (!prevPrev || prevPrev.kind !== "identifier") return false;
const next = tokens[tokenIndex + 1];
if (next && next.text === ".") return false;
if (prevPrev.text === "r") return true;
const prev3 = tokens[tokenIndex - 3];
const prev4 = tokens[tokenIndex - 4];
if (prev3 && prev3.text === "." && prev4 && prev4.kind === "identifier" && prev4.text === "r") return true;
return false;
}
function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () => false) {
const scanned = scanSource(source, filePath);
const index = mergeIndexes(workspaceIndex, scanned.index);
const spans = [];
for (let tokenIndex = 0; tokenIndex < scanned.tokens.length; tokenIndex += 1) {
if (shouldCancel()) break;
const token = scanned.tokens[tokenIndex];
if (token.kind !== "identifier") continue;
let type = null;
let modifiers = [];
const declaration = scanned.declarations.get(token.start);
const context = nearestCall(scanned.contexts, tokenIndex);
if (declaration) {
type = ROLE_TO_TYPE[declaration.role] || null;
modifiers = declaration.modifiers.slice();
} else if (ATOM_KEYWORDS.has(token.text)) {
type = "tapeAtomKeyword";
} else if (COMPONENT_KEYWORDS.has(token.text)) {
type = "tapeComponentKeyword";
} else if (ANNOTATIONS.has(token.text)) {
type = "tapeAnnotation";
} else if (context && context.callee === "atom_bind" && context.argIndex === 0) {
type = "tapeBindType";
} else if (context && context.callee === "atom_phase" && context.argIndex === 0) {
type = "tapePhase";
modifiers = ["declaration"];
} else if (context && context.callee === "atom_ctx" && context.argIndex === 0) {
type = "tapeAtomName";
} else if (context && context.callee === "atom_label" && context.argIndex === 0) {
type = "tapeLabel";
modifiers = ["declaration"];
} else if (context && context.callee === "atom_offset" && context.argIndex <= 1) {
type = "tapeLabel";
} else if (context && context.callee === "atom_reads") {
type = registerType(token.text, index);
if (type) modifiers = ["tapeRead"];
} else if (context && context.callee === "atom_writes") {
type = registerType(token.text, index);
if (type) modifiers = ["tapeWrite"];
} else if (context && context.callee === "atom_auto_reg") {
if (context.argIndex === 0) type = "tapeAtomName";
if (context.argIndex === 1) {
type = "tapeGprRegister";
modifiers = ["declaration", "tapeAuto"];
}
} else if (context && context.callee === "phase_auto_reg") {
if (context.argIndex === 0) type = "tapePhase";
if (context.argIndex === 1) {
type = "tapeGprRegister";
modifiers = ["declaration", "tapeAuto"];
}
}
if (!type && index.bindTypes.has(token.text)) type = "tapeBindType";
if (!type && index.types.has(token.text)) type = "tapeDuffleType";
if (!type && index.attributes.has(token.text)) type = "tapeAttribute";
if (!type) type = registerType(token.text, index);
if (!type && DELAY_SLOT_KEYWORDS.has(token.text)) type = "tapeDelaySlot";
if (!type) {
const domain = index.macros.get(token.text);
if (domain && CONTROL_FLOW_PREFIXES.test(token.text)) {
type = "tapeControlFlow";
}
}
if (!type && isRegUseAccess(scanned.tokens, tokenIndex)) type = "tapeGprRegister";
if (!type) type = instructionType(token.text, index);
if (!type && index.atoms.has(token.text)) type = "tapeAtomName";
if (!type && index.components.has(token.text)) type = "tapeComponentName";
if (!type && index.phases.has(token.text)) type = "tapePhase";
if (!type && index.labels.has(token.text)) type = "tapeLabel";
if (!type) continue;
spans.push({
text: token.text,
type,
typeIndex: TOKEN_TYPE_INDEX.get(type),
modifiers,
modifierMask: modifierMask(modifiers),
start: token.start,
length: token.end - token.start,
line: token.line,
character: token.character,
});
}
spans.sort((left, right) => left.start - right.start || left.length - right.length);
const nonOverlapping = [];
for (const span of spans) {
const previous = nonOverlapping[nonOverlapping.length - 1];
if (!previous || previous.start + previous.length <= span.start) nonOverlapping.push(span);
}
return { spans: nonOverlapping, errors: scanned.errors };
}
module.exports = {
TOKEN_MODIFIERS,
TOKEN_TYPES,
classifyDocument,
modifierMask,
};
+111
View File
@@ -0,0 +1,111 @@
"use strict";
const vscode = require("vscode");
const { TOKEN_MODIFIERS, TOKEN_TYPES, classifyDocument } = require("./classifier");
const { createIndex, mergeIndexes, scanSource } = require("./source-index");
const SOURCE_GLOB = "**/*.{c,h,cc,cpp,cxx,hh,hpp,hxx}";
const EXCLUDE_GLOB = "**/{gen,build,.slop_cache,toolchain,node_modules}/**";
const EXCLUDED_SEGMENTS = new Set(["gen", "build", ".slop_cache", "toolchain", "node_modules"]);
function isExcluded(uri) {
const segments = uri.fsPath.replaceAll("\\", "/").split("/");
return segments.some((segment) => EXCLUDED_SEGMENTS.has(segment));
}
function formatError(filePath, error) {
return `${filePath}:${error.offset}: ${error.kind}`;
}
async function activate(context) {
const output = vscode.window.createOutputChannel("Tape Atom DSL");
const emitter = new vscode.EventEmitter();
const legend = new vscode.SemanticTokensLegend(TOKEN_TYPES, TOKEN_MODIFIERS);
let workspaceIndex = createIndex();
let rebuildGeneration = 0;
let debounceHandle = null;
async function rebuildIndex() {
const generation = ++rebuildGeneration;
const files = await vscode.workspace.findFiles(SOURCE_GLOB, EXCLUDE_GLOB);
let nextIndex = createIndex();
for (const uri of files) {
if (generation !== rebuildGeneration) return;
if (isExcluded(uri)) continue;
try {
const bytes = await vscode.workspace.fs.readFile(uri);
const source = Buffer.from(bytes).toString("utf8");
const result = scanSource(source, uri.fsPath);
nextIndex = mergeIndexes(nextIndex, result.index);
for (const error of result.errors) output.appendLine(formatError(uri.fsPath, error));
} catch (error) {
output.appendLine(`${uri.fsPath}: ${error.stack || error.message || error}`);
}
}
if (generation !== rebuildGeneration) return;
workspaceIndex = nextIndex;
emitter.fire();
}
function scheduleRebuild(uri) {
if (uri && isExcluded(uri)) return;
if (debounceHandle !== null) clearTimeout(debounceHandle);
debounceHandle = setTimeout(() => {
debounceHandle = null;
rebuildIndex().catch((error) => output.appendLine(error.stack || String(error)));
}, 100);
}
const provider = {
onDidChangeSemanticTokens: emitter.event,
provideDocumentSemanticTokens(document, cancellationToken) {
try {
const result = classifyDocument(
document.getText(),
document.uri.fsPath,
workspaceIndex,
() => cancellationToken.isCancellationRequested
);
const builder = new vscode.SemanticTokensBuilder(legend);
for (const span of result.spans) {
if (cancellationToken.isCancellationRequested) break;
builder.push(span.line, span.character, span.length, span.typeIndex, span.modifierMask);
}
for (const error of result.errors) {
output.appendLine(formatError(document.uri.fsPath || document.uri.toString(), error));
}
return builder.build();
} catch (error) {
output.appendLine(`${document.uri}: ${error.stack || error.message || error}`);
return new vscode.SemanticTokensBuilder(legend).build();
}
},
};
const selector = [
{ language: "c", scheme: "file" },
{ language: "c", scheme: "untitled" },
{ language: "cpp", scheme: "file" },
{ language: "cpp", scheme: "untitled" },
];
const watcher = vscode.workspace.createFileSystemWatcher(SOURCE_GLOB);
context.subscriptions.push(
output,
emitter,
watcher,
watcher.onDidCreate(scheduleRebuild),
watcher.onDidChange(scheduleRebuild),
watcher.onDidDelete(scheduleRebuild),
vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend),
{ dispose() { if (debounceHandle !== null) clearTimeout(debounceHandle); } }
);
await rebuildIndex();
}
function deactivate() {}
module.exports = { activate, deactivate };
+186
View File
@@ -0,0 +1,186 @@
"use strict";
function isIdentifierStart(code) {
return code === 95 ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122);
}
function isIdentifierContinue(code) {
return isIdentifierStart(code) || (code >= 48 && code <= 57);
}
function lex(source) {
if (typeof source !== "string") throw new TypeError("source must be a string");
const tokens = [];
const errors = [];
let offset = 0;
let line = 0;
let character = 0;
function advance() {
if (source[offset] === "\r" && source[offset + 1] === "\n") {
offset += 2;
line += 1;
character = 0;
return;
}
if (source[offset] === "\n") {
offset += 1;
line += 1;
character = 0;
return;
}
offset += 1;
character += 1;
}
function pushToken(kind, start, startLine, startCharacter) {
tokens.push({
kind,
text: source.slice(start, offset),
start,
end: offset,
line: startLine,
character: startCharacter,
});
}
while (offset < source.length) {
const ch = source[offset];
if (/\s/.test(ch)) {
advance();
continue;
}
if (ch === "/" && source[offset + 1] === "/") {
while (offset < source.length && source[offset] !== "\r" && source[offset] !== "\n") advance();
continue;
}
if (ch === "/" && source[offset + 1] === "*") {
const start = offset;
advance();
advance();
let closed = false;
while (offset < source.length) {
if (source[offset] === "*" && source[offset + 1] === "/") {
advance();
advance();
closed = true;
break;
}
advance();
}
if (!closed) errors.push({ kind: "unterminated-block-comment", offset: start });
continue;
}
if (ch === "\"" || ch === "'") {
const quote = ch;
const start = offset;
advance();
let closed = false;
while (offset < source.length) {
if (source[offset] === "\\") {
advance();
if (offset < source.length) advance();
continue;
}
if (source[offset] === quote) {
advance();
closed = true;
break;
}
if (source[offset] === "\n" || source[offset] === "\r") break;
advance();
}
if (!closed) errors.push({ kind: "unterminated-literal", offset: start });
continue;
}
const code = source.charCodeAt(offset);
if (isIdentifierStart(code)) {
const start = offset;
const startLine = line;
const startCharacter = character;
advance();
while (offset < source.length && isIdentifierContinue(source.charCodeAt(offset))) advance();
pushToken("identifier", start, startLine, startCharacter);
continue;
}
const start = offset;
const startLine = line;
const startCharacter = character;
advance();
pushToken("punctuation", start, startLine, startCharacter);
}
return { tokens, errors };
}
function buildCallContexts(tokens) {
const contexts = Array.from({ length: tokens.length }, () => []);
const calls = [];
const errors = [];
const stack = [];
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
const token = tokens[tokenIndex];
if (token.text === ")") {
if (stack.length === 0) {
errors.push({ kind: "unmatched-close-paren", offset: token.start });
} else {
const frame = stack.pop();
if (frame.callee !== null) calls.push({ ...frame, closeTokenIndex: tokenIndex });
}
}
contexts[tokenIndex] = stack
.filter((frame) => frame.callee !== null)
.map((frame) => ({
callee: frame.callee,
calleeTokenIndex: frame.calleeTokenIndex,
openTokenIndex: frame.openTokenIndex,
argIndex: frame.argIndex,
}));
if (token.text === "(") {
const previous = tokens[tokenIndex - 1];
const hasCallee = previous && previous.kind === "identifier";
stack.push({
callee: hasCallee ? previous.text : null,
calleeTokenIndex: hasCallee ? tokenIndex - 1 : -1,
openTokenIndex: tokenIndex,
argIndex: 0,
});
continue;
}
if (token.text === "," && stack.length > 0) {
const frame = stack[stack.length - 1];
if (frame.callee !== null) frame.argIndex += 1;
}
}
for (const frame of stack) {
errors.push({ kind: "unmatched-open-paren", offset: tokens[frame.openTokenIndex].start });
}
return { contexts, calls, errors };
}
function nearestCall(contexts, tokenIndex, callee) {
const entries = contexts[tokenIndex] || [];
for (let contextIndex = entries.length - 1; contextIndex >= 0; contextIndex -= 1) {
const entry = entries[contextIndex];
if (callee === undefined || entry.callee === callee) return entry;
}
return null;
}
module.exports = { buildCallContexts, lex, nearestCall };
+81
View File
@@ -0,0 +1,81 @@
{
"name": "tape-atom-syntax",
"displayName": "Tape Atom DSL",
"description": "Source-derived semantic highlighting for the Tape/Atom MIPS macro DSL",
"publisher": "local",
"version": "0.2.0",
"engines": { "vscode": "^1.80.0" },
"categories": ["Programming Languages"],
"activationEvents": ["onLanguage:c", "onLanguage:cpp"],
"main": "./extension.js",
"files": [
"classifier.js",
"extension.js",
"lexer.js",
"source-index.js",
"syntaxes/tape_atom.tmLanguage.json"
],
"scripts": {
"test": "node --test test/*.test.js",
"package": "npx --yes @vscode/vsce@3.6.1 package --allow-missing-repository --skip-license --out tape-atom-syntax-0.2.0.vsix"
},
"contributes": {
"semanticTokenTypes": [
{ "id": "tapeAtomKeyword", "superType": "keyword", "description": "Tape atom declaration keyword" },
{ "id": "tapeAtomName", "superType": "function", "description": "Tape atom name" },
{ "id": "tapeComponentKeyword", "superType": "keyword", "description": "Tape atom component declaration keyword" },
{ "id": "tapeComponentName", "superType": "function", "description": "Tape atom component name" },
{ "id": "tapeAnnotation", "superType": "macro", "description": "Tape atom annotation" },
{ "id": "tapeBindType", "superType": "type", "description": "Tape bind structure type" },
{ "id": "tapePhase", "superType": "label", "description": "Tape atom phase" },
{ "id": "tapeLabel", "superType": "label", "description": "Tape atom branch label" },
{ "id": "tapeCpuInstruction", "superType": "macro", "description": "MIPS CPU instruction emitter" },
{ "id": "tapeControlFlow", "superType": "keyword", "description": "MIPS branch or jump instruction" },
{ "id": "tapeGteInstruction", "superType": "macro", "description": "GTE instruction emitter" },
{ "id": "tapeGpuInstruction", "superType": "macro", "description": "GPU command emitter" },
{ "id": "tapeComponentInstruction", "superType": "macro", "description": "Tape atom component invocation" },
{ "id": "tapeDelaySlot", "superType": "keyword", "description": "Load or branch delay slot annotation" },
{ "id": "tapeGprRegister", "superType": "variable", "description": "MIPS GPR alias" },
{ "id": "tapeCop2Register", "superType": "variable", "description": "COP2 data or control register alias" },
{ "id": "tapeDuffleType", "superType": "type", "description": "Duffle type or type constructor" },
{ "id": "tapeAttribute", "superType": "keyword", "description": "Duffle linkage or storage attribute" }
],
"semanticTokenModifiers": [
{ "id": "tapeRead", "description": "Register declared in atom_reads" },
{ "id": "tapeWrite", "description": "Register declared in atom_writes" },
{ "id": "tapeAuto", "description": "Auto-allocated register" }
],
"semanticTokenScopes": [
{
"language": "c",
"scopes": {
"tapeAtomKeyword": ["keyword.control.duffle.atom"],
"tapeAtomName": ["entity.name.function.duffle.atom"],
"tapeComponentKeyword": ["keyword.control.duffle.component"],
"tapeComponentName": ["entity.name.function.duffle.component"],
"tapeAnnotation": ["support.function.duffle.annotation"],
"tapeBindType": ["entity.name.type.duffle.bind"],
"tapePhase": ["entity.name.tag.duffle.phase"],
"tapeLabel": ["entity.name.label.duffle.atom"],
"tapeCpuInstruction": ["support.function.duffle.cpu"],
"tapeControlFlow": ["keyword.control.duffle.branch"],
"tapeGteInstruction": ["support.function.duffle.gte"],
"tapeGpuInstruction": ["support.function.duffle.gpu"],
"tapeComponentInstruction": ["support.function.duffle.component"],
"tapeDelaySlot": ["keyword.operator.duffle.delayslot"],
"tapeGprRegister": ["variable.other.constant.duffle.gpr"],
"tapeCop2Register": ["variable.other.constant.duffle.cop2"],
"tapeDuffleType": ["storage.type.duffle.type"],
"tapeAttribute": ["storage.modifier.duffle.attr"]
}
}
],
"grammars": [
{
"scopeName": "tape_atom.injection",
"path": "./syntaxes/tape_atom.tmLanguage.json",
"injectTo": ["source.c", "source.cpp"]
}
]
}
}
+234
View File
@@ -0,0 +1,234 @@
"use strict";
const path = require("node:path");
const { buildCallContexts, lex, nearestCall } = require("./lexer");
const BASE_TYPES = [
"B1", "B2", "B4", "B8", "F4", "F8", "S1", "S2", "S4", "S8",
"U1", "U2", "U4", "U8", "MipsAtom", "MipsCode", "Reg",
];
const BASE_ATTRIBUTES = [
"FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_",
"TypeR_", "TypeV_", "align_", "internal", "local_persist", "global",
];
function createIndex() {
return {
atoms: new Set(),
components: new Set(),
componentAliases: new Set(),
macros: new Map(),
registers: new Map(),
bindTypes: new Set(),
types: new Set(BASE_TYPES),
phases: new Set(),
labels: new Set(),
attributes: new Set(BASE_ATTRIBUTES),
};
}
function cloneIndex(source) {
const result = createIndex();
for (const key of ["atoms", "components", "componentAliases", "bindTypes", "types", "phases", "labels", "attributes"]) {
for (const value of source[key]) result[key].add(value);
}
for (const [name, domain] of source.macros) result.macros.set(name, domain);
for (const [name, domain] of source.registers) result.registers.set(name, domain);
return result;
}
function mergeIndexes(...sources) {
const result = createIndex();
for (const source of sources) {
if (!source) continue;
for (const key of ["atoms", "components", "componentAliases", "bindTypes", "types", "phases", "labels", "attributes"]) {
for (const value of source[key]) result[key].add(value);
}
for (const [name, domain] of source.macros) result.macros.set(name, domain);
for (const [name, domain] of source.registers) result.registers.set(name, domain);
}
return result;
}
function domainFromPath(filePath) {
const base = path.basename(filePath.replaceAll("\\", "/")).toLowerCase();
if (base === "mips.h" || base === "mips.atom.c") return "cpu";
if (base === "gte.h" || base === "gte.atom.c") return "gte";
if (base === "gp.h" || base === "gp.atom.c") return "gpu";
return "component";
}
function registerKind(name) {
if (/^R_[A-Za-z0-9_]+$/.test(name)) return "gpr";
if (/^(?:C2_|gte_cr_)[A-Za-z0-9_]+$/.test(name)) return "cop2";
return null;
}
function componentAlias(name) {
return name.startsWith("ac_") ? `mac_${name.slice(3)}` : null;
}
function findFunctionNameBefore(tokens, calleeTokenIndex) {
let closeIndex = calleeTokenIndex - 1;
while (closeIndex >= 0 && tokens[closeIndex].kind === "identifier" && tokens[closeIndex].text === "atom_dbg_skip") {
closeIndex -= 1;
}
if (!tokens[closeIndex] || tokens[closeIndex].text !== ")") return null;
let depth = 1;
for (let tokenIndex = closeIndex - 1; tokenIndex >= 0; tokenIndex -= 1) {
if (tokens[tokenIndex].text === ")") depth += 1;
if (tokens[tokenIndex].text === "(") depth -= 1;
if (depth !== 0) continue;
const name = tokens[tokenIndex - 1];
return name && name.kind === "identifier" ? name : null;
}
return null;
}
function scanSource(source, filePath) {
const lexical = lex(source);
const balanced = buildCallContexts(lexical.tokens);
const tokens = lexical.tokens;
const contexts = balanced.contexts;
const index = createIndex();
const declarations = new Map();
const domain = domainFromPath(filePath);
function mark(token, role, modifiers = ["declaration"]) {
declarations.set(token.start, { role, modifiers });
}
function componentDomain(name) {
if (name.startsWith("ac_gte_") || name.startsWith("mac_gte_")) return "gte";
if (name.startsWith("ac_gp_") || name.startsWith("mac_gp_")) return "gpu";
return "cpu";
}
function addComponent(token) {
index.components.add(token.text);
mark(token, "componentName");
const alias = componentAlias(token.text);
if (alias) {
index.componentAliases.add(alias);
index.macros.set(alias, componentDomain(token.text));
}
}
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
const token = tokens[tokenIndex];
if (token.kind !== "identifier") continue;
const kind = registerKind(token.text);
if (kind) {
index.registers.set(token.text, kind);
if (tokens[tokenIndex + 1] && tokens[tokenIndex + 1].text === "=") {
mark(token, kind === "gpr" ? "gprRegister" : "cop2Register");
}
}
const context = nearestCall(contexts, tokenIndex);
if (context && context.argIndex === 0) {
if (context.callee === "MipsAtom_") {
index.atoms.add(token.text);
mark(token, "atomName");
}
if (context.callee === "MipsAtomComp_") addComponent(token);
if (context.callee === "atom_bind") index.bindTypes.add(token.text);
if (context.callee === "atom_phase" || context.callee === "phase_auto_reg") index.phases.add(token.text);
if (context.callee === "atom_label" || context.callee === "atom_offset") index.labels.add(token.text);
}
const isWrappedType = context && (
((context.callee === "Struct_" || context.callee === "Union_") && context.argIndex === 0) ||
(context.callee === "Enum_" && context.argIndex === 1)
);
if (isWrappedType) {
index.types.add(token.text);
mark(token, token.text.startsWith("Binds_") ? "bindType" : "duffleType");
if (token.text.startsWith("Binds_")) index.bindTypes.add(token.text);
}
if (context && context.callee === "atom_offset" && context.argIndex === 1) index.labels.add(token.text);
if (context && context.callee === "atom_auto_reg") {
if (context.argIndex === 0) index.atoms.add(token.text);
if (context.argIndex === 1) {
index.registers.set(token.text, "gpr");
mark(token, "gprRegister", ["declaration", "tapeAuto"]);
}
}
if (context && context.callee === "phase_auto_reg" && context.argIndex === 1) {
index.registers.set(token.text, "gpr");
mark(token, "gprRegister", ["declaration", "tapeAuto"]);
}
if (token.text === "define" && tokens[tokenIndex - 1] && tokens[tokenIndex - 1].text === "#") {
const name = tokens[tokenIndex + 1];
if (name && name.kind === "identifier" && name.line === token.line) {
if (/^(?:RegUse_|Struct_|Enum_|Union_|TypeR_|TypeV_|Relative_|Binds_)/.test(name.text)) {
index.types.add(name.text);
} else {
index.macros.set(name.text, domain);
}
}
}
if (token.text === "typedef") {
let endIndex = tokenIndex + 1;
let hasBrace = false;
let lastIdentifier = null;
while (endIndex < tokens.length && tokens[endIndex].text !== ";") {
if (tokens[endIndex].text === "{") hasBrace = true;
if (tokens[endIndex].kind === "identifier") lastIdentifier = tokens[endIndex];
endIndex += 1;
}
if (!hasBrace && lastIdentifier) {
index.types.add(lastIdentifier.text);
mark(lastIdentifier, "duffleType");
}
}
if (token.text === "MipsAtom_Proc_") {
const functionName = findFunctionNameBefore(tokens, tokenIndex);
if (functionName) {
const atomName = functionName.text.endsWith("_proc")
? functionName.text.slice(0, -5)
: functionName.text;
index.atoms.add(atomName);
index.atoms.add(functionName.text);
mark(functionName, "atomName");
}
}
if (token.text === "MipsAtomComp_Proc_") {
const functionName = findFunctionNameBefore(tokens, tokenIndex);
if (functionName) addComponent(functionName);
}
}
for (const call of balanced.calls) {
if (domain === "component") continue;
const name = tokens[call.calleeTokenIndex];
const after = tokens[call.closeTokenIndex + 1];
if (!name || !after || after.text !== "{") continue;
if (/^(?:gp0_|gp1_|gte_|mac_)/.test(name.text)) index.macros.set(name.text, domain);
}
return {
index: cloneIndex(index),
declarations,
tokens,
contexts,
errors: [...lexical.errors, ...balanced.errors],
};
}
module.exports = {
createIndex,
domainFromPath,
mergeIndexes,
scanSource,
};
@@ -0,0 +1,71 @@
{
"scopeName": "tape_atom.injection",
"injectionSelector": "L:source.c -comment -string, L:source.cpp -comment -string",
"patterns": [
{ "include": "#atom-declarations" },
{ "include": "#component-declarations" },
{ "include": "#annotation-arguments" },
{ "include": "#annotations" },
{ "include": "#delay-slots" },
{ "include": "#types" },
{ "include": "#attributes" }
],
"repository": {
"atom-declarations": {
"patterns": [
{
"match": "\\b(MipsAtom_)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.duffle.atom" },
"2": { "name": "entity.name.function.duffle.atom" }
}
},
{ "match": "\\bMipsAtom_Proc_\\b", "name": "keyword.control.duffle.atom" },
{ "match": "\\b[A-Za-z_][A-Za-z0-9_]*_proc\\b", "name": "entity.name.function.duffle.atom" }
]
},
"component-declarations": {
"patterns": [
{
"match": "\\b(MipsAtomComp_)\\s*\\(\\s*(ac_[A-Za-z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.duffle.component" },
"2": { "name": "entity.name.function.duffle.component" }
}
},
{ "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" }
]
},
"annotation-arguments": {
"patterns": [
{
"match": "\\b(atom_offset)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*,\\s*([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "support.function.duffle.annotation" },
"2": { "name": "entity.name.label.duffle.atom" },
"3": { "name": "entity.name.label.duffle.atom" }
}
},
{ "match": "(?<=\\batom_bind\\()\\s*Binds_[A-Za-z0-9_]+", "name": "entity.name.type.duffle.bind" },
{ "match": "(?<=\\batom_phase\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.tag.duffle.phase" },
{ "match": "(?<=\\batom_label\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.label.duffle.atom" }
]
},
"annotations": {
"match": "\\b(atom_info|atom_bind|atom_reads|atom_writes|atom_label|atom_offset|atom_reg|atom_type|atom_ctx|atom_phase|atom_auto_reg|phase_auto_reg|atom_dbg_skip)\\b",
"name": "support.function.duffle.annotation"
},
"delay-slots": {
"match": "\\b(LdSlot_|BdSlot_)\\b",
"name": "keyword.operator.duffle.delayslot"
},
"types": {
"match": "\\b(?:Binds_[A-Za-z0-9_]+|RegUse_[A-Za-z0-9_]+)\\b",
"name": "storage.type.duffle.type"
},
"attributes": {
"match": "\\b(?:FI_|I_|NI_|Relative_|align_|internal|local_persist|global)\\b",
"name": "storage.modifier.duffle.attr"
}
}
}
Binary file not shown.
+83
View File
@@ -0,0 +1,83 @@
"use strict";
const assert = require("node:assert/strict");
const test = require("node:test");
const { classifyDocument } = require("../classifier");
const { createIndex } = require("../source-index");
function byText(result, text) {
return result.spans.filter((span) => span.text === text);
}
test("classifyDocument distinguishes declaration, annotation, phase, bind, and label roles", () => {
const source = [
"typedef Struct_(Binds_CubeTri) { U4 PrimCursor; };",
"MipsAtom_(cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_phase(cube_g4),",
"\tatom_reads(R_PrimCursor), atom_writes(R_FaceCursor)) {",
"\tbranch_le_zero(R_T0, atom_offset(cull, exit)),",
"\tatom_label(exit)",
"};",
].join("\n");
const result = classifyDocument(source, "C:/x/code/hello_camera/hello_camera.atom.c", createIndex());
assert.equal(byText(result, "MipsAtom_")[0].type, "tapeAtomKeyword");
assert.deepEqual(byText(result, "cube_g4_face")[0].modifiers, ["declaration"]);
assert.equal(byText(result, "atom_bind")[0].type, "tapeAnnotation");
assert.equal(byText(result, "Binds_CubeTri").at(-1).type, "tapeBindType");
assert.equal(byText(result, "cube_g4")[0].type, "tapePhase");
assert.deepEqual(byText(result, "cube_g4")[0].modifiers, ["declaration"]);
assert.equal(byText(result, "cull")[0].type, "tapeLabel");
assert.equal(byText(result, "exit").every((span) => span.type === "tapeLabel"), true);
});
test("classifyDocument applies read and write modifiers to GPRs", () => {
const source = "atom_info(atom_reads(R_PrimCursor), atom_writes(R_FaceCursor))";
const result = classifyDocument(source, "C:/x/code/test.atom.c", createIndex());
assert.deepEqual(byText(result, "R_PrimCursor")[0].modifiers, ["tapeRead"]);
assert.deepEqual(byText(result, "R_FaceCursor")[0].modifiers, ["tapeWrite"]);
});
test("classifyDocument separates CPU, GTE, GPU, and component domains", () => {
const workspace = createIndex();
workspace.macros.set("load_word", "cpu");
workspace.macros.set("gte_cmdw_rtpt", "gte");
workspace.macros.set("gp1_word_DisplayOn", "gpu");
workspace.macros.set("mac_yield", "component");
workspace.componentAliases.add("mac_yield");
const source = "load_word(R_T0, R_T1, 0), gte_cmdw_rtpt, gp1_word_DisplayOn(), mac_yield(), C2_MAC0, gte_cr_OFX_Code";
const result = classifyDocument(source, "C:/x/code/test.c", workspace);
assert.equal(byText(result, "load_word")[0].type, "tapeCpuInstruction");
assert.equal(byText(result, "gte_cmdw_rtpt")[0].type, "tapeGteInstruction");
assert.equal(byText(result, "gp1_word_DisplayOn")[0].type, "tapeGpuInstruction");
assert.equal(byText(result, "mac_yield")[0].type, "tapeComponentInstruction");
assert.equal(byText(result, "C2_MAC0")[0].type, "tapeCop2Register");
assert.equal(byText(result, "gte_cr_OFX_Code")[0].type, "tapeCop2Register");
});
test("document-local declarations override an empty workspace index", () => {
const source = [
"MipsAtomComp_(ac_new_component) { nop };",
"mac_new_component(),",
].join("\n");
const result = classifyDocument(source, "C:/x/code/duffle/math.atom.c", createIndex());
assert.equal(byText(result, "ac_new_component")[0].type, "tapeComponentName");
assert.equal(byText(result, "mac_new_component")[0].type, "tapeCpuInstruction");
});
test("classifier returns ordered non-overlapping spans and partial malformed output", () => {
const source = "atom_reads(R_A /* broken";
const result = classifyDocument(source, "C:/x/code/test.atom.c", createIndex());
assert.equal(result.errors.some((error) => error.kind === "unterminated-block-comment"), true);
for (let spanIndex = 1; spanIndex < result.spans.length; spanIndex += 1) {
const previous = result.spans[spanIndex - 1];
const current = result.spans[spanIndex];
assert.equal(previous.start + previous.length <= current.start, true);
}
});
+91
View File
@@ -0,0 +1,91 @@
"use strict";
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const { TOKEN_MODIFIERS, TOKEN_TYPES } = require("../classifier");
const ROOT = path.resolve(__dirname, "..");
function readJson(filePath) {
const raw = fs.readFileSync(filePath, "utf8");
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1");
return JSON.parse(stripped);
}
function collectScopeNames(value, output = new Set()) {
if (Array.isArray(value)) {
for (const entry of value) collectScopeNames(entry, output);
return output;
}
if (!value || typeof value !== "object") return output;
if (typeof value.name === "string") output.add(value.name);
for (const child of Object.values(value)) collectScopeNames(child, output);
return output;
}
test("package semantic legend matches classifier exports", () => {
const packageJson = readJson(path.join(ROOT, "package.json"));
const contributedTypes = packageJson.contributes.semanticTokenTypes.map((entry) => entry.id);
const contributedModifiers = packageJson.contributes.semanticTokenModifiers.map((entry) => entry.id);
assert.equal(packageJson.version, "0.2.0");
assert.deepEqual(contributedTypes, TOKEN_TYPES);
assert.deepEqual(contributedModifiers, TOKEN_MODIFIERS.filter((name) => name !== "declaration"));
});
test("package includes runtime files only and acknowledges local-only metadata", () => {
const packageJson = readJson(path.join(ROOT, "package.json"));
assert.deepEqual(packageJson.files, [
"classifier.js",
"extension.js",
"lexer.js",
"source-index.js",
"syntaxes/tape_atom.tmLanguage.json",
]);
assert.equal(packageJson.scripts.package.includes("--allow-missing-repository"), true);
assert.equal(packageJson.scripts.package.includes("--skip-license"), true);
});
test("every semantic token has a scope mapping; DSL-specific tokens also have grammar scopes", () => {
const packageJson = readJson(path.join(ROOT, "package.json"));
const grammar = readJson(path.join(ROOT, "syntaxes", "tape_atom.tmLanguage.json"));
const mappings = packageJson.contributes.semanticTokenScopes[0].scopes;
const grammarScopes = collectScopeNames(grammar);
const grammarRequired = new Set([
"tapeAtomKeyword", "tapeAtomName", "tapeComponentKeyword", "tapeComponentName",
"tapeAnnotation", "tapeBindType", "tapePhase", "tapeLabel",
"tapeDelaySlot", "tapeDuffleType", "tapeAttribute",
]);
for (const tokenType of TOKEN_TYPES) {
assert.equal(Array.isArray(mappings[tokenType]), true, `missing scope mapping: ${tokenType}`);
if (grammarRequired.has(tokenType)) {
assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`);
}
}
});
test("TextMate offset labels stay scoped to atom_offset calls", () => {
const grammar = readJson(path.join(ROOT, "syntaxes", "tape_atom.tmLanguage.json"));
const serialized = JSON.stringify(grammar);
const offsetRule = grammar.repository["annotation-arguments"].patterns
.find((rule) => rule.match.includes("atom_offset"));
assert.equal(serialized.includes("(?<=,)"), false);
assert.equal(offsetRule.captures[1].name, "support.function.duffle.annotation");
assert.equal(offsetRule.captures[2].name, "entity.name.label.duffle.atom");
assert.equal(offsetRule.captures[3].name, "entity.name.label.duffle.atom");
});
test("workspace enables semantic highlighting", () => {
const settings = readJson(path.resolve(ROOT, "..", "settings.json"));
assert.equal(settings["editor.semanticHighlighting.enabled"], true);
const semantic = settings["editor.semanticTokenColorCustomizations"];
assert.equal(semantic.enabled, true);
});
+77
View File
@@ -0,0 +1,77 @@
"use strict";
const assert = require("node:assert/strict");
const test = require("node:test");
const { buildCallContexts, lex, nearestCall } = require("../lexer");
test("lex skips comments, strings, and character literals", () => {
const source = [
"MipsAtom_(visible)",
"// MipsAtom_(line_comment)",
"const char *s = \"atom_reads(R_Hidden)\";",
"char c = '\\''; /* gte_cmdw_hidden */",
"atom_reads(R_Visible)",
].join("\n");
const result = lex(source);
const identifiers = result.tokens
.filter((token) => token.kind === "identifier")
.map((token) => token.text);
assert.deepEqual(result.errors, []);
assert.equal(identifiers.includes("visible"), true);
assert.equal(identifiers.includes("R_Visible"), true);
assert.equal(identifiers.includes("line_comment"), false);
assert.equal(identifiers.includes("R_Hidden"), false);
assert.equal(identifiers.includes("gte_cmdw_hidden"), false);
});
test("lex reports unterminated block comments without returning comment tokens", () => {
const result = lex("R_Visible /* atom_reads(R_Hidden)");
assert.equal(result.tokens.some((token) => token.text === "R_Visible"), true);
assert.equal(result.tokens.some((token) => token.text === "R_Hidden"), false);
assert.deepEqual(result.errors.map((error) => error.kind), ["unterminated-block-comment"]);
});
test("line comments stop at CRLF boundaries", () => {
const result = lex("// atom_reads(R_Hidden)\r\natom_reads(R_Visible)\r\n");
const identifiers = result.tokens
.filter((token) => token.kind === "identifier")
.map((token) => token.text);
assert.equal(identifiers.includes("R_Hidden"), false);
assert.equal(identifiers.includes("R_Visible"), true);
});
test("balanced contexts retain multiline nesting and argument indexes", () => {
const source = [
"atom_info(",
"\tatom_phase(cube_g4),",
"\tatom_reads(R_A, nested(R_B, R_C)),",
"\tatom_writes(R_D)",
")",
].join("\n");
const lexical = lex(source);
const balanced = buildCallContexts(lexical.tokens);
const byText = new Map();
lexical.tokens.forEach((token, index) => {
if (token.kind === "identifier") byText.set(token.text, index);
});
assert.equal(nearestCall(balanced.contexts, byText.get("cube_g4")).callee, "atom_phase");
assert.equal(nearestCall(balanced.contexts, byText.get("R_A")).callee, "atom_reads");
assert.equal(nearestCall(balanced.contexts, byText.get("R_A")).argIndex, 0);
assert.equal(nearestCall(balanced.contexts, byText.get("R_C")).callee, "nested");
assert.equal(nearestCall(balanced.contexts, byText.get("R_D")).callee, "atom_writes");
assert.deepEqual(balanced.errors, []);
});
test("balanced contexts report unmatched parentheses", () => {
const lexical = lex("atom_reads(R_A");
const balanced = buildCallContexts(lexical.tokens);
assert.deepEqual(balanced.errors.map((error) => error.kind), ["unmatched-open-paren"]);
});
+77
View File
@@ -0,0 +1,77 @@
"use strict";
const assert = require("node:assert/strict");
const test = require("node:test");
const {
createIndex,
domainFromPath,
mergeIndexes,
scanSource,
} = require("../source-index");
test("scanSource discovers current atom and component forms", () => {
const source = [
"MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4), atom_reads(R_PrimCursor)) { mac_yield() };",
"MipsAtomComp_(ac_load_pair) { load_word(R_T0, R_T1, 0) };",
"internal MipsAtom* normalize_proc(AtomArena_R aa) MipsAtom_Proc_(aa, { mac_yield() })",
"FI_ void ac_store_pair(MipsAtomBuilder_R ab) atom_dbg_skip MipsAtomComp_Proc_(ab, { store_word(R_T0, R_T1, 0) })",
].join("\n");
const result = scanSource(source, "C:/projects/Pikuma/ps1/code/duffle/mips.atom.c");
assert.equal(result.index.atoms.has("cube_g4_face"), true);
assert.equal(result.index.atoms.has("normalize"), true);
assert.equal(result.index.components.has("ac_load_pair"), true);
assert.equal(result.index.components.has("ac_store_pair"), true);
assert.equal(result.index.componentAliases.has("mac_load_pair"), true);
assert.equal(result.index.componentAliases.has("mac_store_pair"), true);
assert.equal(result.index.macros.get("mac_store_pair"), "cpu");
assert.equal(result.index.phases.has("cube_g4"), true);
assert.equal(result.index.registers.get("R_PrimCursor"), "gpr");
assert.deepEqual(result.errors, []);
});
test("scanSource discovers binds, labels, registers, typedefs, and macro domains", () => {
const source = [
"typedef Struct_(Binds_CubeTri) { U4 PrimCursor; };",
"typedef Enum_(U4, PadStatus) { PadStatus_Ok };",
"typedef U4 const MipsCode;",
"enum { R_PrimCursor = R_T7 atom_reg, C2_Custom = 12, gte_cr_Custom = 13 };",
"#define load_word(rt, base, off) enc_i(rt, base, off)",
"atom_bind(Binds_CubeTri)",
"atom_label(exit)",
"atom_offset(entry, exit)",
].join("\n");
const result = scanSource(source, "C:/projects/Pikuma/ps1/code/duffle/mips.h");
assert.equal(result.index.bindTypes.has("Binds_CubeTri"), true);
assert.equal(result.index.types.has("PadStatus"), true);
assert.equal(result.index.types.has("MipsCode"), true);
assert.equal(result.index.registers.get("R_PrimCursor"), "gpr");
assert.equal(result.index.registers.get("C2_Custom"), "cop2");
assert.equal(result.index.registers.get("gte_cr_Custom"), "cop2");
assert.equal(result.index.macros.get("load_word"), "cpu");
assert.equal(result.index.labels.has("entry"), true);
assert.equal(result.index.labels.has("exit"), true);
});
test("domainFromPath uses the declaration file rather than parent directory names", () => {
assert.equal(domainFromPath("C:/x/code/hello_gte/hello_gte.atom.c"), "component");
assert.equal(domainFromPath("C:/x/code/duffle/mips.h"), "cpu");
assert.equal(domainFromPath("C:/x/code/duffle/gte.atom.c"), "gte");
assert.equal(domainFromPath("C:/x/code/duffle/gp.h"), "gpu");
});
test("mergeIndexes preserves domain-specific aliases", () => {
const left = createIndex();
left.macros.set("load_word", "cpu");
const right = createIndex();
right.componentAliases.add("mac_gte_store");
right.macros.set("mac_gte_store", "gte");
const merged = mergeIndexes(left, right);
assert.equal(merged.macros.get("load_word"), "cpu");
assert.equal(merged.macros.get("mac_gte_store"), "gte");
});
+14 -21
View File
@@ -1,24 +1,17 @@
This is free and unencumbered software released into the public domain.
Copyright (C) 2026 Edward R. Gonzalez
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
+2 -2
View File
@@ -70,8 +70,8 @@
/* ----------------------------------------------------------------------------
* atom_reg (per-enum opt-in marker for the DWARF register-alias registry)
*
* The bare `atom_reg` token adjacent to an enum entry in mips.h / lottes_tape.h flags that alias as debug-visible for scan_source's register_alias_registry.
* The C preprocessor strips it to a comment so no runtime symbol is created; the Lua scanner reads the bare token.
* Bare `atom_reg` token adjacent to an enum entry that alias as debug-visible for scan_source's register_alias_registry.
* Lua scanner reads the bare token.
* ----------------------------------------------------------------------------*/
#define atom_reg /* atom_reg: opt the preceding enum entry into the DWARF registry */
+15 -13
View File
@@ -3,7 +3,7 @@
# include "assert.h"
#endif
#define offset_of(type, member) cast(U8,__builtin_offsetof(type,member))
#define offset_of(type, member) cast(U8,__builtin_offsetof(type,member)) // Compiler builtin version of O_
#define static_assert _Static_assert
#define typeof __typeof__
#define typeof_ptr(ptr) typeof((ptr)[0])
@@ -97,6 +97,7 @@
#define Array_expand(type,len) type Array_sym(type, len)[len]; typedef PtrSet_(Array_sym(type, len))
#define Array_(type,len) Array_expand(type,len)
#define Bit_(id,b) id = (1 << b), tmpl(id,pos) = b
#define Bitmask_(b) (1u << b)
#define Enum_(underlying_type, symbol) underlying_type TSet_(symbol); enum symbol
#define Proc_(symbol) symbol
#define Relative_(symbol) // Does nothing but annotate that a symbol is associated with another.
@@ -139,16 +140,15 @@ enum { false = 0, true = 1, true_overflow, };
typedef void Proc_(VoidFn) (void);
#define kilo(n) (C_(U4, n) << 10)
#define mega(n) (C_(U4, n) << 20)
#define giga(n) (C_(U4, n) << 30)
#define tera(n) (C_(U4, n) << 40)
#define Kilo_(n) (C_(U4, n) << 10)
#define Mega_(n) (C_(U4, n) << 20)
#define Giga_(n) (C_(U4, n) << 30)
#define Tera_(n) (C_(U4, n) << 40)
#define null C_(U4, 0)
#define nullptr C_(void*, 0)
#define O_(type, field) C_(U4, & C_(type*,0)->field)
#define OA_(type, member, idx) C_(U4, & C_(type*,0)->member[idx])
#define OT_(field) O_(typeof_ptr(& field), filed))
#define OT_(field) O_(typeof_ptr(& field), field))
#define S_(data) C_(U4, sizeof(data))
#define sop_1(op,a,b) C_(U1, s1_(a) op s1_(b))
@@ -185,7 +185,7 @@ def_signed_ops(le, <=)
#define alignas _Alignas
#define alignof _Alignof
#define byte_pad(amount, ...) B1 glue(_PAD_, __VA_ARGS__) [amount]
#define pcast(type, data) (C_(type*, & (data)) [0])
#define C_ptr(type, data) (C_(type*, & (data)) [0])
#define dbg_args(...) __VA_ARGS__
@@ -200,6 +200,8 @@ def_signed_ops(le, <=)
#define defer_info(type,expr, ...) for(type info= {__VA_ARGS__}; info.once!=1;++info.once,(expr)) // Defer with tracked state
#define do_while(cond) for (U8 once=0; once!=1 || (cond); ++once)
#define Jmp_nZero_(cond,label) if (cond) goto label;
#pragma endregion Control Flow & Iteration
#define span_iter(type, iter, m_begin, op, m_end) ( \
@@ -216,16 +218,16 @@ def_signed_ops(le, <=)
typedef Span_(S4);
typedef Span_(U4);
#if 0
#pragma region Debug
#define debug_trap() __builtin_debugtrap()
#define debug_trap() __builtin_trap()
#if BUILD_DEBUG
IA_ void assert(U8 cond) { if(cond){return;} else{debug_trap(); ms_exit_process(1);} }
#define assert(cond) if(cond == false){debug_trap();}
#else
#define assert(cond)
# ifndef assert
# include <assert.h>
# endif
#endif
#pragma endregion Debug
#endif
#define GCC_OPTIMIZATION_DISABLE _Pragma("GCC push_options") _Pragma("GCC optimize(\"O0\")")
#define GCC_OPTIMIZATION_ENABLE _Pragma("GCC pop_options")
+72 -60
View File
@@ -99,6 +99,12 @@ WORD_COUNT(mac_sub_v3s4, 3)
, store_half(rt_height, base, offset + O_(Rect_S2,height))
WORD_COUNT(mac_store_rects2, 4)
/* atom_dbg_skip */
#define mac_load_word_imm(dst, imm) \
load_upper_i(dst, u4_hi(imm)) \
, or_i_self( dst, u4_lo(imm))
WORD_COUNT(mac_load_word_imm, 2)
/* atom_dbg_skip */
#define mac_load_tri_indices(r_face_cusor, r_i0, r_i1, r_i2) \
load_half_u(r_i0, r_face_cusor, 0 * S_(S2)) \
@@ -149,16 +155,21 @@ WORD_COUNT(mac_gte_store_g4_p3, 1)
/* atom_dbg_skip */
#define mac_gte_sqr_v3(r_sx, r_sy, r_sz, r_sq_x, r_sq_y, r_sq_z) \
gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \
, nop \
, gte_cmdw_sqr \
mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, nop) \
, gte_mv_from_data_r(r_sq_x, C2_MAC1) \
, gte_mv_from_data_r(r_sq_y, C2_MAC2) \
, gte_mv_from_data_r(r_sq_z, C2_MAC3)
WORD_COUNT(mac_gte_sqr_v3, 8)
/* atom_dbg_skip */
#define mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, nop_slot) \
gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \
, nop_slot \
, gte_cmdw_sqr
WORD_COUNT(mac_gte_sqr_v3s4, 5)
/* atom_dbg_skip */
#define mac_gte_gpf_scale(r_sx, r_sy, r_sz, r_recip_est, r_shift, r_dx, r_dy, r_dz) \
gte_mv_to_data_r(r_recip_est, C2_IR0) \
@@ -175,58 +186,58 @@ WORD_COUNT(mac_gte_sqr_v3, 8)
, shift_aright_var(r_dz, r_dz, r_shift)
WORD_COUNT(mac_gte_gpf_scale, 13)
#define mac_apply_matrix_lv(r_mtx, r_vec, r_out, r_t0, r_t1, r_t2) \
load_word(r_t0, r_mtx, 0) \
, nop \
, gte_mv_to_ctrl_r(r_t0, gte_cr_RT11_Code) \
, load_word(r_t0, r_mtx, 4) \
, nop \
, gte_mv_to_ctrl_r(r_t0, gte_cr_RT12_Code) \
, load_word(r_t0, r_mtx, 8) \
, nop \
, gte_mv_to_ctrl_r(r_t0, gte_cr_RT13_Code) \
, load_word(r_t0, r_mtx, 12) \
, nop \
, gte_mv_to_ctrl_r(r_t0, gte_cr_RT21_Code) \
, load_half_u(r_t0, r_mtx, 16) \
, nop \
, gte_mv_to_ctrl_r(r_t0, gte_cr_RT22_Code) \
, nop2 /* Load PACKED pos into V0 (libgte SVECTOR layout).
* r_vec points to atom-0-staged packed data ((pos.y << 16) | pos.x at +0, pos.z at +4).
* LWC2 base register MUST be the pointer r_vec, NOT the loaded value r_t0. */ \
, load_word(r_t0, r_vec, 0) \
, nop \
, gte_lw(C2_VXY0, r_vec, 0) \
, load_word(r_t0, r_vec, 4) \
, nop \
, gte_lw(C2_VZ0, r_vec, 4) /* RTPS: cv=3 (no translation), sf=1 (no shift, integer), v=0 (V0 input),
* mx=0 (rotation matrix). MAC = RT row · V0 + 0. RTPS also writes
* SXY0/1/2 + SZ0..SZ3 (perspective division); ignored. */ \
, gte_cmdw_rtps_sf1 /* Read MAC1/2/3 → out. */ \
, gte_mv_from_data_r(r_t0, C2_MAC1) \
, gte_mv_from_data_r(r_t1, C2_MAC2) \
, gte_mv_from_data_r(r_t2, C2_MAC3) \
, nop \
, store_word(r_t0, r_out, 0) \
, store_word(r_t1, r_out, 4) \
, store_word(r_t2, r_out, 8)
WORD_COUNT(mac_apply_matrix_lv, 31)
#define mac_trans_matrix(r_mtx, r_off, r_t1) \
load_word(r_t1, r_off, O_(V3_S4,x)) \
, nop \
, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[0])) \
#define mac_trans_mt3s3s4(r_mtx, r_off, r_t0, r_t1, r_t2) \
load_word(r_t0, r_off, O_(V3_S4,x)) \
, load_word(r_t1, r_off, O_(V3_S4,y)) \
, nop \
, load_word(r_t2, r_off, O_(V3_S4,z)) \
, store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])) \
, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])) \
, load_word(r_t1, r_off, O_(V3_S4,z)) \
, nop \
, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[2]))
WORD_COUNT(mac_trans_matrix, 9)
, store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2]))
WORD_COUNT(mac_trans_mt3s3s4, 6)
/* atom_dbg_skip */
#define mac_lzcr_round_even_half_shift(r_shift, r_mag_sq, r_mag_sq_copy) \
and_i(r_shift, r_shift, gte_lzcr_even_mask) \
, or_u(r_mag_sq_copy, r_mag_sq, 0) \
, li_s(r_mag_sq, 31) \
, sub_s(r_mag_sq, r_mag_sq, r_shift) \
, shift_aright(r_mag_sq, r_mag_sq, 1)
WORD_COUNT(mac_lzcr_round_even_half_shift, 5)
#define mac_shift_aright_var_v3(rd_v0, rd_v1, rd_v2, rs_v0, rs_v1, rs_v2, r_shift) \
shift_aright_var(rd_v0, rs_v0, r_shift) \
, shift_aright_var(rd_v1, rs_v1, r_shift) \
, shift_aright_var(rd_v2, rs_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3, 3)
#define mac_shift_aright_var_v3_self(rds_v0, rds_v1, rds_v2, r_shift) \
shift_aright_var(rds_v0, rds_v0, r_shift) \
, shift_aright_var(rds_v1, rds_v1, r_shift) \
, shift_aright_var(rds_v2, rds_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3_self, 3)
#define mac_gte_general_purpose_interopolation(to_ir0, to_ir1, to_ir2, to_ir3, fr_mac1, fr_mac2, fr_mac3, nop_slot1, nop_slot2) \
gte_mv_to_data_r(to_ir0, C2_IR0) \
, gte_mv_to_data_r(to_ir1, C2_IR1) /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ \
, gte_mv_to_data_r(to_ir2, C2_IR2) \
, gte_mv_to_data_r(to_ir3, C2_IR3) /* IR3 = src.z (reloaded) */ \
, LdSlot_ nop_slot1 \
, LdSlot_ nop_slot2 \
, gte_cmdw_gpf \
, gte_mv_from_data_r(fr_mac1, C2_MAC1) \
, gte_mv_from_data_r(fr_mac2, C2_MAC2) \
, gte_mv_from_data_r(fr_mac3, C2_MAC3)
WORD_COUNT(mac_gte_general_purpose_interopolation, 10)
#define mac_gte_mv_from_data_r_mac123(fr_mac1, fr_mac2, fr_mac3) \
gte_mv_from_data_r(fr_mac1, C2_MAC1) \
, gte_mv_from_data_r(fr_mac2, C2_MAC2) \
, gte_mv_from_data_r(fr_mac3, C2_MAC3)
WORD_COUNT(mac_gte_mv_from_data_r_mac123, 3)
/* atom_dbg_skip */
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
load_upper_i(reg_transfer, u4_hi(cmd)) \
, or_i_self( reg_transfer, u4_lo(cmd)) /* load_upper_i(reg_transfer, cmd >> 16), // or_i_self( reg_transfer, cmd & 0xFFFF), */ \
mac_load_word_imm(reg_transfer, cmd) \
, store_word( reg_transfer, reg_base, port)
WORD_COUNT(mac_gcmd_push, 3)
@@ -249,6 +260,7 @@ WORD_COUNT(mac_pack_color_word, 3)
mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b)
WORD_COUNT(mac_format_f3_color, 3)
/* atom_dbg_skip */
#define mac_format_g4_color(r_prim_cursor, r0, g0, b0, r1, g1, b1, r2, g2, b2, r3, g3, b3) \
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0) \
, mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c1), 0, r1,g1,b1) \
@@ -270,16 +282,16 @@ WORD_COUNT(mac_format_g4_color, 12)
WORD_COUNT(mac_insert_ot_tag, 11)
/* atom_dbg_skip */
#define mac_pad_set_centered_axes(r_state, r_scratch) \
load_upper_i(r_scratch, (PadAxis_Centered_Word >> 16) & 0xFFFF) \
, or_i_self( r_scratch, PadAxis_Centered_Word & 0xFFFF) \
, store_word( r_scratch, r_state, O_(PadState,axes))
#define mac_pad_set_centered_axes(state, scratch) \
load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF) \
, or_i_self( scratch, PadAxis_Centered & 0xFFFF) /* mac_load_word_imm(scratch, PadAxis_Centered), */ \
, store_word( scratch, state, O_(PadState,axes))
WORD_COUNT(mac_pad_set_centered_axes, 3)
/* atom_dbg_skip */
#define mac_pad_set_id_byte(r_state, r_id, id_value) \
#define mac_pad_set_id_byte(state, r_id, id_value) \
add_ui( r_id, R_0, id_value) \
, store_byte(r_id, r_state, O_(PadState,id))
, store_byte(r_id, state, O_(PadState,id))
WORD_COUNT(mac_pad_set_id_byte, 2)
/* atom_dbg_skip */
@@ -291,6 +303,6 @@ WORD_COUNT(mac_pad_set_status, 2)
/* atom_dbg_skip */
#define mac_pad_store_inverted_buttons(r_buttons, r_pad_state) \
nor_u( r_buttons, r_buttons, R_0) \
, store_half( r_buttons, r_pad_state, O_(PadState, buttons))
, store_half( r_buttons, r_pad_state, O_(PadState,buttons))
WORD_COUNT(mac_pad_store_inverted_buttons, 2)
+1 -1
View File
@@ -25,7 +25,7 @@
#pragma region duffle
// --- atom: normalize_v3s4 (66 words) ---
// --- atom: normalize_v3s4 (47 words) ---
#define _atom_offset_aligned_done_srav_path 3
#define _atom_offset_srav_path_aligned_done 4
+8 -10
View File
@@ -9,36 +9,34 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(gp_atom_c);
#pragma region MACs (Mips Atom Components)
FI_ Slice_MipsCode ac_gcmd_push(AtomBuilder_R ab, U4 cmd, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_gcmd_push, ab, {
load_upper_i(reg_transfer, u4_hi(cmd)),
or_i_self( reg_transfer, u4_lo(cmd)),
// load_upper_i(reg_transfer, cmd >> 16),
// or_i_self( reg_transfer, cmd & 0xFFFF),
atom_dbg_skip MipsAtomComp_Proc_(ab, {
mac_load_word_imm(reg_transfer, cmd),
store_word( reg_transfer, reg_base, port),
})
FI_ Slice_MipsCode ac_store_rgb8(AtomBuilder_R ab, U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rgb8, ab, {
FI_ Slice_MipsCode ac_store_rgb8(AtomBuilder_R ab, U1 rr, U1 rg, U1 rb, U4 base, U4 offset)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_byte(rr, base, offset + O_(RGB8,r)),
store_byte(rg, base, offset + O_(RGB8,g)),
store_byte(rb, base, offset + O_(RGB8,b)),
})
FI_ Slice_MipsCode ac_pack_color_word(AtomBuilder_R ab, U4 r_base, U4 off, U4 cmd, U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, ab, {
atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_upper_i(R_AT, (cmd) << 8 | (b)),
or_i_self( R_AT, ((g) << 8) | (r)),
store_word( R_AT, r_base, (off)),
})
FI_ Slice_MipsCode ac_format_f3_color(AtomBuilder_R ab, U4 r_base, U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, ab, { mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
atom_dbg_skip MipsAtomComp_Proc_(ab, { mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
FI_ Slice_MipsCode ac_format_g4_color(AtomBuilder_R ab, U4 r_prim_cursor,
U1 r0, U1 g0, U1 b0,
U1 r1, U1 g1, U1 b1,
U1 r2, U1 g2, U1 b2,
U1 r3, U1 g3, U1 b3)
MipsAtomComp_Proc_(ac_format_g4_color, ab, {
atom_dbg_skip MipsAtomComp_Proc_(ab, {
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0),
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c1), 0, r1,g1,b1),
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c2), 0, r2,g2,b2),
@@ -46,7 +44,7 @@ MipsAtomComp_Proc_(ac_format_g4_color, ab, {
})
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. */
I_ Slice_MipsCode ac_insert_ot_tag(AtomBuilder_R ab, U4 r_ot_base, U4 r_prim_cursor, U4 poly_size) MipsAtomComp_Proc_(ac_insert_ot_tag, ab, {
I_ Slice_MipsCode ac_insert_ot_tag(AtomBuilder_R ab, U4 r_ot_base, U4 r_prim_cursor, U4 poly_size) MipsAtomComp_Proc_(ab, {
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1)
add_u_self( R_T1, r_ot_base), // T1 = & OrderingTable[OTZ]
load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
+54 -55
View File
@@ -21,7 +21,7 @@
* 4. Semantic encoders gp0_word_poly_f3(r,g,b)
* 3. Composite encoders enc_color_word(cmd, r, g, b)
* 2. Per-field encoders enc_gp0_color_r(r), enc_gp0_color_g(g), ...
* 1. Bitfield layout consts gp0_color_red_shift = 0, gp0_color_red_mask = 0xFF
* 1. Bitfield layout consts gp0_color_red_shift = 0, gp0_color_red_width = 8
* 0. Opcode IDs gp0_cmd_poly_f3 = 0x20
*
* Vendor mnemonics (gte_mtc2, gte_mfc2, etc.) are NOT in this header.
@@ -74,7 +74,7 @@ enum {
* ============================================================================
* 8-bit GP0 opcodes (the upper byte of a primitive's first word). These are the BYTE only.
* NO macro body past this point uses a raw shift or raw mask.
* Mirrors the OPCODE_SHIFT / RS_SHIFT / REG_MASK convention from mips.h.
* Mirrors the OPCODE_SHIFT / RS_SHIFT convention from mips.h.
* ============================================================================ */
enum {
gp0_cmd_Nop = 0x00,
@@ -116,21 +116,20 @@ enum {
gp0_cmd_SetDrawOffset = 0xE5,
gp0_cmd_SetMaskBit = 0xE6,
/* bitfield shifts / widths / masks ----
/* bitfield shifts / widths ----
* Generic GP0/GP1 command byte (upper 8 bits of every word sent to either port). */
gp0_cmd_shift = 24,
gp0_cmd_width = 8,
gp0_cmd_mask = 0xFF,
/* Color word layout (lives in Poly_F3.color, Poly_G4.c0..c3, etc.):
* bits 31..24 = command byte
* bits 23..16 = BLUE
* bits 15..08 = GREEN
* bits 07..00 = RED (PSX GPU is BGR, NOT RGB) */
gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8, gp0_color_cmd_mask = 0xFF,
gp0_color_blue_shift = 16, gp0_color_blue_width = 8, gp0_color_blue_mask = 0xFF,
gp0_color_green_shift = 8, gp0_color_green_width = 8, gp0_color_green_mask = 0xFF,
gp0_color_red_shift = 0, gp0_color_red_width = 8, gp0_color_red_mask = 0xFF,
gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8,
gp0_color_blue_shift = 16, gp0_color_blue_width = 8,
gp0_color_green_shift = 8, gp0_color_green_width = 8,
gp0_color_red_shift = 0, gp0_color_red_width = 8,
};
/* ============================================================================
@@ -143,12 +142,12 @@ enum {
* ============================================================================ */
/* ---- Layer 1.5: per-field encoders ---- */
#define enc_gp0_cmd(cmd) (((cmd) & gp0_cmd_mask) << gp0_cmd_shift)
#define enc_gp0_cmd(cmd) ((cmd) << gp0_cmd_shift)
#define enc_gp0_color_cmd(cmd) (((cmd) & gp0_color_cmd_mask) << gp0_color_cmd_shift)
#define enc_gp0_color_r(r) (((r) & gp0_color_red_mask) << gp0_color_red_shift)
#define enc_gp0_color_g(g) (((g) & gp0_color_green_mask) << gp0_color_green_shift)
#define enc_gp0_color_b(b) (((b) & gp0_color_blue_mask) << gp0_color_blue_shift)
#define enc_gp0_color_cmd(cmd) ((cmd) << gp0_color_cmd_shift)
#define enc_gp0_color_r(r) ((r) << gp0_color_red_shift)
#define enc_gp0_color_g(g) ((g) << gp0_color_green_shift)
#define enc_gp0_color_b(b) ((b) << gp0_color_blue_shift)
/* ---- Layer 2: composite encoders ---- */
#define enc_color_word(cmd, r, g, b) (enc_gp0_color_cmd(cmd) | enc_gp0_color_r(r) | enc_gp0_color_g(g) | enc_gp0_color_b(b))
@@ -211,38 +210,38 @@ enum {
gp1_disp_Color24 = 0x1,
gp1_disp_VInterlace = 0x1,
/* ---- Layer 1: GP1 display-mode + range + draw-area shifts/masks ---- */
gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2, gp1_disp_hres_mask = 0x3,
gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1, gp1_disp_vres_mask = 0x1,
gp1_disp_color_shift = 4, gp1_disp_color_width = 1, gp1_disp_color_mask = 0x1,
gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1, gp1_disp_interlace_mask = 0x1,
/* ---- Layer 1: GP1 display-mode + range + draw-area shifts/widths ---- */
gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2,
gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1,
gp1_disp_color_shift = 4, gp1_disp_color_width = 1,
gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1,
/* GP1 horizontal display range: bits 0..11 = X2, bits 12..23 = X1 */
gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12, gp1_hrange_x1_mask = 0xFFF,
gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12, gp1_hrange_x2_mask = 0xFFF,
gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12,
gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12,
/* GP1 vertical display range: bits 0..9 = Y2, bits 10..19 = Y1 */
gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10, gp1_vrange_y1_mask = 0x3FF,
gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10, gp1_vrange_y2_mask = 0x3FF,
gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10,
gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10,
/* GP1 draw area (top-left or bottom-right): bits 0..9 = X, bits 10..19 = Y
* (10-bit signed — caller pre-signs and masks with the named mask) */
gp1_draw_x_shift = 0, gp1_draw_x_width = 10, gp1_draw_x_mask = 0x3FF,
gp1_draw_y_shift = 10, gp1_draw_y_width = 10, gp1_draw_y_mask = 0x3FF,
* (10-bit signed — caller pre-signs) */
gp1_draw_x_shift = 0, gp1_draw_x_width = 10,
gp1_draw_y_shift = 10, gp1_draw_y_width = 10,
};
/* ---- Layer 1.5: GP1 per-field encoders ---- */
#define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_shift)
#define enc_gp1_disp_vres(v) (((v) & gp1_disp_vres_mask) << gp1_disp_vres_shift)
#define enc_gp1_disp_color(c) (((c) & gp1_disp_color_mask) << gp1_disp_color_shift)
#define enc_gp1_disp_interlace(i) (((i) & gp1_disp_interlace_mask) << gp1_disp_interlace_shift)
#define enc_gp1_disp_hres(h) ((h) << gp1_disp_hres_shift)
#define enc_gp1_disp_vres(v) ((v) << gp1_disp_vres_shift)
#define enc_gp1_disp_color(c) ((c) << gp1_disp_color_shift)
#define enc_gp1_disp_interlace(i) ((i) << gp1_disp_interlace_shift)
#define enc_gp1_hrange_x1(x1) (((x1) & gp1_hrange_x1_mask) << gp1_hrange_x1_shift)
#define enc_gp1_hrange_x2(x2) (((x2) & gp1_hrange_x2_mask) << gp1_hrange_x2_shift)
#define enc_gp1_vrange_y1(y1) (((y1) & gp1_vrange_y1_mask) << gp1_vrange_y1_shift)
#define enc_gp1_vrange_y2(y2) (((y2) & gp1_vrange_y2_mask) << gp1_vrange_y2_shift)
#define enc_gp1_draw_x(x) (((x) & gp1_draw_x_mask) << gp1_draw_x_shift)
#define enc_gp1_draw_y(y) (((y) & gp1_draw_y_mask) << gp1_draw_y_shift)
#define enc_gp1_hrange_x1(x1) ((x1) << gp1_hrange_x1_shift)
#define enc_gp1_hrange_x2(x2) ((x2) << gp1_hrange_x2_shift)
#define enc_gp1_vrange_y1(y1) ((y1) << gp1_vrange_y1_shift)
#define enc_gp1_vrange_y2(y2) ((y2) << gp1_vrange_y2_shift)
#define enc_gp1_draw_x(x) ((x) << gp1_draw_x_shift)
#define enc_gp1_draw_y(y) ((y) << gp1_draw_y_shift)
/* ---- Layer 2: GP1 composite encoders ---- */
#define enc_gp1_disp_mode_word(h, v, c, i) (enc_gp0_cmd(gp1_cmd_DisplayMode) | enc_gp1_disp_hres(h) | enc_gp1_disp_vres(v) | enc_gp1_disp_color(c) | enc_gp1_disp_interlace(i))
@@ -555,14 +554,14 @@ typedef Struct_(Poly_GT4) {
* bits 12..31 = reserved (zero)
* ============================================================================ */
enum {
/* ---- Layer 1: TPage bitfield shifts / widths / masks ---- */
gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4, gp0_tpage_x_mask = 0xF,
gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1, gp0_tpage_y_mask = 0x1,
gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2, gp0_tpage_semi_trans_mask = 0x3,
gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2, gp0_tpage_color_depth_mask = 0x3,
gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1, gp0_tpage_dither_mask = 0x1,
gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1, gp0_tpage_draw_to_disp_mask = 0x1,
gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1, gp0_tpage_tex_disable_mask = 0x1,
/* ---- Layer 1: TPage bitfield shifts / widths ---- */
gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4,
gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1,
gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2,
gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2,
gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1,
gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1,
gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1,
/* TPage color-depth payload values (NOT bit positions — these go in
* the 2-bit field at gp0_tpage_color_depth_shift). */
@@ -581,13 +580,13 @@ enum {
};
/* ---- Layer 1.5: TPage per-field encoders. Mirrors enc_gte_sf/mx/v in gte.h. ---- */
#define enc_gp0_tpage_x(x) (((x) & gp0_tpage_x_mask) << gp0_tpage_x_shift)
#define enc_gp0_tpage_y(y) (((y) & gp0_tpage_y_mask) << gp0_tpage_y_shift)
#define enc_gp0_tpage_semi_trans(s) (((s) & gp0_tpage_semi_trans_mask) << gp0_tpage_semi_trans_shift)
#define enc_gp0_tpage_color_depth(c) (((c) & gp0_tpage_color_depth_mask) << gp0_tpage_color_depth_shift)
#define enc_gp0_tpage_dither(d) (((d) & gp0_tpage_dither_mask) << gp0_tpage_dither_shift)
#define enc_gp0_tpage_draw_to_disp(d) (((d) & gp0_tpage_draw_to_disp_mask) << gp0_tpage_draw_to_disp_shift)
#define enc_gp0_tpage_tex_disable(t) (((t) & gp0_tpage_tex_disable_mask) << gp0_tpage_tex_disable_shift)
#define enc_gp0_tpage_x(x) ((x) << gp0_tpage_x_shift)
#define enc_gp0_tpage_y(y) ((y) << gp0_tpage_y_shift)
#define enc_gp0_tpage_semi_trans(s) ((s) << gp0_tpage_semi_trans_shift)
#define enc_gp0_tpage_color_depth(c) ((c) << gp0_tpage_color_depth_shift)
#define enc_gp0_tpage_dither(d) ((d) << gp0_tpage_dither_shift)
#define enc_gp0_tpage_draw_to_disp(d) ((d) << gp0_tpage_draw_to_disp_shift)
#define enc_gp0_tpage_tex_disable(t) ((t) << gp0_tpage_tex_disable_shift)
/* ---- Layer 2: TPage composite encoder. Mirrors enc_gte_cmdw in gte.h ---- */
#define enc_gp0_tpage_word(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \
@@ -617,17 +616,17 @@ typedef Struct_(TexturePage) { U4 raw; };
* bits 24..31 = command byte — 0x20 (4bpp load) or 0x25 (8bpp load)
* ============================================================================ */
enum {
/* ---- Layer 1: CLUT bitfield shifts / widths / masks ---- */
gp0_clut_y_shift = 0, gp0_clut_y_width = 6, gp0_clut_y_mask = 0x3F,
gp0_clut_x_shift = 6, gp0_clut_x_width = 9, gp0_clut_x_mask = 0x1FF,
/* ---- Layer 1: CLUT bitfield shifts / widths ---- */
gp0_clut_y_shift = 0, gp0_clut_y_width = 6,
gp0_clut_x_shift = 6, gp0_clut_x_width = 9,
/* CLUT-load cmd-byte variants — the upper byte of the GP0 word. */
gp0_clut_cmd_Load4bpp = 0x20,
gp0_clut_cmd_Load8bpp = 0x25,
};
/* ---- Layer 1.5: CLUT per-field encoders ---- */
#define enc_gp0_clut_x(x) (((x) & gp0_clut_x_mask) << gp0_clut_x_shift)
#define enc_gp0_clut_y(y) (((y) & gp0_clut_y_mask) << gp0_clut_y_shift)
#define enc_gp0_clut_x(x) ((x) << gp0_clut_x_shift)
#define enc_gp0_clut_y(y) ((y) << gp0_clut_y_shift)
/* ---- Layer 2: CLUT composite encoder ---- */
#define enc_gp0_clut_word(cmd, x, y) (enc_gp0_cmd(cmd) | enc_gp0_clut_x(x) | enc_gp0_clut_y(y))
+165 -161
View File
@@ -11,7 +11,8 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(gte_atom_c);
#pragma region MACs (Mips Atom Components)
/* Words: 3; Loads 3 S2 indices from the face array */
FI_ Slice_MipsCode ac_load_tri_indices(AtomBuilder_R ab, U4 r_face_cusor, U4 r_i0, U4 r_i1, U4 r_i2) atom_dbg_skip MipsAtomComp_Proc_(ac_load_tri_indices, ab, {
FI_ Slice_MipsCode ac_load_tri_indices(AtomBuilder_R ab, U4 r_face_cusor, U4 r_i0, U4 r_i1, U4 r_i2)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_half_u(r_i0, r_face_cusor, 0 * S_(S2)),
load_half_u(r_i1, r_face_cusor, 1 * S_(S2)),
load_half_u(r_i2, r_face_cusor, 2 * S_(S2)),
@@ -19,14 +20,14 @@ FI_ Slice_MipsCode ac_load_tri_indices(AtomBuilder_R ab, U4 r_face_cusor, U4 r_i
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3.
* PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). */
FI_ Slice_MipsCode ac_gte_store_f3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_f3, ab, {
FI_ Slice_MipsCode ac_gte_store_f3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_F3,p0)),
gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_F3,p1)),
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_F3,p2)),
})
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE */
I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_load_tri_verts, ab, {
I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ab, {
shift_lleft(R_AT, r_v0, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
shift_lleft(R_AT, r_v1, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
shift_lleft(R_AT, r_v2, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
@@ -37,7 +38,7 @@ I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
FI_ Slice_MipsCode ac_gte_store_g4_p012(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p012, ab, {
FI_ Slice_MipsCode ac_gte_store_g4_p012(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_G4,p0)),
gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_G4,p1)),
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2)),
@@ -47,28 +48,41 @@ FI_ Slice_MipsCode ac_gte_store_g4_p012(AtomBuilder_R ab, U4 r_primitive_cursor)
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
* SXY0 still holds v0.screen from the earlier RTPT.
*/
FI_ Slice_MipsCode ac_gte_store_g4_p3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p3, ab, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) })
FI_ Slice_MipsCode ac_gte_store_g4_p3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ab, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) })
/* ─── STAGE 1 of normalize: SQR + mfc2 MAC1/2/3 ───
* Emits squared magnitude per component (in MAC1/2/3) into caller-provided scratch regs.
* Stage 2 of normalize consumes these directly.
* Words: 8. Clobbers: IR1/2/3, MAC1/2/3. Uses gte_cmdw_sqr (sf=0, lm=1). */
FI_ Slice_MipsCode ac_gte_sqr_v3(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4 r_sq_x, U4 r_sq_y, U4 r_sq_z) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_sqr_v3, ab, {
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3),
nop, gte_cmdw_sqr,
FI_ Slice_MipsCode ac_gte_sqr_v3(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4 r_sq_x, U4 r_sq_y, U4 r_sq_z) atom_dbg_skip MipsAtomComp_Proc_(ab, {
mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, nop),
gte_mv_from_data_r(r_sq_x, C2_MAC1),
gte_mv_from_data_r(r_sq_y, C2_MAC2),
gte_mv_from_data_r(r_sq_z, C2_MAC3),
})
/* ─── SQR FIRE — mtc2 3 GPRs into IR1/IR2/IR3, then fire SQR. ───
* The SQR command always squares IR1/IR2/IR3 — those C2 registers are fixed.
* The GPRs holding the source vector are caller-determined.
* Words: 5 (3 mtc2 + 1 nop hazard + 1 cmd). */
FI_ Slice_MipsCode ac_gte_sqr_v3s4(AtomBuilder_R ab, Reg r_sx, Reg r_sy, Reg r_sz, MipsCode nop_slot)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3),
nop_slot, gte_cmdw_sqr,
})
/* ─── STAGE 4 of normalize: mtc2 IR0..3 + GPF + mfc2 MAC + srav finalize ───
* Reusable standalone — given an IR0 = 1/|v| estimate (typically from a sqrtbl lookup) and a shift count
* (typically (31 - LZCR)/2), multiplies IR0*IR[i] via GPF and shifts right to produce the normalized output.
* Used standalone for "scale vector by scalar".
* Words: 11. Clobbers: IR0..3, MAC1..3. Uses gte_cmdw_gpf (sf=0, lm=0). */
FI_ Slice_MipsCode ac_gte_gpf_scale(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4 r_recip_est, U4 r_shift, U4 r_dx, U4 r_dy, U4 r_dz) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_gpf_scale, ab, {
FI_ Slice_MipsCode ac_gte_gpf_scale(AtomBuilder_R ab,
U4 r_sx, U4 r_sy, U4 r_sz,
U4 r_recip_est, U4 r_shift,
U4 r_dx, U4 r_dy, U4 r_dz)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_mv_to_data_r(r_recip_est, C2_IR0),
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
@@ -83,84 +97,94 @@ FI_ Slice_MipsCode ac_gte_gpf_scale(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz,
shift_aright_var(r_dz, r_dz, r_shift),
})
/* ─── APPLY MATRIX LV (libgte ApplyMatrixLV port) ───
* Atom component — auto-generates mac_apply_matrix_lv Mac composer macro.
* Uses GTE RTPS (cv=1, sf=1, v=0) with lwc2-loaded V0/VZ0 inputs.
* Per PSX-SPX `geometrytransformationenginegte.md` lines 416-418:
* IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12)
* IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12)
* IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12)
* RTPS uses the FULL row of the rotation matrix (not just diagonal like MVMVA with mx=0).
* libgte's `gte_ApplyMatrix` calls `gte_rtv0()` = RTPS cv=1 v=0 mx=0.
* Per `gte.h` line 405 the body sets cv=3 (BK, zero-initialized) so no TR contribution.
*
* Operands:
* r_mtx : MT3_S2S4* (matrix pointer)
* r_vec : U4 (pointer to PACKED V0 data — (pos.y << 16) | pos.x at +0, pos.z at +4)
* r_out : V3_S4* (output pointer; MAC1/2/3 stored here)
* r_t0/1/2 : 3 GPR codes for matrix load + intermediate state
* Words: ~26. Clobbers: r_t0, r_t1, r_t2 (C2 $0..$4, VXY0/VZ0, MAC1/2/3, SXY0/1/2). */
FI_ Slice_MipsCode ac_apply_matrix_lv(AtomBuilder_R ab
, U4 r_mtx, U4 r_vec, U4 r_out
, U4 r_t0, U4 r_t1, U4 r_t2
) MipsAtomComp_Proc_(ac_apply_matrix_lv, ab, {
/* Load MATRIX rows into GTE RT11..RT33 (libgte convention: ctc2 to C2 $0..$4 in order).
* load_half_u zero-extends the last word so RT33 = m[2][2] and TRX = 0. */
load_word(r_t0, r_mtx, 0), nop,
gte_mv_to_ctrl_r(r_t0, gte_cr_RT11_Code),
load_word(r_t0, r_mtx, 4), nop,
gte_mv_to_ctrl_r(r_t0, gte_cr_RT12_Code),
load_word(r_t0, r_mtx, 8), nop,
gte_mv_to_ctrl_r(r_t0, gte_cr_RT13_Code),
load_word(r_t0, r_mtx, 12), nop,
gte_mv_to_ctrl_r(r_t0, gte_cr_RT21_Code),
load_half_u(r_t0, r_mtx, 16), nop,
gte_mv_to_ctrl_r(r_t0, gte_cr_RT22_Code),
nop2,
/* Load PACKED pos into V0 (libgte SVECTOR layout).
* r_vec points to atom-0-staged packed data ((pos.y << 16) | pos.x at +0, pos.z at +4).
* LWC2 base register MUST be the pointer r_vec, NOT the loaded value r_t0. */
load_word(r_t0, r_vec, 0), nop,
gte_lw(C2_VXY0, r_vec, 0),
load_word(r_t0, r_vec, 4), nop,
gte_lw(C2_VZ0, r_vec, 4),
/* RTPS: cv=3 (no translation), sf=1 (no shift, integer), v=0 (V0 input),
* mx=0 (rotation matrix). MAC = RT row · V0 + 0. RTPS also writes
* SXY0/1/2 + SZ0..SZ3 (perspective division); ignored. */
gte_cmdw_rtps_sf1,
/* Read MAC1/2/3 → out. */
gte_mv_from_data_r(r_t0, C2_MAC1),
gte_mv_from_data_r(r_t1, C2_MAC2),
gte_mv_from_data_r(r_t2, C2_MAC3),
nop,
store_word(r_t0, r_out, 0),
store_word(r_t1, r_out, 4),
store_word(r_t2, r_out, 8),
})
/* ─── TRANS MATRIX (libgte TransMatrix port) ───
* Atom component — auto-generates mac_trans_matrix Mac composer macro.
* m->t = v (struct copy; libgte's TransMatrix at 0x8001a540 is just 3 store_words, no GTE, no add).
* Uses 1 GPR (r_t1 = off value) per axis; per-axis load-delay-slot pattern.
* Words: 9. Clobbers: r_t1. */
FI_ Slice_MipsCode ac_trans_matrix(AtomBuilder_R ab
FI_ Slice_MipsCode ac_trans_mt3s3s4(AtomBuilder_R ab
, U4 r_mtx, U4 r_off
, U4 r_t1
) MipsAtomComp_Proc_(ac_trans_matrix, ab, {
load_word(r_t1, r_off, O_(V3_S4,x)),
nop,
store_word(r_t1, r_mtx, O_(MT3_S2S4,t[0])),
, U4 r_t0, U4 r_t1, U4 r_t2
) MipsAtomComp_Proc_(ab, {
load_word(r_t0, r_off, O_(V3_S4,x)),
load_word(r_t1, r_off, O_(V3_S4,y)),
nop,
load_word(r_t2, r_off, O_(V3_S4,z)),
store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])),
store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])),
store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])),
})
load_word(r_t1, r_off, O_(V3_S4,z)),
nop,
store_word(r_t1, r_mtx, O_(MT3_S2S4,t[2])),
/* ─── LZCR ROUND EVEN + HALF-SHIFT ───
* Takes the raw LZCR leading-zero/ones count (from mfc2 C2_LZCR, range 1..32
* per PSX-SPX cop2r31) and the |v|² sum (in r_mag_sq from the MAC1+MAC2+MAC3
* add). Produces:
* r_shift ← LZCR rounded down to even (clear bit 0)
* r_mag_sq_copy ← |v|² sum (moved out of r_mag_sq before it's overwritten)
* r_mag_sq ← (31 - even_LZCR) / 2 = the final srav/GPF shift amount
*
* Rounding to even ensures (31 - LZCR) is always odd, so the >> 1 division
* is consistent — no 0.5 loss. The caller branches on LZCR < 24 to decide
* left-shift vs right-shift of r_mag_sq_copy, then saves the shift count.
*
* Note: C2_LZCR (cop2r31) is a fixed read-only C2 data register — the caller
* must read it via mfc2 from C2_LZCR; there is no register choice at the
* hardware level. Only the GPR that holds the result is caller-determined. */
FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab,
U4 r_shift,
U4 r_mag_sq,
U4 r_mag_sq_copy
)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
and_i(r_shift, r_shift, gte_lzcr_even_mask),
or_u(r_mag_sq_copy, r_mag_sq, 0),
li_s(r_mag_sq, 31),
sub_s(r_mag_sq, r_mag_sq, r_shift),
shift_aright(r_mag_sq, r_mag_sq, 1),
})
FI_ Slice_MipsCode ac_shift_aright_var_v3(AtomBuilder_R ab
, Reg rd_v0, Reg rd_v1, Reg rd_v2
, Reg rs_v0, Reg rs_v1, Reg rs_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rd_v0, rs_v0, r_shift),
shift_aright_var(rd_v1, rs_v1, r_shift),
shift_aright_var(rd_v2, rs_v2, r_shift),
})
FI_ Slice_MipsCode ac_shift_aright_var_v3_self(AtomBuilder_R ab
, Reg rds_v0, Reg rds_v1, Reg rds_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rds_v0, rds_v0, r_shift),
shift_aright_var(rds_v1, rds_v1, r_shift),
shift_aright_var(rds_v2, rds_v2, r_shift),
})
FI_ Slice_MipsCode ac_gte_general_purpose_interopolation(AtomBuilder_R ab
, Reg to_ir0, Reg to_ir1, Reg to_ir2, Reg to_ir3
, Reg fr_mac1, Reg fr_mac2, Reg fr_mac3
, MipsCode nop_slot1, MipsCode nop_slot2)
MipsAtomComp_Proc_(ab, {
gte_mv_to_data_r(to_ir0, C2_IR0),
gte_mv_to_data_r(to_ir1, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */
gte_mv_to_data_r(to_ir2, C2_IR2),
gte_mv_to_data_r(to_ir3, C2_IR3), /* IR3 = src.z (reloaded) */
LdSlot_ nop_slot1,
LdSlot_ nop_slot2,
gte_cmdw_gpf,
gte_mv_from_data_r(fr_mac1, C2_MAC1),
gte_mv_from_data_r(fr_mac2, C2_MAC2),
gte_mv_from_data_r(fr_mac3, C2_MAC3),
})
FI_ Slice_MipsCode gte_mv_from_data_r_mac123(AtomBuilder_R ab
, Reg fr_mac1, Reg fr_mac2, Reg fr_mac3
)
MipsAtomComp_Proc_(ab, {
gte_mv_from_data_r(fr_mac1, C2_MAC1),
gte_mv_from_data_r(fr_mac2, C2_MAC2),
gte_mv_from_data_r(fr_mac3, C2_MAC3),
})
#pragma endregion MACs (Mips Atom Components)
@@ -206,7 +230,7 @@ FI_ Slice_MipsCode ac_trans_matrix(AtomBuilder_R ab
* The later 64 entries (octaves 2-3) are the `srav` branch when the magnitude's top bit is well above bit 24.
*
* 192-entry table is reproduced verbatim from libgte (verified against libpsn00b/psxgte/vector.s:100-123 — 24 rows × 8 halfwords, last entry 0x0804). */
internal RO_ S2 gte_normalize_sqr_tbl[192] align_(2) = {
internal S2 const gte_normalize_sqr_tbl[192] align_(2) = {
0x1000, 0x0fe0, 0x0fc1, 0x0fa3, 0x0f85, 0x0f68, 0x0f4c, 0x0f30,
0x0f15, 0x0efb, 0x0ee1, 0x0ec7, 0x0eae, 0x0e96, 0x0e7e, 0x0e66,
0x0e4f, 0x0e38, 0x0e22, 0x0e0c, 0x0df7, 0x0de2, 0x0dcd, 0x0db9,
@@ -233,6 +257,18 @@ internal RO_ S2 gte_normalize_sqr_tbl[192] align_(2) = {
0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804,
};
#define RegUse_(proc_name) (tmpl(RegUse,proc_name))
typedef Struct_(RegUse_normalize_v3s4_proc) {
Reg scratch; // Scratch base carrier.
Reg src_ptr;
Reg dst_ptr;
Reg recip_est; // |v|² sum + shift-input + sqrtbl[index]
Reg norm; Reg shift;
Reg src_x;
union { Reg mac1_scratch; } t3;
union { Reg mac2_scratch; } t4;
union { Reg shift_count, btarget, lookup_addr, src_z; } t5;
};
/* ─── Full normalize (all 4 stages inline) ───
* Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav).
*
@@ -249,9 +285,9 @@ internal RO_ S2 gte_normalize_sqr_tbl[192] align_(2) = {
* r_mac1_scratch : MAC1 result scratch (also holds aligned |v|² in stage 3)
* r_mac2_scratch : MAC2 result scratch → result.x after stage 4 sra
* r_recip_est : src.y PRESERVED across stages 1-2 → fed to IR2 in stage 4 → result.y
* r_lzcr : |v|² sum (stage 2) → shift count (stage 3) → 1/|v| (stage 4 IR0)
* r_norm : |v|² sum (stage 2) → half-shift (stage 3) → 1/|v| (stage 4 IR0)
* r_shift : shift count SAVED in stage 3 → consumed by stage 4 srav
* r_branch_tmp : src.z PRESERVED across stages 1-2 → fed to IR3 in stage 4 → result.z (also sqrtbl base addr)
* r_branch_tmp : src.z PRESERVED across stages 1-2 → fed to IR3 in stage 4 → result.z (also sqrtbl base addr)
*
* Atom_labels are srav_path / aligned_done
* (NOT namespaced — they're internal to this proc;
@@ -264,95 +300,63 @@ internal RO_ S2 gte_normalize_sqr_tbl[192] align_(2) = {
* Sqrtbl: hardcoded to 0x800185B4 (libgte msc02.rel.data). Note: swapped to local.
* Pipeline: clobbers IR0..3, MAC1..3, LZCS, LZCR.
*/
/* MipsAtom_Proc_ wrapper: declares the static MipsCode[] body, then calls atombuilder_unroll(ab, ...) to copy the encoded instructions into the caller's MipsAtomBuilder arena. */
internal MipsAtom* normalize_v3s4_proc(AtomArena_R aa, U4 r_scratch /* GPR code: scratch base carrier (e.g., R_T4 = R_ResolveScratch) */
, U4 r_src_offset, U4 r_dst_offset /* GPR codes: PARAMETERIZED offsets (caller passes O_ macros) */
, U4 r_src_ptr, U4 r_dst_ptr, U4 r_tmp /* GPR codes: 3 scratch regs (src/dst computed + tmp) */
, U4 r_mac1_scratch, U4 r_mac2_scratch /* GPR codes: 2 more: MAC1/MAC2 scratch */
, U4 r_recip_est /* GPR code: |v|² sum + shift-input + sqrtbl[index] */
, U4 r_lzcr, U4 r_shift /* GPR codes: lzcr + final srav amount */
, U4 r_branch_tmp /* GPR code: scratch (shift count, branch target, lookup addr) */
)
MipsAtom_Proc_(normalize_v3s4, aa, {
add_si(r_src_ptr, r_scratch, r_src_offset), /* r_src_ptr = &src */
add_si(r_dst_ptr, r_scratch, r_dst_offset), /* r_dst_ptr = &dst */
nop,
internal MipsAtom* normalize_v3s4_proc(AtomArena_R aa, U2 src_offset, U2 dst_offset, RegUse_normalize_v3s4_proc r)
MipsAtom_Proc_(aa, {
add_si(r.src_ptr, r.scratch, src_offset), /* r_src_ptr = &src */
/* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp.
* r_tmp holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */
load_word(r_tmp, r_src_ptr, O_(V3_S4,x)),
load_word(r_recip_est, r_src_ptr, O_(V3_S4,y)),
load_word(r_branch_tmp, r_src_ptr, O_(V3_S4,z)),
nop, /* load-delay */
* r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */
mac_load_v3s4(r.src_x, r.recip_est, r.t5.lookup_addr, r.src_ptr, 0),
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
gte_mv_to_data_r(r_tmp, C2_IR1),
gte_mv_to_data_r(r_recip_est, C2_IR2),
gte_mv_to_data_r(r_branch_tmp, C2_IR3),
nop, gte_cmdw_sqr,
LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.t5.src_z, LdSlot_ nop),
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */
gte_mv_from_data_r(r_mac1_scratch, C2_MAC1),
gte_mv_from_data_r(r_mac2_scratch, C2_MAC2),
gte_mv_from_data_r(r_lzcr, C2_MAC3),
nop,
add_u(r_lzcr, r_lzcr, r_mac2_scratch),
add_u(r_lzcr, r_lzcr, r_mac1_scratch),
gte_mv_to_data_r(r_lzcr, C2_LZCS),
nop2,
gte_mv_from_data_r(r_shift, C2_LZCR),
nop,
mac_gte_mv_from_data_r_mac123(r.t3.mac1_scratch, r.t4.mac2_scratch, r.norm), LdSlot_ nop,
add_u_self( r.norm, r.t3.mac1_scratch),
add_u_self( r.norm, r.t4.mac2_scratch),
gte_mv_to_data_r( r.norm, C2_LZCS), LdSlot_ nop2,
gte_mv_from_data_r(r.shift, C2_LZCR), LdSlot_ nop,
/* Stage 3: compute srav amount (r_lzcr) + align |v|² to bit 24.
* IMPORTANT: the sllv/srav below writes the aligned |v|² to r_mac1_scratch (NOT r_lzcr),
* so r_lzcr retains the shift count all the way to the start of stage 4.
*/
and_i( r_shift, r_shift, -2),
or_u(r_mac1_scratch, r_lzcr, 0), /* FIX B: save sum before clobbering r_lzcr with shift count */
li_s( r_lzcr, 31),
sub_s( r_lzcr, r_lzcr, r_shift),
shift_aright(r_lzcr, r_lzcr, 1),
/* Stage 3: round LZCR to even, compute half-shift, align |v|² to bit 24.
* r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2.
* After the component: r_shift = even(LZCR), r_norm = half-shift, r_mac1_scratch = |v|². */
mac_lzcr_round_even_half_shift(r.shift, r.norm, r.t3.mac1_scratch),
/* r_branch_tmp = LZCR - 24 (overwrites r_branch_tmp; src.z no longer needed after SQR) */
add_si( r_branch_tmp, r_shift, -24),
branch_lt_zero(r_branch_tmp, atom_offset(aligned_done, srav_path)), nop, /* FIX A: bltz → srav_path (LZCR<24 path) */
jump_rel(atom_offset(srav_path, aligned_done)), /* FIX A: b → aligned_done (LZCR>=24 path) */
shift_lleft_var(r_mac1_scratch, r_mac1_scratch, r_branch_tmp), /* FIX B: src=sum (r_mac1_scratch), dst=same */
atom_label(srav_path)
li_s( r_branch_tmp, 24),
sub_s( r_branch_tmp, r_branch_tmp, r_shift),
shift_aright_var(r_mac1_scratch, r_mac1_scratch, r_branch_tmp), /* FIX B: src=sum (r_mac1_scratch), dst=same */
add_si( r.t5.btarget, r.shift, -24),
branch_lt_zero(r.t5.btarget, atom_offset(aligned_done, srav_path)), BdSlot_ nop, /* bltz → srav_path (LZCR < 24 path) */
jump_rel(atom_offset(srav_path, aligned_done)), /* b → aligned_done (LZCR >= 24 path) */
BdSlot_ shift_lleft_var(r.t3.mac1_scratch, r.t3.mac1_scratch, r.t5.btarget), /* src=sum (r_mac1_scratch), dst=same */
atom_label(srav_path)
li_s( r.t5.shift_count, 24),
sub_s(r.t5.shift_count, r.t5.shift_count, r.shift),
shift_aright_var(r.t3.mac1_scratch, r.t3.mac1_scratch, r.t5.shift_count), /* src=sum (r_mac1_scratch), dst=same */
atom_label(aligned_done)
/* Save the shift count to r_shift before the next 5 instructions overwrite r_lzcr
* (the sqrtbl lookup loads 1/|v| into r_lzcr, which becomes IR0 in stage 4). */
or_u(r_shift, r_lzcr, 0), /* r_shift ← shift count (preserved through stage 4) */
/* r_mac1_scratch holds |v|² aligned (top bit at bit 7). */
add_si( r_mac1_scratch, r_mac1_scratch, -64),
shift_lleft(r_mac1_scratch, r_mac1_scratch, 1),
load_upper_i(r_branch_tmp, u4_hi(& gte_normalize_sqr_tbl)),
or_i_self( r_branch_tmp, u4_lo(& gte_normalize_sqr_tbl)),
add_u(r_branch_tmp, r_branch_tmp, r_mac1_scratch),
load_half(r_lzcr, r_branch_tmp, 0), nop, /* r_lzcr = sqrtbl[aligned-64] = 1/|v| (IR0 in stage 4) */
// Save the shift count to r_shift before the next 5 instructions overwrite r_norm (the sqrtbl lookup loads 1/|v| into r_norm, which becomes IR0 in stage 4).
or_u(r.shift, r.norm, 0), /* r_shift ← shift count (preserved through stage 4) */
/* r_mac1_scratch holds |v|² aligned (top bit at bit 7). */
add_si( r.t3.mac1_scratch, r.t3.mac1_scratch, -64),
shift_lleft(r.t3.mac1_scratch, r.t3.mac1_scratch, 1),
mac_load_word_imm(r.t5.lookup_addr, & gte_normalize_sqr_tbl), add_u_self(r.t5.lookup_addr, r.t3.mac1_scratch),
load_half(r.norm, r.t5.lookup_addr, 0), /* r_norm = sqrtbl[aligned-64] = 1/|v| (IR0 in stage 4) */
/* FIX bug C: r_branch_tmp held the sqrtbl base+index, NOT src.z. Reload src.z from scratch now that r_branch_tmp is free. */
load_word(r_branch_tmp, r_src_ptr, O_(V3_S4,z)), nop, /* r_branch_tmp = src.z (for IR3 in stage 4) */
/* Stage 4: GPF + srav finalize (r_shift = shift count, r_lzcr = 1/|v|). */
gte_mv_to_data_r(r_lzcr, C2_IR0),
gte_mv_to_data_r(r_tmp, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */
gte_mv_to_data_r(r_recip_est, C2_IR2),
gte_mv_to_data_r(r_branch_tmp, C2_IR3), /* IR3 = src.z (reloaded) */
nop2, gte_cmdw_gpf,
gte_mv_from_data_r(r_mac2_scratch, C2_MAC1),
gte_mv_from_data_r(r_recip_est, C2_MAC2),
gte_mv_from_data_r(r_branch_tmp, C2_MAC3),
shift_aright_var(r_mac2_scratch, r_mac2_scratch, r_shift), /* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */
shift_aright_var(r_recip_est, r_recip_est, r_shift),
shift_aright_var(r_branch_tmp, r_branch_tmp, r_shift),
/* r_branch_tmp held the sqrtbl base+index, NOT src.z. Reload src.z from scratch now that r_branch_tmp is free. */
LdSlot_ load_word(r.t5.src_z, r.src_ptr, O_(V3_S4,z)), /* r_branch_tmp = src.z (for IR3 in stage 4) */
/* Stage 4: GPF + srav finalize (r_shift = shift count, r_norm = 1/|v|). */
LdSlot_ mac_gte_general_purpose_interopolation(
r.norm,
r.src_x, /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */
r.recip_est,
r.t5.src_z, /* IR3 = src.z (reloaded) */
r.t4.mac2_scratch, r.recip_est, r.t5.src_z,
LdSlot_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here.
LdSlot_ nop
),
/* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */
mac_shift_aright_var_v3_self(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.shift),
/* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */
store_word(r_mac2_scratch, r_dst_ptr, O_(V3_S4,x)),
store_word(r_recip_est, r_dst_ptr, O_(V3_S4,y)),
store_word(r_branch_tmp, r_dst_ptr, O_(V3_S4,z)),
mac_store_v3s4(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.dst_ptr, 0),
mac_yield()
})
@@ -372,7 +376,7 @@ internal MipsAtom_(set_gte_mt3s2s4) atom_info(
load_word(R_T3, R_TapePtr, O_(Binds_SetGteMT3S2S4,transform)),
add_ui_self( R_TapePtr, S_(Binds_SetGteMT3S2S4)),
/* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */
load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4),
load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4),
gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12),
load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16),
gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22),
+30 -34
View File
@@ -177,18 +177,17 @@ enum {
* Mirrors the OPCODE_SHIFT / RS_SHIFT convention used in mips.h.
*/
gte_shift_sf = 19, gte_width_sf = 1, gte_mask_sf = 0x1,
gte_shift_mx = 17, gte_width_mx = 2, gte_mask_mx = 0x3,
gte_shift_v = 15, gte_width_v = 2, gte_mask_v = 0x3,
gte_shift_cv = 13, gte_width_cv = 2, gte_mask_cv = 0x3,
gte_shift_lm = 10, gte_width_lm = 1, gte_mask_lm = 0x1,
gte_shift_cmd = 0, gte_width_cmd = 6, gte_mask_cmd = 0x3F,
gte_shift_sf = 19, gte_width_sf = 1,
gte_shift_mx = 17, gte_width_mx = 2,
gte_shift_v = 15, gte_width_v = 2,
gte_shift_cv = 13, gte_width_cv = 2,
gte_shift_lm = 10, gte_width_lm = 1,
gte_shift_cmd = 0, gte_width_cmd = 6,
/* Fake command number (bits 24-20) — IGNORED by the GTE hardware per PSX-SPX `geometrytransformationenginegte.md` line 48.
* libgte's compiler emits non-zero values in this field as a disassembly signature. */
gte_shift_fake_cmd = 20,
gte_width_fake_cmd = 5,
gte_mask_fake_cmd = 0x1F,
};
/* --- GTE Control Register Aliases (Pitfall 1) ---
@@ -351,13 +350,13 @@ enum { _C2_TX_SUBS_ = 0
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
/* Per-field encoders. Each one does (value & mask) << shift on its own. */
#define enc_gte_sf(sf) (((sf) & gte_mask_sf ) << gte_shift_sf )
#define enc_gte_mx(mx) (((mx) & gte_mask_mx ) << gte_shift_mx )
#define enc_gte_v(v) (((v) & gte_mask_v ) << gte_shift_v )
#define enc_gte_cv(cv) (((cv) & gte_mask_cv ) << gte_shift_cv )
#define enc_gte_lm(lm) (((lm) & gte_mask_lm ) << gte_shift_lm )
#define enc_gte_cmd(cmd) (((cmd) & gte_mask_cmd ) << gte_shift_cmd )
#define enc_gte_fake_cmd(x) (((x) & gte_mask_fake_cmd) << gte_shift_fake_cmd)
#define enc_gte_sf(sf) ((sf) << gte_shift_sf )
#define enc_gte_mx(mx) ((mx) << gte_shift_mx )
#define enc_gte_v(v) ((v) << gte_shift_v )
#define enc_gte_cv(cv) ((cv) << gte_shift_cv )
#define enc_gte_lm(lm) ((lm) << gte_shift_lm )
#define enc_gte_cmd(cmd) ((cmd) << gte_shift_cmd )
#define enc_gte_fake_cmd(x) ((x) << gte_shift_fake_cmd)
/* Composite: all six GTE fields + the COP2/CO base. */
#define enc_gte_cmdw(sf, mx, v, cv, lm, cmd) ( \
@@ -440,22 +439,11 @@ enum { _C2_TX_SUBS_ = 0
#define gte_cmdw_mvmva_pass1_c11 (gte_cmd_base | enc_gte_v(2) | enc_gte_mx(3) | enc_gte_cmd(gte_cmd_mvmva))
#define gte_cmdw_mvmva_no_tr gte_cmdw_mvmva_ir
/* MVMVA pass 2 — EXACT C11 ApplyMatrixLV command.
* Command word: 0x4A49E012.
* bits 31-26: 010010 = COP2
* bit 25: 1 (CO set)
* bits 24-20: 01001 = 9 (fake_cmd)
* bit 19: 1 (sf=1)
* bits 18-17: 00 (mx=0, RT matrix)
* bits 16-15: 11 (v=3, IR)
* bits 14-13: 11 (cv=3, no translation)
* bits 5-0: 010010 = MVMVA
* sf=1, mx=0, v=3, cv=3. Pass 2 reads RT matrix, IR input, >>12. */
#define gte_cmdw_mvmva_c11_pass2_exact 0x4A49E012
/* MVMVA pass 1 — C11's exact command: 0x4A41E012.
* bit 25: 1, sf=0, mx=0, v=3, cv=3. Pass 1 reads RT matrix, IR input, no shift. */
#define gte_cmdw_mvmva_c11_pass1_exact 0x4A41E012
/* MVMVA pass 2 — C11 ApplyMatrixLV command.
* Decoded: op_cop2 | CO | fake_cmd=4 | sf=1 (>>12) | mx=0 (RT matrix) | v=3 (IR) | cv=3 (no translation) | lm=0 | cmd=MVMVA.
* Reads (RT row · IR) >> 12 into MAC1/2/3. Per-field composition (no opaque literal)
* keeps the bit layout visible at the call site + matches the libgte C-side byte-exact. */
#define gte_cmdw_mvmva_c11_pass2 (gte_cmd_base | enc_gte_fake_cmd(4) | enc_gte_sf(1) | enc_gte_v(3) | enc_gte_mx(0) | enc_gte_cv(3) | enc_gte_cmd(gte_cmd_mvmva))
/* MVMVA: sf=1 (>>12), mx=0 (RT matrix), v=0 (V0), cv=3 (no TR). */
#define gte_cmdw_mvmva_sf1_mx0_v0_cv3 (gte_cmd_base | enc_gte_sf(1) | enc_gte_cv(3) | enc_gte_v(0) | enc_gte_mx(0) | enc_gte_cmd(gte_cmd_mvmva))
@@ -485,16 +473,24 @@ enum { _C2_TX_SUBS_ = 0
/* GPF — General-purpose Interpolation.
* PSX-SPX `geometrytransformationenginegte.md` §"GPF":
* [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12)
* [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf * 12)
* [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
* Sourced verbatim from libgte msc02 VectorNormal disassembly at 0x8001613c:
* 0x4B90003D = gte_cmd_base | gte_cmdw_gpf_compat | enc_gte_cmd(0x3D)
* bit 19 sf=0
* bit 10 lm=0
* bits 5-0 cmd=0x3D=GPF
* bit 19 sf = 0
* bit 10 lm = 0
* bits 5-0 cmd = 0x3D = GPF
* bits 24-20 = 0x19 (libgte "nonsense SDK command number" signature) */
#define gte_cmdw_gpf (gte_cmd_base | enc_gte_cmd(gte_cmd_gpf) | gte_cmdw_gpf_fake_sig)
/* Mask to round LZCR (leading-zero/ones count, range 1..32 per PSX-SPX cop2r31)
* down to even. The normalize_v3s4 half-shift logic computes (31 - LZCR) >> 1;
* clearing bit 0 ensures the subtraction result is always odd,
* so the >> 1 division is consistent (no 0.5 loss). */
enum {
gte_lzcr_even_mask = 0xFFFE, /* all bits except bit 0 */
};
#define gte_cmdw_rotate_translate_perspective_single gte_cmdw_rtps
#define gte_cmdw_rotate_translate_perspective_triple gte_cmdw_rtpt
/* RGA(Lengyel): RTPS/RTPT consume the matrix expansion of a rigid transformation (rotation matrix + translation vector) loaded into the RT/TR control registers.
+94 -31
View File
@@ -117,10 +117,13 @@ typedef Slice_(MipsAtom);
#define MipsAtom_(sym) MipsCode sym [] align_(4) =
// Used for atoms with value-args
// FI_ void ac_X(args) MipsAtomComp_Proc_(ac_X, { body })
// internal MipsAtom* X_proc(AtomArena_R aa, args) MipsAtom_Proc_(X, aa, { body })
// expands to:
// FI_ void ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return ac_X; }
#define MipsAtom_Proc_(sym, aa, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return atomarena_push(aa, slice_from_array(MipsCode, sym)); }
// internal MipsAtom* X_proc(AtomArena_R aa, args) { MipsCode atom_comp_code[] align_(4) = { body }; return atomarena_push(aa, slice_from_array(MipsCode, atom_comp_code)); }
// The atom name is derived by the Lua metaprogram from the preceding
// `MipsAtom* X_proc(...)` declaration (backward walk from the macro site,
// strips the `_proc` suffix).
#define MipsAtom_Proc_(aa, ...) { MipsCode atom_comp_code[] align_(4) = __VA_ARGS__; return atomarena_push(aa, slice_from_array(MipsCode, atom_comp_code)); }
// Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names).
// MipsAtomComp_(ac_X) { body }
@@ -129,24 +132,24 @@ typedef Slice_(MipsAtom);
#define MipsAtomComp_(sym) MipsCode sym [] align_(4) =
// Used for components with value-args (mandatory `ab` (atom-builder) arg).
// FI_ void ac_X(MipsAtomBuilder_R ab, args) MipsAtomComp_Proc_(ac_X, ab, { body })
// FI_ void ac_X(MipsAtomBuilder_R ab, args) MipsAtomComp_Proc_(ab, { body })
// expands to:
// FI_ void ac_X(MipsAtomBuilder_R ab, args) {
// MipsCode ac_X[] align_(4) = { body };
// atombuilder_unroll(ab, slice_from_array(MipsCode, ac_X));
// MipsCode atom_comp_code[] align_(4) = { body };
// atombuilder_push(ab, slice_from_array(MipsCode, atom_comp_code));
// }
// The body must NOT include mac_yield() (the parent atom yields).
// Inline-only callers (the generated `mac_<name>` aliases) skip this arg via metaprogram filtering;
// escape callers (ac_<name> invoked as a function) pass a long-lived builder.
#define MipsAtomComp_Proc_(sym, ab, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; atombuilder_push(ab, slice_from_array(MipsCode, sym)); }
// The component name is derived by the Lua metaprogram from the preceding `FI_ Slice_MipsCode ac_X(...)` declaration (backward walk from the macro site).
// Inline-only callers (the generated `mac_<name>` aliases) skip the `ab` arg via metaprogram filtering; escape callers (ac_<name> invoked as a function) pass a long-lived builder.
#define MipsAtomComp_Proc_(ab, ...) { MipsCode atom_comp_code[] align_(4) = __VA_ARGS__; atombuilder_push(ab, slice_from_array(MipsCode, atom_comp_code)); }
/* Line-table anchor: gcc only adds a file to the .debug_line file table when the contains line-numbered content.
Files containing only atoms and atom components.
Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms.
The macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table.
The constant is in `.rodata` and unreferenced; the linker may eliminate it.
The two-level concat + `__LINE__` suffix makes the identifier unique per call site
(the identifier embeds the source line, so duplicates across `#include`d files don't collide). */
Macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table.
The constant is in `.rodata` so the linker may eliminate it.
Two-level concat + `__LINE__` suffix makes the identifier unique per call site
(identifier embeds the source line, so duplicates across `#include`d files don't collide). */
#define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0
typedef Slice_MipsAtom Tape;
@@ -284,34 +287,94 @@ FI_ MipsAtom* atomarena_push(AtomArena_R aa, Slice_MipsCode code) {
return C_(MipsAtom*, dest);
}
FI_ void atomarena_reset(AtomArena_R aa) { aa->used = 0; }
#pragma region Atom Arena
#pragma endregion Atom Arena
#pragma region RegFile (Register File Allocator)
// A specialized allocator utilized to help the user track which registers are bound to values
// that must be preserved for the arena's bounds.
// TODO(Ed): Technically we can do this at comp-time with the metaprogram, but we may have namespace conflicts.
// Unless we follow a convention for #define <Scope_Prefix> or something per register allocation boundary.
enum {
RegFileArena_Len,
};
typedef Enum_(U4, RegFileEntry) {
// TODO(Ed): Define RF_Field, each field is maped by index + bit pos.
// the index is the upper portion of a U4 and the bit pos in the lower pos.
/* ABI + tape reserves that are never handed out by alloc. */
U4 const regfile_abi_mask =
(1u << R_0) | (1u << R_AT) |
(1u << R_K0) | (1u << R_K1) |
(1u << R_GP) | (1u << R_SP) |
(1u << R_FP) | (1u << R_RA) |
(1u << R_T8) | (1u << R_T9); /* AtomJmp + TapePtr */
regfileentry_todo_,
// TODO(Ed): Is there a trick we can do with the current register enums to
// just resolve an entry automatically when doing a pin?
};
typedef Struct_(RegFile) {
U1 GPR[RegFileArena_Len];
U1 GTE[RegFileArena_Len];
U1 GP[RegFileArena_Len];
A2_U2 GPR;
A2_U2 GTE;
};
void regfile_pin(U4 register) {
assert(false);
#define regfile(pin_mask) {.GPR={u4_lo(pin_mask), u4_hi(pin_mask)} }
FI_ void regfile_init(RegFile_R rf) {
/* pack the 32-bit ABI mask into the two U2s */
rf->GPR[0] = u4_lo(regfile_abi_mask);
rf->GPR[1] = u4_hi(regfile_abi_mask);
rf->GTE[0] = rf->GTE[1] = 0;
}
FI_ RegFile regfile_make(void) { RegFile rf; regfile_init(& rf); return rf; }
typedef Struct_(RegFile_RInfo) {
U2_R section;
U2 mask;
B2 occupied;
};
FI_ RegFile_RInfo regfile_rinfo(A2_U2 file, Reg r_id) {
U2 s_id = r_id >> 4;
U2_R section = & file[s_id];
U2 mask = u2_(1u << (r_id & 15));
B2 occupied = (section[0] & mask) != 0;
return (RegFile_RInfo){section, mask, occupied};
}
FI_ Reg regfile__alloc_helper(A2_U2 file, Reg r_id) {
Reg result = 0; RegFile_RInfo info = regfile_rinfo(file, r_id);
if (info.occupied == false) {
info.section[0] |= info.mask;
result = r_id;
}
return result;
}
I_ Reg regfile_alloc(RegFile_R rf) {
U2 allocated = 0;
for index_iter(Reg, r_id, R_T0, <=, R_T7) {
allocated = regfile__alloc_helper(rf->GPR, r_id); Jmp_nZero_(allocated,resolved);
}
allocated = regfile__alloc_helper(rf->GPR, R_V0); Jmp_nZero_(allocated,resolved);
allocated = regfile__alloc_helper(rf->GPR, R_V1);
assert(allocated != 0);
resolved: return allocated;
}
FI_ Reg regfile_pin(RegFile_R rf, Reg r_id) {
RegFile_RInfo info = regfile_rinfo(rf->GPR, r_id);
assert(info.occupied == false);
info.section[0] |= info.mask;
return r_id;
}
FI_ void regfile_pin_mask(RegFile_R rf, U4 mask) {
B4 occupied = u4_r(rf->GPR)[0] & mask;
assert(occupied == false);
u4_r(rf->GPR)[0] |= mask;
}
FI_ void regfile_free_mask(RegFile_R rf, U4 mask) {
if (regfile_abi_mask & mask) return;
u4_r(rf->GPR)[0] &= ~mask;
}
FI_ void regfile_free_reg(RegFile_R rf, Reg r_id) {
/* never free the ABI set */
if (regfile_abi_mask & (1u << r_id)) return;
RegFile_RInfo info = regfile_rinfo(rf->GPR, r_id);
info.section[0] &= ~info.mask;
}
FI_ void regfile_reset(RegFile_R rf) {
rf->GPR[0] = u4_lo(regfile_abi_mask);
rf->GPR[1] = u4_hi(regfile_abi_mask);
}
FI_ void regfile_reset_mask(RegFile_R rf, U4 mask) {
rf->GPR[0] = u4_lo(mask);
rf->GPR[1] = u4_hi(mask);
}
#pragma endregion RegFileArena (Register File Allocator)
#pragma region Mips Atom Procs
+8 -6
View File
@@ -9,17 +9,19 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(math_atom_c);
#pragma region MACs (Mips Atom Component)
FI_ Slice_MipsCode ac_load_v2s2(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_load_v2s2, ab, {
// FI_ Slice_MipsCode ac_load_imm
FI_ Slice_MipsCode ac_load_v2s2(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_half( rs_x, r_base, offset + O_(V3_S2,x)),
load_half( rs_y, r_base, offset + O_(V3_S2,y)),
})
FI_ Slice_MipsCode ac_store_v2s2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v2s2, ab, {
FI_ Slice_MipsCode ac_store_v2s2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_half(rt_x, base, offset + O_(V2_S2,x)),
store_half(rt_y, base, offset + O_(V2_S2,y)),
})
FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 rs_z, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_load_v3s4, ab, {
FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 rs_z, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_word( rs_x, r_base, offset + O_(V3_S4,x)),
load_word( rs_y, r_base, offset + O_(V3_S4,y)),
load_word( rs_z, r_base, offset + O_(V3_S4,z)),
@@ -28,7 +30,7 @@ FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 rs_z, U4
#define ac_load_p3s4 ac_load_v3s4
#define mac_load_p3s4 mac_load_v3s4
FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_z, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v3s4, ab, {
FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_z, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_word(rt_x, base, offset + O_(V3_S4,x)),
store_word(rt_y, base, offset + O_(V3_S4,y)),
store_word(rt_z, base, offset + O_(V3_S4,z)),
@@ -37,13 +39,13 @@ FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_z, U4
#define ac_store_p3s4 ac_store_v3s4
#define mac_store_p3s4 mac_store_v3s4
FI_ Slice_MipsCode ac_sub_v3s4(AtomBuilder_R ab, U4 rds_x, U4 rds_y, U4 rds_z, U4 rt_x, U4 rt_y, U4 rt_z) atom_dbg_skip MipsAtomComp_Proc_(ac_sub_v3s4, ab, {
FI_ Slice_MipsCode ac_sub_v3s4(AtomBuilder_R ab, U4 rds_x, U4 rds_y, U4 rds_z, U4 rt_x, U4 rt_y, U4 rt_z) atom_dbg_skip MipsAtomComp_Proc_(ab, {
sub_s(rds_x, rds_x, rt_x),
sub_s(rds_y, rds_y, rt_y),
sub_s(rds_z, rds_z, rt_z),
})
FI_ Slice_MipsCode ac_store_rects2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rects2, ab, {
FI_ Slice_MipsCode ac_store_rects2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_half(rt_x, base, offset + O_(Rect_S2,x)),
store_half(rt_y, base, offset + O_(Rect_S2,y)),
store_half(rt_width, base, offset + O_(Rect_S2,width)),
+11 -5
View File
@@ -24,6 +24,7 @@ enum {
};
typedef Array_(U1, 2);
typedef Array_(U2, 2);
typedef Array_(U4, 2);
typedef Array_(S2, 2);
typedef Array_(S2, 3);
@@ -46,6 +47,9 @@ typedef Struct_(V4_S4) { S4 x; S4 y; S4 z; S4 w; };
// typedef Struct_(P3_S4) { S4 x; S4 y; S4 z; S4 w1; }; // RGA(Lengyel): Affine point with implicit weight one. Storage alias of V3_S4. Use P3_S4 when the value is a point.
typedef V3_S4 P3_S4;
typedef Struct_(R1_U2) { U2 p0; U2 p1; };
typedef Struct_(R1_S2) { S2 p0; S2 p1; };
typedef Struct_(R2_S2) { V2_S2 p0; V2_S2 p1; }; // Range-2 Signed 2-Byte (16-bit)
typedef Struct_(R2_S4) { V2_S4 p0; V2_S4 p1; }; // Range-2 Signed 4-Byte (32-bit)
@@ -64,6 +68,8 @@ typedef Array_(V2_S2, 2);
typedef Array_(V2_S2, 3);
typedef Array_(V2_S2, 4);
#define r1u2(p0,p1) (R1_U2){p0,p1}
enum {
fp_one = (1 << 12),
};
@@ -106,10 +112,10 @@ FI_ void mul_a3s4(A3_S4_R out_a, A3_S4 b) {
(out_a[0])[2] *= b[2];
}
FI_ void add_v3s4 (V3_S4_R out_a, V3_S4 b) { add_a3s4 (pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
FI_ void add_v3s4_fp(V3_S4_R out_a, V3_S4 b) { add_a3s4_fp(pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
FI_ void add_v3s4 (V3_S4_R out_a, V3_S4 b) { add_a3s4 (C_ptr(A3_S4_R, out_a), C_ptr(A3_S4, b)); }
FI_ void add_v3s4_fp(V3_S4_R out_a, V3_S4 b) { add_a3s4_fp(C_ptr(A3_S4_R, out_a), C_ptr(A3_S4, b)); }
FI_ void sub_v3s4 (V3_S4_R out_a, V3_S4 b) { sub_a3s4 (pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
FI_ void sub_v3s4_fp(V3_S4_R out_a, V3_S4 b) { sub_a3s4_fp(pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
FI_ void sub_v3s4 (V3_S4_R out_a, V3_S4 b) { sub_a3s4 (C_ptr(A3_S4_R, out_a), C_ptr(A3_S4, b)); }
FI_ void sub_v3s4_fp(V3_S4_R out_a, V3_S4 b) { sub_a3s4_fp(C_ptr(A3_S4_R, out_a), C_ptr(A3_S4, b)); }
FI_ void mul_v3s4 (V3_S4_R out_a, V3_S4 b) { mul_a3s4 (pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
FI_ void mul_v3s4 (V3_S4_R out_a, V3_S4 b) { mul_a3s4 (C_ptr(A3_S4_R, out_a), C_ptr(A3_S4, b)); }
+1 -1
View File
@@ -73,7 +73,7 @@ typedef Slice_(B1);
#define slice_iter(container, iter) (T_((container).ptr) iter = (container).ptr; iter != slice_end(container); ++ iter)
#define slice_arg_from_array(type, ...) & (tmpl(Slice,type)) { .ptr = Array_decl(type,__VA_ARGS__), .len = Array_len( Array_decl(type,__VA_ARGS__)) }
#define slice_from_array(type, array) (tmpl(Slice,type)) { .ptr = array, .len = S_(array) / S_(type) }
#define slice_from_array(type, array) (tmpl(Slice,type)) { .ptr = array, .len = Array_len(array) }
FI_ void slice_zero_(Slice s) { slice_assert(s); mem_zero(u4_(s.ptr), s.len); }
#define slice_zero(s) slice_zero_(slice_to_ut(s))
+13 -2
View File
@@ -1,12 +1,23 @@
#ifdef INTELLISENSE_DIRECTIVES
# include "gen/macs.h"
# include "gen/offsets.h"
# include "gen/macs.h"
# include "gen/offsets.h"
# include "bios.h"
# include "mips.h"
# include "lottes_tape.h"
#endif
ATOM_FILE_DEBUGGER_LINE_MARKER(mips_atom_c);
#pragma region MACs (Mips Atom Components)
FI_ Slice_MipsCode ac_load_word_imm(AtomBuilder_R ab, Reg dst, U4 imm)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_upper_i(dst, u4_hi(imm)),
or_i_self( dst, u4_lo(imm)),
})
#pragma endregion MACs (Mips Atom Components)
#pragma region Baked Atoms
/* Flushes the Instruction Cache (PSX A-function 0x44 via BIOS stub at 0xA0).
+21 -13
View File
@@ -259,22 +259,22 @@ enum { _BitOffsets = 0
, SHAMT_SHIFT = 6 /* Shift Amount */
, FC_SHIFT = 0
/* Bit Masks to prevent overflow into adjacent fields */
/* IMM_MASK is the 16-bit two's-complement truncation for the immediate field.
* It is NOT a range guard — it is load-bearing for negative branch offsets
* (the metaprogram emits raw signed offsets; the mask truncates them to the
* 16-bit representation the hardware expects). The static analysis
* `immediate_field_width` check validates ranges at build time. */
, OPCODE_MASK = 0x3F
, REG_MASK = 0x1F
, SHAMT_MASK = 0x1F /* Shift Amount */
, FC_MASK = 0x3F
, IMM_MASK = 0xFFFF
};
#define enc_op(op) (((op) & OPCODE_MASK) << OPCODE_SHIFT)
#define enc_rs(rs) (((rs) & REG_MASK) << RS_SHIFT)
#define enc_rt(rt) (((rt) & REG_MASK) << RT_SHIFT)
#define enc_rd(rd) (((rd) & REG_MASK) << RD_SHIFT)
#define enc_shamt(shamt) (((shamt) & SHAMT_MASK) << SHAMT_SHIFT)
#define enc_fc(fc) (((fc) & FC_MASK) << FC_SHIFT)
#define enc_imm(imm) (((imm) & IMM_MASK))
#define enc_op(op) ((op) << OPCODE_SHIFT)
#define enc_rs(rs) ((rs) << RS_SHIFT)
#define enc_rt(rt) ((rt) << RT_SHIFT)
#define enc_rd(rd) ((rd) << RD_SHIFT)
#define enc_shamt(shamt) ((shamt) << SHAMT_SHIFT)
#define enc_fc(fc) ((fc) << FC_SHIFT)
#define enc_imm(imm) ((imm) & IMM_MASK)
/* MIPS R-Type Instruction Format (Register-to-Register) */
#define enc_r(op, rs, rt, rd, shamt, fc) (enc_op(op) | enc_rs(rs) | enc_rt(rt) | enc_rd(rd) | enc_shamt(shamt) | enc_fc(fc))
@@ -318,7 +318,10 @@ enum { _BitOffsets = 0
#define load_half(rt, base, off) enc_i(op_lh, (base), (rt), (off))
#define load_byte_u(rt, base, off) enc_i(op_lbu, (base), (rt), (off))
#define load_half_u(rt, base, off) enc_i(op_lhu, (base), (rt), (off))
#define LdSlot_
#define store_word(rt, base, off) enc_i(op_sw, (base), (rt), (off))
#define add_ui(rt, rs, imm) enc_i(op_addiu, (rs), (rt), (imm))
#define and_i(rt, rs, imm) enc_i(op_andi, (rs), (rt), (imm))
// #define and_si and_i
@@ -379,6 +382,9 @@ enum { _BitOffsets = 0
*/
#define jump(off) enc_i(op_j, R_0, R_0, (off))
// Annotate an instruction as filling a branch-delay slot.
#define BdSlot_
/* jump_rel off — unconditional relative jump (the within-atom-safe `jump`).
* MIPS I R3000A has no "branch always" opcode. The idiom for an unconditional relative jump is `beq $0, $0, off`. */
#define jump_rel(off) branch_equal(R_0, R_0, (off))
@@ -411,6 +417,7 @@ enum { _BitOffsets = 0
#define div_s(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_div)
#define div_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_divu)
// TODO(Ed): Change convention of 'self' to ds for (destination is source)?
#define add_u_self(rd_rs, rt) add_u(rd_rs, rd_rs, rt)
/* --- Arithmetic I-type (immediate) --- */
@@ -457,7 +464,8 @@ enum { _BitOffsets = 0
#define nop2 nop, nop
// li_s — load signed 16-bit immediate into GPR (addiu rt, $0, imm — sign-extends).
#define li_s(rt, imm) add_ui((rt), R_0, (imm))
#define li_s(rt, imm) add_ui((rt), R_0, (imm))
// #define load_imm_s(rt, imm) add_ui((rt), R_0, (imm))
#define load_imm_1w(rt, imm) add_ui((rt), R_0, (imm))
#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm))
+18 -16
View File
@@ -11,18 +11,19 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(pad_atom_c);
#pragma region MACs (Mips Atom Components)
FI_ Slice_MipsCode ac_pad_set_centered_axes(AtomBuilder_R ab, U4 r_state, U4 r_scratch) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_centered_axes, ab, {
load_upper_i(r_scratch, (PadAxis_Centered_Word >> 16) & 0xFFFF),
or_i_self( r_scratch, PadAxis_Centered_Word & 0xFFFF),
store_word( r_scratch, r_state, O_(PadState,axes)),
FI_ Slice_MipsCode ac_pad_set_centered_axes(AtomBuilder_R ab, Reg state, Reg scratch) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF),
or_i_self( scratch, PadAxis_Centered & 0xFFFF),
// mac_load_word_imm(scratch, PadAxis_Centered),
store_word( scratch, state, O_(PadState,axes)),
})
FI_ Slice_MipsCode ac_pad_set_id_byte(AtomBuilder_R ab, U1 r_state, U1 r_id, U1 id_value) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_id_byte, ab, {
FI_ Slice_MipsCode ac_pad_set_id_byte(AtomBuilder_R ab, Reg state, Reg r_id, U1 id_value) atom_dbg_skip MipsAtomComp_Proc_(ab, {
add_ui( r_id, R_0, id_value),
store_byte(r_id, r_state, O_(PadState,id)),
store_byte(r_id, state, O_(PadState,id)),
})
FI_ Slice_MipsCode ac_pad_set_status(AtomBuilder_R ab, U4 r_tmp, U1 r_state, U4 pad_status) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_status, ab, {
FI_ Slice_MipsCode ac_pad_set_status(AtomBuilder_R ab, U4 r_tmp, U1 r_state, U4 pad_status) atom_dbg_skip MipsAtomComp_Proc_(ab, {
add_ui( r_tmp, R_0, pad_status),
store_word(r_tmp, r_state, O_(PadState,status)),
})
@@ -30,9 +31,9 @@ FI_ Slice_MipsCode ac_pad_set_status(AtomBuilder_R ab, U4 r_tmp, U1 r_state, U4
/* Invert r_buttons (active-low → active-high) and store to PadState.buttons.
* r_buttons must already be loaded (the caller is responsible for filling the load-delay slot of
* the preceding load_half_u with an instruction that doesn't read r_buttons). */
FI_ Slice_MipsCode ac_pad_store_inverted_buttons(AtomBuilder_R ab, U1 r_buttons, U1 r_pad_state) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_store_inverted_buttons, ab, {
FI_ Slice_MipsCode ac_pad_store_inverted_buttons(AtomBuilder_R ab, U1 r_buttons, U1 r_pad_state) atom_dbg_skip MipsAtomComp_Proc_(ab, {
nor_u( r_buttons, r_buttons, R_0),
store_half( r_buttons, r_pad_state, O_(PadState, buttons)),
store_half( r_buttons, r_pad_state, O_(PadState,buttons)),
})
#pragma endregion MACs (Mips Atom Components)
@@ -54,12 +55,12 @@ FI_ Slice_MipsCode ac_pad_store_inverted_buttons(AtomBuilder_R ab, U1 r_buttons,
* byte_swap16(x) = (x >> 8) | (x << 8); nor(x, R_0) = ~x. store_half truncates to 16 bits so the upper-16 mask is implicit in the store.
*
* Register use (atom-local; no wave-context touched):
* R_T0 = raw base (kept throughout; axes loads read raw[4..7] from R_T0)
* R_T1 = state base (kept throughout; all stores go through R_T1)
* R_T2 = raw[0] status (alive across the disc/pending/id dispatch, then dead)
* R_T3 = raw[1] id (alive across the id dispatch, then dead)
* R_T4 = scratch (shifts, compares, immediate loads, store values)
* R_T5 = scratch (parallel lui+ori for the 0x80808080 axes constant + byte-swap target)
* R_T0 = raw base : Kept throughout; axes loads read raw[4..7] from R_T0.
* R_T1 = state base : Kept throughout; all stores go through R_T1.
* R_T2 = raw[0] status : Alive across the disc/pending/id dispatch, then dead.
* R_T3 = raw[1] id : Alive across the id dispatch, then dead.
* R_T4 = scratch : Shifts, compares, immediate loads, store values.
* R_T5 = scratch : Parallel lui + ori for the 0x80808080 axes constant + byte-swap target.
*/
enum {
R_PadRaw = R_T0 atom_reg atom_type(U1),
@@ -124,7 +125,8 @@ atom_label(id_dispatch) /* === Case 3-6: ID dispatch */
* R_T5 is then "dead" — only consumed at the analog_pad range check downstream. */
mac_pad_set_status(R_T4, R_PadState, PadStatus_Digital),
load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw, buttons)), /* R_T4 = raw_buttons; */
load_upper_i(R_T5, PadAxis_Centered_Hi), or_i_self(R_T5, PadAxis_Centered_Lo), /* fills the buttons-load's delay slot (doesn't read R_T4) */
mac_load_word_imm(R_T5, PadAxis_Centered), /* fills the buttons-load's delay slot (doesn't read R_T4) */
// load_upper_i(R_T5, PadAxis_Centered_Hi), or_i_self(R_T5, PadAxis_Centered_Lo),
mac_pad_store_inverted_buttons(R_T4, R_PadState), /* R_T4 settled: nor + sh writes ~raw_buttons to state.buttons */
store_word(R_T5, R_PadState, O_(PadState, axes)), /* single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y) */
mac_pad_set_id_byte(R_PadState, R_T4, PadRawId_Digital),
+1 -1
View File
@@ -36,7 +36,7 @@ NI_ void pad_bios_init_start(PadBiosRaw* raw0, PadBiosRaw* raw1)
* $t2 = 0xB0 (BIOS B-table address) */
asm volatile(
asm_words(
or_u( R_A2, R_A0, R_0), /* $a2 = $a1 = raw1 */
or_u( R_A2, R_A1, R_0), /* $a2 = $a1 = raw1 */
add_ui( R_A1, R_0, bios_pad_buffer_size), /* $a1 = 0x22 */
add_ui( R_A3, R_0, bios_pad_buffer_size), /* $a3 = 0x22 */
add_ui( R_T1, R_0, bios_init_pad_2), /* $t1 = 0x12 */
+3 -3
View File
@@ -81,9 +81,9 @@ typedef Enum_(U1, PadUnknownId) {
PadUnknownId_Sentinel = 0xFF,
};
typedef Enum_(U4, PadAxisCentered) {
PadAxis_Centered_Hi = 0x8080,
PadAxis_Centered_Lo = 0x8080,
PadAxis_Centered_Word = 0x80808080U,
PadAxis_Centered_Hi = 0x8080,
PadAxis_Centered_Lo = 0x8080,
PadAxis_Centered = 0x80808080U,
};
typedef Enum_(U1, PadDeadZone) {
PadDeadZone_LowBound = 0x70, /* left_x < LowBound → active; delta = 0x80 - left_x > 0 (rightward pull) */
+29 -109
View File
@@ -26,7 +26,7 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(hello_joypad_atom_c);
#pragma region MACs (Mips Atom components)
FI_ Slice_MipsCode ac_put_disp_env(AtomBuilder_R ab, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_disp_env, ab, {
MipsAtomComp_Proc_(ab, {
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
// Sequence per libpsyx PutDispEnv: DrawArea TL → DrawArea BR → Mask → DrawArea TL → DrawArea BR
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
@@ -37,7 +37,7 @@ MipsAtomComp_Proc_(ac_put_disp_env, ab, {
})
I_ Slice_MipsCode ac_put_draw_env(AtomBuilder_R ab, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_draw_env, ab, {
MipsAtomComp_Proc_(ab, {
/*
* ORIGIN: each code word corresponds to the EXACT value libpsyx's PutDrawEnv function would compute for the same DrawEnv settings.
* References:
@@ -94,34 +94,8 @@ MipsAtomComp_Proc_(ac_put_draw_env, ab, {
#pragma region Atom Procs
// Modular Atoms
/* Scratchpad layout for the resolve_look_at bundle.
* The chain atoms communicate entirely via the wave-context GPR carrier R_ResolveScratch (R_T4) + hardcoded offsets into smem.scratchpad
* (PS1 hardware scratchpad at 0x1F800000).
*
* Atom 0 (input_and_sub) STAGES the C-side inputs (eye, up_in) into the scratchpad;
* AT THE SAME TIME it computes fwd = target - eye and stores it at scratch+0.
* Atoms 1-6 then read/write specific scratchpad offsets internally using
* `r_scratch + hardcoded_offset` — no tape-data pointers are passed between atoms.
* +0 fwd (atom 0 writes; atom 1 reads)
* +16 uz (atom 1 writes; atoms 2 + 4 read)
* +32 right (atom 2 writes; atom 3 reads)
* +48 ux (atom 3 writes; atoms 4 + 6 read)
* +64 up (atom 4 writes; atom 5 reads)
* +80 uy (atom 5 writes; atom 6 reads)
* +96 eye (atom 0 stages from C-side pointer; atom 6 reads)
* +128 up_in (atom 0 stages from C-side pointer; atom 2 reads)
*/
// enum {
// R_LookAt = R_T0 atom_reg atom_type(MT3_S2S4*),
// R_CamEye = R_T1 atom_reg atom_type(P3_S4*),
// R_CamTarget = R_T2 atom_reg atom_type(P3_S4*),
// R_WorldUp = R_T3 atom_reg atom_type(V3_S4*),
// };
enum {
/* Wave-context GPR carrier for the resolve_look_at bundle: the scratch base.
* Set by atom 0 (popped from tape), read by atoms 1-6 (used as pointer base). */
// TODO(Ed): We can resolve scratch at anytime its fixed to a specific address.
R_ResolveScratch = R_T4 atom_reg atom_type(U4*),
#define R_ResolveScratch_Code R_T4_Code
};
@@ -132,30 +106,7 @@ typedef Struct_(Binds_ResolveLookAt) {
V3_S4* up_in;
};
/* ─── ResolveLookAtScratch — offset schema for the resolve_look_at bundle's
* scratchpad slots (PS1 hardware scratchpad at 0x1F800000).
*
* Each slot is 16 bytes: V3_S4 is already 16 bytes (4 × S4 = x/y/z/pad).
* The struct fields are contiguous — slot i starts at offset i*16.
* Used by the assembly via O_(ResolveLookAtScratch, fld.x/y/z) which resolves to a compile-time byte offset.
* NOT a runtime struct — the struct is purely a schema for offsets; the assembly uses `r_scratch + O_(...)` to compute slot addresses at runtime.
*
* Slot producers/consumers (referenced by the resolve_look_at chain atoms):
* +0 fwd 0 writes (target - eye); atom 1 (normalize) reads
* +16 uz 1 writes (normalize fwd); atoms 2 + 4 read (cross operands)
* +32 right 2 writes (cross uz x up_in); atom 3 (normalize) reads
* +48 ux 3 writes (normalize right); atoms 4 + 6 read
* +64 up 4 writes (cross uz x ux); atom 5 (normalize) reads
* +80 uy 5 writes (normalize up); atom 6 reads
* +96 eye 0 stages (C-side input); atom 6 reads (translation column)
* +112 target reserved (currently written nowhere — kept for symmetry w/ eye)
* +128 up_in 0 stages (C-side input); atom 2 reads (cross operand)
*
* Fields use P3_S4 (point) for eye/target (RGA: affine point, implicit weight 1);
* V3_S4 (vector) for fwd/uz/right/ux/up/uy/up_in (RGA: Euclidean vector).
* P3_S4 is a storage alias of V3_S4 (see math.h comment: "Storage alias of V3_S4.
* Use P3_S4 when the value is a point.") — both are 16 bytes.
*/
/* ─── ResolveLookAtScratch — offset schema for the resolve_look_at bundle's */
typedef Struct_(ResolveLookAtScratch) {
V3_S4 fwd; /* offset +0 (16 bytes — 4 S4 fields incl. internal pad) */
V3_S4 uz; /* offset +16 (16 bytes) */
@@ -168,24 +119,7 @@ typedef Struct_(ResolveLookAtScratch) {
V3_S4 up_in; /* offset +128 (16 bytes) */
};
/* ─── resolve_look_at bundle chain atoms ────────────────────────────
* 4 unique atom procs in the resolve_look_at bundle (4 chain atoms + 3 calls to generic normalize_v3s4_proc).
* All 4 chain atoms are runtime-built MipsAtom_Proc_ atoms: each function declares a static MipsCode[] body,
* then calls atombuilder_unroll() to append it to the caller's MipsAtomBuilder arena. resolve_look_at_init()
* uses this pattern to pre-build the bundle into the static arena (smem.resolve_look_at_arena).
*
* Atom roster:
* 0: resolve_look_at__input_and_sub (chain atom)
* 1: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for fwd→uz)
* 2: resolve_look_at__cross_uz_up_in_to_right (chain atom)
* 3: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for right→ux)
* 4: resolve_look_at__cross_uz_ux_to_up (chain atom)
* 5: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for up→uy)
* 6: resolve_look_at__populate_and_translate (chain atom)
*
* The generic normalize_v3s4_proc is a parameterized 4-stage GTE normalize (SQR → mfc2 → LZCS → GPF → srav);
* it accepts scratch base + offset args so any caller (with a scratch base + struct schema) can use it.
*/
/* ─── resolve_look_at bundle chain atoms ──────────────────────────── */
typedef Struct_(Binds_ResolveLookAtSub) {
P3_S4* target; /* U4 (C-side P3_S4* — read by atom 0 directly; NOT a scratchpad address) */
@@ -217,7 +151,7 @@ internal MipsAtom* resolve_look_at__input_and_sub_proc(AtomArena_R aa,
U4 r_scratch
, U4 r_target_ptr,U4 r_eye_ptr, U4 r_up_in_ptr
, U4 r_tmp0, U4 r_tmp1, U4 r_tmp2, U4 r_tmp3
) MipsAtom_Proc_(resolve_look_at__input_and_sub, aa, {
) MipsAtom_Proc_(aa, {
load_word(r_target_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,target)),
load_word(r_eye_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,eye)),
load_word(r_up_in_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,up_in)),
@@ -243,27 +177,12 @@ internal MipsAtom* resolve_look_at__input_and_sub_proc(AtomArena_R aa,
mac_yield()
})
/* Atoms 2 + 4 in the bundle: out = a × b (GTE outer product on IR/D vectors).
* No bind pop — the three operand pointers (a, b, out) are derived in-body from r_scratch + hardcoded_offset.
* Each atom has its own variant because the offsets are baked into the body and each atom uses unique GPRs.
*
* GTE register layout (per PSX-SPX + duffle gte.h):
* IR1/2/3 = a.x/y/z (mtc2)
* VXY0 = b.x (mtc2)
* VZ0 = b.y (mtc2)
* VXY1 = b.z (mtc2)
* OP = outer product
* MAC1/2/3 = out.x/y/z (mfc2)
*
* Pool cost: r_scratch (R_T4 carrier) + 7 body GPRs + R_AT + R_V0 (hardcoded) = 10 GPRs.
*/
/* Atom 2: cross uz × up_in → right. */
internal MipsAtom* resolve_look_at__cross_uz_up_in_to_right_proc(AtomArena_R aa, U4 r_scratch
, U4 r_a, U4 r_b, U4 r_c /* load a.x/y/z; result out.x/y/z */
, U4 r_d /* load b.x */
, U4 r_f, U4 r_g, U4 r_h /* r_f = &right (out ptr), r_g = &uz, r_h = &up_in */
) MipsAtom_Proc_(resolve_look_at__cross_uz_up_in_to_right, aa, {
) MipsAtom_Proc_(aa, {
/* FIX: build packed RT22+RT33 with proper sign extension. */
add_si(r_g, r_scratch, O_(ResolveLookAtScratch,uz)), /* r_g = &uz */
add_si(r_h, r_scratch, O_(ResolveLookAtScratch,up_in)), /* r_h = &up_in */
@@ -347,7 +266,7 @@ internal MipsAtom* resolve_look_at__cross_uz_ux_to_up_proc(AtomArena_R aa, U4 r_
, U4 r_a, U4 r_b, U4 r_c /* load a.x/y/z; result out.x/y/z */
, U4 r_d /* load b.x */
, U4 r_f, U4 r_g, U4 r_h /* r_f = &up (out ptr), r_g = &uz, r_h = &ux */
) MipsAtom_Proc_(resolve_look_at__cross_uz_ux_to_up, aa, {
) MipsAtom_Proc_(aa, {
/* Compute the three scratch pointers from r_scratch. */
add_si(r_g, r_scratch, O_(ResolveLookAtScratch,uz)), /* r_g = &uz */
add_si(r_h, r_scratch, O_(ResolveLookAtScratch,ux)), /* r_h = &ux */
@@ -444,7 +363,7 @@ internal MipsAtom* resolve_look_at__populate_proc(AtomArena_R aa
, U4 r_scratch
, U4 r_pux, U4 r_puy, U4 r_puz
, U4 r_tmp0, U4 r_tmp1, U4 r_tmp2
) MipsAtom_Proc_(resolve_look_at__populate, aa, {
) MipsAtom_Proc_(aa, {
/* Pop look_at* (the matrix output) — advance R_TapePtr by 4 bytes. */
load_word(r_look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)),
add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)),
@@ -507,7 +426,7 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa
, U4 r_peye
, U4 r_look_at
, U4 r_tmp0, U4 r_tmp1, U4 r_tmp2
) MipsAtom_Proc_(resolve_look_at__matrix_vector, aa, {
) MipsAtom_Proc_(aa, {
/* === EXACT C11 ApplyMatrixLV replication ===
* The C11 does:
* 1. ctc2 RT matrix (5 ctc2s to C2[0..4])
@@ -561,9 +480,9 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa
gte_mv_to_data_r(r_tmp2, C2_IR3),
nop2, /* MTC2 retirement (2 slots) */
/* === MVMVA pass 2 EXACT C11 command: 0x4A49E012 ===
/* === MVMVA pass 2 — C11 ApplyMatrixLV command ===
* sf=1, mx=0 (RT), v=3 (IR), cv=3. Reads RT × IR >> 12. */
gte_cmdw_mvmva_c11_pass2_exact,
gte_cmdw_mvmva_c11_pass2,
nop, /* GTE interlock */
/* === mfc2 MAC1/2/3 → r_tmp0/1/2 === */
@@ -592,21 +511,19 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa
* Pool cost: r_look_at (1) + r_scratch (carrier) + r_off_ptr + 1 clobber = 4 GPRs.
*/
I_ MipsAtom* resolve_look_at__trans_matrix_proc(AtomArena_R aa
, U4 r_look_at
, U4 r_scratch
, U4 r_off_ptr
, U4 r_tmp0
) MipsAtom_Proc_(resolve_look_at__trans_matrix, aa, {
, U4 r_look_at, U4 r_scratch, U4 r_off_ptr
, U4 r_tmp0, U4 r_tmp1, U4 r_tmp2
) MipsAtom_Proc_(aa, {
/* Pop look_at* from tape. */
load_word(r_look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)),
add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)),
// load_word(r_Vlook_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)),
// add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)),
/* r_off_ptr = &off (= &scratch.eye since atom 6b overwrote eye with off). */
add_si(r_off_ptr, r_scratch, O_(ResolveLookAtScratch,eye)),
nop,
/* Copy off → look_at.t[] (mac_trans_matrix: m->t = v). */
mac_trans_matrix(r_look_at, r_off_ptr, r_tmp0),
mac_trans_mt3s3s4(r_look_at, r_off_ptr, r_tmp0, r_tmp1, r_tmp2),
mac_yield()
})
@@ -628,7 +545,7 @@ internal MipsAtom_(screen_env_init) atom_info(atom_phase(screen_init)
) {
/* display[0] = (0, 0, 320, 240); rest of struct zeroed. */
add_ui(R_ScreenX, R_0, ScreenRes_X), add_ui(R_ScreenY, R_0, ScreenRes_Y),
mac_store_v2s2(R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DisplayEnv,display_area.width) + OA_(DoubleBuffer,display,0)),
mac_store_v2s2(R_ScreenX, R_ScreenY, R_ScreenBuf, O_(DisplayEnv,display_area.width) + O_(DoubleBuffer,display[0])),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,display_area) + O_(DoubleBuffer,display[0])),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,screen) + O_(DoubleBuffer,display[0])),
store_word(R_0, R_ScreenBuf, O_(DisplayEnv,vinterlace) + O_(DoubleBuffer,display[0])),
@@ -858,32 +775,35 @@ internal MipsAtom_(pad_input_cam) atom_info(atom_bind(Binds_PadInputCam)
// D-pad Left → cam.pos.x -= 50. and_i fulfills BD-slot for load on R_Cam.
and_i(R_T3, R_T0, Pad_Left), branch_le_zero(R_T3, atom_offset(left_x, exit_left_x)), mac_yield_load(),
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.x)),
atom_label(exit_left_x)
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.x)),
atom_label(exit_left_x)
/* D-pad Right → cam.pos.x += 50. Reuses R_T1 from Left. */
and_i(R_T3, R_T0, Pad_Right), branch_le_zero(R_T3, atom_offset(right_x, exit_right_x)), nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.x)),
atom_label(exit_right_x)
atom_label(exit_right_x)
/* D-pad Up → cam.pos.y -= 50. Load pos.y BEFORE the andi. */
load_word(R_T1, R_Cam, O_(Camera,pos.y)),
and_i(R_T3, R_T0, Pad_Up), branch_le_zero(R_T3, atom_offset(up_y, exit_up_y)), nop,
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.y)),
atom_label(exit_up_y)
atom_label(exit_up_y)
/* D-pad Down → cam.pos.y += 50. Reuses R_T1 from Up. */
and_i(R_T3, R_T0, Pad_Down), branch_le_zero(R_T3, atom_offset(down_y, exit_down_y)), nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.y)),
atom_label(exit_down_y)
atom_label(exit_down_y)
/* D-pad Cross → cam.pos.z -= 50. Load pos.z BEFORE the andi. */
load_word(R_T1, R_Cam, O_(Camera,pos.z)),
and_i(R_T3, R_T0, Pad_Cross), branch_le_zero(R_T3, atom_offset(cross_z, exit_cross_z)), nop,
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.z)),
atom_label(exit_cross_z)
atom_label(exit_cross_z)
/* D-pad Circle → cam.pos.z += 50. Reuses R_T1 from Cross. */
and_i(R_T3, R_T0, Pad_Circle), branch_le_zero(R_T3, atom_offset(circle_z, exit_circle_z)), nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.z)),
atom_label(exit_circle_z)
atom_label(exit_circle_z)
mac_yield_tail(),
};
+95 -178
View File
@@ -1,7 +1,7 @@
#pragma region Vendors
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// #include <assert.h>
// #include "libgpu.h"
// #include "libetc.h"
// #include "libgte.h"
@@ -132,13 +132,8 @@ I_ void resolve_look_at_c11(MT3_S2S4* look_at, P3_S4* eye, P3_S4* target, V3_S4*
FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_look_at_c11(& c->look_at, & c->pos, target, up_in); }
/* Pre-build all 7 chain atoms of the resolve_look_at bundle into the static arena.
* Called ONCE from main() before the frame loop.
* After this returns, the smem.resolve_look_at_atom_addrs[] array contains valid MIPS atom pointers
* for the frame-time bundle helper to emit via tb_emit(tb, captured_addr).
*
* 4 unique procs in hello_camera.atom.c (chain atoms 0, 2, 4, 6); atoms 1, 3, 5
* share the GENERIC normalize_v3s4_proc from gte.atom.c (called 3x with different
* O_(ResolveLookAtScratch,...) offsets):
* share the GENERIC normalize_v3s4_proc from gte.atom.c
* 0: resolve_look_at__input_and_sub_proc
* 1: normalize_v3s4_proc (fwd → uz; offsets 0, 16)
* 2: resolve_look_at__cross_uz_up_in_to_right_proc
@@ -146,190 +141,112 @@ FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_lo
* 4: resolve_look_at__cross_uz_ux_to_up_proc
* 5: normalize_v3s4_proc (up → uy; offsets 64, 80)
* 6: resolve_look_at__populate_and_translate_proc
*
* Task 12.16 promotion: the bundle-specific resolve_look_at__chain_normalize_proc
* has been promoted to the generic normalize_v3s4_proc (gte.atom.c), which now
* takes r_scratch + r_src_offset + r_dst_offset as U4 parameters. The 3 callers
* pass O_(ResolveLookAtScratch,...) macros as offset args. The metaprogram emits
* one set of `atom_offset__normalize_v3s4__srav_path__aligned_done` defs
* (namespaced by atom name) in duffle/gen/offsets.h, shared by all 3 callers.
*
* GPR pool per atom: 10 free GPRs (R_T0..R_T3 + R_T5..R_T7 + R_V0 + R_V1 + R_AT).
* R_T4 is reserved as the wave-context carrier (R_ResolveScratch).
*/
/* === EXPLICIT REGISTER ALLOCATION TRACKER ===
* Every GPR used by every atom is tracked below. NO GPR is assigned to
* two atoms at overlapping lifetimes. The tape runtime preserves R_T8/R_T9
* (R_AtomJmp/R_TapePtr) and clobbers R_T0-R_T7, R_AT, R_V0, R_V1.
* R_T4 is reserved as R_ResolveScratch (wave-context carrier).
*
* GPR pool: R_T0($8), R_T1($9), R_T2($10), R_T3($11), R_T5($13),
* R_T6($14), R_T7($15), R_V0($2), R_V1($3), R_AT($1)
* Reserved: R_T4($12) = R_ResolveScratch
* Tape: R_T8($24) = R_AtomJmp, R_T9($25) = R_TapePtr (preserved)
*
* === ATOM 0: input_and_sub (stages eye/up_in, computes fwd) ===
* Pop tape → R_T0(target), R_T1(eye), R_T2(up_in).
* Use R_T3,R_T5,R_T6,R_T7 as temps.
* NO conflict with other atoms (each atom has independent lifetime).
*
* === ATOM 1: normalize fwd→uz ===
* r_src_offset=0, r_dst_offset=16.
* r_src_ptr=R_T0, r_dst_ptr=R_T1, r_tmp=R_T2 (preserved for stage 4).
* r_mac1=R_T3, r_mac2=R_T5, r_recip=R_T6, r_lzcr=R_T7, r_shift=R_V0, r_branch=R_V1.
*
* === ATOM 2: cross uz×up_in→right ===
* r_a=R_T0, r_b=R_T1, r_c=R_T2, r_d=R_T3, r_f(out)=R_T5, r_g=R_T6, r_h=R_T7.
*
* === ATOM 3: normalize right→ux ===
* Same GPR pool as atom 1.
*
* === ATOM 4: cross uz×ux→up ===
* r_a=R_T0, r_b=R_T1, r_c=R_T2, r_d=R_T3, r_f(out)=R_T5, r_g=R_T6, r_h=R_T7.
*
* === ATOM 5: normalize up→uy ===
* Same GPR pool as atom 1.
*
* === ATOM 6a: populate (m[][] from ux/uy/uz, t[]=0) ===
* r_look_at=R_T0 (pop tape), r_scratch=R_T4.
* r_pux=R_T1, r_puy=R_T3, r_puz=R_T5.
* r_tmp0=R_T2, r_tmp1=R_T6, r_tmp2=R_V0.
*
* === ATOM 6a.5: set_gte_mt3s2s4 (ctc2 RT matrix) ===
* BAKED atom. Uses R_T3 internally (hardcoded in gte.atom.c).
* NO conflict — different GPR pool, and the atom body hardcodes R_T3
* as the matrix pointer. We DON'T need to assign R_T3 to atom 6a.5
* because it's a baked atom with its own GPR usage.
*
* === ATOM 6b: matrix_vector (RT * (-eye) >> 12) ===
* r_look_at=R_T0 (pop tape), r_scratch=R_T4.
* r_peye=R_T1.
* r_tmp0=R_T2, r_tmp1=R_T3, r_tmp2=R_T5.
* Uses mac_apply_matrix_lv which internally uses these temps.
*
* === ATOM 6c: trans_matrix (off → look_at->t[]) ===
* r_look_at=R_T0 (pop tape), r_scratch=R_T4.
* r_off_ptr=R_T1.
* r_tmp0=R_T2.
*
* === CONFLICT CHECK ===
* All atoms use the same GPR pool R_T0-R_T3, R_T5-R_T7, R_V0-R_V1.
* But atoms are SEQUENTIAL — each atom's lifetime is disjoint from
* the next atom's lifetime. The tape yield handshake between atoms
* preserves R_TapePtr (R_T9) and R_AtomJmp (R_T8).
*
* The GPR pool is SHARED across atoms (they run sequentially, not
* concurrently). Each atom's build call assigns specific R_T* codes
* for that atom's body. The same R_T* code can be reused across atoms
* because the previous atom's body has already yielded.
*/
internal void resolve_look_at_init(void) {
/* Wrap the static arena in a MipsAtomBuilder. */
AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem));
AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem));
TapeBuilder tb = tb_make(slice_ut_arr(smem.resolve_look_at_atom_addrs));
/* === ATOM 0: input_and_sub === */
U4 const r_target_ptr = R_T0; /* tape pop → target */
U4 const r_eye_ptr = R_T1; /* tape pop → eye */
U4 const r_up_in_ptr = R_T2; /* tape pop → up_in */
U4 const r_tmp0_0 = R_T3;
U4 const r_tmp1_0 = R_T5;
U4 const r_tmp2_0 = R_T6;
U4 const r_tmp3_0 = R_T7;
U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch);
RegFile rf = regfile(pin_mask);
U4 r_target_ptr = regfile_alloc(& rf);
U4 r_eye_ptr = regfile_alloc(& rf);
U4 r_up_in_ptr = regfile_alloc(& rf);
U4 r_tmp0 = regfile_alloc(& rf);
U4 r_tmp1 = regfile_alloc(& rf);
U4 r_tmp2 = regfile_alloc(& rf);
U4 r_tmp3 = regfile_alloc(& rf);
smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab,
R_ResolveScratch,
r_target_ptr, r_eye_ptr, r_up_in_ptr,
r_tmp0_0, r_tmp1_0, r_tmp2_0, r_tmp3_0);
r_tmp0, r_tmp1, r_tmp2, r_tmp3);
/* === ATOM 1: normalize fwd→uz === */
U4 const r_src_offset_1 = O_(ResolveLookAtScratch, fwd);
U4 const r_dst_offset_1 = O_(ResolveLookAtScratch, uz);
U4 const r_src_ptr_1 = R_T0;
U4 const r_dst_ptr_1 = R_T1;
U4 const r_tmp_1 = R_T2;
U4 const r_mac1_1 = R_T3;
U4 const r_mac2_1 = R_T5;
U4 const r_recip_1 = R_T6;
U4 const r_lzcr_1 = R_T7;
U4 const r_shift_1 = R_V0;
U4 const r_branch_1 = R_V1;
U2 src_offset = O_(ResolveLookAtScratch, fwd);
U2 dst_offset = O_(ResolveLookAtScratch, uz);
smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab,
R_ResolveScratch,
r_src_offset_1, r_dst_offset_1,
r_src_ptr_1, r_dst_ptr_1, r_tmp_1,
r_mac1_1, r_mac2_1, r_recip_1, r_lzcr_1,
r_shift_1, r_branch_1);
src_offset, dst_offset, RegUse_(normalize_v3s4_proc){
.scratch = R_ResolveScratch,
.src_ptr = R_T0,
.dst_ptr = R_T1,
.recip_est = R_T6,
.norm = R_T7,
.shift = R_V0,
.src_x = R_T2,
.t3 = R_T3,
.t4 = R_T5,
.t5 = R_V1,
});
/* === ATOM 2: cross uz×up_in→right === */
U4 const r_a_2 = R_T0;
U4 const r_b_2 = R_T1;
U4 const r_c_2 = R_T2;
U4 const r_d_2 = R_T3;
U4 const r_f_2 = R_T5; /* out ptr (HARDCODED in body: scratch+32) */
U4 const r_g_2 = R_T6; /* a ptr = scratch+16 */
U4 const r_h_2 = R_T7; /* b ptr = scratch+128 */
U4 r_a_2 = R_T0;
U4 r_b_2 = R_T1;
U4 r_c_2 = R_T2;
U4 r_d_2 = R_T3;
U4 r_f_2 = R_T5; /* out ptr (HARDCODED in body: scratch+32) */
U4 r_g_2 = R_T6; /* a ptr = scratch+16 */
U4 r_h_2 = R_T7; /* b ptr = scratch+128 */
smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_in_to_right_proc(& ab,
R_ResolveScratch,
r_a_2, r_b_2, r_c_2, r_d_2, r_f_2, r_g_2, r_h_2);
/* === ATOM 3: normalize right→ux === */
U4 const r_src_offset_3 = O_(ResolveLookAtScratch, right);
U4 const r_dst_offset_3 = O_(ResolveLookAtScratch, ux);
U4 const r_src_ptr_3 = R_T0;
U4 const r_dst_ptr_3 = R_T1;
U4 const r_tmp_3 = R_T2;
U4 const r_mac1_3 = R_T3;
U4 const r_mac2_3 = R_T5;
U4 const r_recip_3 = R_T6;
U4 const r_lzcr_3 = R_T7;
U4 const r_shift_3 = R_V0;
U4 const r_branch_3 = R_V1;
src_offset = O_(ResolveLookAtScratch, right);
dst_offset = O_(ResolveLookAtScratch, ux);
smem.resolve_look_at_atom_addrs[3] = normalize_v3s4_proc(& ab,
R_ResolveScratch,
r_src_offset_3, r_dst_offset_3,
r_src_ptr_3, r_dst_ptr_3, r_tmp_3,
r_mac1_3, r_mac2_3, r_recip_3, r_lzcr_3,
r_shift_3, r_branch_3);
src_offset, dst_offset, RegUse_(normalize_v3s4_proc){
.scratch = R_ResolveScratch,
.src_ptr = R_T0,
.dst_ptr = R_T1,
.recip_est = R_T6,
.norm = R_T7,
.shift = R_V0,
.src_x = R_T2,
.t3 = R_T3,
.t4 = R_T5,
.t5 = R_V1,
});
/* === ATOM 4: cross uz×ux→up === */
U4 const r_a_4 = R_T0;
U4 const r_b_4 = R_T1;
U4 const r_c_4 = R_T2;
U4 const r_d_4 = R_T3;
U4 const r_f_4 = R_T5; /* out ptr (HARDCODED: scratch+64) */
U4 const r_g_4 = R_T6; /* a ptr = scratch+16 */
U4 const r_h_4 = R_T7; /* b ptr = scratch+48 */
U4 r_a_4 = R_T0;
U4 r_b_4 = R_T1;
U4 r_c_4 = R_T2;
U4 r_d_4 = R_T3;
U4 r_f_4 = R_T5; /* out ptr (HARDCODED: scratch+64) */
U4 r_g_4 = R_T6; /* a ptr = scratch+16 */
U4 r_h_4 = R_T7; /* b ptr = scratch+48 */
smem.resolve_look_at_atom_addrs[4] = resolve_look_at__cross_uz_ux_to_up_proc(& ab,
R_ResolveScratch,
r_a_4, r_b_4, r_c_4, r_d_4, r_f_4, r_g_4, r_h_4);
/* === ATOM 5: normalize up→uy === */
U4 const r_src_offset_5 = O_(ResolveLookAtScratch, up);
U4 const r_dst_offset_5 = O_(ResolveLookAtScratch, uy);
U4 const r_src_ptr_5 = R_T0;
U4 const r_dst_ptr_5 = R_T1;
U4 const r_tmp_5 = R_T2;
U4 const r_mac1_5 = R_T3;
U4 const r_mac2_5 = R_T5;
U4 const r_recip_5 = R_T6;
U4 const r_lzcr_5 = R_T7;
U4 const r_shift_5 = R_V0;
U4 const r_branch_5 = R_V1;
src_offset = O_(ResolveLookAtScratch, up);
dst_offset = O_(ResolveLookAtScratch, uy);
smem.resolve_look_at_atom_addrs[5] = normalize_v3s4_proc(& ab,
R_ResolveScratch,
r_src_offset_5, r_dst_offset_5,
r_src_ptr_5, r_dst_ptr_5, r_tmp_5,
r_mac1_5, r_mac2_5, r_recip_5, r_lzcr_5,
r_shift_5, r_branch_5);
src_offset, dst_offset,
RegUse_(normalize_v3s4_proc){
.scratch = R_ResolveScratch,
.src_ptr = R_T0,
.dst_ptr = R_T1,
.recip_est = R_T6,
.norm = R_T7,
.shift = R_V0,
.src_x = R_T2,
.t3 = R_T3,
.t4 = R_T5,
.t5 = R_V1,
});
/* === ATOM 6a: populate (m[][] from ux/uy/uz, t[]=0) === */
U4 const r_look_at_6a = R_T0; /* tape pop → look_at* */
U4 const r_scratch_6a = R_ResolveScratch;
U4 const r_pux_6a = R_T1;
U4 const r_puy_6a = R_T3;
U4 const r_puz_6a = R_T5;
U4 const r_tmp0_6a = R_T2;
U4 const r_tmp1_6a = R_T6;
U4 const r_tmp2_6a = R_V0;
U4 r_look_at_6a = R_T0; /* tape pop → look_at* */
U4 r_scratch_6a = R_ResolveScratch;
U4 r_pux_6a = R_T1;
U4 r_puy_6a = R_T3;
U4 r_puz_6a = R_T5;
U4 r_tmp0_6a = R_T2;
U4 r_tmp1_6a = R_T6;
U4 r_tmp2_6a = R_V0;
smem.resolve_look_at_atom_addrs[6] = resolve_look_at__populate_proc(& ab,
r_look_at_6a, r_scratch_6a,
r_pux_6a, r_puy_6a, r_puz_6a,
@@ -346,23 +263,23 @@ internal void resolve_look_at_init(void) {
* Uses mac_apply_matrix_lv component macro which internally uses
* r_t0 for the RT matrix load + V0 load, then r_t0/r_t1/r_t2
* for the mfc2/store. We pass our GPRs. */
U4 const r_scratch_6b = R_ResolveScratch;
U4 const r_peye_6b = R_T1; /* scratch+96 (packed V0 dst, then off dst) */
U4 const r_look_at_6b = R_T0; /* tape pop → look_at* */
U4 const r_tmp0_6b = R_T2;
U4 const r_tmp1_6b = R_T3;
U4 const r_tmp2_6b = R_T5;
U4 r_scratch_6b = R_ResolveScratch;
U4 r_peye_6b = R_T1; /* scratch+96 (packed V0 dst, then off dst) */
U4 r_look_at_6b = R_T0; /* tape pop → look_at* */
U4 r_tmp0_6b = R_T2;
U4 r_tmp1_6b = R_T3;
U4 r_tmp2_6b = R_T5;
smem.resolve_look_at_atom_addrs[8] = resolve_look_at__matrix_vector_proc(& ab,
r_scratch_6b, r_peye_6b, r_look_at_6b,
r_tmp0_6b, r_tmp1_6b, r_tmp2_6b);
/* === ATOM 6c: trans_matrix (off → look_at->t[]) === */
U4 const r_look_at_6c = R_T0; /* tape pop → look_at* */
U4 const r_scratch_6c = R_ResolveScratch;
U4 const r_off_ptr_6c = R_T1; /* &scratch.eye (= off dst) */
U4 const r_tmp0_6c = R_T2;
U4 r_look_at_6c = R_T0; /* tape pop → look_at* */
U4 r_scratch_6c = R_ResolveScratch;
U4 r_off_ptr_6c = R_T1; /* &scratch.eye (= off dst) */
U4 r_tmp0_6c = R_T2;
smem.resolve_look_at_atom_addrs[9] = resolve_look_at__trans_matrix_proc(& ab,
r_look_at_6c, r_scratch_6c, r_off_ptr_6c, r_tmp0_6c);
r_look_at_6c, r_scratch_6c, r_off_ptr_6c, r_tmp0_6c, R_T3, R_T4);
/* Sanity check: arena didn't overflow. */
assert(ab.used <= ResolveLookAtArena_Size);
@@ -409,7 +326,7 @@ I_ void resolve_look_at(
tb_data(tb, u4_(look_at));
}
tb_emit(tb, smem.resolve_look_at_atom_addrs[9]); {
tb_data(tb, u4_(look_at));
// tb_data(tb, u4_(look_at));
}
}
@@ -425,9 +342,9 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
tb_emit_(pad_bios_snapshot);
tb_data_(raw, & smem.pad_raw[0]);
tb_data_(state, & smem.pad[0]);
tb_emit_(pad_bios_snapshot);
tb_data_(raw, & smem.pad_raw[1]);
tb_data_(state, & smem.pad[1]);
// tb_emit_(pad_bios_snapshot);
// tb_data_(raw, & smem.pad_raw[1]);
// tb_data_(state, & smem.pad[1]);
tb_emit_(pad_input_cam);
tb_data_(state, & smem.pad[0]);
+2 -2
View File
@@ -25,7 +25,7 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(hello_joypad_atom_c);
#pragma region MACs (Mips Atom components)
FI_ Slice_MipsCode ac_put_disp_env(MipsAtomBuilder_R ab, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_disp_env, ab, {
MipsAtomComp_Proc_(ab, {
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
// Sequence per libpsyx PutDispEnv: DrawArea TL → DrawArea BR → Mask → DrawArea TL → DrawArea BR
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
@@ -36,7 +36,7 @@ MipsAtomComp_Proc_(ac_put_disp_env, ab, {
})
FI_ Slice_MipsCode ac_put_draw_env(MipsAtomBuilder_R ab, U4 reg_transfer, U4 reg_base, U2 port)
MipsAtomComp_Proc_(ac_put_draw_env, ab, {
MipsAtomComp_Proc_(ab, {
/*
* ORIGIN: each code word corresponds to the EXACT value libpsyx's PutDrawEnv function would compute for the same DrawEnv settings.
* References:
+1
View File
@@ -532,6 +532,7 @@ function build-hello_camera {
$compile_args = @()
$compile_args += $f_debug
$compile_args += ($f_define + 'BUILD_DEBUG')
$compile_args += $f_optimize_none
# $compile_args += $f_optimize_intrinsics
# $compile_args += $f_optimize_size
+196 -1
View File
@@ -1589,6 +1589,8 @@ M.INSTRUCTION_LATENCY = {
["atom_bind"] = 0,
["atom_reads"] = 0,
["atom_writes"] = 0,
["BdSlot_"] = 0,
["LdSlot_"] = 0,
}
-- Default cycle cost for unknown macros.
@@ -1962,6 +1964,52 @@ M.INSTRUCTION_GPR_EFFECTS = {
shift_aright_var = { reads = {2, 3}, writes = {1} },
}
-------------------------------------------------------------------------------
-- IMMEDIATE_FIELD_WIDTHS — maps instruction names to their immediate-argument
-- positions (1-based) and field widths (in bits). Consumed by the
-- `immediate_field_width` static-analysis check. Parallel to
-- INSTRUCTION_GPR_EFFECTS.
--
-- `signed = true` means the field is sign-extended (the value must fit in
-- the signed range). `signed = false` (default) means zero-extended.
-------------------------------------------------------------------------------
M.IMMEDIATE_FIELD_WIDTHS = {
-- CPU I-type immediates: 16-bit signed (addiu/addi/slti sign-extend)
add_ui = { { arg = 3, width = 16, signed = true } },
add_si = { { arg = 3, width = 16, signed = true } },
add_ui_self = { { arg = 2, width = 16, signed = true } },
slt_si = { { arg = 3, width = 16, signed = true } },
slt_ui = { { arg = 3, width = 16, signed = true } },
-- CPU I-type immediates: 16-bit unsigned (andi/ori/xori zero-extend)
and_i = { { arg = 3, width = 16 } },
or_i = { { arg = 3, width = 16 } },
or_i_self = { { arg = 2, width = 16 } },
xor_i = { { arg = 3, width = 16 } },
load_upper_i = { { arg = 2, width = 16 } },
-- Load/store offsets: 16-bit signed
load_word = { { arg = 3, width = 16, signed = true } },
load_half = { { arg = 3, width = 16, signed = true } },
load_half_u = { { arg = 3, width = 16, signed = true } },
load_byte = { { arg = 3, width = 16, signed = true } },
load_byte_u = { { arg = 3, width = 16, signed = true } },
store_word = { { arg = 3, width = 16, signed = true } },
store_half = { { arg = 3, width = 16, signed = true } },
store_byte = { { arg = 3, width = 16, signed = true } },
-- Shift amount: 5-bit unsigned
shift_lleft = { { arg = 3, width = 5 } },
shift_lleft_self = { { arg = 2, width = 5 } },
shift_lright = { { arg = 3, width = 5 } },
shift_aright = { { arg = 3, width = 5 } },
shift_aright_var = { { arg = 3, width = 5 } },
-- Branch offsets: 16-bit signed
branch_equal = { { arg = 3, width = 16, signed = true } },
branch_ne = { { arg = 3, width = 16, signed = true } },
branch_le_zero = { { arg = 2, width = 16, signed = true } },
branch_lt_zero = { { arg = 2, width = 16, signed = true } },
branch_ge_zero = { { arg = 2, width = 16, signed = true } },
branch_gt_zero = { { arg = 2, width = 16, signed = true } },
}
-- Bounded GPR-value rules consumed by the same forward event walk as `INSTRUCTION_GPR_EFFECTS`.
-- A rule describes a literal/constant-producing transform; if its required inputs are not constant, the destination is invalidated rather than carrying a stale value.
-- The lattice is deliberately closed to `{kind = "unknown"}` and `{kind = "constant", value = <U4>}`.
@@ -2681,4 +2729,151 @@ function M.project_emission(body_text, component_index, word_counts, components)
})
end
return M
-------------------------------------------------------------------------------
-- find_function_decl_for — backward walk for MipsAtomComp_Proc_ name extraction.
--
-- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name
-- is derived from the preceding `FI_ Slice_MipsCode ac_X(args)` function
-- declaration. This function walks backward from `before_pos` to find it.
--
-- Returns (raw_name, args_inner) or (nil, nil).
-- raw_name — e.g. "ac_load_word_imm"
-- args_inner — e.g. "AtomBuilder_R ab, Reg dst, U4 imm"
--
-- The walk finds the LAST "Slice_MipsCode" before before_pos, then skips
-- whitespace + qualifiers (FI_, atom_dbg_skip, comments) until it finds an
-- ident followed by "(". That ident is the function name; the parens contents
-- are the args.
-------------------------------------------------------------------------------
function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
local search_pos = 1
local last_match = nil
while true do
local found = source:find("Slice_MipsCode", search_pos, true)
if not found or found >= before_pos then break end
last_match = found
search_pos = found + slice_mips_code_len
end
if not last_match then return nil, nil end
local pos = last_match + slice_mips_code_len
while pos < before_pos do
-- skip whitespace
while pos <= #source do
local c = source:sub(pos, pos)
if c == " " or c == "\t" or c == "\n" or c == "\r" then
pos = pos + 1
else
break
end
end
if pos > #source then break end
-- skip line comments
if source:sub(pos, pos + 1) == "//" then
while pos <= #source and source:sub(pos, pos) ~= "\n" do pos = pos + 1 end
pos = pos + 1
goto continue
end
-- skip block comments
if source:sub(pos, pos + 1) == "/*" then
local close = source:find("*/", pos + 2, true)
if not close then break end
pos = close + 2
goto continue
end
-- try to read an ident
local ident, ident_end = M.read_ident(source, pos)
if not ident then break end
-- check if the next non-ws char after ident is "("
local next_pos = M.skip_ws_and_cmt(source, ident_end)
if source:sub(next_pos, next_pos) == "(" then
local inner = M.read_parens(source, next_pos)
if inner then
return ident, inner
end
end
-- ident not followed by "(" — it's a qualifier (FI_, atom_dbg_skip, etc); skip it
pos = ident_end
::continue::
end
return nil, nil
end
-------------------------------------------------------------------------------
-- find_atom_proc_decl_for — backward walk for MipsAtom_Proc_ name extraction.
--
-- After the `sym` arg was dropped from MipsAtom_Proc_, the atom name is
-- derived from the preceding `MipsAtom* X_proc(args)` function declaration.
-- This function walks backward from `before_pos` to find it.
--
-- Returns (raw_name, args_inner) or (nil, nil).
-- raw_name — e.g. "normalize_v3s4" (the _proc suffix is stripped)
-- args_inner — e.g. "AtomArena_R aa, U4 r_scratch, ..."
--
-- The walk finds the LAST "MipsAtom*" before before_pos, then skips
-- whitespace + qualifiers (internal, I_, FI_, comments) until it finds an
-- ident followed by "(". That ident is the function name (with _proc suffix);
-- the suffix is stripped to get raw_name. The parens contents are the args.
-------------------------------------------------------------------------------
function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
local search_pos = 1
local last_match = nil
while true do
-- plain=true: "*" is literal, no escaping needed
local found = source:find("MipsAtom*", search_pos, true)
if not found or found >= before_pos then break end
last_match = found
search_pos = found + mips_atom_ptr_len
end
if not last_match then return nil, nil end
local pos = last_match + mips_atom_ptr_len
while pos < before_pos do
-- skip whitespace
while pos <= #source do
local c = source:sub(pos, pos)
if c == " " or c == "\t" or c == "\n" or c == "\r" then
pos = pos + 1
else
break
end
end
if pos > #source then break end
-- skip line comments
if source:sub(pos, pos + 1) == "//" then
while pos <= #source and source:sub(pos, pos) ~= "\n" do pos = pos + 1 end
pos = pos + 1
goto continue
end
-- skip block comments
if source:sub(pos, pos + 1) == "/*" then
local close = source:find("*/", pos + 2, true)
if not close then break end
pos = close + 2
goto continue
end
-- try to read an ident
local ident, ident_end = M.read_ident(source, pos)
if not ident then break end
-- check if the next non-ws char after ident is "("
local next_pos = M.skip_ws_and_cmt(source, ident_end)
if source:sub(next_pos, next_pos) == "(" then
local inner = M.read_parens(source, next_pos)
if inner then
-- strip the _proc suffix to get the atom name
local proc_suffix = "_proc"
if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then
return ident:sub(1, #ident - #proc_suffix), inner
end
-- no _proc suffix — return as-is
return ident, inner
end
end
-- ident not followed by "(" — it's a qualifier; skip it
pos = ident_end
::continue::
end
return nil, nil
end
return M
+8 -35
View File
@@ -96,48 +96,21 @@ local M = {}
-- so this file reads it forward rather than re-walking the source.
-- ════════════════════════════════════════════════════════════════════════════
--- Find the args of the function declaration that immediately precedes a `MipsAtomComp_Proc_` invocation of the given name.
--- Find the args of the function declaration that immediately precedes a `MipsAtomComp_Proc_` invocation.
--- Returns the args string (e.g., `"U4 off, U4 code, U1 r, U1 g, U1 b"`) or nil if no function declaration is found.
---
--- Convention: function form is
--- `FI_ Slice_MipsCode ac_X(args) MipsAtomComp_Proc_(ac_X, { body })`
--- We find the LAST occurrence of `"ac_X("` before `before_pos` and extract the args from inside the parens.
--- We then verify the preceding context ends with `Slice_MipsCode`
--- (the function-decl keyword with possible qualifiers between).
--- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name
--- and the args both come from the preceding `FI_ Slice_MipsCode ac_X(args)`
--- declaration. The shared `duffle.find_function_decl_for` helper does the
--- backward walk; this function returns just the args.
---
--- @param source string
--- @param name string
--- @param name string (retained for signature stability; unused — the walk derives the name)
--- @param before_pos integer
--- @return string|nil
local function find_function_args_for(source, name, before_pos)
-- Find the LAST occurrence of `name + "("` in `source[1..before_pos]`.
local name_open = name .. "("
local last_idx = nil
local scan_pos = 1
while true do
-- Pass `before_pos + 1` so string.find only returns positions < before_pos + 1
-- (string.find's 4th arg `plain` is true; we use the 3rd arg `init` for the upper bound).
local found = source:find(name_open, scan_pos, true)
if not found or found >= before_pos then break end
last_idx = found
scan_pos = found + #name_open
end
if not last_idx then return nil end
-- Verify the preceding context ends with "MipsAtom" (with possible qualifiers between).
local before = source:sub(1, last_idx - 1)
local trimmed = duffle.trim(before)
if trimmed:sub(-#MIPS_ATOM) ~= MIPS_ATOM then
-- Preceding context is not a function declaration.
return nil
end
local open_paren = last_idx + #name -- position of "("
-- scan: MipsAtom ac_X(
local inner = duffle.read_parens(source, open_paren)
-- scan: MipsAtom ac_X(<args>)
if not inner then return nil end
return inner
local _, args_inner = duffle.find_function_decl_for(source, before_pos, #MIPS_ATOM)
return args_inner
end
-- ════════════════════════════════════════════════════════════════════════════
+21 -4
View File
@@ -137,6 +137,16 @@ local QUALIFIER_KEYWORDS = {
local AC_PREFIX = "ac_"
local AC_PREFIX_LEN = 3
-- The function-decl keyword that precedes a MipsAtomComp_Proc_ call.
-- Used by the backward walk in duffle.find_function_decl_for.
local SLICE_MIPS_CODE = "Slice_MipsCode"
local SLICE_MIPS_CODE_LEN = #SLICE_MIPS_CODE
-- The return type that precedes a MipsAtom_Proc_ function declaration.
-- Used by the backward walk in duffle.find_atom_proc_decl_for.
local MIPS_ATOM_PTR = "MipsAtom*"
local MIPS_ATOM_PTR_LEN = #MIPS_ATOM_PTR
--- Strip the "ac_" prefix from a component name.
--- Returns the input unchanged if it doesn't start with the prefix.
--- @param raw_name string
@@ -1357,7 +1367,11 @@ local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
local body, close_pos = duffle.read_braces(inner, last_brace_pos)
if close_pos > #inner + 1 then return after_paren end
local raw_name = inner:match("^%s*([%w_]+)") or "?"
-- The component name is derived from the preceding function declaration
-- (`FI_ Slice_MipsCode ac_X(...)`), not from the first macro arg (which
-- is now `ab`). The backward walk finds the function decl before open_paren.
local raw_name = duffle.find_function_decl_for(source, open_paren, SLICE_MIPS_CODE_LEN)
if not raw_name then raw_name = "?" end
local name = strip_ac_prefix(raw_name)
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
local body_off = open_paren + 2 + last_brace_pos
@@ -1398,9 +1412,12 @@ local function parse_mips_atom_proc(source, pos, ident_end, line_of, out)
local body, close_pos = duffle.read_braces(inner, last_brace_pos)
if close_pos > #inner + 1 then return after_paren end
-- The atom name is the FIRST ident of the args (matches MipsAtomComp_Proc_'s "first ident" rule).
-- MipsAtom_Proc_ has no `ac_` prefix; `strip_ac_prefix` is a no-op for unprefixed names.
local raw_name = inner:match("^%s*([%w_]+)") or "?"
-- The atom name is derived from the preceding function declaration
-- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which
-- is now `aa`). The backward walk finds the function decl before open_paren
-- and strips the `_proc` suffix.
local raw_name = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN)
if not raw_name then raw_name = "?" end
local name = strip_ac_prefix(raw_name)
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
local body_off = open_paren + 2 + last_brace_pos
+91
View File
@@ -2566,6 +2566,96 @@ local function check_gte_cr_TR_naming(atom, _pipe_ctx, findings)
end
end
-- check_immediate_field_width — flags integer literals passed to instruction
-- macros that exceed the immediate field width. Reads `IMMEDIATE_FIELD_WIDTHS`
-- from duffle.lua. Only fires on parseable integer literals; register names,
-- O_(...) offsets, atom_offset(...) markers, and enum tokens are skipped.
local function check_immediate_field_width(atom, pipe_ctx, findings)
local widths = duffle.IMMEDIATE_FIELD_WIDTHS or {}
local events = atom.paths and atom.paths.word_events or {}
local line_for_word_event = pipe_ctx.line_for_word_event
for _, ev in ipairs(events) do
local ev_ident = ev.encoder or ev.ident or "?"
local rules = widths[ev_ident]
if rules then
local ev_args = ev.args or {}
local ev_line = line_for_word_event and line_for_word_event(ev) or atom.line
for _, rule in ipairs(rules) do
local arg_str = ev_args[rule.arg]
if arg_str then
local value = parse_integer_literal(arg_str)
if value then
local width = rule.width
local is_signed = rule.signed == true
-- parse_integer_literal returns a U4-wrapped value in [0, 2^32).
-- For signed fields, re-interpret the high bit as the sign.
local signed_value = value
if is_signed and value >= 0x80000000 then
signed_value = value - 0x100000000
end
local lo, hi
if is_signed then
lo = -(bit.lshift(1, width - 1))
hi = bit.lshift(1, width - 1) - 1
else
lo = 0
hi = bit.lshift(1, width) - 1
end
-- For unsigned fields, a negative C literal (high bit set in U4)
-- is valid if the low `width` bits fit — IMM_MASK truncates it.
-- Flag as a warning (code smell), not an error.
local check_value = is_signed and signed_value or value
local field_max = bit.lshift(1, width) - 1
local low_bits_fit = (value % (bit.lshift(1, width))) == value or (is_signed and signed_value >= lo and signed_value <= hi)
if is_signed then
if signed_value < lo or signed_value > hi then
findings[#findings + 1] = {
check = "immediate_field_width",
kind = "error",
atom = atom.name,
line = ev_line,
msg = string.format(
"%s: immediate %d at arg %d overflows %d-bit %s field (valid %d..%d)",
ev_ident, signed_value, rule.arg, width,
"signed", lo, hi),
}
end
else
-- Unsigned field: check if the low `width` bits exceed the field.
-- A negative C literal (U4 >= 0x80000000) whose low bits fit is
-- valid but a code smell — warn, don't error.
local low_bits = value % (bit.lshift(1, width))
if value > field_max then
if value >= 0x80000000 and low_bits <= field_max then
findings[#findings + 1] = {
check = "immediate_field_width",
kind = "warning",
atom = atom.name,
line = ev_line,
msg = string.format(
"%s: negative immediate %d at arg %d on unsigned %d-bit field (truncated to %d by IMM_MASK)",
ev_ident, signed_value, rule.arg, width, low_bits),
}
else
findings[#findings + 1] = {
check = "immediate_field_width",
kind = "error",
atom = atom.name,
line = ev_line,
msg = string.format(
"%s: immediate %d at arg %d overflows %d-bit unsigned field (valid 0..%d)",
ev_ident, value, rule.arg, width, field_max),
}
end
end
end
end
end
end
end
end
end
-- CHECK_RULES — data-driven check dispatch (Muratori: data over control flow)
-- ════════════════════════════════════════════════════════════════════════════
@@ -2595,6 +2685,7 @@ local CHECK_RULES = {
{ name = "gte_cr_alias_writes", per_atom = check_gte_cr_alias_writes },
{ name = "rtdiagonal_completeness", per_atom = check_rtdiagonal_completeness },
{ name = "gte_cr_TR_naming", per_atom = check_gte_cr_TR_naming },
{ name = "immediate_field_width", per_atom = check_immediate_field_width },
{ name = "enum_alias_membership", per_source = check_enum_alias_membership },
{ name = "atom_type_consistency", per_source = check_atom_type_consistency },
{ name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref },