aboutsummaryrefslogtreecommitdiffgithub
path: root/packages/novice-web/novice-web/workers/debugger/debugger-worker.ts
blob: df12bdb48e4bdf3a6240b1cf5579b240d39c3ed5 (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
import { Debugger, getIsa, IO, MachineStateUpdate } from 'novice';
import { BaseWorker } from '../base-worker';
import { DebuggerFrontendMessage, DebuggerWorkerMessage } from './proto';
import { SnitchDebugger } from './snitch-debugger';

class DebuggerWorker extends BaseWorker<DebuggerFrontendMessage,
                                        DebuggerWorkerMessage> {
    private dbg: Debugger|null;
    private io: IO;

    public constructor(ctx: Worker) {
        super(ctx);

        this.dbg = null;
        // TODO: Add actual IO
        this.io = {
            getc: () => Promise.resolve(0),
            putc: (c: number) => {
                this.sendWorkerMessage({ kind: 'putc', c });
            },
        };
    }

    protected onFrontendMessage(msg: DebuggerFrontendMessage): void {
        switch (msg.kind) {
            case 'reset':
                this.dbg = new SnitchDebugger(getIsa(msg.isa), this.io, -1,
                                              this.onUpdates.bind(this));
                break;

            case 'load-sections':
                if (!this.dbg) {
                    throw new Error('must reset before loading sections');
                }

                this.dbg.loadSections(msg.sections);
                break;

            case 'interrupt':
                if (!this.dbg) {
                    throw new Error('must reset before interrupting');
                }

                this.dbg.onInterrupt();
                break;

            case 'step':
                if (!this.dbg) {
                    throw new Error('must reset before stepping');
                }

                this.dbg.step();
                break;

            case 'unstep':
                if (!this.dbg) {
                    throw new Error('must reset before unstepping');
                }

                // Maintain the lie that the debugger "freezes" on a halt by
                // double-unstepping on halts. Without this, after executing a
                // halt, it seems like nothing happened.
                //
                // We do this here instead of the frontend in case the
                // frontend batches up n unstep requests while sitting
                // on a halt before getting any machine state updates
                // from the debugger worker. In such a case, if we did
                // this check in the frontend, we would send 2n unstep
                // requests instead of correctly sending n + 1.
                if (this.dbg.isHalted() && this.dbg.getLogLength() > 1) {
                    this.dbg.unstep();
                }

                this.dbg.unstep();
                break;

            case 'run':
                if (!this.dbg) {
                    throw new Error('must reset before running');
                }

                this.dbg.run();
                break;

            default:
                const _: never = msg;
        }
    }

    private onUpdates(updates: MachineStateUpdate[]): void {
        this.sendWorkerMessage({kind: 'updates', updates});
    }
}

export { DebuggerWorker };