More minor adjustments while reviewing for the article.

This commit is contained in:
ed
2026-09-05 18:52:14 -04:00
parent 1faf3539d8
commit 0e9034cbd4
10 changed files with 296 additions and 214 deletions
@@ -30,7 +30,7 @@
"editorHoverWidget.background": "#2c334b" "editorHoverWidget.background": "#2c334b"
}, },
"semanticTokenColors": { "semanticTokenColors": {
"comment": { "foreground": "#868686", "fontStyle": "italic" }, "comment": { "foreground": "#868686", }, //"fontStyle": "italic" },
"keyword": { "foreground": "#d8bd5b" }, "keyword": { "foreground": "#d8bd5b" },
"string": { "foreground": "#d46a54" }, "string": { "foreground": "#d46a54" },
"number": { "foreground": "#b5cea8" }, "number": { "foreground": "#b5cea8" },
@@ -81,7 +81,7 @@
"tapeDelaySlot": { "foreground": "#ff5647" } "tapeDelaySlot": { "foreground": "#ff5647" }
}, },
"tokenColors": [ "tokenColors": [
{ "scope": ["comment", "comment.block", "comment.line", "comment.block.documentation"], "settings": { "foreground": "#868686", "fontStyle": "italic" } }, { "scope": ["comment", "comment.block", "comment.line", "comment.block.documentation"], "settings": { "foreground": "#868686", } }, //"fontStyle": "italic" } },
{ "scope": ["keyword", "keyword.control", "keyword.other"], "settings": { "foreground": "#d8bd5b" } }, { "scope": ["keyword", "keyword.control", "keyword.other"], "settings": { "foreground": "#d8bd5b" } },
{ "scope": ["string", "string.quoted"], "settings": { "foreground": "#d46a54" } }, { "scope": ["string", "string.quoted"], "settings": { "foreground": "#d46a54" } },
{ "scope": ["string.quoted.other"], "settings": { "foreground": "#d69d85" } }, { "scope": ["string.quoted.other"], "settings": { "foreground": "#d69d85" } },
+91 -46
View File
@@ -33,23 +33,59 @@ const TOKEN_MODIFIER_INDEX = new Map(TOKEN_MODIFIERS.map((name, index) => [name,
const ATOM_KEYWORDS = new Set(["MipsAtom_", "MipsAtom_Proc_"]); const ATOM_KEYWORDS = new Set(["MipsAtom_", "MipsAtom_Proc_"]);
const COMPONENT_KEYWORDS = new Set(["MipsAtomComp_", "MipsAtomComp_Proc_"]); const COMPONENT_KEYWORDS = new Set(["MipsAtomComp_", "MipsAtomComp_Proc_"]);
const ANNOTATIONS = new Set([ const ANNOTATIONS = new Set([
"atom_info", "atom_bind", "atom_reads", "atom_writes", "atom_label", "atom_info",
"atom_offset", "atom_reg", "atom_type", "atom_ctx", "atom_phase", "atom_bind",
"atom_auto_reg", "phase_auto_reg", "atom_dbg_skip", "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 DSL_KEYWORDS = new Set([ const DSL_KEYWORDS = new Set([
"FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", "Array_", "enum", "struct", "union",
"Slice_", "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", "offset_of", "static_assert", "typeof", "typeof_ptr", "typeof_same",
"RO_", "LP_", "gknown", "expect_", "cexpr_", "glue", "tmpl",
"A_", "FI_", "I_", "NI_",
"Array_", "Enum_", "Proc_", "Relative_", "Struct_", "Union_", "Slice_",
// "TypeR_", "TypeV_",
"align_",
"internal", "local_persist", "global",
"RO_", "LP_",
"gknown", "expect_", "cexpr_",
"O_", "OA_", "S_", "C_", "T_", "T_same", "R_", "V_",
"r_", "v_", "rt_", "vt_",
"asm", "asm_words", "asm_rpins", "asm_clobber", "asm", "asm_words", "asm_rpins", "asm_clobber",
"O_", "S_", "C_", "T_", "tmpl", "glue", "r_", "v_", "rt_", "vt_",
"rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem", "rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem",
"u1_", "u2_", "u4_", "u8_", "s1_", "s2_", "s4_", "s8_",
"u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v", "b1_", "b2_", "b4_", "b8_",
"u1_", "u2_", "u4_", "u8_",
"s1_", "s2_", "s4_", "s8_",
"b1_r", "b2_r", "b4_r", "b8_r",
"b1_v", "b2_v", "b4_v", "b8_v",
"u1_r", "u2_r", "u4_r", "u8_r",
"u1_v", "u2_v", "u4_v", "u8_v",
"u4_lo", "u4_hi",
]); ]);
const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_", "DmaSlot_", "GteDelay_"]); const DELAY_SLOT_KEYWORDS = new Set([
"LdSlot_",
"BdSlot_",
"DmaSlot_",
"GteDelay_"
]);
const CONTROL_FLOW_PREFIXES = /^(?:branch_|jump_|call_)/; const CONTROL_FLOW_PREFIXES = /^(?:branch_|jump_|call_)/;
@@ -64,8 +100,8 @@ const ROLE_TO_TYPE = {
function registerType(name, index) { function registerType(name, index) {
const kind = index.registers.get(name); const kind = index.registers.get(name);
if (kind === "gpr" || /^R_[A-Za-z0-9_]+$/.test(name)) return "tapeGprRegister"; 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"; if (kind === "cop2" || /^(?:C2_|gte_cr_)[A-Za-z0-9_] + $/.test(name)) return "tapeCop2Register";
return null; return null;
} }
@@ -100,10 +136,8 @@ function modifierMask(modifiers) {
} }
function isRegUseAccess(tokens, tokenIndex) { function isRegUseAccess(tokens, tokenIndex) {
const prev = tokens[tokenIndex - 1]; const prev = tokens[tokenIndex - 1]; if (!prev || prev.text !== ".") return false;
if (!prev || prev.text !== ".") return false; const prevPrev = tokens[tokenIndex - 2]; if (!prevPrev || prevPrev.kind !== "identifier") return false;
const prevPrev = tokens[tokenIndex - 2];
if (!prevPrev || prevPrev.kind !== "identifier") return false;
const next = tokens[tokenIndex + 1]; const next = tokens[tokenIndex + 1];
if (next && next.text === ".") return false; if (next && next.text === ".") return false;
if (prevPrev.text === "r") return true; if (prevPrev.text === "r") return true;
@@ -120,8 +154,7 @@ function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () =>
for (let tokenIndex = 0; tokenIndex < scanned.tokens.length; tokenIndex += 1) { for (let tokenIndex = 0; tokenIndex < scanned.tokens.length; tokenIndex += 1) {
if (shouldCancel()) break; if (shouldCancel()) break;
const token = scanned.tokens[tokenIndex]; const token = scanned.tokens[tokenIndex]; if (token.kind !== "identifier") continue;
if (token.kind !== "identifier") continue;
let type = null; let type = null;
let modifiers = []; let modifiers = [];
@@ -131,37 +164,49 @@ function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () =>
if (declaration) { if (declaration) {
type = ROLE_TO_TYPE[declaration.role] || null; type = ROLE_TO_TYPE[declaration.role] || null;
modifiers = declaration.modifiers.slice(); modifiers = declaration.modifiers.slice();
} else if (ATOM_KEYWORDS.has(token.text)) { }
else if (ATOM_KEYWORDS.has(token.text)) {
type = "tapeAtomKeyword"; type = "tapeAtomKeyword";
} else if (COMPONENT_KEYWORDS.has(token.text)) { }
else if (COMPONENT_KEYWORDS.has(token.text)) {
type = "keyword"; type = "keyword";
} else if (ANNOTATIONS.has(token.text)) { }
else if (ANNOTATIONS.has(token.text)) {
type = "tapeAnnotation"; type = "tapeAnnotation";
} else if (context && context.callee === "atom_bind" && context.argIndex === 0) { }
else if (context && context.callee === "atom_bind" && context.argIndex === 0) {
type = "tapeBindType"; type = "tapeBindType";
} else if (context && context.callee === "atom_phase" && context.argIndex === 0) { }
else if (context && context.callee === "atom_phase" && context.argIndex === 0) {
type = "tapePhase"; type = "tapePhase";
modifiers = ["declaration"]; modifiers = ["declaration"];
} else if (context && context.callee === "atom_ctx" && context.argIndex === 0) { }
else if (context && context.callee === "atom_ctx" && context.argIndex === 0) {
type = "tapeAtomName"; type = "tapeAtomName";
} else if (context && context.callee === "atom_label" && context.argIndex === 0) { }
else if (context && context.callee === "atom_label" && context.argIndex === 0) {
type = "tapeLabel"; type = "tapeLabel";
modifiers = ["declaration"]; modifiers = ["declaration"];
} else if (context && context.callee === "atom_offset" && context.argIndex <= 1) { }
else if (context && context.callee === "atom_offset" && context.argIndex <= 1) {
type = "tapeLabel"; type = "tapeLabel";
} else if (context && context.callee === "atom_reads") { }
else if (context && context.callee === "atom_reads") {
type = registerType(token.text, index); type = registerType(token.text, index);
if (type) modifiers = ["tapeRead"]; if (type) modifiers = ["tapeRead"];
} else if (context && context.callee === "atom_writes") { }
else if (context && context.callee === "atom_writes") {
type = registerType(token.text, index); type = registerType(token.text, index);
if (type) modifiers = ["tapeWrite"]; if (type) modifiers = ["tapeWrite"];
} else if (context && context.callee === "atom_auto_reg") { }
else if (context && context.callee === "atom_auto_reg") {
if (context.argIndex === 0) type = "tapeAtomName"; if (context.argIndex === 0) type = "tapeAtomName";
if (context.argIndex === 1) { if (context.argIndex === 1) {
type = "tapeGprRegister"; type = "tapeGprRegister";
modifiers = ["declaration", "tapeAuto"]; modifiers = ["declaration", "tapeAuto"];
} }
} else if (context && context.callee === "phase_auto_reg") { }
else if (context && context.callee === "phase_auto_reg") {
if (context.argIndex === 0) type = "tapePhase"; if (context.argIndex === 0) type = "tapePhase";
if (context.argIndex === 1) { if (context.argIndex === 1) {
type = "tapeGprRegister"; type = "tapeGprRegister";
@@ -169,27 +214,27 @@ function classifyDocument(source, filePath, workspaceIndex, shouldCancel = () =>
} }
} }
if (!type && index.bindTypes.has(token.text)) type = "tapeBindType"; if (! type && index.bindTypes.has(token.text)) type = "tapeBindType";
if (!type && DSL_KEYWORDS.has(token.text)) type = "keyword"; if (! type && DSL_KEYWORDS.has(token.text)) type = "keyword";
if (!type && index.types.has(token.text)) type = "tapeDuffleType"; if (! type && index.types.has(token.text)) type = "tapeDuffleType";
if (!type && index.attributes.has(token.text)) type = "tapeAttribute"; if (! type && index.attributes.has(token.text)) type = "tapeAttribute";
if (!type) type = registerType(token.text, index); if (! type) type = registerType(token.text, index);
if (!type && DELAY_SLOT_KEYWORDS.has(token.text)) type = "tapeDelaySlot"; if (! type && DELAY_SLOT_KEYWORDS.has(token.text)) type = "tapeDelaySlot";
if (!type) { if (! type) {
const domain = index.macros.get(token.text); const domain = index.macros.get(token.text);
if (domain === "control" || (domain && CONTROL_FLOW_PREFIXES.test(token.text))) { if (domain === "control" || (domain && CONTROL_FLOW_PREFIXES.test(token.text))) {
type = "tapeControlFlow"; type = "tapeControlFlow";
} }
} }
if (!type && isRegUseAccess(scanned.tokens, tokenIndex)) type = "tapeGprRegister"; if (! type && isRegUseAccess(scanned.tokens, tokenIndex)) type = "tapeGprRegister";
if (!type) type = instructionType(token.text, index); if (! type) type = instructionType(token.text, index);
if (!type && /^(?:Slice_|A[0-9]+_)/.test(token.text)) type = "tapeDuffleType"; if (! type && /^(?:Slice_|A[0-9]+_)/.test(token.text)) type = "tapeDuffleType";
if (!type && /_[RV]$/.test(token.text)) type = "tapeDuffleType"; if (! type && /_[RV]$/.test(token.text)) type = "tapeDuffleType";
if (!type && index.atoms.has(token.text)) type = "tapeAtomName"; if (! type && index.atoms.has(token.text)) type = "tapeAtomName";
if (!type && index.components.has(token.text)) type = "tapeComponentName"; if (! type && index.components.has(token.text)) type = "tapeComponentName";
if (!type && index.phases.has(token.text)) type = "tapePhase"; if (! type && index.phases.has(token.text)) type = "tapePhase";
if (!type && index.labels.has(token.text)) type = "tapeLabel"; if (! type && index.labels.has(token.text)) type = "tapeLabel";
if (!type) continue; if (! type) continue;
spans.push({ spans.push({
text: token.text, text: token.text,
+7 -3
View File
@@ -39,7 +39,8 @@ async function activate(context) {
const result = scanSource(source, uri.fsPath); const result = scanSource(source, uri.fsPath);
nextIndex = mergeIndexes(nextIndex, result.index); nextIndex = mergeIndexes(nextIndex, result.index);
for (const error of result.errors) output.appendLine(formatError(uri.fsPath, error)); for (const error of result.errors) output.appendLine(formatError(uri.fsPath, error));
} catch (error) { }
catch (error) {
output.appendLine(`${uri.fsPath}: ${error.stack || error.message || error}`); output.appendLine(`${uri.fsPath}: ${error.stack || error.message || error}`);
} }
} }
@@ -55,7 +56,9 @@ async function activate(context) {
debounceHandle = setTimeout(() => { debounceHandle = setTimeout(() => {
debounceHandle = null; debounceHandle = null;
rebuildIndex().catch((error) => output.appendLine(error.stack || String(error))); rebuildIndex().catch((error) => output.appendLine(error.stack || String(error)));
}, 100); },
100
);
} }
const provider = { const provider = {
@@ -77,7 +80,8 @@ async function activate(context) {
output.appendLine(formatError(document.uri.fsPath || document.uri.toString(), error)); output.appendLine(formatError(document.uri.fsPath || document.uri.toString(), error));
} }
return builder.build(); return builder.build();
} catch (error) { }
catch (error) {
output.appendLine(`${document.uri}: ${error.stack || error.message || error}`); output.appendLine(`${document.uri}: ${error.stack || error.message || error}`);
return new vscode.SemanticTokensBuilder(legend).build(); return new vscode.SemanticTokensBuilder(legend).build();
} }
+15 -8
View File
@@ -10,7 +10,8 @@ function isIdentifierContinue(code) {
return isIdentifierStart(code) || (code >= 48 && code <= 57); return isIdentifierStart(code) || (code >= 48 && code <= 57);
} }
function lex(source) { function lex(source)
{
if (typeof source !== "string") throw new TypeError("source must be a string"); if (typeof source !== "string") throw new TypeError("source must be a string");
const tokens = []; const tokens = [];
@@ -47,7 +48,8 @@ function lex(source) {
}); });
} }
while (offset < source.length) { while (offset < source.length)
{
const ch = source[offset]; const ch = source[offset];
if (/\s/.test(ch)) { if (/\s/.test(ch)) {
@@ -60,12 +62,14 @@ function lex(source) {
continue; continue;
} }
if (ch === "/" && source[offset + 1] === "*") { if (ch === "/" && source[offset + 1] === "*")
{
const start = offset; const start = offset;
advance(); advance();
advance(); advance();
let closed = false; let closed = false;
while (offset < source.length) { while (offset < source.length)
{
if (source[offset] === "*" && source[offset + 1] === "/") { if (source[offset] === "*" && source[offset + 1] === "/") {
advance(); advance();
advance(); advance();
@@ -78,12 +82,14 @@ function lex(source) {
continue; continue;
} }
if (ch === "\"" || ch === "'") { if (ch === "\"" || ch === "'")
{
const quote = ch; const quote = ch;
const start = offset; const start = offset;
advance(); advance();
let closed = false; let closed = false;
while (offset < source.length) { while (offset < source.length)
{
if (source[offset] === "\\") { if (source[offset] === "\\") {
advance(); advance();
if (offset < source.length) advance(); if (offset < source.length) advance();
@@ -97,7 +103,7 @@ function lex(source) {
if (source[offset] === "\n" || source[offset] === "\r") break; if (source[offset] === "\n" || source[offset] === "\r") break;
advance(); advance();
} }
if (!closed) errors.push({ kind: "unterminated-literal", offset: start }); if (! closed) errors.push({ kind: "unterminated-literal", offset: start });
continue; continue;
} }
@@ -134,7 +140,8 @@ function buildCallContexts(tokens) {
if (token.text === ")") { if (token.text === ")") {
if (stack.length === 0) { if (stack.length === 0) {
errors.push({ kind: "unmatched-close-paren", offset: token.start }); errors.push({ kind: "unmatched-close-paren", offset: token.start });
} else { }
else {
const frame = stack.pop(); const frame = stack.pop();
if (frame.callee !== null) calls.push({ ...frame, closeTokenIndex: tokenIndex }); if (frame.callee !== null) calls.push({ ...frame, closeTokenIndex: tokenIndex });
} }
+20 -11
View File
@@ -4,8 +4,10 @@ const path = require("node:path");
const { buildCallContexts, lex, nearestCall } = require("./lexer"); const { buildCallContexts, lex, nearestCall } = require("./lexer");
const BASE_TYPES = [ const BASE_TYPES = [
"B1", "B2", "B4", "B8", "F4", "F8", "S1", "S2", "S4", "S8", "B1", "B2", "B4", "B8",
"U1", "U2", "U4", "U8", "MipsAtom", "MipsCode", "Reg", "F4", "F8", "S1", "S2", "S4", "S8",
"U1", "U2", "U4", "U8",
"MipsAtom", "MipsCode", "Reg",
]; ];
const C_BUILTINS = new Set([ const C_BUILTINS = new Set([
@@ -15,11 +17,13 @@ const C_BUILTINS = new Set([
]); ]);
const BASE_ATTRIBUTES = [ const BASE_ATTRIBUTES = [
"FI_", "I_", "NI_", "Relative_", "Struct_", "Enum_", "Union_", "Array_", "FI_", "I_", "NI_",
"Slice_", "TypeR_", "TypeV_", "align_", "internal", "local_persist", "global", "Relative_", "Struct_", "Enum_", "Union_", "Array_", "Slice_",
"RO_", "LP_", "gknown", "expect_", "cexpr_", "align_", "internal", "local_persist", "global",
"RO_", "LP_",
"gknown", "expect_", "cexpr_",
"asm", "asm_words", "asm_rpins", "asm_clobber", "asm", "asm_words", "asm_rpins", "asm_clobber",
"O_", "S_", "C_", "T_", "tmpl", "glue", "r_", "v_", "rt_", "vt_", "O_", "OA_", "S_", "C_", "T_", "tmpl", "glue", "r_", "v_", "rt_", "vt_",
"rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem", "rgcc", "r_use", "r_set", "r_mod", "r_imm", "r_mem",
"u1_", "u2_", "u4_", "u8_", "s1_", "s2_", "s4_", "s8_", "u1_", "u2_", "u4_", "u8_", "s1_", "s2_", "s4_", "s8_",
"u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v", "u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v",
@@ -161,7 +165,8 @@ function findFunctionNameBefore(tokens, calleeTokenIndex) {
return null; return null;
} }
function scanSource(source, filePath) { function scanSource(source, filePath)
{
const lexical = lex(source); const lexical = lex(source);
const balanced = buildCallContexts(lexical.tokens); const balanced = buildCallContexts(lexical.tokens);
const tokens = lexical.tokens; const tokens = lexical.tokens;
@@ -191,7 +196,8 @@ function scanSource(source, filePath) {
if (!index.macros.has(alias)) index.macros.set(alias, "component"); if (!index.macros.has(alias)) index.macros.set(alias, "component");
} }
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) { for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1)
{
const token = tokens[tokenIndex]; const token = tokens[tokenIndex];
if (token.kind !== "identifier") continue; if (token.kind !== "identifier") continue;
@@ -240,12 +246,14 @@ function scanSource(source, filePath) {
mark(token, "gprRegister", ["declaration", "tapeAuto"]); mark(token, "gprRegister", ["declaration", "tapeAuto"]);
} }
if (token.text === "define" && tokens[tokenIndex - 1] && tokens[tokenIndex - 1].text === "#") { if (token.text === "define" && tokens[tokenIndex - 1] && tokens[tokenIndex - 1].text === "#")
{
const name = tokens[tokenIndex + 1]; const name = tokens[tokenIndex + 1];
if (name && name.kind === "identifier" && name.line === token.line) { if (name && name.kind === "identifier" && name.line === token.line) {
if (/^(?:RegUse_|Struct_|Enum_|Union_|TypeR_|TypeV_|Relative_|Binds_)/.test(name.text)) { if (/^(?:RegUse_|Struct_|Enum_|Union_|TypeR_|TypeV_|Relative_|Binds_)/.test(name.text)) {
index.types.add(name.text); index.types.add(name.text);
} else if (/^(?:ac_|mac_)/.test(name.text)) { }
else if (/^(?:ac_|mac_)/.test(name.text)) {
const alias = name.text.startsWith("ac_") ? componentAlias(name.text) : name.text; const alias = name.text.startsWith("ac_") ? componentAlias(name.text) : name.text;
const rest = []; const rest = [];
for (let restIndex = tokenIndex + 2; restIndex < tokens.length && tokens[restIndex].line === name.line; restIndex += 1) { for (let restIndex = tokenIndex + 2; restIndex < tokens.length && tokens[restIndex].line === name.line; restIndex += 1) {
@@ -256,7 +264,8 @@ function scanSource(source, filePath) {
index.macros.set(alias, prefixDomain(alias) || "component"); index.macros.set(alias, prefixDomain(alias) || "component");
if (rest.length) index.componentCallees.set(alias, rest); if (rest.length) index.componentCallees.set(alias, rest);
} }
} else { }
else {
index.macros.set(name.text, domain || "utility"); index.macros.set(name.text, domain || "utility");
} }
} }
-1
View File
@@ -121,7 +121,6 @@ typedef unsigned char TSet_(B1);
typedef __UINT16_TYPE__ TSet_(B2); typedef __UINT16_TYPE__ TSet_(B2);
typedef __UINT32_TYPE__ TSet_(B4); typedef __UINT32_TYPE__ TSet_(B4);
#define b1_(value) C_(B1, value) #define b1_(value) C_(B1, value)
#define b2_(value) C_(B2, value) #define b2_(value) C_(B2, value)
#define b4_(value) C_(B4, value) #define b4_(value) C_(B4, value)
+7
View File
@@ -35,6 +35,7 @@
* These do NOT yield. They are expanded inline inside Tape Atoms. * These do NOT yield. They are expanded inline inside Tape Atoms.
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
// The 'Yield' sequence for Tape Atoms (mac_yield). // The 'Yield' sequence for Tape Atoms (mac_yield).
// In Forth this is considered the "NEXT" mechanism.
#define mac_yield(...) \ #define mac_yield(...) \
load_word(R_AtomJmp, R_TapePtr, 0) \ load_word(R_AtomJmp, R_TapePtr, 0) \
LdSlot_ \ LdSlot_ \
@@ -55,6 +56,12 @@ WORD_COUNT(mac_yield_load, 1)
, BdSlot_ nop , BdSlot_ nop
WORD_COUNT(mac_yield_tail, 3) WORD_COUNT(mac_yield_tail, 3)
/* atom_dbg_skip */
#define mac_yield_to(code_ptr) \
jump_reg(code_ptr) \
, BdSlot_ nop
WORD_COUNT(mac_yield_to, 2)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_load_half_v3(tx, ty, tz, base, offset) \ #define mac_load_half_v3(tx, ty, tz, base, offset) \
load_half(tx, base, offset + OA_(U2,[0])) \ load_half(tx, base, offset + OA_(U2,[0])) \
+1 -1
View File
@@ -271,7 +271,7 @@ typedef Struct_(RegUse_normalize_v3s4) {
union { Reg r1, dst_ptr; }; union { Reg r1, dst_ptr; };
union { Reg r2, dst_offset, mac1, v_sqr_aligned; }; union { Reg r2, dst_offset, mac1, v_sqr_aligned; };
union { Reg r3, src_offset, btarget, shift_count, sqrtbl_index; }; union { Reg r3, src_offset, btarget, shift_count, sqrtbl_index; };
union { Reg r4, mac3, v_sqr_sum, scale_exp, srav_shift; }; union { Reg r4, mac3, v_sqr_sum, srav_shift; };
union { Reg r5, lzcr, inv_len; }; union { Reg r5, lzcr, inv_len; };
}; };
/* ─── Full normalize (all 4 stages inline) ─── /* ─── Full normalize (all 4 stages inline) ───
+47 -33
View File
@@ -13,50 +13,58 @@
#pragma region Tape Drive #pragma region Tape Drive
/* ----------------------------------------------------------------------------------------------------------- /* -----------------------------------------------------------------------------------------------------------
* TAPE DRIVE ABI * THREADED ATOMS - TAPE EXECUTION & ABI
* _________
* | ___ |
* | o___o | ,-----<-----.
* |__/___\__| V ^
* \_[Enter]_[A]->[A]->[A]->[A(B)]->[A]->[Exit]
* ----------------------------------------------------------------------------------------------------------- * -----------------------------------------------------------------------------------------------------------
* Note(Ed): One of the main purposes of this codebase is to help me learn this, * This ABI and its associated legos were directly inspired by researching the work of Timothy Lottes and
* as such the information below may not* be entirely realized or finalized conceptually. * Onat Türkçüoğlu; Forth, threaded code system, and various other people or programming techniques.
* ----------------------------------------------------------------------------------------------------------- *
* This ABI and its associated legos were directly inspired by researching the work of * The setup is simple:
* Timothy Lottes and Onat Türkçüoğlu; along with many others. It's the simplest bootstrap of a * A tape is a linear stream containing addresses of directly executable native-code fragments ("Atoms").
* directly executed chain of assemby arrays (Atoms) that terminate with a yield sequence to the next atom. * Most atoms terminate in a small yield sequence which loads the next atom address from the tape.
* These eventually lead to a terminal atom for the tape which is defined below as "tape_exit". * It's a runtime composed of directly executed native machine-code sequences (Atoms) that usually terminate
* in a yield sequence to the next atom. These eventually lead to a terminal atom for the tape
* which is defined below as "tape_exit". Traditionally referred to as Direct Threaded Execution.
* *
* It behaves as one of the simplest runtime harnesses ontop of a host-enviornment's execution engine * It behaves as one of the simplest runtime harnesses ontop of a host-enviornment's execution engine
* to author and compose programs with. From here various conventions can be further applied. * to author and compose programs with. From here various conventions can be further applied.
* To make things easier to understand it may be better to focus on what this ABI does not have. * To make things easier to understand it may be better to focus on what this ABI does not have.
* It does not have have any branching within the tape but relative branches within atoms or between atoms. * The tape itself does not have have any branching behavior.
* Branching nearly is always downstream. Automatic stack usage is non-existent. * Branches, loops, skips, or other control-flow policies must be implemented explicitly by atoms.
* Push/Pop, FIFO, or Arena/Bump data structures are used by atoms explicitly. * Push/Pop, FIFO, or Arena/Bump data structures are utilized by atoms explicitly.
* In it's current form with the C11 macro DSL, the user also has fullfill manual register allocation per atom. * There is no implicit call-stack, return stack, or per-atom stack-frame.
* The user must also explictly handle register allocation per atom (by default).
* However they could procedurally automate it using metaprogramming functionality.
* *
* One of the remarkable things about utilizing this ABI is its essentially interopable with CPUs, GPUs, FPGA, * One of the remarkable things about utilizing this ABI's composition model is that its essentially interopable
* or, basically anything from the 5th generation consoles and onward. * with all modern general purpose machines, or, basically anything from the 5th generation consoles and onward.
* The ABI directly reflects how all computational hardware must be architected in order to execute * This model does not try resolve some optimal runtime for one particular modern machine,
* digital logic effectively on current era tech. * but adheres the the most bare constraints shared our most common kinds of hardware may all execute.
* On the PS1 we don't have access to a few features like multi-threading, speculative execution, or L3 cache; * On the PS1 we don't have access to a few features like multi-threading, speculative execution, or L3 cache;
* but, we can set the foundation for legoing whats required for eventually expanding this ABI's paradigm * but, we can set the foundation for legoing whats required for eventually expanding this ABI's paradigm
* and core atoms to take those newer hardware features into account. For example, you can easily expand * and core atoms to take those newer hardware features into account. For example, you can easily expand
* this to support wave-based execution model on a PS2 or PS3. Not having a stack or * this to support multi-threaded execution model on a PS2 or PS3 (or modern machines).
* automatic register allocation means the user cannot ignore excessive argument shuffle across workload or * Not having an implicit-call-frame boundary means register lifetime and data movement remain visible.
* waves and thier phases. Crossing ABI boundaries to other runtimes that do has obviouss penalties. * Any poor composition becomes obvious and will convey to the initiated user register shuffling,
* spills, reloads, or any unnecessary traffic they may not have intended (no need to dig through disassembly).
* *
* Learning data-oriented code becomes a natural progression. Your not fighting a stack-based procedural * Learning data-oriented code becomes a natural progression. Your not fighting a stack-based procedural
* paradigm that wants to argument shuffle. There is no ambiguity due to the lack of constraints, for example, * paradigm that wants to argument shuffle. There is no ambiguity due to the lack of constraints, for example,
* on how the user may "call" a procedure in traditional random dispatch runtimes. The user does have to * on how the user may "call" a procedure in traditional random dispatch runtimes. The user does have to
* hammer down "rules" or patterns for massaging the compiler to dissolve those call frames; just to get * hammer down "rules" or Ifpatterns for massaging the compiler to dissolve those call frames; just to get
* the asesmbly into its desired form. The form is obvious, and once the user gets to author these compoonents * the asesmbly into its desired form. The form is obvious, and once the user gets to author these compoonents
* it becomes a game of tetris. * it becomes a game of tetris.
* *
* Another feature is this ABI is very compatible with bootstrapping and developing simple toolchains built off * Another feature is this ABI is very compatible with bootstrapping and developing simple toolchains built off
* of bit-packed annotated command streams the user can directly author, maintatain, and immediately execute. * of bit-packed annotated command streams the user can directly author, maintatain, and immediately execute.
* That being like a color forth, or maybe something more familar like an immediate mode library * That being like a color forth, or maybe something more familar like an immediate mode library
* for various systems such as GUIs. This can make the tetris less of a chore with some helpful policy * (for various systems such as GUIs). This can make the tetris less of a chore with some helpful policy
* generation for allocation of registers, helping to choose resuable components, designing DSL on the fly, etc. * generation for allocation of registers, helping to choose resuable components, designing DSL on the fly, etc.
* ----------------------------------------------------------------------------------------------------------- * -----------------------------------------------------------------------------------------------------------
* TODO(Ed): We need pretty ascii diagrams and proper guides, articles, etc.
* -----------------------------------------------------------------------------------------------------------
* For now this ideation has just started functioning. I'm abusing C11 & a lua metaprogram to help establish * For now this ideation has just started functioning. I'm abusing C11 & a lua metaprogram to help establish
* a hybrid toolchain to ideate on a traditional text-based authoring UX for this paradigm. * a hybrid toolchain to ideate on a traditional text-based authoring UX for this paradigm.
* If pcsx-redux provides viable hot-reload and persistent data storage beyond save-states * If pcsx-redux provides viable hot-reload and persistent data storage beyond save-states
@@ -239,6 +247,9 @@ typedef void Proc_(TapeEntryFn)(MipsAtom* tape_ptr);
FI_ void tape_run(Tape tape) { C_(TapeEntryFn*, tape_enter)(tape.ptr); } FI_ void tape_run(Tape tape) { C_(TapeEntryFn*, tape_enter)(tape.ptr); }
// Procedural authoring of tapes: // Procedural authoring of tapes:
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; } FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; }
@@ -273,6 +284,7 @@ FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
// The 'Yield' sequence for Tape Atoms (mac_yield). // The 'Yield' sequence for Tape Atoms (mac_yield).
// In Forth this is considered the "NEXT" mechanism.
atom_dbg_skip MipsAtomComp_(ac_yield) { atom_dbg_skip MipsAtomComp_(ac_yield) {
load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_ load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_
@@ -360,8 +372,7 @@ typedef Struct_(RegFile) { A2_U2 GPR; };
#define regfile(pin_mask) {.GPR={u4_lo(pin_mask), u4_hi(pin_mask)} } #define regfile(pin_mask) {.GPR={u4_lo(pin_mask), u4_hi(pin_mask)} }
FI_ void regfile_init(RegFile_R rf) { FI_ void regfile_init(RegFile_R rf) {
/* pack the 32-bit ABI mask into the two U2s */ /* pack the 32-bit ABI mask into the two U2s */
rf->GPR[0] = u4_lo(regfile_abi_mask); rf->GPR[0] = u4_lo(regfile_abi_mask); rf->GPR[1] = u4_hi(regfile_abi_mask);
rf->GPR[1] = u4_hi(regfile_abi_mask);
} }
FI_ RegFile regfile_make(void) { RegFile rf; regfile_init(& rf); return rf; } FI_ RegFile regfile_make(void) { RegFile rf; regfile_init(& rf); return rf; }
@@ -405,16 +416,19 @@ FI_ void regfile_free_reg(RegFile_R rf, Reg r_id) {
RegFile_RInfo info = regfile_rinfo(rf->GPR, r_id); RegFile_RInfo info = regfile_rinfo(rf->GPR, r_id);
info.section[0] &= ~info.mask; info.section[0] &= ~info.mask;
} }
FI_ void regfile_reset(RegFile_R rf) { FI_ void regfile_reset (RegFile_R rf) { rf->GPR[0] = u4_lo(regfile_abi_mask); rf->GPR[1] = u4_hi(regfile_abi_mask); }
rf->GPR[0] = u4_lo(regfile_abi_mask); FI_ void regfile_reset_to_mask(RegFile_R rf, U4 mask) { rf->GPR[0] = u4_lo(mask); rf->GPR[1] = u4_hi(mask); }
rf->GPR[1] = u4_hi(regfile_abi_mask);
}
FI_ void regfile_reset_to_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 endregion RegFileArena (Register File Allocator)
#pragma region Mips Atom Components (Procedures)
// For doing direct-chaining of "atoms or fragments".
FI_ Slice_MipsCode ac_yield_to(AtomBuilder_R ab, Reg code_ptr) atom_dbg_skip MipsAtomComp_Proc_(ab, {
jump_reg(code_ptr), BdSlot_ nop,
})
#pragma endregion Mips Atom Components (Procedures)
#pragma region Mips Atom Procs #pragma region Mips Atom Procs
/* RegUse structs are a convention to organize register allocations for a mips atom procedure. /* RegUse structs are a convention to organize register allocations for a mips atom procedure.
Unlike the usual enum-based declarations, they provide a namespaced scope and have view types via union declarations. */ Unlike the usual enum-based declarations, they provide a namespaced scope and have view types via union declarations. */
+5 -8
View File
@@ -292,6 +292,8 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
gte_matrix_set_rotation (& smem.tform_view); gte_matrix_set_rotation (& smem.tform_view);
gte_matrix_set_translation(& smem.tform_view); gte_matrix_set_translation(& smem.tform_view);
// TODO(Ed): We should do a bounds check beforehand to confirm pa can hold all tris?
// The tape atoms in-flight should not need to care.
U1* prim_base = u1_r(pa->buf[smem.active_buf_id]); U1* prim_base = u1_r(pa->buf[smem.active_buf_id]);
U1* prim_cursor = prim_base + pa->used; U1* prim_cursor = prim_base + pa->used;
tb.used = 0; tb_scope_run(& tb) { tb.used = 0; tb_scope_run(& tb) {
@@ -317,21 +319,16 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
mt3s2s4_rotation (& smem.floor.rot, & smem.tform_world); mt3s2s4_rotation (& smem.floor.rot, & smem.tform_world);
mt3s2s4_translation(& smem.tform_world, & smem.floor.pos); mt3s2s4_translation(& smem.tform_world, & smem.floor.pos);
mt3s2s4_scale (& smem.tform_world, & smem.floor.scale); mt3s2s4_scale (& smem.tform_world, & smem.floor.scale);
// Combine world and look_at matrix. // Combine world and look_at matrix.
gte_comp_coord_m3s2(& smem.cam.look_at, & smem.tform_world, & smem.tform_view); gte_comp_coord_m3s2(& smem.cam.look_at, & smem.tform_world, & smem.tform_view);
gte_matrix_set_rotation (& smem.tform_view); gte_matrix_set_rotation (& smem.tform_view);
gte_matrix_set_translation(& smem.tform_view); gte_matrix_set_translation(& smem.tform_view);
U1_R prim_base = u1_r(pa->buf[smem.active_buf_id]);
U1_R prim_cursor = prim_base + pa->used;
// TODO(Ed): We should do a bounds check beforehand to confirm pa can hold all tris? // TODO(Ed): We should do a bounds check beforehand to confirm pa can hold all tris?
// The tape atoms in-flight should not need to care. // The tape atoms in-flight should not need to care.
U1_R prim_base = u1_r(pa->buf[smem.active_buf_id]);
// Prepare the tape. (Push protocol to tape) U1_R prim_cursor = prim_base + pa->used;
tb.used = 0; tb_scope_run(& tb) { tb.used = 0; tb_scope_run(& tb) { // Prepare the tape. (Push protocol to tape)
tb_emit(& tb, rbind_floor_f3_face); tb_bind_(& tb, Binds_FloorTri, tb_emit(& tb, rbind_floor_f3_face); tb_bind_(& tb, Binds_FloorTri,
.prim_cursor = prim_cursor, .prim_cursor = prim_cursor,
.face_cursor = smem.floor.faces, .face_cursor = smem.floor.faces,