compiler/
lib/
examples/
std/
arch/
rv64/
asm/
asm.rad
23.3 KiB
decode.rad
14.3 KiB
emit.rad
31.8 KiB
encode.rad
21.6 KiB
isel.rad
51.6 KiB
printer.rad
12.8 KiB
tests.rad
17.2 KiB
rv64.rad
14.4 KiB
char/
collections/
lang/
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
4.1 KiB
README
2.5 KiB
STYLE
2.6 KiB
std.lib
1.2 KiB
std.lib.test
347 B
lib/std/arch/rv64/emit.rad
raw
| 1 | //! RV64 binary emission. |
| 2 | //! |
| 3 | //! Emits RV64 machine code as `u32` list. |
| 4 | |
| 5 | use std::lang::il; |
| 6 | use std::lang::alloc; |
| 7 | use std::lang::gen; |
| 8 | use std::lang::gen::data; |
| 9 | use std::lang::gen::labels; |
| 10 | use std::lang::gen::types; |
| 11 | use std::collections::dict; |
| 12 | use std::mem; |
| 13 | |
| 14 | use super::encode; |
| 15 | |
| 16 | /// Maximum number of instructions in code buffer. |
| 17 | constant MAX_INSTRS: u32 = 2097152; |
| 18 | /// Maximum code length before byte offset overflows signed 32-bits. |
| 19 | constant MAX_CODE_LEN: u32 = 0x7FFFFFFF / super::INSTR_SIZE as u32; |
| 20 | /// Maximum positive value encodable by a signed 32-bit address calculation. |
| 21 | constant MAX_I32_ADDR: u32 = 0x7FFFFFFF; |
| 22 | /// Maximum number of pending branches awaiting patching. |
| 23 | constant MAX_PENDING: u32 = 65536; |
| 24 | /// Maximum number of function entries. |
| 25 | constant MAX_FUNCS: u32 = 4096; |
| 26 | /// Maximum number of debug entries. |
| 27 | constant MAX_DEBUG_ENTRIES: u32 = 524288; |
| 28 | |
| 29 | ////////////////////// |
| 30 | // Emission Context // |
| 31 | ////////////////////// |
| 32 | |
| 33 | /// Branch/jump that needs offset patching after all blocks are emitted. |
| 34 | export record PendingBranch: Copy { |
| 35 | /// Index into code buffer where the branch instruction is. |
| 36 | index: u32, |
| 37 | /// Target block index. |
| 38 | target: u32, |
| 39 | /// Type of branch for re-encoding. |
| 40 | kind: BranchKind, |
| 41 | } |
| 42 | |
| 43 | /// Type of branch instruction. |
| 44 | export union BranchKind: Copy { |
| 45 | /// Conditional branch (B-type encoding). |
| 46 | Cond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg }, |
| 47 | /// Inverted conditional branch (B-type encoding with negated condition). |
| 48 | InvertedCond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg }, |
| 49 | /// Unconditional jump (J-type encoding). |
| 50 | Jump, |
| 51 | } |
| 52 | |
| 53 | /// Function call that needs offset patching. |
| 54 | export record PendingCall: Copy { |
| 55 | /// Index in code buffer where the call was emitted. |
| 56 | index: u32, |
| 57 | /// Target function name. |
| 58 | target: *[u8], |
| 59 | } |
| 60 | |
| 61 | /// Assembly jump that needs offset patching after all text is emitted. |
| 62 | export record PendingJump: Copy { |
| 63 | /// Index in code buffer where the jump was emitted. |
| 64 | index: u32, |
| 65 | /// Target function name. |
| 66 | target: *[u8], |
| 67 | /// Destination register. |
| 68 | rd: gen::Reg, |
| 69 | } |
| 70 | |
| 71 | /// Address load that needs patching after layout is known. |
| 72 | export record PendingAddrLoad: Copy { |
| 73 | /// Index in code buffer where the load was emitted. |
| 74 | index: u32, |
| 75 | /// Target function or data symbol name. |
| 76 | target: *[u8], |
| 77 | /// Destination register. |
| 78 | rd: gen::Reg, |
| 79 | /// Whether this is an absolute data symbol address. |
| 80 | isData: bool, |
| 81 | } |
| 82 | |
| 83 | /// Adjusted base register and offset for addressing. |
| 84 | export record AdjustedOffset: Copy { |
| 85 | /// Base register. |
| 86 | base: gen::Reg, |
| 87 | /// Byte offset from register. |
| 88 | offset: i32, |
| 89 | } |
| 90 | |
| 91 | /// Callee-saved register with its stack offset. |
| 92 | export record SavedReg: Copy { |
| 93 | /// Register to save/restore. |
| 94 | reg: gen::Reg, |
| 95 | /// Offset from SP. |
| 96 | offset: i32, |
| 97 | } |
| 98 | |
| 99 | /// Emission context. Tracks state during code generation. |
| 100 | export record Emitter { |
| 101 | /// First failure recorded during generation. |
| 102 | error: ?super::Error, |
| 103 | /// Emitted instructions storage. |
| 104 | code: *mut [u32], |
| 105 | /// Current number of emitted instructions. |
| 106 | codeLen: u32, |
| 107 | /// Local branches needing offset patching. |
| 108 | pendingBranches: *mut [PendingBranch], |
| 109 | /// Function calls needing offset patching. |
| 110 | pendingCalls: *mut [PendingCall], |
| 111 | /// Assembly jumps needing offset patching. |
| 112 | pendingJumps: *mut [PendingJump], |
| 113 | /// Function address loads needing offset patching. |
| 114 | pendingAddrLoads: *mut [PendingAddrLoad], |
| 115 | /// Block label tracking. |
| 116 | labels: labels::Labels, |
| 117 | /// Function start positions for printing. |
| 118 | funcs: *mut [types::FuncAddr], |
| 119 | /// Debug entries mapping PCs to source locations. |
| 120 | debugEntries: *mut [types::DebugEntry], |
| 121 | /// Number of debug entries recorded. |
| 122 | debugEntriesLen: u32, |
| 123 | } |
| 124 | |
| 125 | /// Computed stack frame layout for a function. |
| 126 | export record Frame: Copy { |
| 127 | /// Total frame size in bytes (aligned). |
| 128 | totalSize: i32, |
| 129 | /// Callee-saved registers and their offsets. |
| 130 | // TODO: Use constant length when language supports it. |
| 131 | savedRegs: [SavedReg; super::NUM_SAVED_REGISTERS], |
| 132 | /// Number of saved registers. |
| 133 | savedRegsLen: u32, |
| 134 | /// Epilogue block index for return jumps. |
| 135 | epilogueBlock: u32, |
| 136 | /// Whether this is a leaf function. Leaf functions |
| 137 | /// skip saving/restoring RA since it is never clobbered. |
| 138 | isLeaf: bool, |
| 139 | /// Whether the function has dynamic stack allocations. |
| 140 | /// When false, SP never changes after the prologue. |
| 141 | isDynamic: bool, |
| 142 | } |
| 143 | |
| 144 | /// Compute frame layout from local size and used callee-saved registers. |
| 145 | export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame throws (super::Error) { |
| 146 | let mut frame = Frame { |
| 147 | totalSize: 0, |
| 148 | savedRegs: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS], |
| 149 | savedRegsLen: 0, |
| 150 | epilogueBlock, |
| 151 | isLeaf, |
| 152 | isDynamic, |
| 153 | }; |
| 154 | // Skip frame allocation for leaf functions with no locals and no |
| 155 | // callee-saved registers. Leaf functions don't call other functions, |
| 156 | // so RA is never clobbered and doesn't need saving. |
| 157 | if isLeaf and localSize == 0 and usedCalleeSaved == 0 { |
| 158 | return frame; |
| 159 | } |
| 160 | // Compute total frame size. Includes RA and FP registers. |
| 161 | let savedRegs = mem::popCount(usedCalleeSaved) + 2; |
| 162 | if localSize < 0 or usedCalleeSaved >> (super::NUM_SAVED_REGISTERS as u32) <> 0 { |
| 163 | throw super::Error::Capacity; |
| 164 | } |
| 165 | let total64 = (localSize as u64 + savedRegs as u64 * super::DWORD_SIZE as u64 |
| 166 | + super::STACK_ALIGNMENT as u64 - 1) & ~(super::STACK_ALIGNMENT as u64 - 1); |
| 167 | if total64 > 0x7fffffff { |
| 168 | throw super::Error::Capacity; |
| 169 | } |
| 170 | let totalSize = total64 as i32; |
| 171 | set frame.totalSize = totalSize; |
| 172 | |
| 173 | // Build list of callee-saved registers with offsets. |
| 174 | let mut offset = totalSize - (super::DWORD_SIZE * 3); |
| 175 | for reg, i in super::CALLEE_SAVED { |
| 176 | // Check if this register is in use. |
| 177 | if (usedCalleeSaved & (1 << i)) <> 0 { |
| 178 | set frame.savedRegs[frame.savedRegsLen] = SavedReg { |
| 179 | reg, |
| 180 | offset, |
| 181 | }; |
| 182 | set frame.savedRegsLen += 1; |
| 183 | set offset -= super::DWORD_SIZE; |
| 184 | } |
| 185 | } |
| 186 | return frame; |
| 187 | } |
| 188 | |
| 189 | /// Create a new emitter. |
| 190 | export unsafe fn emitter(arena: &mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) { |
| 191 | let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS); |
| 192 | let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING); |
| 193 | let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING); |
| 194 | let pendingJumps = try alloc::allocSlice(arena, @sizeOf(PendingJump), @alignOf(PendingJump), MAX_PENDING); |
| 195 | let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING); |
| 196 | let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN); |
| 197 | let funcEntries = try alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), labels::FUNC_TABLE_SIZE); |
| 198 | let funcs = try alloc::allocSlice(arena, @sizeOf(types::FuncAddr), @alignOf(types::FuncAddr), MAX_FUNCS); |
| 199 | |
| 200 | let mut debugEntries: *mut [types::DebugEntry] = &mut []; |
| 201 | if debug { |
| 202 | set debugEntries = try alloc::allocSlice( |
| 203 | arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES |
| 204 | ) as *mut [types::DebugEntry]; |
| 205 | } |
| 206 | let mut pendingBranchesBuf = pendingBranches as *mut [PendingBranch]; |
| 207 | let mut pendingCallsBuf = pendingCalls as *mut [PendingCall]; |
| 208 | let mut pendingJumpsBuf = pendingJumps as *mut [PendingJump]; |
| 209 | let mut pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad]; |
| 210 | let mut funcsBuf = funcs as *mut [types::FuncAddr]; |
| 211 | set pendingBranchesBuf.len = 0; |
| 212 | set pendingCallsBuf.len = 0; |
| 213 | set pendingJumpsBuf.len = 0; |
| 214 | set pendingAddrLoadsBuf.len = 0; |
| 215 | set funcsBuf.len = 0; |
| 216 | return Emitter { |
| 217 | error: nil, |
| 218 | code: code as *mut [u32], |
| 219 | codeLen: 0, |
| 220 | pendingBranches: pendingBranchesBuf, |
| 221 | pendingCalls: pendingCallsBuf, |
| 222 | pendingJumps: pendingJumpsBuf, |
| 223 | pendingAddrLoads: pendingAddrLoadsBuf, |
| 224 | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
| 225 | funcs: funcsBuf, |
| 226 | debugEntries, |
| 227 | debugEntriesLen: 0, |
| 228 | }; |
| 229 | } |
| 230 | |
| 231 | /////////////////////// |
| 232 | // Emission Helpers // |
| 233 | /////////////////////// |
| 234 | |
| 235 | /// Emit a single instruction. |
| 236 | export fn emit(e: &mut Emitter, instr: u32) { |
| 237 | if e.error <> nil { |
| 238 | return; |
| 239 | } |
| 240 | if e.codeLen >= e.code.len { |
| 241 | set e.error = super::Error::Capacity; |
| 242 | return; |
| 243 | } |
| 244 | set e.code[e.codeLen] = instr; |
| 245 | set e.codeLen += 1; |
| 246 | } |
| 247 | |
| 248 | /// Compute branch offset to a function by name. |
| 249 | export fn branchOffsetToFunc(e: &mut Emitter, srcIndex: u32, name: *[u8]) -> i32 { |
| 250 | if e.error <> nil { |
| 251 | return 0; |
| 252 | } |
| 253 | let target = dict::get(&e.labels.funcs, name) else { |
| 254 | set e.error = super::Error::Symbol; |
| 255 | return 0; |
| 256 | }; |
| 257 | let offset = target as i64 - srcIndex as i64 * super::INSTR_SIZE as i64; |
| 258 | if offset < -0x80000000 or offset > 0x7fffffff { |
| 259 | set e.error = super::Error::Relocation; |
| 260 | return 0; |
| 261 | } |
| 262 | return offset as i32; |
| 263 | } |
| 264 | |
| 265 | /// Patch an instruction at a given index. |
| 266 | export fn patch(e: &mut Emitter, index: u32, instr: u32) { |
| 267 | if e.error <> nil { |
| 268 | return; |
| 269 | } |
| 270 | if index >= e.codeLen or index >= e.code.len { |
| 271 | set e.error = super::Error::Capacity; |
| 272 | return; |
| 273 | } |
| 274 | set e.code[index] = instr; |
| 275 | } |
| 276 | |
| 277 | /// Record a block's address for branch resolution. |
| 278 | export fn recordBlock(e: &mut Emitter, blockIdx: u32) { |
| 279 | if e.error <> nil { |
| 280 | return; |
| 281 | } |
| 282 | if e.codeLen > MAX_CODE_LEN or blockIdx >= e.labels.blockOffsets.len { |
| 283 | set e.error = super::Error::Capacity; |
| 284 | return; |
| 285 | } |
| 286 | labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE); |
| 287 | } |
| 288 | |
| 289 | /// Record a function's code offset for call resolution. |
| 290 | export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) { |
| 291 | let codeLen = e.codeLen; |
| 292 | recordFuncOffsetAt(e, name, codeLen); |
| 293 | } |
| 294 | |
| 295 | /// Record a function's code offset at `index` for call resolution. |
| 296 | export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) { |
| 297 | if e.error <> nil { |
| 298 | return; |
| 299 | } |
| 300 | if index > MAX_CODE_LEN or e.labels.funcs.entries.len < 2 { |
| 301 | set e.error = super::Error::Capacity; |
| 302 | return; |
| 303 | } |
| 304 | if name.len == 0 { |
| 305 | set e.error = super::Error::Symbol; |
| 306 | return; |
| 307 | } |
| 308 | if dict::get(&e.labels.funcs, name) == nil and |
| 309 | e.labels.funcs.count >= e.labels.funcs.entries.len / 2 { |
| 310 | set e.error = super::Error::Capacity; |
| 311 | return; |
| 312 | } |
| 313 | dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE); |
| 314 | } |
| 315 | |
| 316 | /// Record a function's start position for printing. |
| 317 | export fn recordFunc(e: &mut Emitter, name: *[u8]) { |
| 318 | let codeLen = e.codeLen; |
| 319 | recordFuncAt(e, name, codeLen); |
| 320 | } |
| 321 | |
| 322 | /// Record a function's start position at `index` for printing. |
| 323 | export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) { |
| 324 | if e.error <> nil { |
| 325 | return; |
| 326 | } |
| 327 | if e.funcs.len >= e.funcs.cap { |
| 328 | set e.error = super::Error::Capacity; |
| 329 | return; |
| 330 | } |
| 331 | let count = e.funcs.len; |
| 332 | unsafe { |
| 333 | set e.funcs.len = count + 1; |
| 334 | } |
| 335 | set e.funcs[count] = types::FuncAddr { name, index }; |
| 336 | } |
| 337 | |
| 338 | /// Record a local branch needing later patching. |
| 339 | /// Unconditional jumps use a single slot (J-type, +-1MB range). |
| 340 | /// Conditional branches use two slots (B-type has only +-4KB range, |
| 341 | /// so large functions may need the inverted-branch + JAL fallback). |
| 342 | export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) { |
| 343 | if e.error <> nil { |
| 344 | return; |
| 345 | } |
| 346 | if e.pendingBranches.len >= e.pendingBranches.cap { |
| 347 | set e.error = super::Error::Capacity; |
| 348 | return; |
| 349 | } |
| 350 | let count = e.pendingBranches.len; |
| 351 | unsafe { |
| 352 | set e.pendingBranches.len = count + 1; |
| 353 | } |
| 354 | set e.pendingBranches[count] = PendingBranch { |
| 355 | index: e.codeLen, |
| 356 | target: targetBlock, |
| 357 | kind: kind, |
| 358 | }; |
| 359 | |
| 360 | emit(e, encode::nop()); // First slot, always needed. |
| 361 | |
| 362 | match kind { |
| 363 | case BranchKind::Jump => {}, |
| 364 | else => emit(e, encode::nop()), // Second slot for conditional branches. |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | /// Record a function call needing later patching. |
| 369 | /// Emits placeholder instructions that will be patched later. |
| 370 | /// Uses two slots to support long-distance calls. |
| 371 | export fn recordCall(e: &mut Emitter, target: *[u8]) { |
| 372 | if e.error <> nil { |
| 373 | return; |
| 374 | } |
| 375 | if e.pendingCalls.len >= e.pendingCalls.cap { |
| 376 | set e.error = super::Error::Capacity; |
| 377 | return; |
| 378 | } |
| 379 | let count = e.pendingCalls.len; |
| 380 | unsafe { |
| 381 | set e.pendingCalls.len = count + 1; |
| 382 | } |
| 383 | set e.pendingCalls[count] = PendingCall { |
| 384 | index: e.codeLen, |
| 385 | target, |
| 386 | }; |
| 387 | |
| 388 | emit(e, encode::nop()); // Placeholder for AUIPC. |
| 389 | emit(e, encode::nop()); // Placeholder for JALR. |
| 390 | } |
| 391 | |
| 392 | /// Record a jump emitted by assembly that needs whole-program patching. |
| 393 | export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
| 394 | if e.error <> nil { |
| 395 | return; |
| 396 | } |
| 397 | if e.pendingJumps.len >= e.pendingJumps.cap { |
| 398 | set e.error = super::Error::Capacity; |
| 399 | return; |
| 400 | } |
| 401 | let count = e.pendingJumps.len; |
| 402 | unsafe { |
| 403 | set e.pendingJumps.len = count + 1; |
| 404 | } |
| 405 | set e.pendingJumps[count] = PendingJump { |
| 406 | index, |
| 407 | target, |
| 408 | rd, |
| 409 | }; |
| 410 | } |
| 411 | |
| 412 | /// Record a function address load needing later patching. |
| 413 | /// Emits placeholder instructions that will be patched to load the function's address. |
| 414 | /// Uses two slots to compute long-distance addresses. |
| 415 | export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
| 416 | let codeLen = e.codeLen; |
| 417 | recordAddrLoadAt(e, target, rd, codeLen); |
| 418 | |
| 419 | emit(e, encode::nop()); // Placeholder for AUIPC. |
| 420 | emit(e, encode::nop()); // Placeholder for ADDI. |
| 421 | } |
| 422 | |
| 423 | /// Record a function address load already reserved by assembly. |
| 424 | export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
| 425 | if e.error <> nil { |
| 426 | return; |
| 427 | } |
| 428 | if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap { |
| 429 | set e.error = super::Error::Capacity; |
| 430 | return; |
| 431 | } |
| 432 | let count = e.pendingAddrLoads.len; |
| 433 | unsafe { |
| 434 | set e.pendingAddrLoads.len = count + 1; |
| 435 | } |
| 436 | set e.pendingAddrLoads[count] = PendingAddrLoad { |
| 437 | index, |
| 438 | target, |
| 439 | rd: rd, |
| 440 | isData: false, |
| 441 | }; |
| 442 | } |
| 443 | |
| 444 | /// Record a data address load needing later patching. |
| 445 | /// Uses an absolute 32-bit load sequence matching the current data memory map. |
| 446 | export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
| 447 | if e.error <> nil { |
| 448 | return; |
| 449 | } |
| 450 | if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap { |
| 451 | set e.error = super::Error::Capacity; |
| 452 | return; |
| 453 | } |
| 454 | let count = e.pendingAddrLoads.len; |
| 455 | unsafe { |
| 456 | set e.pendingAddrLoads.len = count + 1; |
| 457 | } |
| 458 | set e.pendingAddrLoads[count] = PendingAddrLoad { |
| 459 | index: e.codeLen, |
| 460 | target, |
| 461 | rd: rd, |
| 462 | isData: true, |
| 463 | }; |
| 464 | |
| 465 | emit(e, encode::nop()); // Placeholder for LUI. |
| 466 | emit(e, encode::nop()); // Placeholder for ADDIW. |
| 467 | } |
| 468 | |
| 469 | /// Patch local branches and clear the pending list. |
| 470 | /// |
| 471 | /// Called after each function. |
| 472 | /// |
| 473 | /// Uses two-instruction sequences: short branches use `branch` and `nop`, |
| 474 | /// long branches use inverted branch and `jal` or `auipc` and `jalr`. |
| 475 | export unsafe fn patchLocalBranches(e: &mut Emitter) { |
| 476 | for i in 0..e.pendingBranches.len { |
| 477 | let p = e.pendingBranches[i]; |
| 478 | if e.error <> nil { |
| 479 | return; |
| 480 | } |
| 481 | if p.target >= e.labels.blockCount or p.target >= e.labels.blockOffsets.len { |
| 482 | set e.error = super::Error::Symbol; |
| 483 | return; |
| 484 | } |
| 485 | let offset64 = e.labels.blockOffsets[p.target] as i64 - p.index as i64 * super::INSTR_SIZE as i64; |
| 486 | if offset64 < -0x80000000 or offset64 > 0x7fffffff { |
| 487 | set e.error = super::Error::Relocation; |
| 488 | return; |
| 489 | } |
| 490 | let offset = offset64 as i32; |
| 491 | match p.kind { |
| 492 | case BranchKind::Cond { op, rs1, rs2 } => { |
| 493 | if encode::isBranchImm(offset) { |
| 494 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset)); |
| 495 | patch(e, p.index + 1, encode::nop()); |
| 496 | } else { |
| 497 | let adjusted = offset64 - super::INSTR_SIZE as i64; |
| 498 | if adjusted < -0x100000 or adjusted > 0xffffe { |
| 499 | set e.error = super::Error::Relocation; |
| 500 | return; |
| 501 | } |
| 502 | let adj = adjusted as i32; |
| 503 | if not encode::isJumpImm(adj) { |
| 504 | set e.error = super::Error::Relocation; |
| 505 | return; |
| 506 | } |
| 507 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
| 508 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
| 509 | } |
| 510 | }, |
| 511 | case BranchKind::InvertedCond { op, rs1, rs2 } => { |
| 512 | if encode::isBranchImm(offset) { |
| 513 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset)); |
| 514 | patch(e, p.index + 1, encode::nop()); |
| 515 | } else { |
| 516 | let adjusted = offset64 - super::INSTR_SIZE as i64; |
| 517 | if adjusted < -0x100000 or adjusted > 0xffffe { |
| 518 | set e.error = super::Error::Relocation; |
| 519 | return; |
| 520 | } |
| 521 | let adj = adjusted as i32; |
| 522 | if not encode::isJumpImm(adj) { |
| 523 | set e.error = super::Error::Relocation; |
| 524 | return; |
| 525 | } |
| 526 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
| 527 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
| 528 | } |
| 529 | }, |
| 530 | case BranchKind::Jump => { |
| 531 | // Single-slot jump (J-type, +-1MB range). |
| 532 | if not encode::isJumpImm(offset) { |
| 533 | set e.error = super::Error::Relocation; |
| 534 | return; |
| 535 | } |
| 536 | patch(e, p.index, encode::jal(super::ZERO, offset)); |
| 537 | }, |
| 538 | } |
| 539 | } |
| 540 | set e.pendingBranches.len = 0; |
| 541 | } |
| 542 | |
| 543 | /// Encode a conditional branch instruction. |
| 544 | fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 { |
| 545 | match op { |
| 546 | case il::CmpOp::Eq => return encode::beq(rs1, rs2, offset), |
| 547 | case il::CmpOp::Ne => return encode::bne(rs1, rs2, offset), |
| 548 | case il::CmpOp::Slt => return encode::blt(rs1, rs2, offset), |
| 549 | case il::CmpOp::Ult => return encode::bltu(rs1, rs2, offset), |
| 550 | } |
| 551 | } |
| 552 | |
| 553 | /// Encode an inverted conditional branch instruction. |
| 554 | fn encodeInvertedBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 { |
| 555 | match op { |
| 556 | case il::CmpOp::Eq => return encode::bne(rs1, rs2, offset), |
| 557 | case il::CmpOp::Ne => return encode::beq(rs1, rs2, offset), |
| 558 | case il::CmpOp::Slt => return encode::bge(rs1, rs2, offset), |
| 559 | case il::CmpOp::Ult => return encode::bgeu(rs1, rs2, offset), |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | /// Patch all pending function calls. |
| 564 | /// Called after all functions have been generated. |
| 565 | export fn patchCalls(e: &mut Emitter) { |
| 566 | for i in 0..e.pendingCalls.len { |
| 567 | let p = e.pendingCalls[i]; |
| 568 | let offset = branchOffsetToFunc(e, p.index, p.target); |
| 569 | let s = splitImm(offset); |
| 570 | |
| 571 | // `AUIPC scratch, hi(offset)`. |
| 572 | patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi)); |
| 573 | // `JALR ra, scratch, lo(offset)`. |
| 574 | patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo)); |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | /// Patch all pending assembly jumps. |
| 579 | export fn patchJumps(e: &mut Emitter) { |
| 580 | for i in 0..e.pendingJumps.len { |
| 581 | let p = e.pendingJumps[i]; |
| 582 | let offset = branchOffsetToFunc(e, p.index, p.target); |
| 583 | |
| 584 | if e.error <> nil { |
| 585 | return; |
| 586 | } |
| 587 | if not encode::isJumpImm(offset) { |
| 588 | set e.error = super::Error::Relocation; |
| 589 | return; |
| 590 | } |
| 591 | patch(e, p.index, encode::jal(p.rd, offset)); |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | /// Patch all pending function and data address loads. |
| 596 | /// Called after all functions have been generated and data layout is known. |
| 597 | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) { |
| 598 | for i in 0..e.pendingAddrLoads.len { |
| 599 | if e.error <> nil { |
| 600 | return; |
| 601 | } |
| 602 | let p = e.pendingAddrLoads[i]; |
| 603 | if p.isData { |
| 604 | let addr = data::lookupAddr(dataSymMap, p.target) else { |
| 605 | set e.error = super::Error::Symbol; |
| 606 | return; |
| 607 | }; |
| 608 | if addr > MAX_I32_ADDR { |
| 609 | set e.error = super::Error::Relocation; |
| 610 | return; |
| 611 | } |
| 612 | let s = splitImm(addr as i32); |
| 613 | |
| 614 | patch(e, p.index, encode::lui(p.rd, s.hi)); |
| 615 | patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo)); |
| 616 | |
| 617 | continue; |
| 618 | } |
| 619 | |
| 620 | let offset = branchOffsetToFunc(e, p.index, p.target); |
| 621 | let s = splitImm(offset); |
| 622 | // `AUIPC rd, hi(offset)`. |
| 623 | patch(e, p.index, encode::auipc(p.rd, s.hi)); |
| 624 | // `ADDI rd, rd, lo(offset)`. |
| 625 | patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo)); |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | ///////////////////////// |
| 630 | // Immediate Handling // |
| 631 | ///////////////////////// |
| 632 | |
| 633 | /// Split immediate into `hi` and `lo` bits. |
| 634 | export record SplitImm: Copy { |
| 635 | /// Upper 20 bits. |
| 636 | hi: i32, |
| 637 | /// Lower 12 bits. |
| 638 | lo: i32, |
| 639 | } |
| 640 | |
| 641 | /// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences. |
| 642 | /// Handles sign extension: if *lo* is negative, increment *hi*. |
| 643 | export fn splitImm(imm: i32) -> SplitImm { |
| 644 | let lo = imm & 0xFFF; |
| 645 | let mut hi = (imm >> 12) & 0xFFFFF; |
| 646 | // If `lo`'s sign bit is set, it will be sign-extended to negative. |
| 647 | // Compensate by incrementing `hi`. |
| 648 | if (lo & 0x800) <> 0 { |
| 649 | set hi += 1; |
| 650 | return SplitImm { hi, lo: lo | 0xFFFFF000 as i32 }; |
| 651 | } |
| 652 | return SplitImm { hi, lo }; |
| 653 | } |
| 654 | |
| 655 | /// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`]. |
| 656 | /// Returns adjusted base register and remaining offset. |
| 657 | /// |
| 658 | /// When the offset fits a 12-bit signed immediate, returns it unchanged. |
| 659 | /// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition. |
| 660 | fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset { |
| 661 | if offset >= super::MIN_IMM and offset <= super::MAX_IMM { |
| 662 | return AdjustedOffset { base, offset }; |
| 663 | } |
| 664 | let s = splitImm(offset); |
| 665 | emit(e, encode::lui(super::ADDR_SCRATCH, s.hi)); |
| 666 | emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base)); |
| 667 | |
| 668 | return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo }; |
| 669 | } |
| 670 | |
| 671 | /// Load an immediate value into a register. |
| 672 | /// Handles the full range of 64-bit immediates. |
| 673 | /// For values fitting in 12 bits, uses a single `ADDI`. |
| 674 | /// For values fitting in 32 bits, uses `LUI` + `ADDIW`. |
| 675 | /// For wider values, loads upper and lower halves then combines with shift and add. |
| 676 | export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) { |
| 677 | let immMin = super::MIN_IMM as i64; |
| 678 | let immMax = super::MAX_IMM as i64; |
| 679 | |
| 680 | if imm >= immMin and imm <= immMax { |
| 681 | emit(e, encode::addi(rd, super::ZERO, imm as i32)); |
| 682 | return; |
| 683 | } |
| 684 | // Check if the value fits in 32 bits (sign-extended). |
| 685 | let lo32 = imm as i32; |
| 686 | if lo32 as i64 == imm { |
| 687 | let s = splitImm(lo32); |
| 688 | emit(e, encode::lui(rd, s.hi)); |
| 689 | if s.lo <> 0 { |
| 690 | emit(e, encode::addiw(rd, rd, s.lo)); |
| 691 | } |
| 692 | return; |
| 693 | } |
| 694 | // Full 64-bit immediate: use only rd, no scratch registers. |
| 695 | // Load upper 32 bits first via the 32-bit path (LUI+ADDIW), |
| 696 | // then shift and add lower bits in 11-bit groups to avoid |
| 697 | // sign-extension issues with ADDI's 12-bit signed immediate. |
| 698 | let hi32 = (imm >> 32) as i32; |
| 699 | let lower = imm as i32; |
| 700 | |
| 701 | // Load upper 32 bits. |
| 702 | loadImm(e, rd, hi32 as i64); |
| 703 | // Shift left by 11, add bits [31:21]. |
| 704 | emit(e, encode::slli(rd, rd, 11)); |
| 705 | let chunkHi = (lower >> 21) & 0x7FF; |
| 706 | if chunkHi <> 0 { |
| 707 | emit(e, encode::addi(rd, rd, chunkHi)); |
| 708 | } |
| 709 | // Shift left by 11, add bits [20:10]. |
| 710 | emit(e, encode::slli(rd, rd, 11)); |
| 711 | let chunkMid = (lower >> 10) & 0x7FF; |
| 712 | if chunkMid <> 0 { |
| 713 | emit(e, encode::addi(rd, rd, chunkMid)); |
| 714 | } |
| 715 | // Shift left by 10, add bits [9:0]. |
| 716 | emit(e, encode::slli(rd, rd, 10)); |
| 717 | let chunkLo = lower & 0x3FF; |
| 718 | if chunkLo <> 0 { |
| 719 | emit(e, encode::addi(rd, rd, chunkLo)); |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | /// Emit add-immediate, handling large immediates. |
| 724 | export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) { |
| 725 | if imm >= super::MIN_IMM and imm <= super::MAX_IMM { |
| 726 | emit(e, encode::addi(rd, rs, imm)); |
| 727 | } else { |
| 728 | loadImm(e, super::SCRATCH1, imm as i64); |
| 729 | emit(e, encode::add(rd, rs, super::SCRATCH1)); |
| 730 | } |
| 731 | } |
| 732 | |
| 733 | //////////////////////// |
| 734 | // Load/Store Helpers // |
| 735 | //////////////////////// |
| 736 | |
| 737 | /// Emit unsigned load with automatic offset adjustment. |
| 738 | export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
| 739 | let adj = adjustOffset(e, base, offset); |
| 740 | match typ { |
| 741 | case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)), |
| 742 | case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)), |
| 743 | case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)), |
| 744 | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | /// Emit signed load with automatic offset adjustment. |
| 749 | export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
| 750 | let adj = adjustOffset(e, base, offset); |
| 751 | match typ { |
| 752 | case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)), |
| 753 | case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)), |
| 754 | case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)), |
| 755 | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | /// Emit store with automatic offset adjustment. |
| 760 | export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
| 761 | let adj = adjustOffset(e, base, offset); |
| 762 | match typ { |
| 763 | case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)), |
| 764 | case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)), |
| 765 | case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)), |
| 766 | case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)), |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | /// Emit 64-bit load with automatic offset adjustment. |
| 771 | export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
| 772 | let adj = adjustOffset(e, base, offset); |
| 773 | emit(e, encode::ld(rd, adj.base, adj.offset)); |
| 774 | } |
| 775 | |
| 776 | /// Emit 64-bit store with automatic offset adjustment. |
| 777 | export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
| 778 | let adj = adjustOffset(e, base, offset); |
| 779 | emit(e, encode::sd(rs, adj.base, adj.offset)); |
| 780 | } |
| 781 | |
| 782 | /// Emit 32-bit load with automatic offset adjustment. |
| 783 | export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
| 784 | let adj = adjustOffset(e, base, offset); |
| 785 | emit(e, encode::lw(rd, adj.base, adj.offset)); |
| 786 | } |
| 787 | |
| 788 | /// Emit 32-bit store with automatic offset adjustment. |
| 789 | export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
| 790 | let adj = adjustOffset(e, base, offset); |
| 791 | emit(e, encode::sw(rs, adj.base, adj.offset)); |
| 792 | } |
| 793 | |
| 794 | /// Emit 8-bit load with automatic offset adjustment. |
| 795 | export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
| 796 | let adj = adjustOffset(e, base, offset); |
| 797 | emit(e, encode::lb(rd, adj.base, adj.offset)); |
| 798 | } |
| 799 | |
| 800 | /// Emit 8-bit store with automatic offset adjustment. |
| 801 | export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
| 802 | let adj = adjustOffset(e, base, offset); |
| 803 | emit(e, encode::sb(rs, adj.base, adj.offset)); |
| 804 | } |
| 805 | |
| 806 | ////////////////////////// |
| 807 | // Prologue / Epilogue // |
| 808 | ////////////////////////// |
| 809 | |
| 810 | /// Emit function prologue. |
| 811 | /// Allocate the frame and save registers. Save FP only for dynamic frames. |
| 812 | export fn emitPrologue(e: &mut Emitter, frame: &Frame) { |
| 813 | // Fast path: leaf function with no locals. |
| 814 | if frame.totalSize == 0 { |
| 815 | return; |
| 816 | } |
| 817 | let totalSize = frame.totalSize; |
| 818 | |
| 819 | // Allocate stack frame. |
| 820 | let negFrame = 0 - totalSize; |
| 821 | if negFrame >= super::MIN_IMM { |
| 822 | emit(e, encode::addi(super::SP, super::SP, negFrame)); |
| 823 | } else { |
| 824 | loadImm(e, super::SCRATCH1, totalSize as i64); |
| 825 | emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1)); |
| 826 | } |
| 827 | // Save return address. |
| 828 | if not frame.isLeaf { |
| 829 | emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
| 830 | } |
| 831 | // Save and set FP only when dynamic allocations can move SP. |
| 832 | if frame.isDynamic { |
| 833 | emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
| 834 | emitAddImm(e, super::FP, super::SP, totalSize); |
| 835 | } |
| 836 | // Save callee-saved registers. |
| 837 | for i in 0..frame.savedRegsLen { |
| 838 | let sr = frame.savedRegs[i]; |
| 839 | emitSd(e, sr.reg, super::SP, sr.offset); |
| 840 | } |
| 841 | } |
| 842 | |
| 843 | /// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions. |
| 844 | export fn emitReturn(e: &mut Emitter, frame: &Frame) { |
| 845 | if frame.totalSize == 0 { |
| 846 | // Leaf function: no frame to tear down, emit ret directly. |
| 847 | emit(e, encode::ret()); |
| 848 | return; |
| 849 | } |
| 850 | recordBranch(e, frame.epilogueBlock, BranchKind::Jump); |
| 851 | } |
| 852 | |
| 853 | /// Emit function epilogue. |
| 854 | /// Restore saved registers and release the frame. Restore FP only for dynamic frames. |
| 855 | export fn emitEpilogue(e: &mut Emitter, frame: &Frame) { |
| 856 | // Record epilogue block address for return jumps. |
| 857 | recordBlock(e, frame.epilogueBlock); |
| 858 | |
| 859 | // Fast path: leaf function with no locals. |
| 860 | if frame.totalSize == 0 { |
| 861 | emit(e, encode::ret()); |
| 862 | return; |
| 863 | } |
| 864 | let totalSize = frame.totalSize; |
| 865 | |
| 866 | // Restore SP to post-prologue value. Only needed when dynamic stack |
| 867 | // allocation may have moved SP. |
| 868 | if frame.isDynamic { |
| 869 | emitAddImm(e, super::SP, super::FP, 0 - totalSize); |
| 870 | } |
| 871 | // Restore callee-saved registers. |
| 872 | for i in 0..frame.savedRegsLen { |
| 873 | let sr = frame.savedRegs[i]; |
| 874 | emitLd(e, sr.reg, super::SP, sr.offset); |
| 875 | } |
| 876 | // Restore FP only if the prologue saved and changed it. |
| 877 | if frame.isDynamic { |
| 878 | emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
| 879 | } |
| 880 | // Restore return address. |
| 881 | if not frame.isLeaf { |
| 882 | emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
| 883 | } |
| 884 | // Deallocate stack frame. |
| 885 | emitAddImm(e, super::SP, super::SP, totalSize); |
| 886 | emit(e, encode::ret()); |
| 887 | } |
| 888 | |
| 889 | ////////////////// |
| 890 | // Code Access // |
| 891 | ////////////////// |
| 892 | |
| 893 | /// Get emitted code as a slice. |
| 894 | export fn getCode(e: &Emitter) -> *[u32] { |
| 895 | return &e.code[..e.codeLen]; |
| 896 | } |
| 897 | |
| 898 | /// Record a debug entry mapping the current PC to a source location. |
| 899 | /// Deduplicates consecutive entries with the same location. |
| 900 | export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) { |
| 901 | if e.error <> nil { |
| 902 | return; |
| 903 | } |
| 904 | let pc = e.codeLen * super::INSTR_SIZE as u32; |
| 905 | |
| 906 | // Skip if this is the same location as the previous entry. |
| 907 | if e.debugEntriesLen > 0 { |
| 908 | let prev = &e.debugEntries[e.debugEntriesLen - 1]; |
| 909 | if prev.offset == loc.offset and prev.moduleId == loc.moduleId { |
| 910 | return; |
| 911 | } |
| 912 | } |
| 913 | if e.debugEntriesLen >= e.debugEntries.len { |
| 914 | set e.error = super::Error::Capacity; |
| 915 | return; |
| 916 | } |
| 917 | set e.debugEntries[e.debugEntriesLen] = types::DebugEntry { |
| 918 | pc, |
| 919 | moduleId: loc.moduleId, |
| 920 | offset: loc.offset, |
| 921 | }; |
| 922 | set e.debugEntriesLen += 1; |
| 923 | } |
| 924 | |
| 925 | /// Get debug entries as a slice. |
| 926 | export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] { |
| 927 | return &e.debugEntries[..e.debugEntriesLen]; |
| 928 | } |
| 929 | |
| 930 | /// Return the first error recorded during emission. |
| 931 | export fn check(e: &Emitter) throws (super::Error) { |
| 932 | if let error = e.error { |
| 933 | throw error; |
| 934 | } |
| 935 | } |