rv64: Return bounded code generation errors
2416812ed6ba8766cbb31a6360723c4008c1068d388136cf19d8155d9969f7ad
1 parent
591e5557
Makefile
+9 -1
| 21 | 21 | ||
| 22 | 22 | # Verify the emulator binary exists. |
|
| 23 | 23 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 24 | 24 | ||
| 25 | 25 | default: emulator $(RAD_BIN) |
|
| 26 | - | test: emulator seed-test std-test bin-test |
|
| 26 | + | test: emulator seed-test std-test backend-test bin-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 126 | 126 | .PHONY: test clean default seed-test std-test bin-test seed \ |
|
| 127 | 127 | clean-std-test clean-bin-test clean-rad emulator |
|
| 128 | 128 | .SUFFIXES: |
|
| 129 | 129 | .DELETE_ON_ERROR: |
|
| 130 | 130 | .SILENT: |
|
| 131 | + | ||
| 132 | + | # Backend bounds use production std to keep compiler test input bounded. |
|
| 133 | + | .PHONY: backend-test |
|
| 134 | + | backend-test: test/backend.rv64 |
|
| 135 | + | @$(EMU) $(EMU_FLAGS) -run test/backend.rv64 |
|
| 136 | + | ||
| 137 | + | test/backend.rv64: test/backend.rad $(STD_LIB) $(RAD_BIN) |
|
| 138 | + | @$(RADIANCE) $(STD) -pkg backend -mod test/backend.rad -entry backend -o $@ |
compiler/radiance.rad
+14 -3
| 989 | 989 | case CodegenEntryMode::DefaultEntry => { |
|
| 990 | 990 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
|
| 991 | 991 | } |
|
| 992 | 992 | else => {} |
|
| 993 | 993 | } |
|
| 994 | - | let mut generator = rv64::beginProgram( |
|
| 994 | + | let mut generator = try rv64::beginProgram( |
|
| 995 | 995 | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug }, |
|
| 996 | 996 | &mut res.arena |
|
| 997 | - | ); |
|
| 997 | + | ) catch { |
|
| 998 | + | throw Error::Other; |
|
| 999 | + | }; |
|
| 998 | 1000 | let mut codegenCtx = codegen::Context { |
|
| 999 | 1001 | generator: &mut generator, |
|
| 1000 | 1002 | fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena, |
|
| 1001 | 1003 | }; |
|
| 1002 | 1004 | let mut low = lower::lowerer( |
| 1022 | 1024 | else => {} |
|
| 1023 | 1025 | } |
|
| 1024 | 1026 | if let path = codegenOptions.logPath { |
|
| 1025 | 1027 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1026 | 1028 | } |
|
| 1027 | - | return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]); |
|
| 1029 | + | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch error { |
|
| 1030 | + | match error { |
|
| 1031 | + | case rv64::Error::Allocation => io::printError("radiance: code generation allocation failed\n"), |
|
| 1032 | + | case rv64::Error::Capacity => io::printError("radiance: code generation capacity exceeded\n"), |
|
| 1033 | + | case rv64::Error::Symbol => io::printError("radiance: invalid code generation symbol\n"), |
|
| 1034 | + | case rv64::Error::Data(_) => io::printError("radiance: data section generation failed\n"), |
|
| 1035 | + | case rv64::Error::Relocation => io::printError("radiance: code generation relocation out of range\n"), |
|
| 1036 | + | } |
|
| 1037 | + | throw Error::Other; |
|
| 1038 | + | }; |
|
| 1028 | 1039 | } |
|
| 1029 | 1040 | ||
| 1030 | 1041 | /// Lower, optionally dump, and optionally generate binary output. |
|
| 1031 | 1042 | unsafe fn compile( |
|
| 1032 | 1043 | ctx: *unsafe mut CompileContext, |
lib/std/arch/rv64.rad
+59 -17
| 27 | 27 | use std::lang::gen::labels; |
|
| 28 | 28 | use std::lang::gen::regalloc; |
|
| 29 | 29 | use std::lang::gen::data; |
|
| 30 | 30 | use std::lang::gen::types; |
|
| 31 | 31 | ||
| 32 | + | /// Recoverable code-generation failure. |
|
| 33 | + | export union Error: Copy { |
|
| 34 | + | /// The code-generation or function arena is full. |
|
| 35 | + | Allocation, |
|
| 36 | + | /// A fixed output or metadata table is full. |
|
| 37 | + | Capacity, |
|
| 38 | + | /// A required symbol is missing or invalid. |
|
| 39 | + | Symbol, |
|
| 40 | + | /// A branch or address exceeds its instruction range. |
|
| 41 | + | Relocation, |
|
| 42 | + | /// Data section layout or emission failed. |
|
| 43 | + | Data(data::Error), |
|
| 44 | + | } |
|
| 45 | + | ||
| 32 | 46 | //////////////// |
|
| 33 | 47 | // Registers // |
|
| 34 | 48 | //////////////// |
|
| 35 | 49 | ||
| 36 | 50 | export constant ZERO: gen::Reg = gen::Reg(0); /// Hard-wired zero. |
| 212 | 226 | ||
| 213 | 227 | /// Begin RV64 code generation for a program's global state. |
|
| 214 | 228 | export unsafe fn beginProgram( |
|
| 215 | 229 | options: ProgramOptions, |
|
| 216 | 230 | arena: &mut alloc::Arena |
|
| 217 | - | ) -> Generator { |
|
| 218 | - | let mut e = try! emit::emitter(arena, options.debug); |
|
| 231 | + | ) -> Generator throws (Error) { |
|
| 232 | + | let checkpoint = alloc::save(arena); |
|
| 233 | + | let mut e = try emit::emitter(arena, options.debug) catch { |
|
| 234 | + | alloc::restore(arena, checkpoint); |
|
| 235 | + | throw Error::Allocation; |
|
| 236 | + | }; |
|
| 219 | 237 | ||
| 220 | 238 | // Emit placeholder entry jump when requested. |
|
| 221 | 239 | // We'll patch this at the end once we know where the function is. |
|
| 222 | 240 | match options.entryPatch { |
|
| 223 | 241 | case EntryPatch::Reserved(_) => { |
| 237 | 255 | export unsafe fn generateFunction( |
|
| 238 | 256 | generator: &mut Generator, |
|
| 239 | 257 | func: *unsafe il::Fn, |
|
| 240 | 258 | arena: &mut alloc::Arena |
|
| 241 | 259 | ) { |
|
| 242 | - | if func.isExtern { |
|
| 260 | + | if generator.e.error <> nil or func.isExtern { |
|
| 243 | 261 | return; |
|
| 244 | 262 | } |
|
| 245 | 263 | let checkpoint = alloc::save(arena); |
|
| 246 | 264 | let config = targetConfig(); |
|
| 247 | - | let ralloc = try! regalloc::allocate(func, &config, arena); |
|
| 265 | + | let ralloc = try regalloc::allocate(func, &config, arena) catch { |
|
| 266 | + | alloc::restore(arena, checkpoint); |
|
| 267 | + | set generator.e.error = Error::Allocation; |
|
| 268 | + | return; |
|
| 269 | + | }; |
|
| 248 | 270 | ||
| 249 | 271 | isel::selectFn(&mut generator.e, &ralloc, func); |
|
| 250 | 272 | ||
| 251 | 273 | // Reclaim unused memory after instruction selection. |
|
| 252 | 274 | alloc::restore(arena, checkpoint); |
| 304 | 326 | globalData: &[il::Data], |
|
| 305 | 327 | storage: Storage, |
|
| 306 | 328 | roDataPrefix: *[u8], |
|
| 307 | 329 | roDataBuf: &mut [u8], |
|
| 308 | 330 | rwDataBuf: &mut [u8] |
|
| 309 | - | ) -> Program { |
|
| 331 | + | ) -> Program throws (Error) { |
|
| 332 | + | try emit::check(&generator.e); |
|
| 310 | 333 | // Build data map after function lowering. Function-local literals can add |
|
| 311 | 334 | // global data while functions are lowered, so final layout belongs here. |
|
| 312 | 335 | let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage |
|
| 313 | 336 | else panic "expected code generation storage"; |
|
| 314 | 337 | let mut dataSymCount: u32 = 0; |
|
| 315 | - | let roLayoutSize = data::layoutSectionAtOffset( |
|
| 338 | + | let roLayoutSize = try data::layoutSectionAtOffset( |
|
| 316 | 339 | globalData, symbolBuf, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true |
|
| 317 | - | ); |
|
| 318 | - | data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false); |
|
| 340 | + | ) catch error { |
|
| 341 | + | throw Error::Data(error); |
|
| 342 | + | }; |
|
| 343 | + | try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false) catch error { |
|
| 344 | + | throw Error::Data(error); |
|
| 345 | + | }; |
|
| 319 | 346 | ||
| 320 | 347 | let dataSyms = &symbolBuf[..dataSymCount]; |
|
| 321 | - | let dataSymMap = data::buildMap(dataSyms, dataSymEntries); |
|
| 322 | - | let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32); |
|
| 348 | + | let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch error { |
|
| 349 | + | throw Error::Data(error); |
|
| 350 | + | }; |
|
| 351 | + | let codeBase64 = (RO_DATA_BASE as u64 + roLayoutSize as u64 + DWORD_SIZE as u64 - 1) |
|
| 352 | + | & ~(DWORD_SIZE as u64 - 1); |
|
| 353 | + | if codeBase64 > 0xffffffff { |
|
| 354 | + | throw Error::Data(data::Error::Overflow); |
|
| 355 | + | } |
|
| 356 | + | let codeBase = codeBase64 as u32; |
|
| 323 | 357 | ||
| 324 | 358 | match generator.entryPatch { |
|
| 325 | 359 | case EntryPatch::Reserved(targetName) => { |
|
| 326 | 360 | let target = targetName else { |
|
| 327 | - | panic "finishProgram: entry jump reserved without default function"; |
|
| 361 | + | throw Error::Symbol; |
|
| 328 | 362 | }; |
|
| 329 | - | let offset = emit::branchOffsetToFunc(&generator.e, 0, target); |
|
| 363 | + | let offset = emit::branchOffsetToFunc(&mut generator.e, 0, target); |
|
| 330 | 364 | let s = emit::splitImm(offset); |
|
| 331 | 365 | ||
| 332 | 366 | emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi)); |
|
| 333 | 367 | emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo)); |
|
| 334 | 368 | } |
| 337 | 371 | // Patch function calls and address loads now that all functions are emitted. |
|
| 338 | 372 | emit::patchJumps(&mut generator.e); |
|
| 339 | 373 | emit::patchCalls(&mut generator.e); |
|
| 340 | 374 | emit::patchAddrLoads(&mut generator.e, &dataSymMap); |
|
| 341 | 375 | ||
| 376 | + | try emit::check(&generator.e); |
|
| 377 | + | ||
| 342 | 378 | // Emit data sections. |
|
| 343 | - | assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow"; |
|
| 379 | + | if roDataPrefix.len > roDataBuf.len { |
|
| 380 | + | throw Error::Capacity; |
|
| 381 | + | } |
|
| 344 | 382 | try! mem::copy(roDataBuf, roDataPrefix); |
|
| 345 | 383 | ||
| 346 | - | let roDataSize = data::emitSectionAtOffset( |
|
| 384 | + | let roDataSize = try data::emitSectionAtOffset( |
|
| 347 | 385 | globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len |
|
| 348 | - | ); |
|
| 349 | - | let rwDataSize = data::emitSection( |
|
| 386 | + | ) catch error { |
|
| 387 | + | throw Error::Data(error); |
|
| 388 | + | }; |
|
| 389 | + | let rwDataSize = try data::emitSection( |
|
| 350 | 390 | globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false |
|
| 351 | - | ); |
|
| 391 | + | ) catch error { |
|
| 392 | + | throw Error::Data(error); |
|
| 393 | + | }; |
|
| 352 | 394 | return Program { |
|
| 353 | 395 | code: emit::getCode(&generator.e), |
|
| 354 | 396 | funcs: &generator.e.funcs[..], |
|
| 355 | 397 | roDataSize, |
|
| 356 | 398 | rwDataSize, |
lib/std/arch/rv64/emit.rad
+224 -43
| 96 | 96 | offset: i32, |
|
| 97 | 97 | } |
|
| 98 | 98 | ||
| 99 | 99 | /// Emission context. Tracks state during code generation. |
|
| 100 | 100 | export record Emitter { |
|
| 101 | - | /// Allocator for growing append-backed emitter lists. |
|
| 102 | - | allocator: alloc::Allocator, |
|
| 101 | + | /// First failure recorded during generation. |
|
| 102 | + | error: ?super::Error, |
|
| 103 | 103 | /// Emitted instructions storage. |
|
| 104 | 104 | code: *mut [u32], |
|
| 105 | 105 | /// Current number of emitted instructions. |
|
| 106 | 106 | codeLen: u32, |
|
| 107 | 107 | /// Local branches needing offset patching. |
| 140 | 140 | /// When false, SP never changes after the prologue. |
|
| 141 | 141 | isDynamic: bool, |
|
| 142 | 142 | } |
|
| 143 | 143 | ||
| 144 | 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 { |
|
| 145 | + | export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame throws (super::Error) { |
|
| 146 | 146 | let mut frame = Frame { |
|
| 147 | 147 | totalSize: 0, |
|
| 148 | 148 | savedRegs: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS], |
|
| 149 | 149 | savedRegsLen: 0, |
|
| 150 | 150 | epilogueBlock, |
| 157 | 157 | if isLeaf and localSize == 0 and usedCalleeSaved == 0 { |
|
| 158 | 158 | return frame; |
|
| 159 | 159 | } |
|
| 160 | 160 | // Compute total frame size. Includes RA and FP registers. |
|
| 161 | 161 | let savedRegs = mem::popCount(usedCalleeSaved) + 2; |
|
| 162 | - | let totalSize = mem::alignUpI32( |
|
| 163 | - | localSize + savedRegs * super::DWORD_SIZE, |
|
| 164 | - | super::STACK_ALIGNMENT |
|
| 165 | - | ); |
|
| 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; |
|
| 166 | 171 | set frame.totalSize = totalSize; |
|
| 167 | 172 | ||
| 168 | 173 | // Build list of callee-saved registers with offsets. |
|
| 169 | 174 | let mut offset = totalSize - (super::DWORD_SIZE * 3); |
|
| 170 | 175 | for reg, i in super::CALLEE_SAVED { |
| 196 | 201 | if debug { |
|
| 197 | 202 | set debugEntries = try alloc::allocSlice( |
|
| 198 | 203 | arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES |
|
| 199 | 204 | ) as *mut [types::DebugEntry]; |
|
| 200 | 205 | } |
|
| 201 | - | let pendingBranchesBuf = pendingBranches as *mut [PendingBranch]; |
|
| 202 | - | let pendingCallsBuf = pendingCalls as *mut [PendingCall]; |
|
| 203 | - | let pendingJumpsBuf = pendingJumps as *mut [PendingJump]; |
|
| 204 | - | let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad]; |
|
| 205 | - | let funcsBuf = funcs as *mut [types::FuncAddr]; |
|
| 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; |
|
| 206 | 216 | return Emitter { |
|
| 207 | - | allocator: alloc::arenaAllocator(arena), |
|
| 217 | + | error: nil, |
|
| 208 | 218 | code: code as *mut [u32], |
|
| 209 | 219 | codeLen: 0, |
|
| 210 | - | pendingBranches: &mut pendingBranchesBuf[..0], |
|
| 211 | - | pendingCalls: &mut pendingCallsBuf[..0], |
|
| 212 | - | pendingJumps: &mut pendingJumpsBuf[..0], |
|
| 213 | - | pendingAddrLoads: &mut pendingAddrLoadsBuf[..0], |
|
| 220 | + | pendingBranches: pendingBranchesBuf, |
|
| 221 | + | pendingCalls: pendingCallsBuf, |
|
| 222 | + | pendingJumps: pendingJumpsBuf, |
|
| 223 | + | pendingAddrLoads: pendingAddrLoadsBuf, |
|
| 214 | 224 | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
|
| 215 | - | funcs: &mut funcsBuf[..0], |
|
| 225 | + | funcs: funcsBuf, |
|
| 216 | 226 | debugEntries, |
|
| 217 | 227 | debugEntriesLen: 0, |
|
| 218 | 228 | }; |
|
| 219 | 229 | } |
|
| 220 | 230 |
| 222 | 232 | // Emission Helpers // |
|
| 223 | 233 | /////////////////////// |
|
| 224 | 234 | ||
| 225 | 235 | /// Emit a single instruction. |
|
| 226 | 236 | export fn emit(e: &mut Emitter, instr: u32) { |
|
| 227 | - | assert e.codeLen < e.code.len, "emit: code buffer full"; |
|
| 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 | + | } |
|
| 228 | 244 | set e.code[e.codeLen] = instr; |
|
| 229 | 245 | set e.codeLen += 1; |
|
| 230 | 246 | } |
|
| 231 | 247 | ||
| 232 | 248 | /// Compute branch offset to a function by name. |
|
| 233 | - | export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 { |
|
| 234 | - | return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE); |
|
| 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; |
|
| 235 | 263 | } |
|
| 236 | 264 | ||
| 237 | 265 | /// Patch an instruction at a given index. |
|
| 238 | 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 | + | } |
|
| 239 | 274 | set e.code[index] = instr; |
|
| 240 | 275 | } |
|
| 241 | 276 | ||
| 242 | 277 | /// Record a block's address for branch resolution. |
|
| 243 | 278 | export fn recordBlock(e: &mut Emitter, blockIdx: u32) { |
|
| 244 | - | assert e.codeLen <= MAX_CODE_LEN; |
|
| 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 | + | } |
|
| 245 | 286 | labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE); |
|
| 246 | 287 | } |
|
| 247 | 288 | ||
| 248 | 289 | /// Record a function's code offset for call resolution. |
|
| 249 | 290 | export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) { |
| 251 | 292 | recordFuncOffsetAt(e, name, codeLen); |
|
| 252 | 293 | } |
|
| 253 | 294 | ||
| 254 | 295 | /// Record a function's code offset at `index` for call resolution. |
|
| 255 | 296 | export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) { |
|
| 256 | - | assert index <= MAX_CODE_LEN; |
|
| 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 | + | } |
|
| 257 | 313 | dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE); |
|
| 258 | 314 | } |
|
| 259 | 315 | ||
| 260 | 316 | /// Record a function's start position for printing. |
|
| 261 | 317 | export fn recordFunc(e: &mut Emitter, name: *[u8]) { |
| 263 | 319 | recordFuncAt(e, name, codeLen); |
|
| 264 | 320 | } |
|
| 265 | 321 | ||
| 266 | 322 | /// Record a function's start position at `index` for printing. |
|
| 267 | 323 | export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) { |
|
| 268 | - | e.funcs.append(types::FuncAddr { name, index }, e.allocator); |
|
| 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 }; |
|
| 269 | 336 | } |
|
| 270 | 337 | ||
| 271 | 338 | /// Record a local branch needing later patching. |
|
| 272 | 339 | /// Unconditional jumps use a single slot (J-type, +-1MB range). |
|
| 273 | 340 | /// Conditional branches use two slots (B-type has only +-4KB range, |
|
| 274 | 341 | /// so large functions may need the inverted-branch + JAL fallback). |
|
| 275 | 342 | export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) { |
|
| 276 | - | e.pendingBranches.append(PendingBranch { |
|
| 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 { |
|
| 277 | 355 | index: e.codeLen, |
|
| 278 | 356 | target: targetBlock, |
|
| 279 | 357 | kind: kind, |
|
| 280 | - | }, e.allocator); |
|
| 358 | + | }; |
|
| 281 | 359 | ||
| 282 | 360 | emit(e, encode::nop()); // First slot, always needed. |
|
| 283 | 361 | ||
| 284 | 362 | match kind { |
|
| 285 | 363 | case BranchKind::Jump => {}, |
| 289 | 367 | ||
| 290 | 368 | /// Record a function call needing later patching. |
|
| 291 | 369 | /// Emits placeholder instructions that will be patched later. |
|
| 292 | 370 | /// Uses two slots to support long-distance calls. |
|
| 293 | 371 | export fn recordCall(e: &mut Emitter, target: *[u8]) { |
|
| 294 | - | e.pendingCalls.append(PendingCall { |
|
| 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 { |
|
| 295 | 384 | index: e.codeLen, |
|
| 296 | 385 | target, |
|
| 297 | - | }, e.allocator); |
|
| 386 | + | }; |
|
| 298 | 387 | ||
| 299 | 388 | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 300 | 389 | emit(e, encode::nop()); // Placeholder for JALR. |
|
| 301 | 390 | } |
|
| 302 | 391 | ||
| 303 | 392 | /// Record a jump emitted by assembly that needs whole-program patching. |
|
| 304 | 393 | export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 305 | - | e.pendingJumps.append(PendingJump { |
|
| 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 { |
|
| 306 | 406 | index, |
|
| 307 | 407 | target, |
|
| 308 | 408 | rd, |
|
| 309 | - | }, e.allocator); |
|
| 409 | + | }; |
|
| 310 | 410 | } |
|
| 311 | 411 | ||
| 312 | 412 | /// Record a function address load needing later patching. |
|
| 313 | 413 | /// Emits placeholder instructions that will be patched to load the function's address. |
|
| 314 | 414 | /// Uses two slots to compute long-distance addresses. |
| 320 | 420 | emit(e, encode::nop()); // Placeholder for ADDI. |
|
| 321 | 421 | } |
|
| 322 | 422 | ||
| 323 | 423 | /// Record a function address load already reserved by assembly. |
|
| 324 | 424 | export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 325 | - | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 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 { |
|
| 326 | 437 | index, |
|
| 327 | 438 | target, |
|
| 328 | 439 | rd: rd, |
|
| 329 | 440 | isData: false, |
|
| 330 | - | }, e.allocator); |
|
| 441 | + | }; |
|
| 331 | 442 | } |
|
| 332 | 443 | ||
| 333 | 444 | /// Record a data address load needing later patching. |
|
| 334 | 445 | /// Uses an absolute 32-bit load sequence matching the current data memory map. |
|
| 335 | 446 | export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 336 | - | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 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 { |
|
| 337 | 459 | index: e.codeLen, |
|
| 338 | 460 | target, |
|
| 339 | 461 | rd: rd, |
|
| 340 | 462 | isData: true, |
|
| 341 | - | }, e.allocator); |
|
| 463 | + | }; |
|
| 342 | 464 | ||
| 343 | 465 | emit(e, encode::nop()); // Placeholder for LUI. |
|
| 344 | 466 | emit(e, encode::nop()); // Placeholder for ADDIW. |
|
| 345 | 467 | } |
|
| 346 | 468 |
| 351 | 473 | /// Uses two-instruction sequences: short branches use `branch` and `nop`, |
|
| 352 | 474 | /// long branches use inverted branch and `jal` or `auipc` and `jalr`. |
|
| 353 | 475 | export unsafe fn patchLocalBranches(e: &mut Emitter) { |
|
| 354 | 476 | for i in 0..e.pendingBranches.len { |
|
| 355 | 477 | let p = e.pendingBranches[i]; |
|
| 356 | - | let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE); |
|
| 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; |
|
| 357 | 491 | match p.kind { |
|
| 358 | 492 | case BranchKind::Cond { op, rs1, rs2 } => { |
|
| 359 | 493 | if encode::isBranchImm(offset) { |
|
| 360 | 494 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset)); |
|
| 361 | 495 | patch(e, p.index + 1, encode::nop()); |
|
| 362 | 496 | } else { |
|
| 363 | - | let adj = offset - super::INSTR_SIZE; |
|
| 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 | + | } |
|
| 364 | 507 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 365 | 508 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 366 | 509 | } |
|
| 367 | 510 | }, |
|
| 368 | 511 | case BranchKind::InvertedCond { op, rs1, rs2 } => { |
|
| 369 | 512 | if encode::isBranchImm(offset) { |
|
| 370 | 513 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset)); |
|
| 371 | 514 | patch(e, p.index + 1, encode::nop()); |
|
| 372 | 515 | } else { |
|
| 373 | - | let adj = offset - super::INSTR_SIZE; |
|
| 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 | + | } |
|
| 374 | 526 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 375 | 527 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 376 | 528 | } |
|
| 377 | 529 | }, |
|
| 378 | 530 | case BranchKind::Jump => { |
|
| 379 | 531 | // Single-slot jump (J-type, +-1MB range). |
|
| 380 | - | assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large"; |
|
| 532 | + | if not encode::isJumpImm(offset) { |
|
| 533 | + | set e.error = super::Error::Relocation; |
|
| 534 | + | return; |
|
| 535 | + | } |
|
| 381 | 536 | patch(e, p.index, encode::jal(super::ZERO, offset)); |
|
| 382 | 537 | }, |
|
| 383 | 538 | } |
|
| 384 | 539 | } |
|
| 385 | - | set e.pendingBranches = &mut e.pendingBranches[..0]; |
|
| 540 | + | set e.pendingBranches.len = 0; |
|
| 386 | 541 | } |
|
| 387 | 542 | ||
| 388 | 543 | /// Encode a conditional branch instruction. |
|
| 389 | 544 | fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 { |
|
| 390 | 545 | match op { |
| 424 | 579 | export fn patchJumps(e: &mut Emitter) { |
|
| 425 | 580 | for i in 0..e.pendingJumps.len { |
|
| 426 | 581 | let p = e.pendingJumps[i]; |
|
| 427 | 582 | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 428 | 583 | ||
| 429 | - | assert encode::isJumpImm(offset), "patchJumps: jump offset too large"; |
|
| 584 | + | if e.error <> nil { |
|
| 585 | + | return; |
|
| 586 | + | } |
|
| 587 | + | if not encode::isJumpImm(offset) { |
|
| 588 | + | set e.error = super::Error::Relocation; |
|
| 589 | + | return; |
|
| 590 | + | } |
|
| 430 | 591 | patch(e, p.index, encode::jal(p.rd, offset)); |
|
| 431 | 592 | } |
|
| 432 | 593 | } |
|
| 433 | 594 | ||
| 434 | 595 | /// Patch all pending function and data address loads. |
|
| 435 | 596 | /// Called after all functions have been generated and data layout is known. |
|
| 436 | 597 | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) { |
|
| 437 | 598 | for i in 0..e.pendingAddrLoads.len { |
|
| 599 | + | if e.error <> nil { |
|
| 600 | + | return; |
|
| 601 | + | } |
|
| 438 | 602 | let p = e.pendingAddrLoads[i]; |
|
| 439 | 603 | if p.isData { |
|
| 440 | 604 | let addr = data::lookupAddr(dataSymMap, p.target) else { |
|
| 441 | - | panic "patchAddrLoads: data symbol not found"; |
|
| 605 | + | set e.error = super::Error::Symbol; |
|
| 606 | + | return; |
|
| 442 | 607 | }; |
|
| 443 | - | assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large"; |
|
| 608 | + | if addr > MAX_I32_ADDR { |
|
| 609 | + | set e.error = super::Error::Relocation; |
|
| 610 | + | return; |
|
| 611 | + | } |
|
| 444 | 612 | let s = splitImm(addr as i32); |
|
| 445 | 613 | ||
| 446 | 614 | patch(e, p.index, encode::lui(p.rd, s.hi)); |
|
| 447 | 615 | patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo)); |
|
| 448 | 616 |
| 728 | 896 | } |
|
| 729 | 897 | ||
| 730 | 898 | /// Record a debug entry mapping the current PC to a source location. |
|
| 731 | 899 | /// Deduplicates consecutive entries with the same location. |
|
| 732 | 900 | export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) { |
|
| 901 | + | if e.error <> nil { |
|
| 902 | + | return; |
|
| 903 | + | } |
|
| 733 | 904 | let pc = e.codeLen * super::INSTR_SIZE as u32; |
|
| 734 | 905 | ||
| 735 | 906 | // Skip if this is the same location as the previous entry. |
|
| 736 | 907 | if e.debugEntriesLen > 0 { |
|
| 737 | 908 | let prev = &e.debugEntries[e.debugEntriesLen - 1]; |
|
| 738 | 909 | if prev.offset == loc.offset and prev.moduleId == loc.moduleId { |
|
| 739 | 910 | return; |
|
| 740 | 911 | } |
|
| 741 | 912 | } |
|
| 742 | - | assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full"; |
|
| 913 | + | if e.debugEntriesLen >= e.debugEntries.len { |
|
| 914 | + | set e.error = super::Error::Capacity; |
|
| 915 | + | return; |
|
| 916 | + | } |
|
| 743 | 917 | set e.debugEntries[e.debugEntriesLen] = types::DebugEntry { |
|
| 744 | 918 | pc, |
|
| 745 | 919 | moduleId: loc.moduleId, |
|
| 746 | 920 | offset: loc.offset, |
|
| 747 | 921 | }; |
| 750 | 924 | ||
| 751 | 925 | /// Get debug entries as a slice. |
|
| 752 | 926 | export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] { |
|
| 753 | 927 | return &e.debugEntries[..e.debugEntriesLen]; |
|
| 754 | 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 | + | } |
lib/std/arch/rv64/isel.rad
+44 -9
| 272 | 272 | isDynamic: bool, |
|
| 273 | 273 | } |
|
| 274 | 274 | ||
| 275 | 275 | /// Pre-scan all blocks for constant-sized reserve instructions. |
|
| 276 | 276 | /// Returns the total size needed for all static reserves, respecting alignment. |
|
| 277 | - | unsafe fn computeReserveInfo(func: *unsafe il::Fn) -> ReserveInfo { |
|
| 277 | + | unsafe fn computeReserveInfo(func: *unsafe il::Fn) -> ReserveInfo throws (super::Error) { |
|
| 278 | 278 | let mut offset: i32 = 0; |
|
| 279 | 279 | let mut isDynamic = false; |
|
| 280 | 280 | ||
| 281 | 281 | for b in 0..func.blocks.len { |
|
| 282 | 282 | let block = &func.blocks[b]; |
|
| 283 | 283 | for instr in block.instrs { |
|
| 284 | 284 | match instr { |
|
| 285 | 285 | case il::Instr::Reserve { size, alignment, .. } => { |
|
| 286 | 286 | if let case il::Val::Imm(sz) = size { |
|
| 287 | - | set offset = mem::alignUpI32(offset, alignment as i32); |
|
| 288 | - | set offset += sz as i32; |
|
| 287 | + | if alignment == 0 or (alignment & (alignment - 1)) <> 0 or sz < 0 { |
|
| 288 | + | throw super::Error::Capacity; |
|
| 289 | + | } |
|
| 290 | + | let aligned = (offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1); |
|
| 291 | + | if aligned > 0x7fffffff or sz as u64 > 0x7fffffff - aligned { |
|
| 292 | + | throw super::Error::Capacity; |
|
| 293 | + | } |
|
| 294 | + | set offset = (aligned + sz as u64) as i32; |
|
| 289 | 295 | } else { |
|
| 290 | 296 | set isDynamic = true; |
|
| 291 | 297 | } |
|
| 292 | 298 | }, |
|
| 293 | 299 | else => {}, |
| 301 | 307 | export unsafe fn selectFn( |
|
| 302 | 308 | e: &mut emit::Emitter, |
|
| 303 | 309 | ralloc: ®alloc::AllocResult, |
|
| 304 | 310 | func: *unsafe il::Fn |
|
| 305 | 311 | ) { |
|
| 312 | + | if e.error <> nil { |
|
| 313 | + | return; |
|
| 314 | + | } |
|
| 306 | 315 | // Reset block offsets for this function. |
|
| 307 | 316 | labels::resetBlocks(&mut e.labels); |
|
| 308 | 317 | // Pre-scan for constant-sized reserves to promote to fixed frame slots. |
|
| 309 | - | let reserveInfo = computeReserveInfo(func); |
|
| 318 | + | let reserveInfo = try computeReserveInfo(func) catch error { |
|
| 319 | + | set e.error = error; |
|
| 320 | + | return; |
|
| 321 | + | }; |
|
| 322 | + | if ralloc.spill.frameSize < 0 or |
|
| 323 | + | reserveInfo.size as u64 + ralloc.spill.frameSize as u64 > 0x7fffffff { |
|
| 324 | + | set e.error = super::Error::Capacity; |
|
| 325 | + | return; |
|
| 326 | + | } |
|
| 310 | 327 | let isLeaf = func.isLeaf; |
|
| 311 | 328 | // Compute frame layout from spill slots, reserve slots, and used callee-saved registers. |
|
| 312 | - | let frame = emit::computeFrame( |
|
| 329 | + | let frame = try emit::computeFrame( |
|
| 313 | 330 | ralloc.spill.frameSize + reserveInfo.size, |
|
| 314 | 331 | ralloc.usedCalleeSaved, |
|
| 315 | 332 | func.blocks.len, |
|
| 316 | 333 | isLeaf, |
|
| 317 | 334 | reserveInfo.isDynamic |
|
| 318 | - | ); |
|
| 335 | + | ) catch error { |
|
| 336 | + | set e.error = error; |
|
| 337 | + | return; |
|
| 338 | + | }; |
|
| 319 | 339 | // Synthetic block indices start after real blocks and the epilogue block. |
|
| 320 | 340 | let mut s = Selector { |
|
| 321 | 341 | e: e as *unsafe mut emit::Emitter, |
|
| 322 | 342 | ralloc: ralloc as *unsafe regalloc::AllocResult, |
|
| 323 | 343 | frameSize: frame.totalSize, |
| 350 | 370 | } |
|
| 351 | 371 | } |
|
| 352 | 372 | ||
| 353 | 373 | // Emit each block. |
|
| 354 | 374 | for i in 0..func.blocks.len { |
|
| 375 | + | if s.e.error <> nil { |
|
| 376 | + | return; |
|
| 377 | + | } |
|
| 355 | 378 | selectBlock(&mut s, i, &func.blocks[i], &frame, func); |
|
| 356 | 379 | } |
|
| 357 | 380 | // Emit epilogue. |
|
| 358 | 381 | emit::emitEpilogue(s.e, &frame); |
|
| 359 | 382 | // Patch local branches now that all blocks are emitted. |
| 370 | 393 | // moved to the parameter registers by the predecessor's terminator. |
|
| 371 | 394 | ||
| 372 | 395 | // Process each instruction, auto-committing any pending spill after each. |
|
| 373 | 396 | let hasLocs = block.locs.len > 0; |
|
| 374 | 397 | for instr, i in block.instrs { |
|
| 398 | + | if s.e.error <> nil { |
|
| 399 | + | return; |
|
| 400 | + | } |
|
| 375 | 401 | // Record debug location before emitting machine instructions. |
|
| 376 | 402 | if hasLocs { |
|
| 377 | 403 | emit::recordSrcLoc(s.e, block.locs[i]); |
|
| 378 | 404 | } |
|
| 379 | 405 | set s.pendingSpill = nil; |
| 389 | 415 | } |
|
| 390 | 416 | } |
|
| 391 | 417 | ||
| 392 | 418 | /// Select instructions for a single IL instruction. |
|
| 393 | 419 | unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) { |
|
| 420 | + | if s.e.error <> nil { |
|
| 421 | + | return; |
|
| 422 | + | } |
|
| 394 | 423 | match instr { |
|
| 395 | 424 | case il::Instr::BinOp { op, typ, dst, a, b } => { |
|
| 396 | 425 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 397 | 426 | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 398 | 427 | selectAluBinOp(s, op, typ, rd, rs1, b); |
| 709 | 738 | if let case il::Val::Reg(r) = func { |
|
| 710 | 739 | let target = getSrcReg(s, r, super::SCRATCH2); |
|
| 711 | 740 | emitMv(s, super::SCRATCH2, target); |
|
| 712 | 741 | } |
|
| 713 | 742 | // Move arguments to A0-A7 using parallel move resolution. |
|
| 714 | - | assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments"; |
|
| 743 | + | if args.len > super::ARG_REGS.len { |
|
| 744 | + | set s.e.error = super::Error::Capacity; |
|
| 745 | + | return; |
|
| 746 | + | } |
|
| 715 | 747 | emitParallelMoves(s, &super::ARG_REGS[..], args); |
|
| 716 | 748 | ||
| 717 | 749 | // Emit call. |
|
| 718 | 750 | match func { |
|
| 719 | 751 | case il::Val::FnAddr(name) => { |
| 1167 | 1199 | /// |
|
| 1168 | 1200 | /// Handles spilled destinations directly, then delegates to [`emitParallelMoves`] |
|
| 1169 | 1201 | /// for the remaining register-to-register parallel move resolution. Edges that |
|
| 1170 | 1202 | /// would overwrite an unconsumed spill source are unsupported. |
|
| 1171 | 1203 | unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) { |
|
| 1172 | - | if args.len == 0 { |
|
| 1204 | + | if s.e.error <> nil or args.len == 0 { |
|
| 1173 | 1205 | return; |
|
| 1174 | 1206 | } |
|
| 1175 | 1207 | let block = &func.blocks[target]; |
|
| 1176 | 1208 | assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch"; |
|
| 1177 | - | assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments"; |
|
| 1209 | + | if args.len > MAX_BLOCK_ARGS { |
|
| 1210 | + | set s.e.error = super::Error::Capacity; |
|
| 1211 | + | return; |
|
| 1212 | + | } |
|
| 1178 | 1213 | ||
| 1179 | 1214 | // The parallel-move resolver only handles register destinations. Keep eager |
|
| 1180 | 1215 | // stores for independent spill slots, but reject dependencies that would |
|
| 1181 | 1216 | // require stack staging rather than silently miscompiling them. |
|
| 1182 | 1217 | for arg, i in args { |
lib/std/arch/rv64/tests.rad
+1 -49
| 3 | 3 | //! These tests verify that instruction encodings match the RISC-V specification |
|
| 4 | 4 | //! by comparing against known-good values. |
|
| 5 | 5 | ||
| 6 | 6 | use std::testing; |
|
| 7 | 7 | use std::lang::alloc; |
|
| 8 | - | use std::lang::il; |
|
| 9 | - | use std::lang::gen::bitset; |
|
| 10 | - | use std::lang::gen::regalloc; |
|
| 11 | 8 | use std::collections::dict; |
|
| 12 | 9 | ||
| 13 | 10 | use super::encode; |
|
| 14 | 11 | use super::asm; |
|
| 15 | 12 |
| 38 | 35 | section: asm::Section::Text, |
|
| 39 | 36 | offset: super::INSTR_SIZE, |
|
| 40 | 37 | isExported: true, |
|
| 41 | 38 | }; |
|
| 42 | 39 | ||
| 43 | - | let mut generator = super::beginProgram( |
|
| 40 | + | let mut generator = try! super::beginProgram( |
|
| 44 | 41 | super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false }, |
|
| 45 | 42 | &mut arena |
|
| 46 | 43 | ); |
|
| 47 | 44 | super::addAssembly( |
|
| 48 | 45 | &mut generator, |
| 554 | 551 | try testing::expect(encode::isJumpImm(1048574)); // Max positive even |
|
| 555 | 552 | try testing::expect(encode::isJumpImm(-1048576)); // Min negative |
|
| 556 | 553 | try testing::expectNot(encode::isJumpImm(1)); // Must be even |
|
| 557 | 554 | try testing::expectNot(encode::isJumpImm(1048576)); // Out of range |
|
| 558 | 555 | } |
|
| 559 | - | ||
| 560 | - | /// Register limit checks use bounded scratch storage. |
|
| 561 | - | static REGISTER_SCRATCH: [u8; 65536] = [0; 65536]; |
|
| 562 | - | ||
| 563 | - | /// Register numbers at or above the supported count return allocation errors. |
|
| 564 | - | @test unsafe fn testRegisterLimit() throws (testing::TestError) { |
|
| 565 | - | for number in [8191, 8192, 0xffffffff] { |
|
| 566 | - | let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 567 | - | let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }]; |
|
| 568 | - | let func = il::Fn { |
|
| 569 | - | name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 570 | - | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 571 | - | }; |
|
| 572 | - | let mut failed = false; |
|
| 573 | - | try regalloc::liveness::analyze(&func, &mut arena) catch { |
|
| 574 | - | set failed = true; |
|
| 575 | - | }; |
|
| 576 | - | assert failed == (number >= 8192); |
|
| 577 | - | if failed { |
|
| 578 | - | assert arena.offset == 0; |
|
| 579 | - | } |
|
| 580 | - | } |
|
| 581 | - | } |
|
| 582 | - | ||
| 583 | - | /// Excess live values return an error before the spill candidate table overflows. |
|
| 584 | - | @test unsafe fn testSpillCandidateLimit() throws (testing::TestError) { |
|
| 585 | - | let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 586 | - | let mut liveSet = try! bitset::allocate(&mut arena, 257); |
|
| 587 | - | for i in 0..257 { |
|
| 588 | - | bitset::put(&mut liveSet, i); |
|
| 589 | - | } |
|
| 590 | - | let mut out = [liveSet]; |
|
| 591 | - | let live = regalloc::liveness::LiveInfo { |
|
| 592 | - | liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257, |
|
| 593 | - | }; |
|
| 594 | - | let func = il::Fn { |
|
| 595 | - | name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 596 | - | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 597 | - | }; |
|
| 598 | - | let mut failed = false; |
|
| 599 | - | try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch { |
|
| 600 | - | set failed = true; |
|
| 601 | - | }; |
|
| 602 | - | assert failed; |
|
| 603 | - | } |
lib/std/lang/gen/data.rad
+79 -15
| 13 | 13 | ||
| 14 | 14 | /// Size of the data symbol hash table. Must be a power of two |
|
| 15 | 15 | /// and at least twice the size of [`MAX_DATA_SYMS`]. |
|
| 16 | 16 | export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2; |
|
| 17 | 17 | ||
| 18 | + | /// Data section layout or emission failure. |
|
| 19 | + | export union Error: Copy { |
|
| 20 | + | /// A required symbol is missing or duplicated. |
|
| 21 | + | Symbol, |
|
| 22 | + | /// A symbol table or output buffer is full. |
|
| 23 | + | Capacity, |
|
| 24 | + | /// A size or address exceeds the supported range. |
|
| 25 | + | Overflow, |
|
| 26 | + | /// Alignment is zero or is not a power of two. |
|
| 27 | + | Alignment, |
|
| 28 | + | } |
|
| 29 | + | ||
| 18 | 30 | /// Data symbol entry mapping name to address. |
|
| 19 | 31 | export record DataSym: Copy { |
|
| 20 | 32 | /// Symbol name. |
|
| 21 | 33 | name: *[u8], |
|
| 22 | 34 | /// Absolute address, including data base address. |
| 39 | 51 | items: &[il::Data], |
|
| 40 | 52 | syms: &mut [DataSym], |
|
| 41 | 53 | count: &mut u32, |
|
| 42 | 54 | base: u32, |
|
| 43 | 55 | readOnly: bool |
|
| 44 | - | ) -> u32 { |
|
| 45 | - | return layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 56 | + | ) -> u32 throws (Error) { |
|
| 57 | + | return try layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 46 | 58 | } |
|
| 47 | 59 | ||
| 48 | 60 | /// Lay out data symbols for a single section starting at [`startOffset`]. |
|
| 49 | 61 | export fn layoutSectionAtOffset( |
|
| 50 | 62 | items: &[il::Data], |
|
| 51 | 63 | syms: &mut [DataSym], |
|
| 52 | 64 | count: &mut u32, |
|
| 53 | 65 | base: u32, |
|
| 54 | 66 | startOffset: u32, |
|
| 55 | 67 | readOnly: bool |
|
| 56 | - | ) -> u32 { |
|
| 68 | + | ) -> u32 throws (Error) { |
|
| 57 | 69 | let mut offset: u32 = startOffset; |
|
| 58 | 70 | ||
| 59 | 71 | // Data requiring sidecar image bytes first. |
|
| 60 | 72 | for i in 0..items.len { |
|
| 61 | 73 | let data = items[i]; |
|
| 62 | 74 | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 63 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 75 | + | if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 { |
|
| 76 | + | throw Error::Alignment; |
|
| 77 | + | } |
|
| 78 | + | let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1); |
|
| 79 | + | if aligned + data.size as u64 > 0xffffffff or base as u64 + aligned + data.size as u64 > 0xffffffff { |
|
| 80 | + | throw Error::Overflow; |
|
| 81 | + | } |
|
| 82 | + | if *count >= syms.len { |
|
| 83 | + | throw Error::Capacity; |
|
| 84 | + | } |
|
| 85 | + | set offset = aligned as u32; |
|
| 64 | 86 | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 65 | 87 | set *count += 1; |
|
| 66 | 88 | set offset += data.size; |
|
| 67 | 89 | } |
|
| 68 | 90 | } |
|
| 69 | 91 | // Zero-initialized data after. |
|
| 70 | 92 | for i in 0..items.len { |
|
| 71 | 93 | let data = items[i]; |
|
| 72 | 94 | if data.readOnly == readOnly and data.isZeroInit { |
|
| 73 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 95 | + | if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 { |
|
| 96 | + | throw Error::Alignment; |
|
| 97 | + | } |
|
| 98 | + | let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1); |
|
| 99 | + | if aligned + data.size as u64 > 0xffffffff or base as u64 + aligned + data.size as u64 > 0xffffffff { |
|
| 100 | + | throw Error::Overflow; |
|
| 101 | + | } |
|
| 102 | + | if *count >= syms.len { |
|
| 103 | + | throw Error::Capacity; |
|
| 104 | + | } |
|
| 105 | + | set offset = aligned as u32; |
|
| 74 | 106 | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 75 | 107 | set *count += 1; |
|
| 76 | 108 | set offset += data.size; |
|
| 77 | 109 | } |
|
| 78 | 110 | } |
| 87 | 119 | dataSymMap: &DataSymMap, |
|
| 88 | 120 | fnLabels: &labels::Labels, |
|
| 89 | 121 | codeBase: u32, |
|
| 90 | 122 | buf: &mut [u8], |
|
| 91 | 123 | readOnly: bool |
|
| 92 | - | ) -> u32 { |
|
| 93 | - | return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0); |
|
| 124 | + | ) -> u32 throws (Error) { |
|
| 125 | + | return try emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0); |
|
| 94 | 126 | } |
|
| 95 | 127 | ||
| 96 | 128 | /// Emit data bytes for a single section starting at `startOffset`. |
|
| 97 | 129 | export unsafe fn emitSectionAtOffset( |
|
| 98 | 130 | items: &[il::Data], |
| 100 | 132 | fnLabels: &labels::Labels, |
|
| 101 | 133 | codeBase: u32, |
|
| 102 | 134 | buf: &mut [u8], |
|
| 103 | 135 | readOnly: bool, |
|
| 104 | 136 | startOffset: u32 |
|
| 105 | - | ) -> u32 { |
|
| 137 | + | ) -> u32 throws (Error) { |
|
| 138 | + | if startOffset > buf.len { |
|
| 139 | + | throw Error::Capacity; |
|
| 140 | + | } |
|
| 106 | 141 | let mut offset: u32 = startOffset; |
|
| 107 | 142 | ||
| 108 | 143 | for i in 0..items.len { |
|
| 109 | 144 | let data = items[i]; |
|
| 110 | 145 | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 111 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 112 | - | assert offset + data.size <= buf.len, "emitSectionAtOffset: buffer overflow"; |
|
| 146 | + | if data.alignment == 0 or (data.alignment & (data.alignment - 1)) <> 0 { |
|
| 147 | + | throw Error::Alignment; |
|
| 148 | + | } |
|
| 149 | + | let aligned = (offset as u64 + data.alignment as u64 - 1) & ~(data.alignment as u64 - 1); |
|
| 150 | + | if aligned > buf.len as u64 or data.size as u64 > buf.len as u64 - aligned { |
|
| 151 | + | throw Error::Capacity; |
|
| 152 | + | } |
|
| 153 | + | set offset = aligned as u32; |
|
| 154 | + | let end = offset + data.size; |
|
| 113 | 155 | for j in 0..data.values.len { |
|
| 114 | 156 | let v = &data.values[j]; |
|
| 157 | + | let mut width: u32 = 1; |
|
| 158 | + | match v.item { |
|
| 159 | + | case il::DataItem::Val { typ, .. } => set width = il::typeSize(typ), |
|
| 160 | + | case il::DataItem::Sym(_), il::DataItem::Fn(_) => set width = 8, |
|
| 161 | + | case il::DataItem::Str(text) => set width = text.len, |
|
| 162 | + | case il::DataItem::Undef => {} |
|
| 163 | + | } |
|
| 164 | + | if width as u64 * v.count as u64 > (end - offset) as u64 { |
|
| 165 | + | throw Error::Capacity; |
|
| 166 | + | } |
|
| 115 | 167 | for _ in 0..v.count { |
|
| 116 | 168 | match v.item { |
|
| 117 | 169 | case il::DataItem::Val { typ, val } => { |
|
| 118 | 170 | let size = il::typeSize(typ); |
|
| 119 | 171 | try! mem::copy(&mut buf[offset..], @sliceOf(&val as &u8, size)); |
|
| 120 | 172 | ||
| 121 | 173 | set offset += size; |
|
| 122 | 174 | }, |
|
| 123 | 175 | case il::DataItem::Sym(name) => { |
|
| 124 | 176 | let addr = lookupAddr(dataSymMap, name) else { |
|
| 125 | - | panic "emitSectionAtOffset: data symbol not found"; |
|
| 177 | + | throw Error::Symbol; |
|
| 126 | 178 | }; |
|
| 127 | 179 | let addr64: u64 = addr as u64; |
|
| 128 | 180 | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 129 | 181 | ||
| 130 | 182 | set offset += @sizeOf(u64); |
|
| 131 | 183 | }, |
|
| 132 | 184 | case il::DataItem::Fn(name) => { |
|
| 133 | - | let addr = codeBase + labels::funcOffset(fnLabels, name) as u32; |
|
| 134 | - | let addr64: u64 = addr as u64; |
|
| 185 | + | let functionOffset = dict::get(&fnLabels.funcs, name) else { |
|
| 186 | + | throw Error::Symbol; |
|
| 187 | + | }; |
|
| 188 | + | if functionOffset < 0 or codeBase as u64 + functionOffset as u64 > 0xffffffff { |
|
| 189 | + | throw Error::Overflow; |
|
| 190 | + | } |
|
| 191 | + | let addr64 = codeBase as u64 + functionOffset as u64; |
|
| 135 | 192 | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 136 | 193 | ||
| 137 | 194 | set offset += @sizeOf(*u8); |
|
| 138 | 195 | }, |
|
| 139 | 196 | case il::DataItem::Str(s) => { |
| 151 | 208 | } |
|
| 152 | 209 | return offset; |
|
| 153 | 210 | } |
|
| 154 | 211 | ||
| 155 | 212 | /// Build a hash-indexed data symbol map from the laid-out symbols. |
|
| 156 | - | /// The `entries` slice must have length `DATA_SYM_TABLE_SIZE`. |
|
| 157 | - | export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap { |
|
| 213 | + | /// The table length must be a power of two and at least twice the symbol count. |
|
| 214 | + | /// The minimum table length is two. |
|
| 215 | + | export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap throws (Error) { |
|
| 216 | + | if entries.len < 2 or (entries.len & (entries.len - 1)) <> 0 or syms.len > entries.len / 2 { |
|
| 217 | + | throw Error::Capacity; |
|
| 218 | + | } |
|
| 158 | 219 | let mut d = dict::init(entries); |
|
| 159 | 220 | for i in 0..syms.len { |
|
| 221 | + | if syms[i].name.len == 0 or dict::get(&d, syms[i].name) <> nil { |
|
| 222 | + | throw Error::Symbol; |
|
| 223 | + | } |
|
| 160 | 224 | dict::insert(&mut d, syms[i].name, syms[i].addr as i32); |
|
| 161 | 225 | } |
|
| 162 | 226 | return DataSymMap { dict: d, syms }; |
|
| 163 | 227 | } |
|
| 164 | 228 |
test/backend.rad
added
+555 -0
| 1 | + | //! Executable checks for recoverable backend bounds. |
|
| 2 | + | use std::lang::alloc; |
|
| 3 | + | use std::lang::il; |
|
| 4 | + | use std::lang::gen::bitset; |
|
| 5 | + | use std::lang::gen::data; |
|
| 6 | + | use std::lang::gen::regalloc; |
|
| 7 | + | use std::collections::dict; |
|
| 8 | + | use std::arch::rv64; |
|
| 9 | + | use std::arch::rv64::encode; |
|
| 10 | + | ||
| 11 | + | /// A backend check could not obtain its required test storage. |
|
| 12 | + | union TestError: Copy { |
|
| 13 | + | /// Test setup failed. |
|
| 14 | + | Failed, |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Reusable emitter storage. |
|
| 18 | + | static ASSEMBLY_ARENA_STORAGE: [u8; 16777216] = [0; 16777216]; |
|
| 19 | + | ||
| 20 | + | /// Run all backend boundary checks. |
|
| 21 | + | @default unsafe fn main() -> u32 { |
|
| 22 | + | try testBackendBounds() catch { |
|
| 23 | + | panic "backend bounds failed"; |
|
| 24 | + | }; |
|
| 25 | + | return 0; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Register limit checks use bounded scratch storage. |
|
| 29 | + | static REGISTER_SCRATCH: [u8; 65536] = [0; 65536]; |
|
| 30 | + | ||
| 31 | + | /// Register numbers at or above the supported count return allocation errors. |
|
| 32 | + | unsafe fn testRegisterLimit() throws (TestError) { |
|
| 33 | + | for number in [8191, 8192, 0xffffffff] { |
|
| 34 | + | let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 35 | + | let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }]; |
|
| 36 | + | let func = il::Fn { |
|
| 37 | + | name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 38 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 39 | + | }; |
|
| 40 | + | let mut failed = false; |
|
| 41 | + | try regalloc::liveness::analyze(&func, &mut arena) catch { |
|
| 42 | + | set failed = true; |
|
| 43 | + | }; |
|
| 44 | + | assert failed == (number >= 8192); |
|
| 45 | + | if failed { |
|
| 46 | + | assert arena.offset == 0; |
|
| 47 | + | } |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Excess live values return an error before the spill candidate table overflows. |
|
| 52 | + | unsafe fn testSpillCandidateLimit() throws (TestError) { |
|
| 53 | + | let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 54 | + | let mut liveSet = try! bitset::allocate(&mut arena, 257); |
|
| 55 | + | for i in 0..257 { |
|
| 56 | + | bitset::put(&mut liveSet, i); |
|
| 57 | + | } |
|
| 58 | + | let mut out = [liveSet]; |
|
| 59 | + | let live = regalloc::liveness::LiveInfo { |
|
| 60 | + | liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257, |
|
| 61 | + | }; |
|
| 62 | + | let func = il::Fn { |
|
| 63 | + | name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 64 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 65 | + | }; |
|
| 66 | + | let mut failed = false; |
|
| 67 | + | try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch { |
|
| 68 | + | set failed = true; |
|
| 69 | + | }; |
|
| 70 | + | assert failed; |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Frame size arithmetic must not wrap into a negative stack allocation. |
|
| 74 | + | unsafe fn testFrameSizeOverflow() throws (TestError) { |
|
| 75 | + | for size in [0x7fffffff, -1] { |
|
| 76 | + | let mut failed = false; |
|
| 77 | + | try rv64::emit::computeFrame(size, 0, 0, false, false) catch error { |
|
| 78 | + | assert error == rv64::Error::Capacity; |
|
| 79 | + | set failed = true; |
|
| 80 | + | }; |
|
| 81 | + | assert failed; |
|
| 82 | + | } |
|
| 83 | + | let frame = try rv64::emit::computeFrame(16, 0, 0, false, false) catch { |
|
| 84 | + | throw TestError::Failed; |
|
| 85 | + | }; |
|
| 86 | + | assert frame.totalSize == 32; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Oversized reserves must fail before narrowing their byte counts. |
|
| 90 | + | unsafe fn testReserveOverflow() throws (TestError) { |
|
| 91 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 92 | + | let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 93 | + | let mut generator = try! rv64::beginProgram( |
|
| 94 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 95 | + | ); |
|
| 96 | + | let mut instructions = [ |
|
| 97 | + | il::Instr::Reserve { dst: il::Reg { n: 0 }, size: il::Val::Imm(0x100000000), alignment: 8 }, |
|
| 98 | + | il::Instr::Ret { val: nil }, |
|
| 99 | + | ]; |
|
| 100 | + | let func = il::Fn { |
|
| 101 | + | name: "reserve", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 102 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 103 | + | }; |
|
| 104 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 105 | + | assert generator.e.error == rv64::Error::Capacity; |
|
| 106 | + | assert scratch.offset == 0; |
|
| 107 | + | ||
| 108 | + | let config = rv64::targetConfig(); |
|
| 109 | + | let ralloc = try! regalloc::allocate(&func, &config, &mut scratch); |
|
| 110 | + | set generator.e.error = rv64::Error::Symbol; |
|
| 111 | + | rv64::isel::selectFn(&mut generator.e, &ralloc, &func); |
|
| 112 | + | assert generator.e.error == rv64::Error::Symbol; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | /// An exhausted instruction buffer records failure and stops writes. |
|
| 116 | + | unsafe fn testCodeCapacity() throws (TestError) { |
|
| 117 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 118 | + | let mut e = try rv64::emit::emitter(&mut arena, false) catch { |
|
| 119 | + | throw TestError::Failed; |
|
| 120 | + | }; |
|
| 121 | + | set e.code = &mut e.code[..0]; |
|
| 122 | + | rv64::emit::emit(&mut e, encode::nop()); |
|
| 123 | + | assert e.error == rv64::Error::Capacity; |
|
| 124 | + | rv64::emit::emit(&mut e, encode::ebreak()); |
|
| 125 | + | assert e.codeLen == 0; |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// Generator initialization must report exhausted storage without trapping. |
|
| 129 | + | unsafe fn testGeneratorAllocationFailure() throws (TestError) { |
|
| 130 | + | let mut arena = alloc::new(&mut REGISTER_SCRATCH[..64]); |
|
| 131 | + | set arena.offset = 8; |
|
| 132 | + | let mut failed = false; |
|
| 133 | + | try rv64::beginProgram( |
|
| 134 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 135 | + | ) catch error { |
|
| 136 | + | assert error == rv64::Error::Allocation; |
|
| 137 | + | set failed = true; |
|
| 138 | + | }; |
|
| 139 | + | assert failed; |
|
| 140 | + | assert arena.offset == 8; |
|
| 141 | + | } |
|
| 142 | + | ||
| 143 | + | /// Function allocation failure preserves the scratch checkpoint. |
|
| 144 | + | unsafe fn testFunctionAllocationFailure() throws (TestError) { |
|
| 145 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 146 | + | let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..8]); |
|
| 147 | + | set scratch.offset = 8; |
|
| 148 | + | let mut generator = try! rv64::beginProgram( |
|
| 149 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 150 | + | ); |
|
| 151 | + | let mut instructions = [il::Instr::Ret { val: il::Val::Imm(0) }]; |
|
| 152 | + | let func = il::Fn { |
|
| 153 | + | name: "allocation", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 154 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 155 | + | }; |
|
| 156 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 157 | + | assert generator.e.error == rv64::Error::Allocation; |
|
| 158 | + | assert scratch.offset == 8; |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Full metadata tables reject appends before requesting more storage. |
|
| 162 | + | unsafe fn testMetadataCapacity() throws (TestError) { |
|
| 163 | + | for kind in 0..5 { |
|
| 164 | + | let mut e = boundsEmitter(); |
|
| 165 | + | match kind { |
|
| 166 | + | case 0 => { |
|
| 167 | + | set e.pendingBranches.len = 0; |
|
| 168 | + | set e.pendingBranches.cap = 0; |
|
| 169 | + | rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump); |
|
| 170 | + | } |
|
| 171 | + | case 1 => { |
|
| 172 | + | set e.pendingCalls.len = 0; |
|
| 173 | + | set e.pendingCalls.cap = 0; |
|
| 174 | + | rv64::emit::recordCall(&mut e, "call"); |
|
| 175 | + | } |
|
| 176 | + | case 2 => { |
|
| 177 | + | set e.pendingJumps.len = 0; |
|
| 178 | + | set e.pendingJumps.cap = 0; |
|
| 179 | + | rv64::emit::recordJumpAt(&mut e, "jump", rv64::ZERO, 0); |
|
| 180 | + | } |
|
| 181 | + | case 3 => { |
|
| 182 | + | set e.pendingAddrLoads.len = 0; |
|
| 183 | + | set e.pendingAddrLoads.cap = 0; |
|
| 184 | + | rv64::emit::recordDataAddrLoad(&mut e, "data", rv64::A0); |
|
| 185 | + | } |
|
| 186 | + | else => { |
|
| 187 | + | set e.funcs.len = 0; |
|
| 188 | + | set e.funcs.cap = 0; |
|
| 189 | + | rv64::emit::recordFunc(&mut e, "function"); |
|
| 190 | + | } |
|
| 191 | + | } |
|
| 192 | + | assert e.error == rv64::Error::Capacity; |
|
| 193 | + | let length = e.codeLen; |
|
| 194 | + | rv64::emit::emit(&mut e, encode::nop()); |
|
| 195 | + | assert e.codeLen == length; |
|
| 196 | + | } |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | /// Debug entries report exhausted storage without writing beyond the buffer. |
|
| 200 | + | unsafe fn testDebugCapacity() throws (TestError) { |
|
| 201 | + | let mut e = boundsEmitter(); |
|
| 202 | + | rv64::emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 }); |
|
| 203 | + | assert e.error == rv64::Error::Capacity; |
|
| 204 | + | assert e.debugEntriesLen == 0; |
|
| 205 | + | } |
|
| 206 | + | ||
| 207 | + | /// Block labels report an out-of-range table index. |
|
| 208 | + | unsafe fn testBlockCapacity() throws (TestError) { |
|
| 209 | + | let mut e = boundsEmitter(); |
|
| 210 | + | let count = e.labels.blockOffsets.len; |
|
| 211 | + | rv64::emit::recordBlock(&mut e, count); |
|
| 212 | + | assert e.error == rv64::Error::Capacity; |
|
| 213 | + | assert e.labels.blockCount == 0; |
|
| 214 | + | } |
|
| 215 | + | ||
| 216 | + | /// Unresolved calls preserve their placeholder instructions and report a symbol error. |
|
| 217 | + | unsafe fn testMissingCallSymbol() throws (TestError) { |
|
| 218 | + | let mut e = boundsEmitter(); |
|
| 219 | + | rv64::emit::recordCall(&mut e, "missing"); |
|
| 220 | + | rv64::emit::patchCalls(&mut e); |
|
| 221 | + | assert e.error == rv64::Error::Symbol; |
|
| 222 | + | assert e.code[0] == encode::nop(); |
|
| 223 | + | assert e.code[1] == encode::nop(); |
|
| 224 | + | } |
|
| 225 | + | ||
| 226 | + | /// Local jumps outside their immediate range preserve the reserved instruction. |
|
| 227 | + | unsafe fn testLocalJumpRange() throws (TestError) { |
|
| 228 | + | let mut e = boundsEmitter(); |
|
| 229 | + | rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump); |
|
| 230 | + | set e.labels.blockOffsets[0] = 0x200000; |
|
| 231 | + | set e.labels.blockCount = 1; |
|
| 232 | + | rv64::emit::patchLocalBranches(&mut e); |
|
| 233 | + | assert e.error == rv64::Error::Relocation; |
|
| 234 | + | assert e.code[0] == encode::nop(); |
|
| 235 | + | } |
|
| 236 | + | ||
| 237 | + | /// Assembly jumps reject targets beyond the J-type displacement range. |
|
| 238 | + | unsafe fn testAssemblyJumpRange() throws (TestError) { |
|
| 239 | + | let mut e = boundsEmitter(); |
|
| 240 | + | rv64::emit::emit(&mut e, encode::nop()); |
|
| 241 | + | rv64::emit::recordJumpAt(&mut e, "far", rv64::ZERO, 0); |
|
| 242 | + | rv64::emit::recordFuncOffsetAt(&mut e, "far", 0x80000); |
|
| 243 | + | rv64::emit::patchJumps(&mut e); |
|
| 244 | + | assert e.error == rv64::Error::Relocation; |
|
| 245 | + | assert e.code[0] == encode::nop(); |
|
| 246 | + | } |
|
| 247 | + | ||
| 248 | + | /// Missing data symbols preserve address-load placeholders. |
|
| 249 | + | unsafe fn testMissingDataSymbol() throws (TestError) { |
|
| 250 | + | let mut e = boundsEmitter(); |
|
| 251 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 252 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 253 | + | rv64::emit::recordDataAddrLoad(&mut e, "missing", rv64::A0); |
|
| 254 | + | rv64::emit::patchAddrLoads(&mut e, &map); |
|
| 255 | + | assert e.error == rv64::Error::Symbol; |
|
| 256 | + | assert e.code[0] == encode::nop(); |
|
| 257 | + | assert e.code[1] == encode::nop(); |
|
| 258 | + | } |
|
| 259 | + | ||
| 260 | + | /// Data addresses outside the supported signed address range are rejected. |
|
| 261 | + | unsafe fn testDataAddressRange() throws (TestError) { |
|
| 262 | + | let mut e = boundsEmitter(); |
|
| 263 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 264 | + | static symbols: [data::DataSym; 1] = [data::DataSym { name: "far", addr: 0x80000000 }]; |
|
| 265 | + | let map = try! data::buildMap(&symbols[..], &mut entries[..]); |
|
| 266 | + | rv64::emit::recordDataAddrLoad(&mut e, "far", rv64::A0); |
|
| 267 | + | rv64::emit::patchAddrLoads(&mut e, &map); |
|
| 268 | + | assert e.error == rv64::Error::Relocation; |
|
| 269 | + | assert e.code[0] == encode::nop(); |
|
| 270 | + | assert e.code[1] == encode::nop(); |
|
| 271 | + | } |
|
| 272 | + | ||
| 273 | + | /// Data section sizes must not wrap when alignment exceeds the address space. |
|
| 274 | + | unsafe fn testDataLayoutOverflow() throws (TestError) { |
|
| 275 | + | let items = [il::Data { name: "value", size: 8, alignment: 8, readOnly: true, isZeroInit: false, values: &[] }]; |
|
| 276 | + | let mut symbols: [data::DataSym; 1] = undefined; |
|
| 277 | + | let mut count: u32 = 0; |
|
| 278 | + | let mut failed = false; |
|
| 279 | + | try data::layoutSectionAtOffset(&items[..], &mut symbols[..], &mut count, 0, 0xfffffffc, true) catch error { |
|
| 280 | + | assert error == data::Error::Overflow; |
|
| 281 | + | set failed = true; |
|
| 282 | + | }; |
|
| 283 | + | assert failed; |
|
| 284 | + | assert count == 0; |
|
| 285 | + | } |
|
| 286 | + | ||
| 287 | + | /// Initializers must not write past their declared data slot. |
|
| 288 | + | unsafe fn testDataInitializerBounds() throws (TestError) { |
|
| 289 | + | try testDataMapCapacity(); |
|
| 290 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 291 | + | let e = try! rv64::emit::emitter(&mut arena, false); |
|
| 292 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 293 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 294 | + | let mut items = [il::Data { |
|
| 295 | + | name: "small", size: 1, alignment: 1, readOnly: true, isZeroInit: false, |
|
| 296 | + | values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 7 }, count: 1 }], |
|
| 297 | + | }]; |
|
| 298 | + | let mut bytes = [0xaa as u8; 16]; |
|
| 299 | + | let mut failed = false; |
|
| 300 | + | try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error { |
|
| 301 | + | assert error == data::Error::Capacity; |
|
| 302 | + | set failed = true; |
|
| 303 | + | }; |
|
| 304 | + | assert failed; |
|
| 305 | + | assert bytes[0] == 0xaa and bytes[1] == 0xaa; |
|
| 306 | + | ||
| 307 | + | set items[0].size = 8; |
|
| 308 | + | let written = try! data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..8], true); |
|
| 309 | + | assert written == 8; |
|
| 310 | + | assert bytes[0] == 7; |
|
| 311 | + | for i in 1..8 { |
|
| 312 | + | assert bytes[i] == 0; |
|
| 313 | + | } |
|
| 314 | + | assert bytes[8] == 0xaa; |
|
| 315 | + | } |
|
| 316 | + | ||
| 317 | + | /// Data maps must reject inputs beyond the backing dictionary capacity. |
|
| 318 | + | unsafe fn testDataMapCapacity() throws (TestError) { |
|
| 319 | + | static mapCapacityEntries: [dict::Entry; 2] = undefined; |
|
| 320 | + | static mapCapacitySymbols: [data::DataSym; 2] = [ |
|
| 321 | + | data::DataSym { name: "first", addr: 0 }, |
|
| 322 | + | data::DataSym { name: "second", addr: 8 }, |
|
| 323 | + | ]; |
|
| 324 | + | let mut failed = false; |
|
| 325 | + | try data::buildMap(&mapCapacitySymbols[..], &mut mapCapacityEntries[..]) catch error { |
|
| 326 | + | assert error == data::Error::Capacity; |
|
| 327 | + | set failed = true; |
|
| 328 | + | }; |
|
| 329 | + | assert failed; |
|
| 330 | + | } |
|
| 331 | + | ||
| 332 | + | /// Create an emitter in reusable static storage for bounds checks. |
|
| 333 | + | unsafe fn boundsEmitter() -> rv64::emit::Emitter { |
|
| 334 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 335 | + | return try! rv64::emit::emitter(&mut arena, false); |
|
| 336 | + | } |
|
| 337 | + | ||
| 338 | + | /// Verify recoverable backend bounds and allocation failures. |
|
| 339 | + | unsafe fn testBackendBounds() throws (TestError) { |
|
| 340 | + | try testFunctionMapCapacity(); |
|
| 341 | + | try testRegisterLimit(); |
|
| 342 | + | try testSpillCandidateLimit(); |
|
| 343 | + | try testFrameSizeOverflow(); |
|
| 344 | + | try testReserveOverflow(); |
|
| 345 | + | try testCallArgumentCapacity(); |
|
| 346 | + | try testBlockArgumentCapacity(); |
|
| 347 | + | try testCodeCapacity(); |
|
| 348 | + | try testGeneratorAllocationFailure(); |
|
| 349 | + | try testFunctionAllocationFailure(); |
|
| 350 | + | try testMetadataCapacity(); |
|
| 351 | + | try testDebugCapacity(); |
|
| 352 | + | try testBlockCapacity(); |
|
| 353 | + | try testMissingCallSymbol(); |
|
| 354 | + | try testLocalJumpRange(); |
|
| 355 | + | try testAssemblyJumpRange(); |
|
| 356 | + | try testMissingDataSymbol(); |
|
| 357 | + | try testDataAddressRange(); |
|
| 358 | + | try testDataLayoutOverflow(); |
|
| 359 | + | try testDataAlignment(); |
|
| 360 | + | try testDataInitializerBounds(); |
|
| 361 | + | try testDataEmissionSymbol(); |
|
| 362 | + | try testFunctionInitializerSymbol(); |
|
| 363 | + | try testOutputPrefixCapacity(); |
|
| 364 | + | try testMissingEntry(); |
|
| 365 | + | try testCodeBaseOverflow(); |
|
| 366 | + | } |
|
| 367 | + | ||
| 368 | + | /// Function labels reject a full dictionary before insertion. |
|
| 369 | + | unsafe fn testFunctionMapCapacity() throws (TestError) { |
|
| 370 | + | let mut e = boundsEmitter(); |
|
| 371 | + | static entries: [dict::Entry; 2] = undefined; |
|
| 372 | + | set e.labels.funcs = dict::init(&mut entries[..]); |
|
| 373 | + | rv64::emit::recordFuncOffsetAt(&mut e, "first", 0); |
|
| 374 | + | rv64::emit::recordFuncOffsetAt(&mut e, "second", 1); |
|
| 375 | + | assert e.error == rv64::Error::Capacity; |
|
| 376 | + | assert e.labels.funcs.count == 1; |
|
| 377 | + | } |
|
| 378 | + | ||
| 379 | + | /// Data initializers report unresolved symbols before writing the slot. |
|
| 380 | + | unsafe fn testDataEmissionSymbol() throws (TestError) { |
|
| 381 | + | let e = boundsEmitter(); |
|
| 382 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 383 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 384 | + | let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false, |
|
| 385 | + | values: &[il::DataValue { item: il::DataItem::Sym("missing"), count: 1 }] }]; |
|
| 386 | + | let mut bytes = [0xaa as u8; 8]; |
|
| 387 | + | let mut failed = false; |
|
| 388 | + | try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error { |
|
| 389 | + | assert error == data::Error::Symbol; |
|
| 390 | + | set failed = true; |
|
| 391 | + | }; |
|
| 392 | + | assert failed; |
|
| 393 | + | assert bytes[0] == 0xaa; |
|
| 394 | + | } |
|
| 395 | + | ||
| 396 | + | /// Function initializers report unresolved symbols before writing the slot. |
|
| 397 | + | unsafe fn testFunctionInitializerSymbol() throws (TestError) { |
|
| 398 | + | let e = boundsEmitter(); |
|
| 399 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 400 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 401 | + | let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false, |
|
| 402 | + | values: &[il::DataValue { item: il::DataItem::Fn("missing"), count: 1 }] }]; |
|
| 403 | + | let mut bytes = [0xaa as u8; 8]; |
|
| 404 | + | let mut failed = false; |
|
| 405 | + | try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error { |
|
| 406 | + | assert error == data::Error::Symbol; |
|
| 407 | + | set failed = true; |
|
| 408 | + | }; |
|
| 409 | + | assert failed; |
|
| 410 | + | assert bytes[0] == 0xaa; |
|
| 411 | + | } |
|
| 412 | + | ||
| 413 | + | /// The read-only prefix must fit its output buffer. |
|
| 414 | + | unsafe fn testOutputPrefixCapacity() throws (TestError) { |
|
| 415 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 416 | + | let mut generator = try! rv64::beginProgram( |
|
| 417 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 418 | + | ); |
|
| 419 | + | static symbols: [data::DataSym; 1] = undefined; |
|
| 420 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 421 | + | let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] }; |
|
| 422 | + | let mut failed = false; |
|
| 423 | + | try rv64::finishProgram(&mut generator, &[], storage, "x", &mut [], &mut []) catch error { |
|
| 424 | + | assert error == rv64::Error::Capacity; |
|
| 425 | + | set failed = true; |
|
| 426 | + | }; |
|
| 427 | + | assert failed; |
|
| 428 | + | } |
|
| 429 | + | ||
| 430 | + | /// A reserved entry jump requires an entry symbol. |
|
| 431 | + | unsafe fn testMissingEntry() throws (TestError) { |
|
| 432 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 433 | + | let mut generator = try! rv64::beginProgram( |
|
| 434 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::Reserved(nil), debug: false }, &mut arena |
|
| 435 | + | ); |
|
| 436 | + | static symbols: [data::DataSym; 1] = undefined; |
|
| 437 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 438 | + | let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] }; |
|
| 439 | + | let mut failed = false; |
|
| 440 | + | try rv64::finishProgram(&mut generator, &[], storage, "", &mut [], &mut []) catch error { |
|
| 441 | + | assert error == rv64::Error::Symbol; |
|
| 442 | + | set failed = true; |
|
| 443 | + | }; |
|
| 444 | + | assert failed; |
|
| 445 | + | } |
|
| 446 | + | ||
| 447 | + | /// Code placement must not wrap after the read-only section. |
|
| 448 | + | unsafe fn testCodeBaseOverflow() throws (TestError) { |
|
| 449 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 450 | + | let mut generator = try! rv64::beginProgram( |
|
| 451 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 452 | + | ); |
|
| 453 | + | static symbols: [data::DataSym; 1] = undefined; |
|
| 454 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 455 | + | let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] }; |
|
| 456 | + | let items = [il::Data { name: "large", size: 0xfffeffff, alignment: 1, readOnly: true, isZeroInit: true, values: &[] }]; |
|
| 457 | + | let mut failed = false; |
|
| 458 | + | try rv64::finishProgram(&mut generator, &items[..], storage, "", &mut [], &mut []) catch error { |
|
| 459 | + | assert error == rv64::Error::Data(data::Error::Overflow); |
|
| 460 | + | set failed = true; |
|
| 461 | + | }; |
|
| 462 | + | assert failed; |
|
| 463 | + | } |
|
| 464 | + | ||
| 465 | + | /// Call arguments must fit the architectural argument register bank. |
|
| 466 | + | unsafe fn testCallArgumentCapacity() throws (TestError) { |
|
| 467 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 468 | + | let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 469 | + | let mut generator = try! rv64::beginProgram( |
|
| 470 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 471 | + | ); |
|
| 472 | + | let args = [il::Val::Imm(0); 9]; |
|
| 473 | + | let mut instructions = [ |
|
| 474 | + | il::Instr::Call { retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..] }, |
|
| 475 | + | il::Instr::Ret { val: nil }, |
|
| 476 | + | ]; |
|
| 477 | + | let func = il::Fn { |
|
| 478 | + | name: "call", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: false, |
|
| 479 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 480 | + | }; |
|
| 481 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 482 | + | assert generator.e.error == rv64::Error::Capacity; |
|
| 483 | + | assert scratch.offset == 0; |
|
| 484 | + | ||
| 485 | + | set generator.e.error = nil; |
|
| 486 | + | set instructions[0] = il::Instr::Call { |
|
| 487 | + | retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..8], |
|
| 488 | + | }; |
|
| 489 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 490 | + | assert generator.e.error == nil; |
|
| 491 | + | assert scratch.offset == 0; |
|
| 492 | + | } |
|
| 493 | + | ||
| 494 | + | /// Block argument lists must fit the parallel move storage. |
|
| 495 | + | unsafe fn testBlockArgumentCapacity() throws (TestError) { |
|
| 496 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 497 | + | let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]); |
|
| 498 | + | let mut generator = try! rv64::beginProgram( |
|
| 499 | + | rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena |
|
| 500 | + | ); |
|
| 501 | + | let mut args = [il::Val::Imm(0); 17]; |
|
| 502 | + | let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }; 17]; |
|
| 503 | + | for i in 0..params.len { |
|
| 504 | + | set params[i].value.n = i; |
|
| 505 | + | } |
|
| 506 | + | let mut entry = [il::Instr::Jmp { target: 1, args: &mut args[..] }]; |
|
| 507 | + | let mut exit = [il::Instr::Ret { val: nil }]; |
|
| 508 | + | let mut blocks = [ |
|
| 509 | + | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
|
| 510 | + | il::Block { label: "exit", params: ¶ms[..], instrs: &mut exit[..], locs: &[], preds: &[0], loopDepth: 0 }, |
|
| 511 | + | ]; |
|
| 512 | + | let func = il::Fn { |
|
| 513 | + | name: "block", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
|
| 514 | + | blocks: &blocks[..], |
|
| 515 | + | }; |
|
| 516 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 517 | + | assert generator.e.error == rv64::Error::Capacity; |
|
| 518 | + | assert scratch.offset == 0; |
|
| 519 | + | ||
| 520 | + | set generator.e.error = nil; |
|
| 521 | + | set entry[0] = il::Instr::Jmp { target: 1, args: &mut args[..16] }; |
|
| 522 | + | set blocks[1].params = ¶ms[..16]; |
|
| 523 | + | rv64::generateFunction(&mut generator, &func, &mut scratch); |
|
| 524 | + | assert generator.e.error == nil; |
|
| 525 | + | assert scratch.offset == 0; |
|
| 526 | + | } |
|
| 527 | + | ||
| 528 | + | /// Initialized and zero-filled data require power-of-two alignment. |
|
| 529 | + | unsafe fn testDataAlignment() throws (TestError) { |
|
| 530 | + | for zeroInit in [false, true] { |
|
| 531 | + | for alignment in [0, 3, 8] { |
|
| 532 | + | let items = [il::Data { |
|
| 533 | + | name: "aligned", size: 8, alignment, readOnly: true, |
|
| 534 | + | isZeroInit: zeroInit, values: &[], |
|
| 535 | + | }]; |
|
| 536 | + | let mut symbols: [data::DataSym; 1] = undefined; |
|
| 537 | + | let mut count: u32 = 0; |
|
| 538 | + | let mut failed = false; |
|
| 539 | + | let size = try data::layoutSectionAtOffset( |
|
| 540 | + | &items[..], &mut symbols[..], &mut count, 16, 1, true |
|
| 541 | + | ) catch error { |
|
| 542 | + | assert error == data::Error::Alignment; |
|
| 543 | + | set failed = true; |
|
| 544 | + | 0; |
|
| 545 | + | }; |
|
| 546 | + | assert failed == (alignment <> 8); |
|
| 547 | + | if not failed { |
|
| 548 | + | assert size == 16 and count == 1; |
|
| 549 | + | assert symbols[0].addr == 24; |
|
| 550 | + | } else { |
|
| 551 | + | assert count == 0; |
|
| 552 | + | } |
|
| 553 | + | } |
|
| 554 | + | } |
|
| 555 | + | } |