Skip to content

Instantly share code, notes, and snippets.

@mendes5
Created May 16, 2024 04:58
Show Gist options
  • Save mendes5/99115912c4d14b81e531aa495b71fe87 to your computer and use it in GitHub Desktop.
Save mendes5/99115912c4d14b81e531aa495b71fe87 to your computer and use it in GitHub Desktop.
const REG = {
RAX: 0,
RBX: 1,
RCX: 2,
RDX: 3,
} as const;
const ADDRESSING_MODES = {
AUTO: 0b00,
REGISTER: 0b01,
IMMEDIATE: 0b10,
MEMORY: 0b11,
};
const a = (mode = 'AAAA') => {
if (mode.length > 4) {
throw new Error('Invalid adressing mode shorthand');
}
const modeNumber = [...mode].map((value, index) => {
if (value === 'A') return ADDRESSING_MODES.AUTO;
if (value === 'I') return ADDRESSING_MODES.IMMEDIATE;
if (value === 'R') return ADDRESSING_MODES.REGISTER;
if (value === 'M') return ADDRESSING_MODES.MEMORY;
throw new Error(`${value} at position ${index} is not a valid adressing mode shorthand`);
});
return encodeAdressingModes(modeNumber);
}
const encodeAdressingModes = (arr: number[]) => {
let prefix = 0b00000000;
if (arr.length > 4) {
throw new Error('Cannot encode more than 4 operands adressing modes');
}
let position = 0;
for (const mode of arr) {
prefix |= (mode << position++ * 2);
}
return prefix;
};
const getAddressMode = (addressingMode: number, position: 0 | 1 | 2 | 3): number => {
return (addressingMode >> position * 2) & 0b00000011;
};
const read = (computer: Computer, value: number, adressingMode: number, operand: 0 | 1 | 2 | 3) => {
const mode = getAddressMode(adressingMode, operand);
switch (mode) {
case ADDRESSING_MODES.AUTO:
throw new Error('Tried to read an adress on a instruction which considers the addressing mode AUTO for this operand, the read operation should be implemented on the instruction itself, or this instruction as no flexible adressing modes');
case ADDRESSING_MODES.IMMEDIATE:
return value;
case ADDRESSING_MODES.MEMORY:
return computer.memory[value];
case ADDRESSING_MODES.REGISTER:
return computer.registers[value];
default:
throw new Error('Invalid addressing mode');
}
};
const write = (computer: Computer, data: number, value: number, adressingMode: number, operand: 0 | 1 | 2 | 3) => {
const mode = getAddressMode(adressingMode, operand);
switch (mode) {
case ADDRESSING_MODES.AUTO:
throw new Error('Tried to write to an adress on a instruction which considers the addressing mode AUTO for this operand, the read operation should be implemented on the instruction itself, or this instruction as no flexible adressing modes');
case ADDRESSING_MODES.IMMEDIATE:
throw new Error('IMMEDIATE is not a valid adressing mode for a write operation');
case ADDRESSING_MODES.MEMORY:
computer.memory[value] = data;
case ADDRESSING_MODES.REGISTER:
computer.registers[value] = data;
}
};
let iota = 0;
const I = {
/**
* SRC, DST
*/
LOAD: iota++,
/**
* SRC, DST
*/
STORE: iota++,
/**
* L, R, OUT
*/
ADD: iota++,
/**
* L, R, OUT
*/
MULTIPLY: iota++,
/**
* L, R, OUT
*/
SUB: iota++,
/**
* IN, OUT
*/
NEGATE: iota++,
/**
* L, R, OUT
*/
AND: iota++,
/**
* L, R, OUT
*/
OR: iota++,
/**
* IN, OUT
*/
NOT: iota++,
/**
* LOC
*/
JMP: iota++,
/**
* COND, LOC
*/
JMP_IF_ZERO: iota++,
/**
*
*/
EXIT: 255,
} as const;
const NUM_ARGS = {
[I.LOAD]: 2,
[I.STORE]: 2,
[I.ADD]: 3,
[I.SUB]: 3,
[I.MULTIPLY]: 3,
[I.NEGATE]: 2,
[I.AND]: 3,
[I.OR]: 3,
[I.NOT]: 2,
[I.JMP]: 1,
[I.JMP_IF_ZERO]: 2,
[I.EXIT]: 0,
};
const NAMES = {
[I.LOAD]: 'LOAD',
[I.STORE]: 'STORE',
[I.ADD]: 'ADD',
[I.SUB]: 'SUB',
[I.MULTIPLY]: 'MULTIPLY',
[I.NEGATE]: 'NEGATE',
[I.AND]: 'AND',
[I.OR]: 'OR',
[I.NOT]: 'NOT',
[I.JMP]: 'JMP',
[I.JMP_IF_ZERO]: 'JMP_IF_ZERO',
[I.EXIT]: 'EXIT',
};
const NEEDS_ADDRESSING_MODE_BYTE = {
[I.LOAD]: false,
[I.STORE]: false,
[I.ADD]: true,
[I.SUB]: true,
[I.MULTIPLY]: true,
[I.NEGATE]: true,
[I.AND]: true,
[I.OR]: true,
[I.NOT]: true,
[I.JMP]: true,
[I.JMP_IF_ZERO]: true,
[I.EXIT]: false,
};
type Computer = {
programCounter: number;
memory: number[];
registers: number[];
}
/**
* Fetches the current instruction
*/
const cpuFetch = (computer: Computer): [number, number] => {
const instruction = computer.memory[computer.programCounter];
computer.programCounter += 1;
if (NEEDS_ADDRESSING_MODE_BYTE[instruction]) {
const adressingMode = computer.memory[computer.programCounter];
computer.programCounter += 1;
return [instruction, adressingMode];
}
return [instruction, 0];
};
/**
* Decodes the current instruction while
* also incrementing the program counter
*/
const cpuDecode = (computer: Computer, instruction: number): number[] => {
const num_arguments = NUM_ARGS[instruction];
const args = computer.memory.slice(computer.programCounter, computer.programCounter + num_arguments);
computer.programCounter += num_arguments;
return args;
};
// Full auto
const DEFAULT_ADDRESS_MODE =
ADDRESSING_MODES.AUTO &
(ADDRESSING_MODES.AUTO << 2) &
(ADDRESSING_MODES.AUTO << 4) &
(ADDRESSING_MODES.AUTO << 6);
/**
* Executes the given instruction
*/
const cpuExecute = (computer: Computer, instruction: number, operands: number[], adressMode: number) => {
if (instruction === I.LOAD) {
const [srcMem, dstReg] = operands;
computer.registers[dstReg] = computer.memory[srcMem]
} else if (instruction === I.STORE) {
const [srcReg, dstMem] = operands;
computer.memory[dstMem] = computer.registers[srcReg]
} else if (instruction === I.ADD) {
const [lReg, rReg, outReg] = operands;
const result = read(computer, lReg, adressMode, 0) + read(computer, rReg, adressMode, 1);
write(computer, result, outReg, adressMode, 2);
} else if (instruction === I.MULTIPLY) {
const [lReg, rReg, outReg] = operands;
const result = read(computer, lReg, adressMode, 0) * read(computer, rReg, adressMode, 1);
write(computer, result, outReg, adressMode, 2);
} else if (instruction === I.NEGATE) {
const [inReg, outReg] = operands;
const result = -read(computer, inReg, adressMode, 0);
write(computer, result, outReg, adressMode, 1);
} else if (instruction === I.AND) {
const [lReg, rReg, outReg] = operands;
const result = read(computer, lReg, adressMode, 0) & read(computer, rReg, adressMode, 1);
write(computer, result, outReg, adressMode, 2);
} else if (instruction === I.SUB) {
const [lReg, rReg, outReg] = operands;
const result = read(computer, lReg, adressMode, 0) - read(computer, rReg, adressMode, 1);
write(computer, result, outReg, adressMode, 2);
} else if (instruction === I.OR) {
const [lReg, rReg, outReg] = operands;
const result = read(computer, lReg, adressMode, 0) | read(computer, rReg, adressMode, 1);
write(computer, result, outReg, adressMode, 2);
} else if (instruction === I.NOT) {
const [inReg, outReg] = operands;
const result = ~read(computer, inReg, adressMode, 0);
write(computer, result, outReg, adressMode, 1);
} else if (instruction === I.JMP) {
const [locReg] = operands;
computer.programCounter = read(computer, locReg, adressMode, 0);
} else if (instruction === I.JMP_IF_ZERO) {
const [condReg, locReg] = operands;
if (read(computer, condReg, adressMode, 0) === 0) {
computer.programCounter = read(computer, locReg, adressMode, 1);
}
} else if (instruction === I.EXIT) {
return false;
} else {
throw new Error(`Invalid op_code: ${instruction}`);
}
return true;
}
const debug = false;
/**
* Does a full cpu cicle.
* @returns a boolean indicating whenever we should keep executing.
*/
const cpuCycle = (computer: Computer) => {
const [instruction, addressMode] = cpuFetch(computer);
if (debug) {
console.log(`@${computer.programCounter} ${NAMES[instruction]}`);
}
const operands = cpuDecode(computer, instruction);
return cpuExecute(computer, instruction, operands, addressMode);
}
/**
* Executes a program
* @param tickCallback optional tick
*/
const runComputer = (computer: Computer, tickCallback?: () => void) => {
while (cpuCycle(computer)) tickCallback?.();
}
/**
* Just to better illustrate
* that those are computer.memory adresses
*/
const MEM = <T>(i: T) => i;
/**
* Sample loop increment a number from 0 to 1_000
```c
void main() {
int state = 0;
int limit = 1000;
while (state != limit) {
state++
}
}
```
But a more direct translation
of the bytecode is this:
```c
void main() {
int state = 0;
int increment = 1;
int limit = 1000;
prog:
int a = state;
int b = increment;
int c = a + b;
state = c;
int difference = state - limit;
if (difference === 0)
goto end;
else
goto prog;
end:
return;
}
```
*/
const PROGRAM = [
I.LOAD, MEM(132), REG.RDX,
I.LOAD, MEM(130), REG.RAX,
I.LOAD, MEM(131), REG.RBX,
I.ADD, a('RRR'), REG.RAX, REG.RBX, REG.RCX,
I.STORE, REG.RCX, MEM(130),
I.SUB, a('RRR'), REG.RCX, REG.RDX, REG.RAX,
I.LOAD, MEM(133), REG.RBX,
I.JMP_IF_ZERO, a('RR'), REG.RAX, REG.RBX,
I.LOAD, MEM(134), REG.RBX,
I.JMP, a('R'), REG.RBX,
I.EXIT,
] as const;
/**
* How to write computer programs:
* * Supported instructions are in the `I` constant
* * You can manually look at `NEEDS_ADDRESSING_MODE_BYTE` to check if you need to specify the adressing modes of the operands
* - If you need you use the `a` function to specify, you put a string in the order of the arguments, so for example
* - `I.LOAD, a('IR'), 100, REG.EAX,` the `IR` stands for: "First operand immediate, second operand register"
* - Available modes are `A`: Auto, `I`: Immediate, `R`: Register, `M`: Memory
* - If you don't need just skip the adressing byte
* * Then you can use either `REG` to specify registers as operands, or `MEM` to specify memory locations
*/
const computer: Computer = {
programCounter: 0,
memory: new Array(250).fill(0),
registers: new Array(4).fill(0),
};
// usually this goes before the actual program
computer.memory[130] = 0; // state
computer.memory[131] = 1; // constant 1
computer.memory[132] = 1000; // constant 1000
computer.memory[133] = 35; // location of end of program
computer.memory[134] = 0; // location of start of program
computer.memory.splice(0, PROGRAM.length, ...PROGRAM);
runComputer(computer);
// Result should be stored here:
console.log(computer.memory[130]);
Based of https://gist.github.com/mikex86/36e6eeef5a5c53de9792ec224e25c45c
Just converted to typescript, overenginered adding adressing modes, and made a program that counts from 0 to 1000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment