master
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4const Target = std.Target;
5
6const Zcu = @import("../../Zcu.zig");
7const Mir = @import("Mir.zig");
8const abi = @import("abi.zig");
9
10pub const Memory = struct {
11 base: Base,
12 mod: Mod,
13
14 pub const Base = union(enum) {
15 reg: Register,
16 frame: FrameIndex,
17 };
18
19 pub const Mod = struct {
20 size: Size,
21 unsigned: bool,
22 disp: i32 = 0,
23 };
24
25 pub const Size = enum(u4) {
26 /// Byte, 1 byte
27 byte,
28 /// Half word, 2 bytes
29 hword,
30 /// Word, 4 bytes
31 word,
32 /// Double word, 8 Bytes
33 dword,
34
35 pub fn fromByteSize(size: u64) Size {
36 return switch (size) {
37 1...1 => .byte,
38 2...2 => .hword,
39 3...4 => .word,
40 5...8 => .dword,
41 else => std.debug.panic("fromByteSize {}", .{size}),
42 };
43 }
44
45 pub fn fromBitSize(bit_size: u64) Size {
46 return switch (bit_size) {
47 8 => .byte,
48 16 => .hword,
49 32 => .word,
50 64 => .dword,
51 else => unreachable,
52 };
53 }
54
55 pub fn bitSize(s: Size) u64 {
56 return switch (s) {
57 .byte => 8,
58 .hword => 16,
59 .word => 32,
60 .dword => 64,
61 };
62 }
63 };
64
65 /// Asserts `mem` can be represented as a `FrameLoc`.
66 pub fn toFrameLoc(mem: Memory, mir: Mir) Mir.FrameLoc {
67 const offset: i32 = mem.mod.disp;
68
69 switch (mem.base) {
70 .reg => |reg| {
71 return .{
72 .base = reg,
73 .disp = offset,
74 };
75 },
76 .frame => |index| {
77 const base_loc = mir.frame_locs.get(@intFromEnum(index));
78 return .{
79 .base = base_loc.base,
80 .disp = base_loc.disp + offset,
81 };
82 },
83 }
84 }
85};
86
87pub const Immediate = union(enum) {
88 signed: i32,
89 unsigned: u64,
90
91 pub fn u(x: u64) Immediate {
92 return .{ .unsigned = x };
93 }
94
95 pub fn s(x: i32) Immediate {
96 return .{ .signed = x };
97 }
98
99 pub fn asSigned(imm: Immediate, bit_size: u64) i64 {
100 return switch (imm) {
101 .signed => |x| switch (bit_size) {
102 1, 8 => @as(i8, @intCast(x)),
103 16 => @as(i16, @intCast(x)),
104 32, 64 => x,
105 else => unreachable,
106 },
107 .unsigned => |x| switch (bit_size) {
108 1, 8 => @as(i8, @bitCast(@as(u8, @intCast(x)))),
109 16 => @as(i16, @bitCast(@as(u16, @intCast(x)))),
110 32 => @as(i32, @bitCast(@as(u32, @intCast(x)))),
111 64 => @bitCast(x),
112 else => unreachable,
113 },
114 };
115 }
116
117 pub fn asBits(imm: Immediate, comptime T: type) T {
118 const int_info = @typeInfo(T).int;
119 if (int_info.signedness != .unsigned) @compileError("Immediate.asBits needs unsigned T");
120 return switch (imm) {
121 .signed => |x| @bitCast(@as(std.meta.Int(.signed, int_info.bits), @intCast(x))),
122 .unsigned => |x| @intCast(x),
123 };
124 }
125};
126
127pub const CSR = enum(u12) {
128 vl = 0xC20,
129 vtype = 0xC21,
130 vlenb = 0xC22,
131};
132
133pub const Register = enum(u8) {
134 // zig fmt: off
135
136 // base extension registers
137
138 zero, // zero
139 ra, // return address. caller saved
140 sp, // stack pointer. callee saved.
141 gp, // global pointer
142 tp, // thread pointer
143 t0, t1, t2, // temporaries. caller saved.
144 s0, // s0/fp, callee saved.
145 s1, // callee saved.
146 a0, a1, // fn args/return values. caller saved.
147 a2, a3, a4, a5, a6, a7, // fn args. caller saved.
148 s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved.
149 t3, t4, t5, t6, // caller saved
150
151 x0, x1, x2, x3, x4, x5, x6, x7,
152 x8, x9, x10, x11, x12, x13, x14, x15,
153 x16, x17, x18, x19, x20, x21, x22, x23,
154 x24, x25, x26, x27, x28, x29, x30, x31,
155
156
157 // F extension registers
158
159 ft0, ft1, ft2, ft3, ft4, ft5, ft6, ft7, // float temporaries. caller saved.
160 fs0, fs1, // float saved. callee saved.
161 fa0, fa1, // float arg/ret. caller saved.
162 fa2, fa3, fa4, fa5, fa6, fa7, // float arg. called saved.
163 fs2, fs3, fs4, fs5, fs6, fs7, fs8, fs9, fs10, fs11, // float saved. callee saved.
164 ft8, ft9, ft10, ft11, // foat temporaries. calller saved.
165
166 // this register is accessed only through API instructions instead of directly
167 // fcsr,
168
169 f0, f1, f2, f3, f4, f5, f6, f7,
170 f8, f9, f10, f11, f12, f13, f14, f15,
171 f16, f17, f18, f19, f20, f21, f22, f23,
172 f24, f25, f26, f27, f28, f29, f30, f31,
173
174
175 // V extension registers
176 v0, v1, v2, v3, v4, v5, v6, v7,
177 v8, v9, v10, v11, v12, v13, v14, v15,
178 v16, v17, v18, v19, v20, v21, v22, v23,
179 v24, v25, v26, v27, v28, v29, v30, v31,
180
181 // zig fmt: on
182
183 /// in RISC-V registers are stored as 5 bit IDs and a register can have
184 /// two names. Example being `zero` and `x0` are the same register and have the
185 /// same ID, but are two different entries in the enum. We store floating point
186 /// registers in the same enum. RISC-V uses the same IDs for `f0` and `x0` by
187 /// infering which register is being talked about given the instruction it's in.
188 ///
189 /// The goal of this function is to return the same ID for `zero` and `x0` but two
190 /// seperate IDs for `x0` and `f0`. We will assume that each register set has 32 registers
191 /// and is repeated twice, once for the named version, once for the number version.
192 pub fn id(reg: Register) std.math.IntFittingRange(0, @typeInfo(Register).@"enum".fields.len) {
193 const base = switch (@intFromEnum(reg)) {
194 // zig fmt: off
195 @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => @intFromEnum(Register.zero),
196 @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => @intFromEnum(Register.ft0),
197 @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => @intFromEnum(Register.v0),
198 else => unreachable,
199 // zig fmt: on
200 };
201
202 return @intCast(base + reg.encodeId());
203 }
204
205 pub fn encodeId(reg: Register) u5 {
206 return @truncate(@intFromEnum(reg));
207 }
208
209 pub fn dwarfNum(reg: Register) u8 {
210 return reg.id();
211 }
212
213 pub fn bitSize(reg: Register, zcu: *const Zcu) u32 {
214 return switch (@intFromEnum(reg)) {
215 // zig fmt: off
216 @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => 64,
217 @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => if (zcu.getTarget().cpu.has(.riscv, .d)) 64 else 32,
218 @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => 256, // TODO: look at suggestVectorSize
219 else => unreachable,
220 // zig fmt: on
221 };
222 }
223
224 pub fn class(reg: Register) abi.RegisterClass {
225 return switch (@intFromEnum(reg)) {
226 // zig fmt: off
227 @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => .int,
228 @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => .float,
229 @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => .vector,
230 else => unreachable,
231 // zig fmt: on
232 };
233 }
234};
235
236pub const FrameIndex = enum(u32) {
237 /// This index refers to the return address.
238 ret_addr,
239 /// This index refers to the frame pointer.
240 base_ptr,
241 /// This index refers to the entire stack frame.
242 stack_frame,
243 /// This index referes to where in the stack frame the args are spilled to.
244 args_frame,
245 /// This index referes to a frame dedicated to setting up args for function called
246 /// in this function. Useful for aligning args separately.
247 call_frame,
248 /// This index referes to the frame where callee saved registers are spilled and restored from.
249 spill_frame,
250 /// Other indices are used for local variable stack slots
251 _,
252
253 pub const named_count = @typeInfo(FrameIndex).@"enum".fields.len;
254
255 pub fn isNamed(fi: FrameIndex) bool {
256 return @intFromEnum(fi) < named_count;
257 }
258};
259
260/// A linker symbol not yet allocated in VM.
261pub const Symbol = struct {
262 /// Index of the containing atom.
263 atom_index: u32,
264 /// Index into the linker's symbol table.
265 sym_index: u32,
266};
267
268pub const VType = packed struct(u8) {
269 vlmul: VlMul,
270 vsew: VSew,
271 vta: bool,
272 vma: bool,
273};
274
275const VSew = enum(u3) {
276 @"8" = 0b000,
277 @"16" = 0b001,
278 @"32" = 0b010,
279 @"64" = 0b011,
280};
281
282const VlMul = enum(u3) {
283 mf8 = 0b101,
284 mf4 = 0b110,
285 mf2 = 0b111,
286 m1 = 0b000,
287 m2 = 0b001,
288 m4 = 0b010,
289 m8 = 0b011,
290};