-
Notifications
You must be signed in to change notification settings - Fork 0
/
cm2.zig
222 lines (206 loc) · 6.33 KB
/
cm2.zig
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
const std = @import("std");
// Type of virtual registers and instruction data.
const Register = usize;
// Instruction opcode.
const Opcode = enum(Register) {
li,
cp,
bge,
add,
sub,
call,
ret,
put,
exit,
};
// Reference to the function executed on each Instruction.
const Operation = fn (pc: Register, sp: Register) void;
// CM2 bytecode format, after complilation.
// FIXME: this should be extern instead.
const Instruction = packed struct {
const Data = usize;
op: Operation,
d0: Data = 0,
d1: Data = 0,
d2: Data = 0,
};
// A mapping from Instruction opcodes to operations.
const operations: std.EnumArray(Opcode, Operation) = blk: {
var map = std.EnumArray(Opcode, Operation).initUndefined();
var iter = map.iterator();
while (iter.next()) |entry| {
entry.value.* = @field(Machine, @tagName(entry.key));
}
break :blk map;
};
// const operations: std.EnumArray(Opcode, Operation) = undefined;
// Maximum size of input files, arbitrarily set to 1GB.
const max_input_size = 1024 * 1024 * 1024;
// Finite stack of registers.
var stack = [_]Register{0} ** 2048;
// Compiled program, initilized once after reading bytecode.
var program: []Instruction = undefined;
pub fn main() !void {
if (std.os.argv.len != 2) {
try usage();
std.os.exit(1);
} else {
try start(std.mem.sliceTo(std.os.argv[1], 0));
}
}
// Print commandline usage information.
pub fn usage() !void {
const help =
\\Usage: {s} filename
\\
;
try std.io.getStdOut().writer().print(help, .{std.os.argv[0]});
}
// Report fatal error and exit with code 1.
pub fn report(comptime format: []const u8, args: anytype) noreturn {
std.io.getStdErr().writer().print(format, args) catch {
// All hope's lost :c
};
std.os.exit(1);
}
// Start CM2.
pub fn start(filename: []u8) !void {
const f = std.fs.cwd().openFile(filename, .{}) catch
report("error: could not open file {s}", .{filename});
defer f.close();
var r = std.io.bufferedReader(f.reader());
var input = r.reader().readAllAlloc(std.heap.page_allocator, max_input_size) catch
report("error: could not read file {s} into memory", .{filename});
var code = std.mem.bytesAsSlice([4]Register, input);
for (code) |*c, i| {
const op = std.meta.intToEnum(Opcode, c[0]) catch
report("error: invalid opcode {} at instruction #{}", .{ c[0], i });
c[0] = @ptrToInt(operations.get(op));
}
program = std.mem.bytesAsSlice(
Instruction,
@alignCast(@alignOf(Instruction), input),
);
@call(
.{},
program[0].op,
.{ 0, 0 },
);
}
// All operations of CM2.
const Machine = struct {
// Load an immediate value into rX.
// > li rX imm N/A
pub fn li(pc: Register, sp: Register) void {
const c = program[pc];
stack[sp - c.d0] = c.d1;
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
// Copy the value of rY to rX.
// > cp rX rY N/A
pub fn cp(pc: Register, sp: Register) void {
const c = program[pc];
stack[sp - c.d0] = stack[sp - c.d1];
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
// Branch to lbl if rX is greater than or equal to rY.
// > bge rX rY lbl
pub fn bge(pc: Register, sp: Register) void {
const c = program[pc];
// FIXME: this is a weird way of writing this.
var o: Operation = undefined;
var p: Register = undefined;
if (stack[sp - c.d0] >= stack[sp - c.d1]) {
o = program[c.d2].op;
p = c.d2;
} else {
o = program[pc + 1].op;
p = pc + 1;
}
@call(.{ .modifier = .always_tail }, o, .{ p, sp });
}
// Return from a function by collapsing its frame.
// > ret N/A N/A N/A
pub fn ret(pc: Register, sp: Register) void {
// Thanks Zig :P
_ = pc;
_ = sp;
return;
}
// Write into rX the value of (rY + rZ).
// > add rX rY rZ
pub fn add(pc: Register, sp: Register) void {
const c = program[pc];
stack[sp - c.d0] = stack[sp - c.d1] + stack[sp - c.d2];
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
// Write into rX the value of (rY - rZ).
// > sub rX rY rZ
pub fn sub(pc: Register, sp: Register) void {
const c = program[pc];
stack[sp - c.d0] = stack[sp - c.d1] - stack[sp - c.d2];
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
// Call function with imm1 arguments, imm2 frame size and lbl branch target.
// > call imm1 imm2 lbl
// [ rN, ..., r0 ]
// ^ sp (rX = stack[X])
// Arguments are r1, r2, .. r(imm1), they're copied from the current frame to next frame.
// The return value is r0, it's copied from the next frame to the current frame.
// There is no need for a return address, execution will return here if and only if we reach a `ret`.
pub fn call(pc: Register, sp: Register) void {
const c = program[pc];
const bp = sp + c.d1;
std.mem.copy(
Register,
stack[(bp - c.d0)..bp],
stack[(sp - c.d0)..sp],
);
@call(
.{},
program[c.d2].op,
.{ c.d2, bp },
);
stack[sp] = stack[bp];
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
// Exit with rX code.
// > exit rX N/A N/A
pub fn exit(pc: Register, sp: Register) void {
const c = program[pc];
std.process.exit(@intCast(u8, stack[sp - c.d0]));
}
// Print a register to stdout.
// > put rX N/A N/A
pub fn put(pc: Register, sp: Register) void {
const c = program[pc];
std.io.getStdOut().writer().print("{}\n", .{stack[sp - c.d0]}) catch {
// TODO: handle this by throwing an exception catchable by the host language.
};
@call(
.{ .modifier = .always_tail },
program[pc + 1].op,
.{ pc + 1, sp },
);
}
};