hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/bridge/bridge.zig
blob: a7e0e582cb5797bb7fbbc4d908a3191046f2dd11 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// so the thing is right, revo isnt really that low level and we need to have a
// thin bridge to translate shit over. its just to setup the vm, serial ports,
// heap shit like that

const std = @import("std");
const revo = @import("revo");
const serial = @import("serial.zig");
const hosts = @import("hosts.zig");

pub const Sink = serial.Sink;
pub const debug_io = serial.debug_io;
pub const Fb = hosts.Fb;
pub const KernelOps = hosts.KernelOps;

// the vm outlives boot() now, so we juts make this hoe static
var g_vm: ?*revo.VM = null;

// NOTE: ORDER MATTERS
const init_program = blk: {
    var s: []const u8 = @embedFile("k_font") ++ "\n" ++
        @embedFile("k_console") ++ "\n" ++
        @embedFile("k_vmm") ++ "\n" ++
        @embedFile("k_fs") ++ "\n" ++
        @embedFile("k_vfs") ++ "\n" ++
        @embedFile("k_io") ++ "\n" ++
        @embedFile("k_seed") ++ "\n";

    // gils shit
    const gils_embedded = .{ "gils", "ed" };
    for (gils_embedded) |name| s = s ++ @embedFile("k_gils_" ++ name) ++ "\n";

    s = s ++ @embedFile("k_proc") ++ "\n" ++
        @embedFile("k_ttf") ++ "\n" ++
        @embedFile("k_shell") ++ "\n" ++
        @embedFile("k_tty") ++ "\n" ++
        @embedFile("k_input") ++ "\n" ++
        @embedFile("k_main");
    break :blk s;
};

pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void {
    serial.setSink(out);
    hosts.setFb(fb);
    hosts.setKops(ops);

    // note: we drive the vm directly (compile -> run) instead of the high level
    // runtime.eval wrapper, which has a latent type error in revo that only
    // trips when analyzed. same low level path revos own wasm entry uses
    const VM = revo.VM;
    const vm = alloc.create(VM) catch {
        out("bridge: OOM creating VM\r\n");
        return;
    };
    vm.* = VM.init(.{
        .alloc = alloc,
        .io = serial.serial_io,
        .argv = &.{},
        .diag_alloc = alloc,
    }) catch {
        out("bridge: VM.init failed\r\n");
        return;
    };
    out("bridge: revo VM up\r\n");

    hosts.passShitToRevo(vm) catch {
        out("bridge: registering primitives failed\r\n");
        return;
    };

    const build_result = revo.lang.build(vm, .{ .name = "kernel", .text = init_program }, .{}) catch {
        out("bridge: compile threw\r\n");
        return;
    };
    const artifact = switch (build_result) {
        .ok => |art| art,
        .err => |failure| {
            out("bridge: compile error: ");
            var sw = serial.SinkWriter.init();
            revo.lang.renderError(alloc, &sw.interface, .{ .name = "kernel", .text = init_program }, failure, .{}) catch {};
            out("\r\n");
            return;
        },
    };

    // note: on purpose we dont free artifact.instructions/spans cause on_keys
    //       bytecode lives in there and we call it for the life of the kernel
    vm.setProgramDebugInfo(artifact.spans, "", "kernel") catch {
        out("bridge: setProgramDebugInfo failed\r\n");
        return;
    };

    const eval_result = revo.run.runBytecodeReport(vm, "kernel", artifact.instructions) catch {
        out("bridge: eval threw\r\n");
        return;
    };
    switch (eval_result) {
        .ok => g_vm = vm, // handler is defined so hand the vm to the event loop
        .err => |failure| {
            out("\r\nbridge: runtime error: ");
            var sw = serial.SinkWriter.init();
            failure.render(alloc, &sw.interface, init_program, false) catch {};
            out("\r\n");
        },
    }
}

// called from the boot event loop for each raw scancode
// hand it to revos on_key
// runs in normal context (not the isr), so allocation is fine here
// i think
pub fn onKey(scancode: u8) void {
    const vm = g_vm orelse return;
    const cb = vm.getGlobal("on_key") orelse return;
    if (cb.asFunction() == null) return;
    g_vm_running = true;
    defer g_vm_running = false;
    _ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(scancode)}, null) catch |err| {
        serial.emit("bridge: on_key threw: ");
        serial.emit(@errorName(err));
        serial.emit("\r\n");
    };
    flushConsole(vm);
}

fn flushConsole(vm: *revo.VM) void {
    const func = vm.getGlobal("flush_console") orelse return;
    if (func.asFunction() == null) return;
    _ = vm.callFunctionParts(func, null, &.{}, null) catch {};
}

pub fn onTick(ticks: u64) void {
    const vm = g_vm orelse return;
    const cb = vm.getGlobal("on_tick") orelse return;
    if (cb.asFunction() == null) return;
    g_vm_running = true;
    defer g_vm_running = false;
    _ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(ticks)}, null) catch {};
}

var g_vm_running: bool = false;
pub fn vmBusy() bool {
    return g_vm_running;
}

pub fn fsSize(name: []const u8) i64 {
    const vm = g_vm orelse return -1;
    const func = vm.getGlobal("fs_size") orelse return -1;
    const name_val = vm.ownValueString(name) catch return -1;
    g_vm_running = true;
    defer g_vm_running = false;
    const result = vm.callFunctionParts(func, null, &[_]revo.Value{name_val}, null) catch return -1;
    return @intFromFloat(result.asNumOpt() orelse return -1);
}

pub fn fsReadInto(name: []const u8, offset: u64, dst: []u8) usize {
    const vm = g_vm orelse return 0;
    const func = vm.getGlobal("fs_read_bytes") orelse return 0;
    const name_val = vm.ownValueString(name) catch return 0;
    g_vm_running = true;
    defer g_vm_running = false;
    const result = vm.callFunctionParts(func, null, &[_]revo.Value{
        name_val,
        revo.Value.new.num(offset),
        revo.Value.new.num(dst.len),
    }, null) catch return 0;
    const sid = result.asString() orelse return 0;
    const bytes = vm.stringValue(sid);
    const count = @min(bytes.len, dst.len);
    @memcpy(dst[0..count], bytes[0..count]);
    return count;
}

pub fn fsStore(name: []const u8, data: []const u8) bool {
    const vm = g_vm orelse return false;
    const func = vm.getGlobal("fs_store") orelse return false;
    const name_val = vm.ownValueString(name) catch return false;
    const data_val = vm.ownValueString(data) catch return false;
    g_vm_running = true;
    defer g_vm_running = false;
    const result = vm.callFunctionParts(func, null, &[_]revo.Value{ name_val, data_val }, null) catch return false;
    return (result.asNumOpt() orelse 0) == 1;
}

pub fn fsWriteAt(name: []const u8, offset: u64, data: []const u8) bool {
    const vm = g_vm orelse return false;
    const func = vm.getGlobal("fs_write_at") orelse return false;
    const name_val = vm.ownValueString(name) catch return false;
    const data_val = vm.ownValueString(data) catch return false;
    g_vm_running = true;
    defer g_vm_running = false;
    const result = vm.callFunctionParts(func, null, &[_]revo.Value{ name_val, revo.Value.new.num(@as(f64, @floatFromInt(offset))), data_val }, null) catch return false;
    return (result.asNumOpt() orelse 0) == 1;
}