aboutsummaryrefslogtreecommitdiffgithub
path: root/packages/novice/novice/simulator/simulator.ts
blob: 3e21a80105f26bd741cb3d835e81255d7c35b0f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { Fields, FullMachineState, InstructionSpec, IO, Isa,
         MachineCodeSection, MachineStateLogEntry, MachineStateUpdate, Reg,
         RegIdentifier } from '../isa';
import { Memory } from './mem';

class Simulator implements Memory {
    protected state: FullMachineState;
    protected isa: Isa;
    protected io: IO;
    protected maxExec: number;
    protected log: MachineStateLogEntry[];
    protected numExec: number;

    public constructor(isa: Isa, io: IO, maxExec: number) {
        this.state = isa.initMachineState();
        this.isa = isa;
        this.io = io;
        this.maxExec = maxExec;
        this.log = [];
        this.numExec = 0;
    }

    public getPc(): number { return this.state.pc; }

    public isHalted(): boolean { return this.state.halted; }

    // TODO: make this immutable somehow
    public getRegs() { return this.state.regs; }

    public getNumExec() { return this.numExec; }

    public getLogLength(): number {
        return this.log.length;
    }

    public loadSections(sections: MachineCodeSection[]): void {
        const updates: MachineStateUpdate[] = [];

        for (const section of sections) {
            for (let i = 0; i < section.words.length; i++) {
                updates.push({kind: 'mem',
                              addr: section.startAddr + i,
                              val: section.words[i]});
            }
        }

        // Don't create a log entry because no need to ever undo this
        this.applyUpdates(updates);
    }

    public async step(): Promise<void> {
        // If already halted, do nothing
        if (this.state.halted) {
            return;
        }

        this.numExec++;

        const ir = this.load(this.state.pc);
        const [instr, fields] = this.decode(ir);
        // Don't pass the incremented PC
        const state = {pc: this.state.pc,
                       reg: this.reg.bind(this),
                       load: this.load.bind(this)};
        const ret = instr.sim(state, this.io, fields);
        const updates: MachineStateUpdate[] =
            (Promise.resolve(ret) === ret)
            ? await (ret as Promise<MachineStateUpdate[]>)
            : ret as MachineStateUpdate[];

        // Increment PC
        updates.unshift({ kind: 'pc', where: this.state.pc + this.isa.spec.pc.increment });
        this.pushLogEntry(instr, fields, updates);
    }

    public async run(): Promise<void> {
        while (!this.state.halted) {
            if (this.maxExec >= 0 && this.numExec >= this.maxExec) {
                throw new Error(`hit maximum executed instruction count ` +
                                `${this.maxExec}. this may indicate an ` +
                                `infinite loop in code`);
            }

            await this.step();
        }
    }

    public pushLogEntry(instr: InstructionSpec, fields: Fields,
                        updates: MachineStateUpdate[]): void {
        const undo = this.applyUpdates(updates);
        const logEntry: MachineStateLogEntry = {instr, fields, updates,
                                                undo};
        this.log.push(logEntry);
    }

    public rewind(): void {
        while (this.log.length) {
            this.unstep();
        }
    }

    public unstep(): void {
        this.popLogEntry();
    }

    public popLogEntry(): MachineStateLogEntry {
        const logEntry = this.log.pop();

        if (!logEntry) {
            throw new Error('already at the beginning of time');
        }

        this.applyUpdates(logEntry.undo);
        return logEntry;
    }

    public load(addr: number): number {
        return this.isa.stateLoad(this.state, addr);
    }

    public store(addr: number, val: number): void {
        this.isa.stateStore(this.state, addr, val);
    }

    public reg(id: RegIdentifier): number {
        return this.isa.stateReg(this.state, id);
    }

    public regSet(id: RegIdentifier, val: number) {
        this.isa.stateRegSet(this.state, id, val);
    }

    public decode(ir: number): [InstructionSpec, Fields] {
        return this.isa.decode(ir);
    }

    // Return a list of corresponding undoing updates
    protected applyUpdates(updates: MachineStateUpdate[]):
            MachineStateUpdate[] {
        const [newState, undos] =
            this.isa.stateApplyUpdates(this.state, updates);
        this.state = newState;
        return undos;
    }
}

export { Simulator };