Fix V8 emulator bugs

This commit is contained in:
Asraelite 2024-01-09 21:05:38 +01:00
parent c45ad79440
commit 476972f85a
29 changed files with 281 additions and 18 deletions

View file

@ -39,6 +39,10 @@
<label for="config-raw-output-input">raw output: </label> <label for="config-raw-output-input">raw output: </label>
<input type="checkbox" id="config-raw-output-input"> <input type="checkbox" id="config-raw-output-input">
</div> </div>
<div id="config-focus-emulator">
<label for="config-focus-emulator-input">focus emulator: </label>
<input type="checkbox" id="config-focus-emulator-input">
</div>
<!-- <div id="config-line-numbers"> <!-- <div id="config-line-numbers">
<label for="config-line-numbers-input">line numbers: </label> <label for="config-line-numbers-input">line numbers: </label>
<input type="checkbox" id="config-labels-input"> <input type="checkbox" id="config-labels-input">

View file

@ -8,7 +8,7 @@ import { ArchName, ArchSpecification, AssemblyInput, AssemblyOutput, Instruction
import { toNote } from './util.js'; import { toNote } from './util.js';
const CONFIG_VERSION: string = '1'; const CONFIG_VERSION: string = '1';
const EMULATOR_SPEED = 60; const EMULATOR_SPEED = 30;
window.addEventListener('load', init); window.addEventListener('load', init);
@ -51,6 +51,7 @@ type ConfigurationSpecification = {
syntaxHighlighting: string; syntaxHighlighting: string;
inlineSourceFormat: string; inlineSourceFormat: string;
rawOutput: boolean; rawOutput: boolean;
focusEmulator: boolean;
sendDelay: number; sendDelay: number;
}; };
@ -62,6 +63,7 @@ class Configuration {
syntaxHighlighting: string; syntaxHighlighting: string;
inlineSourceFormat: string; inlineSourceFormat: string;
rawOutput: boolean; rawOutput: boolean;
focusEmulator: boolean;
sendDelay: number; sendDelay: number;
private formElement: HTMLFormElement; private formElement: HTMLFormElement;
@ -73,6 +75,7 @@ class Configuration {
private targetArchDocsButton: HTMLButtonElement; private targetArchDocsButton: HTMLButtonElement;
private syntaxHighlightingSelect: HTMLSelectElement; private syntaxHighlightingSelect: HTMLSelectElement;
private rawOutputCheckbox: HTMLInputElement; private rawOutputCheckbox: HTMLInputElement;
private focusEmulatorCheckbox: HTMLInputElement;
private sendDelayInput: HTMLInputElement; private sendDelayInput: HTMLInputElement;
constructor() { constructor() {
@ -85,6 +88,7 @@ class Configuration {
this.targetArchDocsButton = document.getElementById('config-target-arch-docs-button') as HTMLButtonElement; this.targetArchDocsButton = document.getElementById('config-target-arch-docs-button') as HTMLButtonElement;
this.syntaxHighlightingSelect = document.getElementById('config-syntax-highlighting-select') as HTMLSelectElement; this.syntaxHighlightingSelect = document.getElementById('config-syntax-highlighting-select') as HTMLSelectElement;
this.rawOutputCheckbox = document.getElementById('config-raw-output-input') as HTMLInputElement; this.rawOutputCheckbox = document.getElementById('config-raw-output-input') as HTMLInputElement;
this.focusEmulatorCheckbox = document.getElementById('config-focus-emulator-input') as HTMLInputElement;
this.sendDelayInput = document.getElementById('control-send-speed') as HTMLInputElement; this.sendDelayInput = document.getElementById('control-send-speed') as HTMLInputElement;
this.formElement.addEventListener('change', () => { this.formElement.addEventListener('change', () => {
@ -115,10 +119,17 @@ class Configuration {
this.machineCodeShowLabels = this.labelsCheckbox.checked; this.machineCodeShowLabels = this.labelsCheckbox.checked;
this.inlineSourceFormat = this.inlineSourceSelect.value; this.inlineSourceFormat = this.inlineSourceSelect.value;
this.rawOutput = this.rawOutputCheckbox.checked; this.rawOutput = this.rawOutputCheckbox.checked;
this.focusEmulator = this.focusEmulatorCheckbox.checked;
this.sendDelay = parseInt(this.sendDelayInput.value); this.sendDelay = parseInt(this.sendDelayInput.value);
document.getElementById('assembly').className = `theme-${this.syntaxHighlighting}`; document.getElementById('assembly').className = `theme-${this.syntaxHighlighting}`;
if (this.focusEmulator) {
document.getElementById('page').classList.add('emulator');
} else {
document.getElementById('page').classList.remove('emulator');
}
this.saveToLocalStorage(); this.saveToLocalStorage();
} }
@ -139,6 +150,7 @@ class Configuration {
configuration.targetArch = parsed.targetArch; configuration.targetArch = parsed.targetArch;
configuration.syntaxHighlighting = parsed.syntaxHighlighting; configuration.syntaxHighlighting = parsed.syntaxHighlighting;
configuration.inlineSourceFormat = parsed.inlineSourceFormat; configuration.inlineSourceFormat = parsed.inlineSourceFormat;
configuration.focusEmulator = parsed.focusEmulator;
configuration.rawOutput = parsed.rawOutput; configuration.rawOutput = parsed.rawOutput;
configuration.sendDelay = parsed.sendDelay; configuration.sendDelay = parsed.sendDelay;
@ -149,6 +161,7 @@ class Configuration {
configuration.syntaxHighlightingSelect.value = configuration.syntaxHighlighting; configuration.syntaxHighlightingSelect.value = configuration.syntaxHighlighting;
configuration.inlineSourceSelect.value = configuration.inlineSourceFormat; configuration.inlineSourceSelect.value = configuration.inlineSourceFormat;
configuration.rawOutputCheckbox.checked = configuration.rawOutput; configuration.rawOutputCheckbox.checked = configuration.rawOutput;
configuration.focusEmulatorCheckbox.checked = configuration.focusEmulator;
configuration.sendDelayInput.value = configuration.sendDelay.toString(); configuration.sendDelayInput.value = configuration.sendDelay.toString();
} }
} }
@ -169,6 +182,7 @@ class Configuration {
inlineSourceFormat: this.inlineSourceFormat, inlineSourceFormat: this.inlineSourceFormat,
syntaxHighlighting: this.syntaxHighlighting, syntaxHighlighting: this.syntaxHighlighting,
rawOutput: this.rawOutput, rawOutput: this.rawOutput,
focusEmulator: this.focusEmulator,
sendDelay: this.sendDelay, sendDelay: this.sendDelay,
}; };
localStorage.setItem('configuration', JSON.stringify(values)); localStorage.setItem('configuration', JSON.stringify(values));

View file

@ -35,6 +35,10 @@ input[type="checkbox"] {
"assembly-status machine-code-status"; "assembly-status machine-code-status";
} }
#page.emulator {
grid-template-rows: 0fr 1fr 300px;
}
@media (max-width: 900px) { @media (max-width: 900px) {
#page { #page {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -62,7 +66,7 @@ input[type="checkbox"] {
grid-template-areas: grid-template-areas:
"target-arch syntax-highlighting labels" "target-arch syntax-highlighting labels"
"output-format source raw-output" "output-format source raw-output"
"address-format none none"; "address-format breakpoints focus-emulator";
} }
#controls>form>div { #controls>form>div {
@ -81,6 +85,14 @@ input[type="checkbox"] {
grid-area: raw-output; grid-area: raw-output;
} }
#config-focus-emulator {
grid-area: focus-emulator;
}
#config-breakpoints {
grid-area: breakpoints;
}
#config-line-numbers { #config-line-numbers {
grid-area: config-line-numbers; grid-area: config-line-numbers;
} }

View file

@ -618,6 +618,7 @@ class ParvaEmulator implements Emulator {
screen: [] as Array<Array<number>>, screen: [] as Array<Array<number>>,
screenBuffer: [] as Array<Array<number>>, screenBuffer: [] as Array<Array<number>>,
}; };
previousFrameAccessedMemory: boolean = false;
constructor() { } constructor() { }
@ -632,6 +633,12 @@ class ParvaEmulator implements Emulator {
} }
step() { step() {
}
stepCore(): boolean {
let coreTerminates = false;
const instruction = this.memory[this.pc] ?? 0; const instruction = this.memory[this.pc] ?? 0;
const bits = instruction.toString(2).padStart(24, '0'); const bits = instruction.toString(2).padStart(24, '0');
@ -715,6 +722,8 @@ class ParvaEmulator implements Emulator {
this.memory[address + 1] = valueD1; this.memory[address + 1] = valueD1;
} }
} }
this.previousFrameAccessedMemory = true;
} else if (operationType === '11') { } else if (operationType === '11') {
// branching // branching
const special = condition.startsWith('11'); const special = condition.startsWith('11');
@ -747,6 +756,7 @@ class ParvaEmulator implements Emulator {
result = !result; result = !result;
} }
if (result) { if (result) {
coreTerminates = true;
this.pc = targetAddress - 1; this.pc = targetAddress - 1;
} }
} }
@ -755,6 +765,7 @@ class ParvaEmulator implements Emulator {
this.pc &= 0xffffff; this.pc &= 0xffffff;
this.cycle += 1; this.cycle += 1;
this.cycle &= 0xffffff; this.cycle &= 0xffffff;
return coreTerminates;
} }
ioOut(device: number, command: number, valueA: number, valueB: number) { ioOut(device: number, command: number, valueA: number, valueB: number) {

View file

@ -440,16 +440,16 @@ class V8Emulator implements Emulator {
this.pc = this.memory[immediate] ?? 0; this.pc = this.memory[immediate] ?? 0;
} else if (bits === '00000100') { } else if (bits === '00000100') {
// jz imm // jz imm
this.pc = this.registers[0] === 0 ? immediate : this.pc + 1; this.pc = this.zeroFlag ? immediate : this.pc + 2;
} else if (bits === '00000101') { } else if (bits === '00000101') {
// jnz imm // jnz imm
this.pc = this.registers[0] !== 0 ? immediate : this.pc + 1; this.pc = !this.zeroFlag ? immediate : this.pc + 2;
} else if (bits === '00000110') { } else if (bits === '00000110') {
// jc imm // jc imm
this.pc = this.carryFlag ? immediate : this.pc + 1; this.pc = this.carryFlag ? immediate : this.pc + 2;
} else if (bits === '00000111') { } else if (bits === '00000111') {
// jnc imm // jnc imm
this.pc = !this.carryFlag ? immediate : this.pc + 1; this.pc = !this.carryFlag ? immediate : this.pc + 2;
} else if (bits === '00001000') { } else if (bits === '00001000') {
// jsr imm // jsr imm
this.push(this.pc + 2); this.push(this.pc + 2);
@ -486,16 +486,16 @@ class V8Emulator implements Emulator {
this.aluOp(operand, (x, a) => x + a + (this.carryFlag ? 1 : 0)); this.aluOp(operand, (x, a) => x + a + (this.carryFlag ? 1 : 0));
} else if (bits.startsWith('00011')) { } else if (bits.startsWith('00011')) {
// inc // inc
this.aluOp(operand, (x, a) => x + 1); this.aluOp(operand, (x, a) => x + 1, false);
} else if (bits.startsWith('00100')) { } else if (bits.startsWith('00100')) {
// sbc // sbc
this.aluOp(operand, (x, a) => x - a - (this.carryFlag ? 1 : 0)); this.aluOp(operand, (x, a) => x - a - (this.carryFlag ? 1 : 0));
} else if (bits.startsWith('00101')) { } else if (bits.startsWith('00101')) {
// dec // dec
this.aluOp(operand, (x, a) => x - 1); this.aluOp(operand, (x, a) => x - 1, false);
} else if (bits.startsWith('00110')) { } else if (bits.startsWith('00110')) {
// not // not
this.aluOp(operand, (x, a) => (~x) & 0xff); this.aluOp(operand, (x, a) => (~x) & 0xff, false);
} else if (bits.startsWith('00111')) { } else if (bits.startsWith('00111')) {
// xor // xor
this.aluOp(operand, (x, a) => x ^ a); this.aluOp(operand, (x, a) => x ^ a);
@ -519,11 +519,11 @@ class V8Emulator implements Emulator {
this.pc += 1; this.pc += 1;
} else if (bits.startsWith('01100')) { } else if (bits.startsWith('01100')) {
// rol // rol
this.aluOp(operand, (x, a) => (x << 1) + (x >>> 7)); this.aluOp(operand, (x, a) => (x << 1) + (x >>> 7), false);
} else if (bits.startsWith('01101')) { } else if (bits.startsWith('01101')) {
// ror // ror
const initialValue = this.getRegister(operand); const initialValue = this.getRegister(operand);
this.aluOp(operand, (x, a) => (x >>> 1) + ((x & 1) << 7)); this.aluOp(operand, (x, a) => (x >>> 1) + ((x & 1) << 7), false);
this.carryFlag = (initialValue & 1) === 1; this.carryFlag = (initialValue & 1) === 1;
} else if (bits.startsWith('01110')) { } else if (bits.startsWith('01110')) {
// cmp // cmp
@ -575,13 +575,13 @@ class V8Emulator implements Emulator {
} }
} }
aluOp(register: string, fn: (x: number, a: number) => number) { aluOp(register: string, fn: (x: number, a: number) => number, destinationIsA = true) {
const operandValue = this.getRegister(register); const operandValue = this.getRegister(register);
const aValue = this.registers[0]; const aValue = this.registers[0];
const result = fn(operandValue, aValue); const result = fn(operandValue, aValue);
this.zeroFlag = result === 0; this.zeroFlag = result === 0;
this.carryFlag = result > 0xff; this.carryFlag = (result & 0xff) !== result;
this.registers[0] = result & 0xff; this.setRegister(destinationIsA ? '0' : register, result & 0xff);
this.pc += 1; this.pc += 1;
} }
@ -634,8 +634,12 @@ class V8Emulator implements Emulator {
memoryString += ' '; memoryString += ' ';
} }
} }
const controlRegistersString = `<span style="background-color: #fcb55b">pc: ${this.pc.toString(16).padStart(2, '0')}</span>, `
+ `<span style="background-color: cyan">stack pointer: ${this.memory[STACK_POINTER_ADDRESS].toString(16).padStart(2, '0')}</span>, `
+ `carry flag: ${this.carryFlag ? 1 : 0}, `
+ `zero flag: ${this.zeroFlag ? 1 : 0}`;
return ` return `
<pre><span style="background-color: #fcb55b">pc: ${this.pc.toString(16).padStart(2, '0')}</span>,<span style="background-color: cyan"> stack pointer: ${this.memory[STACK_POINTER_ADDRESS].toString(16).padStart(2, '0')}</span></pre> <pre>${controlRegistersString}</pre>
<pre>${registersString}</pre> <pre>${registersString}</pre>
<pre>memory:\n${memoryString}</pre> <pre>memory:\n${memoryString}</pre>
`; `;

View file

@ -166,19 +166,19 @@ blt x2, x3, 2
add x0, x0, x1 add x0, x0, x1
slli x1, x1, 6 slli x1, x1, 6
srli x2, x0, 6 srli x2, x0, 6
and x2, x0, 0o77 andi x2, x0, 0o77
blt x2, x3, end_score_change blt x2, x3, end_score_change
add x0, x0, x1 add x0, x0, x1
slli x1, x1, 6 slli x1, x1, 6
srli x2, x0, 6 srli x2, x0, 6
and x2, x0, 0o77 andi x2, x0, 0o77
blt x2, x3, end_score_change blt x2, x3, end_score_change
add x0, x0, x1 add x0, x0, x1
slli x1, x1, 6 slli x1, x1, 6
srli x2 x0, 6 srli x2 x0, 6
and x2, x0, 0o77 andi x2, x0, 0o77
blt x2, x3, end_score_change blt x2, x3, end_score_change
add x0, x0, x1 add x0, x0, x1

View file

@ -0,0 +1,9 @@
# alloc
.export malloc, free, realloc
# a0 = size
# a1 = alignment
malloc:

View file

@ -0,0 +1,11 @@
# boot
.address 0
entry:
# todo: get bit representing if we're in supervisor mode and put in a0
beqz a0, user_mode_trap
b boot_sequence
user_mode_trap:
int
wfi

View file

@ -0,0 +1,47 @@
# cat
.extern f_open, f_close, f_read, write
.string_encoding system
main: # fn (args: [cstr8]) -> int
mv s0, a0
push a1
li s1, 0
arg_loop:
lw t0, 1(sp)
beq s1, t0, end
lw a0, 0(s0)
call f_open # a0 = file descriptor
bltz a0, error_open
push a0
call f_read # a0 = pointer to contents
bltz a0, error_read
mv a1, a0
li a0, 1 # stdout
call write
pop a0
call f_close
addi s1, s1, 1
b arg_loop
error_read:
pop zero
mv s0, a1
call f_close
mv a1, s0
error_open:
mv s0, a1
li a0, 1
li a1, error_message
call write
mv a1, s0
call write
b end
end:
li a0, 0
ret
error_message:
.string "\fr" # color red
.string "Error: \0"

View file

@ -0,0 +1,104 @@
# hashmap
.extern malloc
.export hashmap__new
# structure:
# <entry count>
# <capacity magnitude> (capacity = 2 ^ capacity magnitude)
# {
# <hash>
# <value>
# } * 2^capacity
# Uses open addressing
# a0 = initial capacity magnitude
# returns: pointer to hashmap
hashmap__new:
push ra
push s0
mv s0, a0
li a0, 1
sll a0, a0, s0
addi a0, s0, 2
li a1, 0
call malloc
li t0, 0
zero_fill_loop:
addi t0, t0, 1
sw zero, [a0 + t0 + 2]
bne t0, s0 zero_fill_loop
sw zero, [a0 + s0 + 0]
sw s0, [a0 + s0 + 1]
pop s0
pop ra
ret
index_from_hash:
beqz a1, error
li t2, 1
lw a3, [a0 + 1]
sll t2, t2, a3
addi t2, t2, -1
and t1, a1, t2
add t1, t1, t1
jr t0
# a0 = pointer to hashmap
# a1 = 24-bit hash of key
# returns: value
hashmap__get:
jal t0, index_from_hash
get_loop:
lw t2, [a0 + t1 + 2] # t2 = hash of entry key
beq t2, a1, entry_found
beqz t2, entry_not_found
addi t1, t1, 2
b get_loop
entry_found:
lw a0, [a0 + t0 + 3]
ret
entry_not_found:
li a0, 0
ret
# a0 = pointer to hashmap
# a1 = 24-bit hash of key
# a2 = 24-bit value
# returns: pointer to value
hashmap__insert:
lw a3, [a0] # entry count
lw a4, [a0 + 1] # capacity magnitude
li t0, 1
addi a5, a4, -1
sll a5, t0, a5 # capacity / 2
ble a3, a5, insert_no_resize
slli a4, a4, 2 # capacity * 2
jal s0, resize
insert_no_resize:
jal t0, index_from_hash
add a3, a3, a3
insert_loop:
lw t2, [a0 + t1 + 2] # t2 = hash of entry key
addi t1, t1, 2
bnez t2 slot_found
beq t2, a1, entry_found
beqz t2, entry_not_found
addi t1, t1, 1
b insert_loop
# a4 = new capacity
resize:
push a0, a1, a2, a3, a4
mv a0, a4
mv s0, t0
call hashmap__new
pop t0, a1, a2, a3, a4
addi a4, a4, 1
sw a4, [a0]

View file

@ -0,0 +1,47 @@
# terminal
.extern f_open, f_close, f_read, write
.string_encoding system
main: # fn (args: [cstr8]) -> int
mv s0, a0
push a1
li s1, 0
arg_loop:
lw t0, 1(sp)
beq s1, t0, end
lw a0, 0(s0)
call f_open # a0 = file descriptor
bltz a0, error_open
push a0
call f_read # a0 = pointer to contents
bltz a0, error_read
mv a1, a0
li a0, 1 # stdout
call write
pop a0
call f_close
addi s1, s1, 1
b arg_loop
error_read:
pop zero
mv s0, a1
call f_close
mv a1, s0
error_open:
mv s0, a1
li a0, 1
li a1, error_message
call write
mv a1, s0
call write
b end
end:
li a0, 0
ret
error_message:
.string "\fr" # color red
.string "Error: \0"