rv64: Propagate code generation errors directly
5573da2c8e071c59ab4bf8da6e65249b0bd4747c4b758dc33223fdb4ca7f76a4
Make RV64 program construction, instruction selection, relocation, data layout, and output-buffer failures recoverable. Propagate these failures through the streaming lowerer to the compiler driver, where they are reported as code generation errors. Preserve arena checkpoints and output placeholders on failed operations, and add focused backend failure regressions.
1 parent
9b9efcd0
compiler/radiance.rad
+16 -4
| 563 | 563 | throw error(&["module has no AST"]); |
|
| 564 | 564 | }; |
|
| 565 | 565 | pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]); |
|
| 566 | 566 | ||
| 567 | 567 | try lower::lowerModule(low, modId, modAst, isRoot) catch err { |
|
| 568 | + | if err == lower::LowerError::OutputFailed { |
|
| 569 | + | throw Error::Other; |
|
| 570 | + | } |
|
| 568 | 571 | io::printError("radiance: "); |
|
| 569 | 572 | io::printError("internal error during lowering: "); |
|
| 570 | 573 | lower::printError(err); |
|
| 571 | 574 | io::printError("\n"); |
|
| 572 | 575 |
| 942 | 945 | throw error(&["fatal:", "assembly rodata too large"]); |
|
| 943 | 946 | } |
|
| 944 | 947 | try! mem::copy(&mut ASM_RO_DATA_BUF[*asmDataLen..], program.data); |
|
| 945 | 948 | set *asmDataLen += program.data.len; |
|
| 946 | 949 | ||
| 947 | - | rv64::addAssembly(generator, program); |
|
| 950 | + | try rv64::addAssembly(generator, program) catch error { |
|
| 951 | + | codegen::printError(error); |
|
| 952 | + | throw Error::Other; |
|
| 953 | + | }; |
|
| 948 | 954 | } |
|
| 949 | 955 | ||
| 950 | 956 | /// Assemble all inputs collected in the package inputs. |
|
| 951 | 957 | unsafe fn assembleAsmInputs( |
|
| 952 | 958 | ctx: &CompileContext, |
| 990 | 996 | case CodegenEntryMode::DefaultEntry => { |
|
| 991 | 997 | set entryPatch = rv64::EntryPatch::Reserved(nil); |
|
| 992 | 998 | } |
|
| 993 | 999 | else => {} |
|
| 994 | 1000 | } |
|
| 995 | - | let mut generator = rv64::beginProgram( |
|
| 1001 | + | let mut generator = try rv64::beginProgram( |
|
| 996 | 1002 | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug }, |
|
| 997 | 1003 | &mut res.arena |
|
| 998 | - | ); |
|
| 1004 | + | ) catch error { |
|
| 1005 | + | codegen::printError(error); |
|
| 1006 | + | throw Error::Other; |
|
| 1007 | + | }; |
|
| 999 | 1008 | let mut codegenCtx = codegen::Context { |
|
| 1000 | 1009 | generator: &mut generator, |
|
| 1001 | 1010 | fnArena: (&mut *fnArena) as *unsafe mut alloc::Arena, |
|
| 1002 | 1011 | }; |
|
| 1003 | 1012 | let mut low = lower::lowerer( |
| 1023 | 1032 | else => {} |
|
| 1024 | 1033 | } |
|
| 1025 | 1034 | if let path = codegenOptions.logPath { |
|
| 1026 | 1035 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1027 | 1036 | } |
|
| 1028 | - | return rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]); |
|
| 1037 | + | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch error { |
|
| 1038 | + | codegen::printError(error); |
|
| 1039 | + | throw Error::Other; |
|
| 1040 | + | }; |
|
| 1029 | 1041 | } |
|
| 1030 | 1042 | ||
| 1031 | 1043 | /// Lower, optionally dump, and optionally generate binary output. |
|
| 1032 | 1044 | unsafe fn compile( |
|
| 1033 | 1045 | ctx: *unsafe mut CompileContext, |
compiler/radiance/codegen.rad
+31 -4
| 1 | 1 | //! Streaming code generation with caller-managed context storage. |
|
| 2 | 2 | ||
| 3 | 3 | use std::arch::rv64; |
|
| 4 | + | use std::io; |
|
| 4 | 5 | use std::lang::alloc; |
|
| 5 | 6 | use std::lang::il; |
|
| 6 | 7 | use std::lang::lower; |
|
| 8 | + | use std::lang::gen::data; |
|
| 7 | 9 | ||
| 8 | 10 | /// State carried by the streaming lowerer/codegen callback. |
|
| 9 | 11 | export record Context: Copy { |
|
| 10 | 12 | /// Generator receiving functions. It must remain valid for every callback. |
|
| 11 | 13 | generator: *unsafe mut rv64::Generator, |
| 13 | 15 | fnArena: *unsafe mut alloc::Arena, |
|
| 14 | 16 | } |
|
| 15 | 17 | ||
| 16 | 18 | /// Emit one lowered function to machine code and reclaim its IL arena. |
|
| 17 | 19 | /// The context must point to a valid `Context` with live generator and arena storage. |
|
| 18 | - | export unsafe fn emit(ctxPtr: *unsafe mut opaque, func: *unsafe il::Fn, role: lower::FnRole) { |
|
| 20 | + | export unsafe fn emit(ctxPtr: *unsafe mut opaque, func: *unsafe il::Fn, role: lower::FnRole) throws (lower::LowerError) { |
|
| 19 | 21 | let ctx = ctxPtr as *unsafe mut Context; |
|
| 22 | + | try generateFunction(ctx, func, role) catch error { |
|
| 23 | + | alloc::reset(ctx.fnArena); |
|
| 24 | + | printError(error); |
|
| 25 | + | throw lower::LowerError::OutputFailed; |
|
| 26 | + | }; |
|
| 27 | + | alloc::reset(ctx.fnArena); |
|
| 28 | + | } |
|
| 20 | 29 | ||
| 30 | + | /// Generate a function and register its entry symbol when required. |
|
| 31 | + | unsafe fn generateFunction(ctx: *unsafe mut Context, func: *unsafe il::Fn, role: lower::FnRole) throws (rv64::Error) { |
|
| 21 | 32 | match role { |
|
| 22 | 33 | case lower::FnRole::Default => { |
|
| 23 | - | rv64::recordFunctionAlias(ctx.generator, super::DEFAULT_ENTRY_SYMBOL); |
|
| 34 | + | try rv64::recordFunctionAlias(ctx.generator, super::DEFAULT_ENTRY_SYMBOL); |
|
| 24 | 35 | match ctx.generator.entryPatch { |
|
| 25 | 36 | case rv64::EntryPatch::Reserved(_) => { |
|
| 26 | 37 | set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name); |
|
| 27 | 38 | } |
|
| 28 | 39 | // Startup assembly calls the default function through its |
| 32 | 43 | } |
|
| 33 | 44 | else => {} |
|
| 34 | 45 | } |
|
| 35 | 46 | let generator = ctx.generator; |
|
| 36 | 47 | let arena = ctx.fnArena; |
|
| 37 | - | rv64::generateFunction(generator, func, arena); |
|
| 38 | - | alloc::reset(ctx.fnArena); |
|
| 48 | + | try rv64::generateFunction(generator, func, arena); |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Report a code generation failure. |
|
| 52 | + | export fn printError(error: rv64::Error) { |
|
| 53 | + | match error { |
|
| 54 | + | case rv64::Error::Allocation => io::printError("radiance: code generation allocation failed\n"), |
|
| 55 | + | case rv64::Error::Capacity => io::printError("radiance: code generation capacity exceeded\n"), |
|
| 56 | + | case rv64::Error::Symbol => io::printError("radiance: invalid code generation symbol\n"), |
|
| 57 | + | case rv64::Error::Data(error) => { |
|
| 58 | + | match error { |
|
| 59 | + | case data::Error::Symbol => io::printError("radiance: missing data symbol\n"), |
|
| 60 | + | case data::Error::Capacity => io::printError("radiance: data section capacity exceeded\n"), |
|
| 61 | + | case data::Error::Overflow => io::printError("radiance: data section size or address overflow\n"), |
|
| 62 | + | } |
|
| 63 | + | }, |
|
| 64 | + | case rv64::Error::Relocation => io::printError("radiance: code generation relocation out of range\n"), |
|
| 65 | + | } |
|
| 39 | 66 | } |
lib/std/arch/rv64.rad
+72 -32
| 20 | 20 | @test mod tests; |
|
| 21 | 21 | ||
| 22 | 22 | use std::mem; |
|
| 23 | 23 | use std::collections::dict; |
|
| 24 | 24 | use std::lang::il; |
|
| 25 | + | use std::lang::parser; |
|
| 25 | 26 | use std::lang::alloc; |
|
| 26 | 27 | use std::lang::gen; |
|
| 27 | 28 | use std::lang::gen::labels; |
|
| 28 | 29 | use std::lang::gen::regalloc; |
|
| 29 | 30 | use std::lang::gen::data; |
|
| 30 | 31 | use std::lang::gen::types; |
|
| 31 | 32 | ||
| 33 | + | /// Recoverable code-generation failure. |
|
| 34 | + | export union Error: Copy { |
|
| 35 | + | /// The code-generation or function arena is full. |
|
| 36 | + | Allocation, |
|
| 37 | + | /// A fixed output or metadata table is full. |
|
| 38 | + | Capacity, |
|
| 39 | + | /// A required symbol is missing or invalid. |
|
| 40 | + | Symbol, |
|
| 41 | + | /// A branch or address exceeds its instruction range. |
|
| 42 | + | Relocation, |
|
| 43 | + | /// Data section layout or emission failed. |
|
| 44 | + | Data(data::Error), |
|
| 45 | + | } |
|
| 46 | + | ||
| 32 | 47 | //////////////// |
|
| 33 | 48 | // Registers // |
|
| 34 | 49 | //////////////// |
|
| 35 | 50 | ||
| 36 | 51 | export constant ZERO: gen::Reg = gen::Reg(0); /// Hard-wired zero. |
| 212 | 227 | ||
| 213 | 228 | /// Begin RV64 code generation for a program's global state. |
|
| 214 | 229 | export unsafe fn beginProgram( |
|
| 215 | 230 | options: ProgramOptions, |
|
| 216 | 231 | arena: &mut alloc::Arena |
|
| 217 | - | ) -> Generator { |
|
| 218 | - | let mut e = try! emit::emitter(arena, options.debug); |
|
| 232 | + | ) -> Generator throws (Error) { |
|
| 233 | + | let checkpoint = alloc::save(arena); |
|
| 234 | + | let mut e = try emit::emitter(arena, options.debug) catch { |
|
| 235 | + | alloc::restore(arena, checkpoint); |
|
| 236 | + | throw Error::Allocation; |
|
| 237 | + | }; |
|
| 219 | 238 | ||
| 220 | 239 | // Emit placeholder entry jump when requested. |
|
| 221 | 240 | // We'll patch this at the end once we know where the function is. |
|
| 222 | 241 | match options.entryPatch { |
|
| 223 | 242 | case EntryPatch::Reserved(_) => { |
|
| 224 | - | emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump. |
|
| 225 | - | emit::emit(&mut e, encode::nop()); // |
|
| 243 | + | try emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump. |
|
| 244 | + | try emit::emit(&mut e, encode::nop()); // |
|
| 226 | 245 | } |
|
| 227 | 246 | else => {} |
|
| 228 | 247 | } |
|
| 229 | 248 | ||
| 230 | 249 | return Generator { |
| 236 | 255 | /// Generate code for one IL function. |
|
| 237 | 256 | export unsafe fn generateFunction( |
|
| 238 | 257 | generator: &mut Generator, |
|
| 239 | 258 | func: *unsafe il::Fn, |
|
| 240 | 259 | arena: &mut alloc::Arena |
|
| 241 | - | ) { |
|
| 260 | + | ) throws (Error) { |
|
| 242 | 261 | if func.isExtern { |
|
| 243 | 262 | return; |
|
| 244 | 263 | } |
|
| 245 | 264 | let checkpoint = alloc::save(arena); |
|
| 246 | 265 | let config = targetConfig(); |
|
| 247 | - | let ralloc = try! regalloc::allocate(func, &config, arena); |
|
| 266 | + | let ralloc = try regalloc::allocate(func, &config, arena) catch { |
|
| 267 | + | alloc::restore(arena, checkpoint); |
|
| 268 | + | throw Error::Allocation; |
|
| 269 | + | }; |
|
| 248 | 270 | ||
| 249 | - | isel::selectFn(&mut generator.e, &ralloc, func); |
|
| 271 | + | try isel::selectFn(&mut generator.e, &ralloc, func) catch error { |
|
| 272 | + | alloc::restore(arena, checkpoint); |
|
| 273 | + | throw error; |
|
| 274 | + | }; |
|
| 250 | 275 | ||
| 251 | 276 | // Reclaim unused memory after instruction selection. |
|
| 252 | 277 | alloc::restore(arena, checkpoint); |
|
| 253 | 278 | } |
|
| 254 | 279 | ||
| 255 | 280 | /// Record an alternate name for the next function emitted. |
|
| 256 | - | export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) { |
|
| 257 | - | let codeLen = generator.e.codeLen; |
|
| 258 | - | emit::recordFuncOffsetAt(&mut generator.e, name, codeLen); |
|
| 281 | + | export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) throws (Error) { |
|
| 282 | + | let codeLen = generator.e.code.len; |
|
| 283 | + | try emit::recordFuncOffsetAt(&mut generator.e, name, codeLen); |
|
| 259 | 284 | } |
|
| 260 | 285 | ||
| 261 | 286 | /// Add the text section of an assembled program to the generator. |
|
| 262 | 287 | /// |
|
| 263 | 288 | /// This function snapshots the generator's current code length as the base |
| 268 | 293 | /// separate assembly inputs may reuse the same local names. |
|
| 269 | 294 | /// |
|
| 270 | 295 | /// Non-text symbols are ignored here because assembled data is not appended to |
|
| 271 | 296 | /// the generator's text stream. The driver merges assembled data into the RO data |
|
| 272 | 297 | /// prefix separately and passes that data to [`finishProgram`]. |
|
| 273 | - | export fn addAssembly(generator: &mut Generator, program: asm::Program) { |
|
| 274 | - | let baseIndex = generator.e.codeLen; |
|
| 298 | + | export fn addAssembly(generator: &mut Generator, program: asm::Program) throws (Error) { |
|
| 299 | + | let baseIndex = generator.e.code.len; |
|
| 275 | 300 | ||
| 276 | 301 | for symbol in program.symbols { |
|
| 277 | 302 | if symbol.section == asm::Section::Text { |
|
| 278 | 303 | let index = baseIndex + ((symbol.offset as u32) / INSTR_SIZE as u32); |
|
| 279 | - | emit::recordFuncAt(&mut generator.e, symbol.name, index); |
|
| 304 | + | try emit::recordFuncAt(&mut generator.e, symbol.name, index); |
|
| 280 | 305 | if symbol.isExported { |
|
| 281 | - | emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index); |
|
| 306 | + | try emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index); |
|
| 282 | 307 | } |
|
| 283 | 308 | } |
|
| 284 | 309 | } |
|
| 285 | 310 | for fixup in program.externalFixups { |
|
| 286 | 311 | match fixup.info { |
|
| 287 | 312 | case asm::FixupInfo::Jal { rd, index } => { |
|
| 288 | - | emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 313 | + | try emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 289 | 314 | } |
|
| 290 | 315 | case asm::FixupInfo::Addr { rd, index } => { |
|
| 291 | - | emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 316 | + | try emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 292 | 317 | } |
|
| 293 | 318 | else => panic "addAssembly: invalid external fixup", |
|
| 294 | 319 | } |
|
| 295 | 320 | } |
|
| 296 | 321 | for word in program.text { |
|
| 297 | - | emit::emit(&mut generator.e, word); |
|
| 322 | + | try emit::emit(&mut generator.e, word); |
|
| 298 | 323 | } |
|
| 299 | 324 | } |
|
| 300 | 325 | ||
| 301 | 326 | /// Finish RV64 code generation and return the emitted program. |
|
| 302 | 327 | export unsafe fn finishProgram( |
| 304 | 329 | globalData: &[il::Data], |
|
| 305 | 330 | storage: Storage, |
|
| 306 | 331 | roDataPrefix: *[u8], |
|
| 307 | 332 | roDataBuf: &mut [u8], |
|
| 308 | 333 | rwDataBuf: &mut [u8] |
|
| 309 | - | ) -> Program { |
|
| 334 | + | ) -> Program throws (Error) { |
|
| 310 | 335 | // Build data map after function lowering. Function-local literals can add |
|
| 311 | 336 | // global data while functions are lowered, so final layout belongs here. |
|
| 312 | 337 | let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage |
|
| 313 | 338 | else panic "expected code generation storage"; |
|
| 314 | 339 | let mut dataSymCount: u32 = 0; |
|
| 315 | - | let roLayoutSize = data::layoutSectionAtOffset( |
|
| 340 | + | let roLayoutSize = try data::layoutSectionAtOffset( |
|
| 316 | 341 | globalData, symbolBuf, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true |
|
| 317 | - | ); |
|
| 318 | - | data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false); |
|
| 342 | + | ) catch error { |
|
| 343 | + | throw Error::Data(error); |
|
| 344 | + | }; |
|
| 345 | + | try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, RW_DATA_BASE, false) catch error { |
|
| 346 | + | throw Error::Data(error); |
|
| 347 | + | }; |
|
| 319 | 348 | ||
| 320 | 349 | let dataSyms = &symbolBuf[..dataSymCount]; |
|
| 321 | - | let dataSymMap = data::buildMap(dataSyms, dataSymEntries); |
|
| 350 | + | let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch error { |
|
| 351 | + | throw Error::Data(error); |
|
| 352 | + | }; |
|
| 353 | + | if roLayoutSize > parser::U32_MAX - RO_DATA_BASE - (DWORD_SIZE as u32 - 1) { |
|
| 354 | + | throw Error::Data(data::Error::Overflow); |
|
| 355 | + | } |
|
| 322 | 356 | let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE 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 = try 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 | } |
|
| 335 | 369 | else => {} |
|
| 336 | 370 | } |
|
| 337 | 371 | // Patch function calls and address loads now that all functions are emitted. |
|
| 338 | - | emit::patchJumps(&mut generator.e); |
|
| 339 | - | emit::patchCalls(&mut generator.e); |
|
| 340 | - | emit::patchAddrLoads(&mut generator.e, &dataSymMap); |
|
| 372 | + | try emit::patchJumps(&mut generator.e); |
|
| 373 | + | try emit::patchCalls(&mut generator.e); |
|
| 374 | + | try emit::patchAddrLoads(&mut generator.e, &dataSymMap); |
|
| 341 | 375 | ||
| 342 | 376 | // Emit data sections. |
|
| 343 | - | assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow"; |
|
| 377 | + | if roDataPrefix.len > roDataBuf.len { |
|
| 378 | + | throw Error::Capacity; |
|
| 379 | + | } |
|
| 344 | 380 | try! mem::copy(roDataBuf, roDataPrefix); |
|
| 345 | 381 | ||
| 346 | - | let roDataSize = data::emitSectionAtOffset( |
|
| 382 | + | let roDataSize = try data::emitSectionAtOffset( |
|
| 347 | 383 | globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len |
|
| 348 | - | ); |
|
| 349 | - | let rwDataSize = data::emitSection( |
|
| 384 | + | ) catch error { |
|
| 385 | + | throw Error::Data(error); |
|
| 386 | + | }; |
|
| 387 | + | let rwDataSize = try data::emitSection( |
|
| 350 | 388 | globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false |
|
| 351 | - | ); |
|
| 389 | + | ) catch error { |
|
| 390 | + | throw Error::Data(error); |
|
| 391 | + | }; |
|
| 352 | 392 | return Program { |
|
| 353 | 393 | code: emit::getCode(&generator.e), |
|
| 354 | 394 | funcs: &generator.e.funcs[..], |
|
| 355 | 395 | roDataSize, |
|
| 356 | 396 | rwDataSize, |
lib/std/arch/rv64/asm.rad
+2 -0
| 84 | 84 | externalFixups: *[Fixup], |
|
| 85 | 85 | } |
|
| 86 | 86 | ||
| 87 | 87 | /// Errors reported while assembling source text. |
|
| 88 | 88 | export union Error: Copy { |
|
| 89 | + | /// An assembler metadata table or symbol buffer is full. |
|
| 90 | + | Capacity, |
|
| 89 | 91 | /// Invalid syntax or operand form at a source offset. |
|
| 90 | 92 | Invalid { offset: u32, message: *[u8] }, |
|
| 91 | 93 | /// The source emitted more text words than the caller-provided buffer holds. |
|
| 92 | 94 | TextOverflow, |
|
| 93 | 95 | /// The source emitted more data bytes than the caller-provided buffer holds. |
lib/std/arch/rv64/asm/emit.rad
+8 -4
| 7 | 7 | use std::collections::dict; |
|
| 8 | 8 | use std::lang::alloc; |
|
| 9 | 9 | use std::lang::gen; |
|
| 10 | 10 | ||
| 11 | 11 | /// Define a symbol at the current text or data offset. |
|
| 12 | - | export unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8]) { |
|
| 12 | + | export unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8]) throws (super::Error) { |
|
| 13 | 13 | let idx = a.symbols.len; |
|
| 14 | 14 | let offset: i32 = a.data.len as i32 |
|
| 15 | 15 | if a.section == super::Section::Data |
|
| 16 | 16 | else a.text.len as i32 * rv64::INSTR_SIZE; |
|
| 17 | 17 | ||
| 18 | - | try! a.symbols.append(super::Symbol { |
|
| 18 | + | try a.symbols.append(super::Symbol { |
|
| 19 | 19 | name, |
|
| 20 | 20 | section: a.section, |
|
| 21 | 21 | offset, |
|
| 22 | 22 | isExported: dict::get(&a.exportMap, name) <> nil, |
|
| 23 | - | }, alloc::arenaAllocator(a.arena)); |
|
| 24 | - | dict::insert(&mut a.symbolMap, name, idx as i32); |
|
| 23 | + | }, alloc::arenaAllocator(a.arena)) catch { |
|
| 24 | + | throw super::Error::Capacity; |
|
| 25 | + | }; |
|
| 26 | + | try dict::insert(&mut a.symbolMap, name, idx as i32) catch { |
|
| 27 | + | throw super::Error::Capacity; |
|
| 28 | + | }; |
|
| 25 | 29 | } |
|
| 26 | 30 | ||
| 27 | 31 | /// Append one encoded instruction word to the text section. |
|
| 28 | 32 | export unsafe fn emitText(a: &mut super::Assembler, word: u32) throws (super::Error) { |
|
| 29 | 33 | try a.text.append(word, alloc::arenaAllocator(a.arena)) catch { |
lib/std/arch/rv64/asm/parser.rad
+7 -3
| 100 | 100 | /// Define a label at the current text or data offset. |
|
| 101 | 101 | unsafe fn defineSymbol(a: &mut super::Assembler, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 102 | 102 | if dict::get(&a.symbolMap, name) <> nil { |
|
| 103 | 103 | throw failOnToken(tok, "duplicate label"); |
|
| 104 | 104 | } |
|
| 105 | - | emit::defineSymbol(a, name); |
|
| 105 | + | try emit::defineSymbol(a, name); |
|
| 106 | 106 | } |
|
| 107 | 107 | ||
| 108 | 108 | /// Emit a parsed integer data value after applying source-level range checks. |
|
| 109 | 109 | unsafe fn emitDataValue(a: &mut super::Assembler, value: i64, width: super::DataWidth) throws (super::Error) { |
|
| 110 | 110 | match width { |
| 540 | 540 | /// Parse a `.constant` directive. |
|
| 541 | 541 | unsafe fn parseConstantDirective(a: &mut super::Assembler) throws (super::Error) { |
|
| 542 | 542 | let name = try parseSymbolName(a); |
|
| 543 | 543 | let value = try expectI32Value(a, try parseExpr(a), "constant out of range"); |
|
| 544 | 544 | ||
| 545 | - | dict::insert(&mut a.constMap, name, value); |
|
| 545 | + | try dict::insert(&mut a.constMap, name, value) catch { |
|
| 546 | + | throw super::Error::Capacity; |
|
| 547 | + | }; |
|
| 546 | 548 | } |
|
| 547 | 549 | ||
| 548 | 550 | /// Parse a `.export` directive. |
|
| 549 | 551 | unsafe fn parseExportDirective(a: &mut super::Assembler) throws (super::Error) { |
|
| 550 | 552 | let name = try parseLabelName(a); |
|
| 551 | - | dict::insert(&mut a.exportMap, name, 1); |
|
| 553 | + | try dict::insert(&mut a.exportMap, name, 1) catch { |
|
| 554 | + | throw super::Error::Capacity; |
|
| 555 | + | }; |
|
| 552 | 556 | if let idx = dict::get(&a.symbolMap, name) { |
|
| 553 | 557 | set a.symbols[idx as u32].isExported = true; |
|
| 554 | 558 | } |
|
| 555 | 559 | } |
|
| 556 | 560 |
lib/std/arch/rv64/asm/tests.rad
+2 -1
| 1 | 1 | //! RV64 assembler tests. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | + | use std::collections::dict; |
|
| 4 | 5 | use std::mem; |
|
| 5 | 6 | use std::lang::alloc; |
|
| 6 | 7 | use std::lang::sexpr; |
|
| 7 | 8 | use std::lang::strings; |
|
| 8 | 9 | use std::arch::rv64; |
|
| 9 | 10 | use std::arch::rv64::encode; |
|
| 10 | 11 | use std::arch::rv64::printer; |
|
| 11 | 12 | ||
| 12 | 13 | use super::scanner; |
|
| 13 | 14 | ||
| 15 | + | unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 14 | 16 | static ASM_ARENA_STORAGE: [u8; 65536] = [0; 65536]; |
|
| 15 | 17 | static ASM_TEXT_STORAGE: [u32; 256] = [0; 256]; |
|
| 16 | 18 | static ASM_DATA_STORAGE: [u8; 1024] = [0; 1024]; |
|
| 17 | - | unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 18 | 19 | static PRINT_ARENA_STORAGE: [u8; 1024] = [0; 1024]; |
|
| 19 | 20 | static PRINT_BUFFER: [u8; 128] = [0; 128]; |
|
| 20 | 21 | ||
| 21 | 22 | unsafe fn assembleSource(source: *[u8]) -> super::Program throws (testing::TestError) { |
|
| 22 | 23 | let mut arena = alloc::new(&mut ASM_ARENA_STORAGE[..]); |
lib/std/arch/rv64/emit.rad
+196 -159
| 13 | 13 | ||
| 14 | 14 | use super::encode; |
|
| 15 | 15 | ||
| 16 | 16 | /// Maximum number of instructions in code buffer. |
|
| 17 | 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 | 18 | /// Maximum positive value encodable by a signed 32-bit address calculation. |
|
| 21 | 19 | constant MAX_I32_ADDR: u32 = 0x7FFFFFFF; |
|
| 20 | + | /// Minimum value encodable by a signed 32-bit address calculation. |
|
| 21 | + | constant MIN_I32_ADDR: i64 = -(MAX_I32_ADDR as i64) - 1; |
|
| 22 | + | /// Maximum code length before byte offset overflows signed 32-bits. |
|
| 23 | + | constant MAX_CODE_LEN: u32 = MAX_I32_ADDR / super::INSTR_SIZE as u32; |
|
| 22 | 24 | /// Maximum number of pending branches awaiting patching. |
|
| 23 | 25 | constant MAX_PENDING: u32 = 65536; |
|
| 24 | 26 | /// Maximum number of function entries. |
|
| 25 | 27 | constant MAX_FUNCS: u32 = 4096; |
|
| 26 | 28 | /// Maximum number of debug entries. |
| 100 | 102 | export record Emitter { |
|
| 101 | 103 | /// Allocator for growing append-backed emitter lists. |
|
| 102 | 104 | allocator: alloc::Allocator, |
|
| 103 | 105 | /// Emitted instructions storage. |
|
| 104 | 106 | code: *mut [u32], |
|
| 105 | - | /// Current number of emitted instructions. |
|
| 106 | - | codeLen: u32, |
|
| 107 | 107 | /// Local branches needing offset patching. |
|
| 108 | 108 | pendingBranches: *mut [PendingBranch], |
|
| 109 | 109 | /// Function calls needing offset patching. |
|
| 110 | 110 | pendingCalls: *mut [PendingCall], |
|
| 111 | 111 | /// Assembly jumps needing offset patching. |
| 116 | 116 | labels: labels::Labels, |
|
| 117 | 117 | /// Function start positions for printing. |
|
| 118 | 118 | funcs: *mut [types::FuncAddr], |
|
| 119 | 119 | /// Debug entries mapping PCs to source locations. |
|
| 120 | 120 | debugEntries: *mut [types::DebugEntry], |
|
| 121 | - | /// Number of debug entries recorded. |
|
| 122 | - | debugEntriesLen: u32, |
|
| 123 | 121 | } |
|
| 124 | 122 | ||
| 125 | 123 | /// Computed stack frame layout for a function. |
|
| 126 | 124 | export record Frame: Copy { |
|
| 127 | 125 | /// Total frame size in bytes (aligned). |
| 140 | 138 | /// When false, SP never changes after the prologue. |
|
| 141 | 139 | isDynamic: bool, |
|
| 142 | 140 | } |
|
| 143 | 141 | ||
| 144 | 142 | /// 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 { |
|
| 143 | + | export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame throws (super::Error) { |
|
| 146 | 144 | let mut frame = Frame { |
|
| 147 | 145 | totalSize: 0, |
|
| 148 | 146 | savedRegs: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS], |
|
| 149 | 147 | savedRegsLen: 0, |
|
| 150 | 148 | epilogueBlock, |
| 157 | 155 | if isLeaf and localSize == 0 and usedCalleeSaved == 0 { |
|
| 158 | 156 | return frame; |
|
| 159 | 157 | } |
|
| 160 | 158 | // Compute total frame size. Includes RA and FP registers. |
|
| 161 | 159 | let savedRegs = mem::popCount(usedCalleeSaved) + 2; |
|
| 162 | - | let totalSize = mem::alignUpI32( |
|
| 163 | - | localSize + savedRegs * super::DWORD_SIZE, |
|
| 164 | - | super::STACK_ALIGNMENT |
|
| 165 | - | ); |
|
| 160 | + | let savedSize = savedRegs * super::DWORD_SIZE; |
|
| 161 | + | if localSize > MAX_I32_ADDR as i32 - savedSize - (super::STACK_ALIGNMENT - 1) { |
|
| 162 | + | throw super::Error::Capacity; |
|
| 163 | + | } |
|
| 164 | + | let totalSize = mem::alignUpI32(localSize + savedSize, super::STACK_ALIGNMENT); |
|
| 166 | 165 | set frame.totalSize = totalSize; |
|
| 167 | 166 | ||
| 168 | 167 | // Build list of callee-saved registers with offsets. |
|
| 169 | 168 | let mut offset = totalSize - (super::DWORD_SIZE * 3); |
|
| 170 | 169 | for reg, i in super::CALLEE_SAVED { |
| 196 | 195 | if debug { |
|
| 197 | 196 | set debugEntries = try alloc::allocSlice( |
|
| 198 | 197 | arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES |
|
| 199 | 198 | ) as *mut [types::DebugEntry]; |
|
| 200 | 199 | } |
|
| 200 | + | let codeBuf = code as *mut [u32]; |
|
| 201 | 201 | let pendingBranchesBuf = pendingBranches as *mut [PendingBranch]; |
|
| 202 | 202 | let pendingCallsBuf = pendingCalls as *mut [PendingCall]; |
|
| 203 | 203 | let pendingJumpsBuf = pendingJumps as *mut [PendingJump]; |
|
| 204 | 204 | let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad]; |
|
| 205 | 205 | let funcsBuf = funcs as *mut [types::FuncAddr]; |
|
| 206 | 206 | ||
| 207 | 207 | return Emitter { |
|
| 208 | 208 | allocator: alloc::arenaAllocator(arena), |
|
| 209 | - | code: code as *mut [u32], |
|
| 210 | - | codeLen: 0, |
|
| 209 | + | code: &mut codeBuf[..0], |
|
| 211 | 210 | pendingBranches: &mut pendingBranchesBuf[..0], |
|
| 212 | 211 | pendingCalls: &mut pendingCallsBuf[..0], |
|
| 213 | 212 | pendingJumps: &mut pendingJumpsBuf[..0], |
|
| 214 | 213 | pendingAddrLoads: &mut pendingAddrLoadsBuf[..0], |
|
| 215 | 214 | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
|
| 216 | 215 | funcs: &mut funcsBuf[..0], |
|
| 217 | - | debugEntries, |
|
| 218 | - | debugEntriesLen: 0, |
|
| 216 | + | debugEntries: &mut debugEntries[..0], |
|
| 219 | 217 | }; |
|
| 220 | 218 | } |
|
| 221 | 219 | ||
| 222 | 220 | /////////////////////// |
|
| 223 | 221 | // Emission Helpers // |
|
| 224 | 222 | /////////////////////// |
|
| 225 | 223 | ||
| 226 | 224 | /// Emit a single instruction. |
|
| 227 | - | export fn emit(e: &mut Emitter, instr: u32) { |
|
| 228 | - | assert e.codeLen < e.code.len, "emit: code buffer full"; |
|
| 229 | - | set e.code[e.codeLen] = instr; |
|
| 230 | - | set e.codeLen += 1; |
|
| 225 | + | export fn emit(e: &mut Emitter, instr: u32) throws (super::Error) { |
|
| 226 | + | try e.code.append(instr, e.allocator) catch { |
|
| 227 | + | throw super::Error::Allocation; |
|
| 228 | + | }; |
|
| 231 | 229 | } |
|
| 232 | 230 | ||
| 233 | 231 | /// Compute branch offset to a function by name. |
|
| 234 | - | export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 { |
|
| 235 | - | return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE); |
|
| 232 | + | export fn branchOffsetToFunc(e: &Emitter, srcIndex: u32, name: *[u8]) -> i32 throws (super::Error) { |
|
| 233 | + | let target = dict::get(&e.labels.funcs, name) else { |
|
| 234 | + | throw super::Error::Symbol; |
|
| 235 | + | }; |
|
| 236 | + | let offset = target as i64 - srcIndex as i64 * super::INSTR_SIZE as i64; |
|
| 237 | + | if offset < MIN_I32_ADDR or offset > MAX_I32_ADDR as i64 { |
|
| 238 | + | throw super::Error::Relocation; |
|
| 239 | + | } |
|
| 240 | + | return offset as i32; |
|
| 236 | 241 | } |
|
| 237 | 242 | ||
| 238 | 243 | /// Patch an instruction at a given index. |
|
| 239 | 244 | export fn patch(e: &mut Emitter, index: u32, instr: u32) { |
|
| 240 | 245 | set e.code[index] = instr; |
|
| 241 | 246 | } |
|
| 242 | 247 | ||
| 243 | 248 | /// Record a block's address for branch resolution. |
|
| 244 | - | export fn recordBlock(e: &mut Emitter, blockIdx: u32) { |
|
| 245 | - | assert e.codeLen <= MAX_CODE_LEN; |
|
| 246 | - | labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE); |
|
| 249 | + | export fn recordBlock(e: &mut Emitter, blockIdx: u32) throws (super::Error) { |
|
| 250 | + | if e.code.len > MAX_CODE_LEN { |
|
| 251 | + | throw super::Error::Relocation; |
|
| 252 | + | } |
|
| 253 | + | try labels::recordBlock(&mut e.labels, blockIdx, e.code.len as i32 * super::INSTR_SIZE) catch { |
|
| 254 | + | throw super::Error::Capacity; |
|
| 255 | + | }; |
|
| 247 | 256 | } |
|
| 248 | 257 | ||
| 249 | 258 | /// Record a function's code offset for call resolution. |
|
| 250 | - | export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) { |
|
| 251 | - | let codeLen = e.codeLen; |
|
| 252 | - | recordFuncOffsetAt(e, name, codeLen); |
|
| 259 | + | export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) throws (super::Error) { |
|
| 260 | + | let codeLen = e.code.len; |
|
| 261 | + | try recordFuncOffsetAt(e, name, codeLen); |
|
| 253 | 262 | } |
|
| 254 | 263 | ||
| 255 | 264 | /// Record a function's code offset at `index` for call resolution. |
|
| 256 | - | export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) { |
|
| 257 | - | assert index <= MAX_CODE_LEN; |
|
| 258 | - | dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE); |
|
| 265 | + | export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) throws (super::Error) { |
|
| 266 | + | if index > MAX_CODE_LEN { |
|
| 267 | + | throw super::Error::Relocation; |
|
| 268 | + | } |
|
| 269 | + | try dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE) catch { |
|
| 270 | + | throw super::Error::Capacity; |
|
| 271 | + | }; |
|
| 259 | 272 | } |
|
| 260 | 273 | ||
| 261 | 274 | /// Record a function's start position for printing. |
|
| 262 | - | export fn recordFunc(e: &mut Emitter, name: *[u8]) { |
|
| 263 | - | let codeLen = e.codeLen; |
|
| 264 | - | recordFuncAt(e, name, codeLen); |
|
| 275 | + | export fn recordFunc(e: &mut Emitter, name: *[u8]) throws (super::Error) { |
|
| 276 | + | let codeLen = e.code.len; |
|
| 277 | + | try recordFuncAt(e, name, codeLen); |
|
| 265 | 278 | } |
|
| 266 | 279 | ||
| 267 | 280 | /// Record a function's start position at `index` for printing. |
|
| 268 | - | export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) { |
|
| 269 | - | try! e.funcs.append(types::FuncAddr { name, index }, e.allocator); |
|
| 281 | + | export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) throws (super::Error) { |
|
| 282 | + | try e.funcs.append(types::FuncAddr { name, index }, e.allocator) catch { |
|
| 283 | + | throw super::Error::Allocation; |
|
| 284 | + | }; |
|
| 270 | 285 | } |
|
| 271 | 286 | ||
| 272 | 287 | /// Record a local branch needing later patching. |
|
| 273 | 288 | /// Unconditional jumps use a single slot (J-type, +-1MB range). |
|
| 274 | 289 | /// Conditional branches use two slots (B-type has only +-4KB range, |
|
| 275 | 290 | /// so large functions may need the inverted-branch + JAL fallback). |
|
| 276 | - | export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) { |
|
| 277 | - | try! e.pendingBranches.append(PendingBranch { |
|
| 278 | - | index: e.codeLen, |
|
| 291 | + | export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) throws (super::Error) { |
|
| 292 | + | try e.pendingBranches.append(PendingBranch { |
|
| 293 | + | index: e.code.len, |
|
| 279 | 294 | target: targetBlock, |
|
| 280 | 295 | kind: kind, |
|
| 281 | - | }, e.allocator); |
|
| 296 | + | }, e.allocator) catch { |
|
| 297 | + | throw super::Error::Allocation; |
|
| 298 | + | }; |
|
| 282 | 299 | ||
| 283 | - | emit(e, encode::nop()); // First slot, always needed. |
|
| 300 | + | try emit(e, encode::nop()); // First slot, always needed. |
|
| 284 | 301 | ||
| 285 | 302 | match kind { |
|
| 286 | 303 | case BranchKind::Jump => {}, |
|
| 287 | - | else => emit(e, encode::nop()), // Second slot for conditional branches. |
|
| 304 | + | else => try emit(e, encode::nop()), // Second slot for conditional branches. |
|
| 288 | 305 | } |
|
| 289 | 306 | } |
|
| 290 | 307 | ||
| 291 | 308 | /// Record a function call needing later patching. |
|
| 292 | 309 | /// Emits placeholder instructions that will be patched later. |
|
| 293 | 310 | /// Uses two slots to support long-distance calls. |
|
| 294 | - | export fn recordCall(e: &mut Emitter, target: *[u8]) { |
|
| 295 | - | try! e.pendingCalls.append(PendingCall { |
|
| 296 | - | index: e.codeLen, |
|
| 311 | + | export fn recordCall(e: &mut Emitter, target: *[u8]) throws (super::Error) { |
|
| 312 | + | try e.pendingCalls.append(PendingCall { |
|
| 313 | + | index: e.code.len, |
|
| 297 | 314 | target, |
|
| 298 | - | }, e.allocator); |
|
| 315 | + | }, e.allocator) catch { |
|
| 316 | + | throw super::Error::Allocation; |
|
| 317 | + | }; |
|
| 299 | 318 | ||
| 300 | - | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 301 | - | emit(e, encode::nop()); // Placeholder for JALR. |
|
| 319 | + | try emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 320 | + | try emit(e, encode::nop()); // Placeholder for JALR. |
|
| 302 | 321 | } |
|
| 303 | 322 | ||
| 304 | 323 | /// Record a jump emitted by assembly that needs whole-program patching. |
|
| 305 | - | export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 306 | - | try! e.pendingJumps.append(PendingJump { |
|
| 324 | + | export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) throws (super::Error) { |
|
| 325 | + | try e.pendingJumps.append(PendingJump { |
|
| 307 | 326 | index, |
|
| 308 | 327 | target, |
|
| 309 | 328 | rd, |
|
| 310 | - | }, e.allocator); |
|
| 329 | + | }, e.allocator) catch { |
|
| 330 | + | throw super::Error::Allocation; |
|
| 331 | + | }; |
|
| 311 | 332 | } |
|
| 312 | 333 | ||
| 313 | 334 | /// Record a function address load needing later patching. |
|
| 314 | 335 | /// Emits placeholder instructions that will be patched to load the function's address. |
|
| 315 | 336 | /// Uses two slots to compute long-distance addresses. |
|
| 316 | - | export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 317 | - | let codeLen = e.codeLen; |
|
| 318 | - | recordAddrLoadAt(e, target, rd, codeLen); |
|
| 337 | + | export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) throws (super::Error) { |
|
| 338 | + | let codeLen = e.code.len; |
|
| 339 | + | try recordAddrLoadAt(e, target, rd, codeLen); |
|
| 319 | 340 | ||
| 320 | - | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 321 | - | emit(e, encode::nop()); // Placeholder for ADDI. |
|
| 341 | + | try emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 342 | + | try emit(e, encode::nop()); // Placeholder for ADDI. |
|
| 322 | 343 | } |
|
| 323 | 344 | ||
| 324 | 345 | /// Record a function address load already reserved by assembly. |
|
| 325 | - | export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 326 | - | try! e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 346 | + | export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) throws (super::Error) { |
|
| 347 | + | try e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 327 | 348 | index, |
|
| 328 | 349 | target, |
|
| 329 | 350 | rd: rd, |
|
| 330 | 351 | isData: false, |
|
| 331 | - | }, e.allocator); |
|
| 352 | + | }, e.allocator) catch { |
|
| 353 | + | throw super::Error::Allocation; |
|
| 354 | + | }; |
|
| 332 | 355 | } |
|
| 333 | 356 | ||
| 334 | 357 | /// Record a data address load needing later patching. |
|
| 335 | 358 | /// Uses an absolute 32-bit load sequence matching the current data memory map. |
|
| 336 | - | export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 337 | - | try! e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 338 | - | index: e.codeLen, |
|
| 359 | + | export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) throws (super::Error) { |
|
| 360 | + | try e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 361 | + | index: e.code.len, |
|
| 339 | 362 | target, |
|
| 340 | 363 | rd: rd, |
|
| 341 | 364 | isData: true, |
|
| 342 | - | }, e.allocator); |
|
| 365 | + | }, e.allocator) catch { |
|
| 366 | + | throw super::Error::Allocation; |
|
| 367 | + | }; |
|
| 343 | 368 | ||
| 344 | - | emit(e, encode::nop()); // Placeholder for LUI. |
|
| 345 | - | emit(e, encode::nop()); // Placeholder for ADDIW. |
|
| 369 | + | try emit(e, encode::nop()); // Placeholder for LUI. |
|
| 370 | + | try emit(e, encode::nop()); // Placeholder for ADDIW. |
|
| 346 | 371 | } |
|
| 347 | 372 | ||
| 348 | 373 | /// Patch local branches and clear the pending list. |
|
| 349 | 374 | /// |
|
| 350 | 375 | /// Called after each function. |
|
| 351 | 376 | /// |
|
| 352 | 377 | /// Uses two-instruction sequences: short branches use `branch` and `nop`, |
|
| 353 | 378 | /// long branches use inverted branch and `jal` or `auipc` and `jalr`. |
|
| 354 | - | export unsafe fn patchLocalBranches(e: &mut Emitter) { |
|
| 379 | + | export unsafe fn patchLocalBranches(e: &mut Emitter) throws (super::Error) { |
|
| 355 | 380 | for i in 0..e.pendingBranches.len { |
|
| 356 | 381 | let p = e.pendingBranches[i]; |
|
| 357 | 382 | let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE); |
|
| 358 | 383 | match p.kind { |
|
| 359 | 384 | case BranchKind::Cond { op, rs1, rs2 } => { |
|
| 360 | 385 | if encode::isBranchImm(offset) { |
|
| 361 | 386 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset)); |
|
| 362 | 387 | patch(e, p.index + 1, encode::nop()); |
|
| 363 | 388 | } else { |
|
| 364 | 389 | let adj = offset - super::INSTR_SIZE; |
|
| 390 | + | if not encode::isJumpImm(adj) { |
|
| 391 | + | throw super::Error::Relocation; |
|
| 392 | + | } |
|
| 365 | 393 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 366 | 394 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 367 | 395 | } |
|
| 368 | 396 | }, |
|
| 369 | 397 | case BranchKind::InvertedCond { op, rs1, rs2 } => { |
|
| 370 | 398 | if encode::isBranchImm(offset) { |
|
| 371 | 399 | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset)); |
|
| 372 | 400 | patch(e, p.index + 1, encode::nop()); |
|
| 373 | 401 | } else { |
|
| 374 | 402 | let adj = offset - super::INSTR_SIZE; |
|
| 403 | + | if not encode::isJumpImm(adj) { |
|
| 404 | + | throw super::Error::Relocation; |
|
| 405 | + | } |
|
| 375 | 406 | patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 376 | 407 | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 377 | 408 | } |
|
| 378 | 409 | }, |
|
| 379 | 410 | case BranchKind::Jump => { |
|
| 380 | 411 | // Single-slot jump (J-type, +-1MB range). |
|
| 381 | - | assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large"; |
|
| 412 | + | if not encode::isJumpImm(offset) { |
|
| 413 | + | throw super::Error::Relocation; |
|
| 414 | + | } |
|
| 382 | 415 | patch(e, p.index, encode::jal(super::ZERO, offset)); |
|
| 383 | 416 | }, |
|
| 384 | 417 | } |
|
| 385 | 418 | } |
|
| 386 | - | set e.pendingBranches = &mut e.pendingBranches[..0]; |
|
| 419 | + | set e.pendingBranches.len = 0; |
|
| 387 | 420 | } |
|
| 388 | 421 | ||
| 389 | 422 | /// Encode a conditional branch instruction. |
|
| 390 | 423 | fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 { |
|
| 391 | 424 | match op { |
| 406 | 439 | } |
|
| 407 | 440 | } |
|
| 408 | 441 | ||
| 409 | 442 | /// Patch all pending function calls. |
|
| 410 | 443 | /// Called after all functions have been generated. |
|
| 411 | - | export fn patchCalls(e: &mut Emitter) { |
|
| 444 | + | export fn patchCalls(e: &mut Emitter) throws (super::Error) { |
|
| 412 | 445 | for i in 0..e.pendingCalls.len { |
|
| 413 | 446 | let p = e.pendingCalls[i]; |
|
| 414 | - | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 447 | + | let offset = try branchOffsetToFunc(e, p.index, p.target); |
|
| 415 | 448 | let s = splitImm(offset); |
|
| 416 | 449 | ||
| 417 | 450 | // `AUIPC scratch, hi(offset)`. |
|
| 418 | 451 | patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi)); |
|
| 419 | 452 | // `JALR ra, scratch, lo(offset)`. |
|
| 420 | 453 | patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo)); |
|
| 421 | 454 | } |
|
| 422 | 455 | } |
|
| 423 | 456 | ||
| 424 | 457 | /// Patch all pending assembly jumps. |
|
| 425 | - | export fn patchJumps(e: &mut Emitter) { |
|
| 458 | + | export fn patchJumps(e: &mut Emitter) throws (super::Error) { |
|
| 426 | 459 | for i in 0..e.pendingJumps.len { |
|
| 427 | 460 | let p = e.pendingJumps[i]; |
|
| 428 | - | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 461 | + | let offset = try branchOffsetToFunc(e, p.index, p.target); |
|
| 429 | 462 | ||
| 430 | - | assert encode::isJumpImm(offset), "patchJumps: jump offset too large"; |
|
| 463 | + | if not encode::isJumpImm(offset) { |
|
| 464 | + | throw super::Error::Relocation; |
|
| 465 | + | } |
|
| 431 | 466 | patch(e, p.index, encode::jal(p.rd, offset)); |
|
| 432 | 467 | } |
|
| 433 | 468 | } |
|
| 434 | 469 | ||
| 435 | 470 | /// Patch all pending function and data address loads. |
|
| 436 | 471 | /// Called after all functions have been generated and data layout is known. |
|
| 437 | - | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) { |
|
| 472 | + | export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) throws (super::Error) { |
|
| 438 | 473 | for i in 0..e.pendingAddrLoads.len { |
|
| 439 | 474 | let p = e.pendingAddrLoads[i]; |
|
| 440 | 475 | if p.isData { |
|
| 441 | 476 | let addr = data::lookupAddr(dataSymMap, p.target) else { |
|
| 442 | - | panic "patchAddrLoads: data symbol not found"; |
|
| 477 | + | throw super::Error::Symbol; |
|
| 443 | 478 | }; |
|
| 444 | - | assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large"; |
|
| 479 | + | if addr > MAX_I32_ADDR { |
|
| 480 | + | throw super::Error::Relocation; |
|
| 481 | + | } |
|
| 445 | 482 | let s = splitImm(addr as i32); |
|
| 446 | 483 | ||
| 447 | 484 | patch(e, p.index, encode::lui(p.rd, s.hi)); |
|
| 448 | 485 | patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo)); |
|
| 449 | 486 | ||
| 450 | 487 | continue; |
|
| 451 | 488 | } |
|
| 452 | 489 | ||
| 453 | - | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 490 | + | let offset = try branchOffsetToFunc(e, p.index, p.target); |
|
| 454 | 491 | let s = splitImm(offset); |
|
| 455 | 492 | // `AUIPC rd, hi(offset)`. |
|
| 456 | 493 | patch(e, p.index, encode::auipc(p.rd, s.hi)); |
|
| 457 | 494 | // `ADDI rd, rd, lo(offset)`. |
|
| 458 | 495 | patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo)); |
| 488 | 525 | /// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`]. |
|
| 489 | 526 | /// Returns adjusted base register and remaining offset. |
|
| 490 | 527 | /// |
|
| 491 | 528 | /// When the offset fits a 12-bit signed immediate, returns it unchanged. |
|
| 492 | 529 | /// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition. |
|
| 493 | - | fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset { |
|
| 530 | + | fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset throws (super::Error) { |
|
| 494 | 531 | if offset >= super::MIN_IMM and offset <= super::MAX_IMM { |
|
| 495 | 532 | return AdjustedOffset { base, offset }; |
|
| 496 | 533 | } |
|
| 497 | 534 | let s = splitImm(offset); |
|
| 498 | - | emit(e, encode::lui(super::ADDR_SCRATCH, s.hi)); |
|
| 499 | - | emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base)); |
|
| 535 | + | try emit(e, encode::lui(super::ADDR_SCRATCH, s.hi)); |
|
| 536 | + | try emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base)); |
|
| 500 | 537 | ||
| 501 | 538 | return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo }; |
|
| 502 | 539 | } |
|
| 503 | 540 | ||
| 504 | 541 | /// Load an immediate value into a register. |
|
| 505 | 542 | /// Handles the full range of 64-bit immediates. |
|
| 506 | 543 | /// For values fitting in 12 bits, uses a single `ADDI`. |
|
| 507 | 544 | /// For values fitting in 32 bits, uses `LUI` + `ADDIW`. |
|
| 508 | 545 | /// For wider values, loads upper and lower halves then combines with shift and add. |
|
| 509 | - | export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) { |
|
| 546 | + | export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) throws (super::Error) { |
|
| 510 | 547 | let immMin = super::MIN_IMM as i64; |
|
| 511 | 548 | let immMax = super::MAX_IMM as i64; |
|
| 512 | 549 | ||
| 513 | 550 | if imm >= immMin and imm <= immMax { |
|
| 514 | - | emit(e, encode::addi(rd, super::ZERO, imm as i32)); |
|
| 551 | + | try emit(e, encode::addi(rd, super::ZERO, imm as i32)); |
|
| 515 | 552 | return; |
|
| 516 | 553 | } |
|
| 517 | 554 | // Check if the value fits in 32 bits (sign-extended). |
|
| 518 | 555 | let lo32 = imm as i32; |
|
| 519 | 556 | if lo32 as i64 == imm { |
|
| 520 | 557 | let s = splitImm(lo32); |
|
| 521 | - | emit(e, encode::lui(rd, s.hi)); |
|
| 558 | + | try emit(e, encode::lui(rd, s.hi)); |
|
| 522 | 559 | if s.lo <> 0 { |
|
| 523 | - | emit(e, encode::addiw(rd, rd, s.lo)); |
|
| 560 | + | try emit(e, encode::addiw(rd, rd, s.lo)); |
|
| 524 | 561 | } |
|
| 525 | 562 | return; |
|
| 526 | 563 | } |
|
| 527 | 564 | // Full 64-bit immediate: use only rd, no scratch registers. |
|
| 528 | 565 | // Load upper 32 bits first via the 32-bit path (LUI+ADDIW), |
| 530 | 567 | // sign-extension issues with ADDI's 12-bit signed immediate. |
|
| 531 | 568 | let hi32 = (imm >> 32) as i32; |
|
| 532 | 569 | let lower = imm as i32; |
|
| 533 | 570 | ||
| 534 | 571 | // Load upper 32 bits. |
|
| 535 | - | loadImm(e, rd, hi32 as i64); |
|
| 572 | + | try loadImm(e, rd, hi32 as i64); |
|
| 536 | 573 | // Shift left by 11, add bits [31:21]. |
|
| 537 | - | emit(e, encode::slli(rd, rd, 11)); |
|
| 574 | + | try emit(e, encode::slli(rd, rd, 11)); |
|
| 538 | 575 | let chunkHi = (lower >> 21) & 0x7FF; |
|
| 539 | 576 | if chunkHi <> 0 { |
|
| 540 | - | emit(e, encode::addi(rd, rd, chunkHi)); |
|
| 577 | + | try emit(e, encode::addi(rd, rd, chunkHi)); |
|
| 541 | 578 | } |
|
| 542 | 579 | // Shift left by 11, add bits [20:10]. |
|
| 543 | - | emit(e, encode::slli(rd, rd, 11)); |
|
| 580 | + | try emit(e, encode::slli(rd, rd, 11)); |
|
| 544 | 581 | let chunkMid = (lower >> 10) & 0x7FF; |
|
| 545 | 582 | if chunkMid <> 0 { |
|
| 546 | - | emit(e, encode::addi(rd, rd, chunkMid)); |
|
| 583 | + | try emit(e, encode::addi(rd, rd, chunkMid)); |
|
| 547 | 584 | } |
|
| 548 | 585 | // Shift left by 10, add bits [9:0]. |
|
| 549 | - | emit(e, encode::slli(rd, rd, 10)); |
|
| 586 | + | try emit(e, encode::slli(rd, rd, 10)); |
|
| 550 | 587 | let chunkLo = lower & 0x3FF; |
|
| 551 | 588 | if chunkLo <> 0 { |
|
| 552 | - | emit(e, encode::addi(rd, rd, chunkLo)); |
|
| 589 | + | try emit(e, encode::addi(rd, rd, chunkLo)); |
|
| 553 | 590 | } |
|
| 554 | 591 | } |
|
| 555 | 592 | ||
| 556 | 593 | /// Emit add-immediate, handling large immediates. |
|
| 557 | - | export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) { |
|
| 594 | + | export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) throws (super::Error) { |
|
| 558 | 595 | if imm >= super::MIN_IMM and imm <= super::MAX_IMM { |
|
| 559 | - | emit(e, encode::addi(rd, rs, imm)); |
|
| 596 | + | try emit(e, encode::addi(rd, rs, imm)); |
|
| 560 | 597 | } else { |
|
| 561 | - | loadImm(e, super::SCRATCH1, imm as i64); |
|
| 562 | - | emit(e, encode::add(rd, rs, super::SCRATCH1)); |
|
| 598 | + | try loadImm(e, super::SCRATCH1, imm as i64); |
|
| 599 | + | try emit(e, encode::add(rd, rs, super::SCRATCH1)); |
|
| 563 | 600 | } |
|
| 564 | 601 | } |
|
| 565 | 602 | ||
| 566 | 603 | //////////////////////// |
|
| 567 | 604 | // Load/Store Helpers // |
|
| 568 | 605 | //////////////////////// |
|
| 569 | 606 | ||
| 570 | 607 | /// Emit unsigned load with automatic offset adjustment. |
|
| 571 | - | export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
|
| 572 | - | let adj = adjustOffset(e, base, offset); |
|
| 608 | + | export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) { |
|
| 609 | + | let adj = try adjustOffset(e, base, offset); |
|
| 573 | 610 | match typ { |
|
| 574 | - | case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)), |
|
| 575 | - | case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)), |
|
| 576 | - | case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)), |
|
| 577 | - | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 611 | + | case il::Type::W8 => try emit(e, encode::lbu(rd, adj.base, adj.offset)), |
|
| 612 | + | case il::Type::W16 => try emit(e, encode::lhu(rd, adj.base, adj.offset)), |
|
| 613 | + | case il::Type::W32 => try emit(e, encode::lwu(rd, adj.base, adj.offset)), |
|
| 614 | + | case il::Type::W64 => try emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 578 | 615 | } |
|
| 579 | 616 | } |
|
| 580 | 617 | ||
| 581 | 618 | /// Emit signed load with automatic offset adjustment. |
|
| 582 | - | export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
|
| 583 | - | let adj = adjustOffset(e, base, offset); |
|
| 619 | + | export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) { |
|
| 620 | + | let adj = try adjustOffset(e, base, offset); |
|
| 584 | 621 | match typ { |
|
| 585 | - | case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)), |
|
| 586 | - | case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)), |
|
| 587 | - | case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)), |
|
| 588 | - | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 622 | + | case il::Type::W8 => try emit(e, encode::lb(rd, adj.base, adj.offset)), |
|
| 623 | + | case il::Type::W16 => try emit(e, encode::lh(rd, adj.base, adj.offset)), |
|
| 624 | + | case il::Type::W32 => try emit(e, encode::lw(rd, adj.base, adj.offset)), |
|
| 625 | + | case il::Type::W64 => try emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 589 | 626 | } |
|
| 590 | 627 | } |
|
| 591 | 628 | ||
| 592 | 629 | /// Emit store with automatic offset adjustment. |
|
| 593 | - | export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) { |
|
| 594 | - | let adj = adjustOffset(e, base, offset); |
|
| 630 | + | export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) throws (super::Error) { |
|
| 631 | + | let adj = try adjustOffset(e, base, offset); |
|
| 595 | 632 | match typ { |
|
| 596 | - | case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)), |
|
| 597 | - | case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)), |
|
| 598 | - | case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)), |
|
| 599 | - | case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)), |
|
| 633 | + | case il::Type::W8 => try emit(e, encode::sb(rs, adj.base, adj.offset)), |
|
| 634 | + | case il::Type::W16 => try emit(e, encode::sh(rs, adj.base, adj.offset)), |
|
| 635 | + | case il::Type::W32 => try emit(e, encode::sw(rs, adj.base, adj.offset)), |
|
| 636 | + | case il::Type::W64 => try emit(e, encode::sd(rs, adj.base, adj.offset)), |
|
| 600 | 637 | } |
|
| 601 | 638 | } |
|
| 602 | 639 | ||
| 603 | 640 | /// Emit 64-bit load with automatic offset adjustment. |
|
| 604 | - | export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 605 | - | let adj = adjustOffset(e, base, offset); |
|
| 606 | - | emit(e, encode::ld(rd, adj.base, adj.offset)); |
|
| 641 | + | export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 642 | + | let adj = try adjustOffset(e, base, offset); |
|
| 643 | + | try emit(e, encode::ld(rd, adj.base, adj.offset)); |
|
| 607 | 644 | } |
|
| 608 | 645 | ||
| 609 | 646 | /// Emit 64-bit store with automatic offset adjustment. |
|
| 610 | - | export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 611 | - | let adj = adjustOffset(e, base, offset); |
|
| 612 | - | emit(e, encode::sd(rs, adj.base, adj.offset)); |
|
| 647 | + | export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 648 | + | let adj = try adjustOffset(e, base, offset); |
|
| 649 | + | try emit(e, encode::sd(rs, adj.base, adj.offset)); |
|
| 613 | 650 | } |
|
| 614 | 651 | ||
| 615 | 652 | /// Emit 32-bit load with automatic offset adjustment. |
|
| 616 | - | export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 617 | - | let adj = adjustOffset(e, base, offset); |
|
| 618 | - | emit(e, encode::lw(rd, adj.base, adj.offset)); |
|
| 653 | + | export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 654 | + | let adj = try adjustOffset(e, base, offset); |
|
| 655 | + | try emit(e, encode::lw(rd, adj.base, adj.offset)); |
|
| 619 | 656 | } |
|
| 620 | 657 | ||
| 621 | 658 | /// Emit 32-bit store with automatic offset adjustment. |
|
| 622 | - | export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 623 | - | let adj = adjustOffset(e, base, offset); |
|
| 624 | - | emit(e, encode::sw(rs, adj.base, adj.offset)); |
|
| 659 | + | export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 660 | + | let adj = try adjustOffset(e, base, offset); |
|
| 661 | + | try emit(e, encode::sw(rs, adj.base, adj.offset)); |
|
| 625 | 662 | } |
|
| 626 | 663 | ||
| 627 | 664 | /// Emit 8-bit load with automatic offset adjustment. |
|
| 628 | - | export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 629 | - | let adj = adjustOffset(e, base, offset); |
|
| 630 | - | emit(e, encode::lb(rd, adj.base, adj.offset)); |
|
| 665 | + | export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 666 | + | let adj = try adjustOffset(e, base, offset); |
|
| 667 | + | try emit(e, encode::lb(rd, adj.base, adj.offset)); |
|
| 631 | 668 | } |
|
| 632 | 669 | ||
| 633 | 670 | /// Emit 8-bit store with automatic offset adjustment. |
|
| 634 | - | export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) { |
|
| 635 | - | let adj = adjustOffset(e, base, offset); |
|
| 636 | - | emit(e, encode::sb(rs, adj.base, adj.offset)); |
|
| 671 | + | export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) throws (super::Error) { |
|
| 672 | + | let adj = try adjustOffset(e, base, offset); |
|
| 673 | + | try emit(e, encode::sb(rs, adj.base, adj.offset)); |
|
| 637 | 674 | } |
|
| 638 | 675 | ||
| 639 | 676 | ////////////////////////// |
|
| 640 | 677 | // Prologue / Epilogue // |
|
| 641 | 678 | ////////////////////////// |
|
| 642 | 679 | ||
| 643 | 680 | /// Emit function prologue. |
|
| 644 | 681 | /// Allocate the frame and save registers. Save FP only for dynamic frames. |
|
| 645 | - | export fn emitPrologue(e: &mut Emitter, frame: &Frame) { |
|
| 682 | + | export fn emitPrologue(e: &mut Emitter, frame: &Frame) throws (super::Error) { |
|
| 646 | 683 | // Fast path: leaf function with no locals. |
|
| 647 | 684 | if frame.totalSize == 0 { |
|
| 648 | 685 | return; |
|
| 649 | 686 | } |
|
| 650 | 687 | let totalSize = frame.totalSize; |
|
| 651 | 688 | ||
| 652 | 689 | // Allocate stack frame. |
|
| 653 | 690 | let negFrame = 0 - totalSize; |
|
| 654 | 691 | if negFrame >= super::MIN_IMM { |
|
| 655 | - | emit(e, encode::addi(super::SP, super::SP, negFrame)); |
|
| 692 | + | try emit(e, encode::addi(super::SP, super::SP, negFrame)); |
|
| 656 | 693 | } else { |
|
| 657 | - | loadImm(e, super::SCRATCH1, totalSize as i64); |
|
| 658 | - | emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1)); |
|
| 694 | + | try loadImm(e, super::SCRATCH1, totalSize as i64); |
|
| 695 | + | try emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1)); |
|
| 659 | 696 | } |
|
| 660 | 697 | // Save return address. |
|
| 661 | 698 | if not frame.isLeaf { |
|
| 662 | - | emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 699 | + | try emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 663 | 700 | } |
|
| 664 | 701 | // Save and set FP only when dynamic allocations can move SP. |
|
| 665 | 702 | if frame.isDynamic { |
|
| 666 | - | emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 667 | - | emitAddImm(e, super::FP, super::SP, totalSize); |
|
| 703 | + | try emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 704 | + | try emitAddImm(e, super::FP, super::SP, totalSize); |
|
| 668 | 705 | } |
|
| 669 | 706 | // Save callee-saved registers. |
|
| 670 | 707 | for i in 0..frame.savedRegsLen { |
|
| 671 | 708 | let sr = frame.savedRegs[i]; |
|
| 672 | - | emitSd(e, sr.reg, super::SP, sr.offset); |
|
| 709 | + | try emitSd(e, sr.reg, super::SP, sr.offset); |
|
| 673 | 710 | } |
|
| 674 | 711 | } |
|
| 675 | 712 | ||
| 676 | 713 | /// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions. |
|
| 677 | - | export fn emitReturn(e: &mut Emitter, frame: &Frame) { |
|
| 714 | + | export fn emitReturn(e: &mut Emitter, frame: &Frame) throws (super::Error) { |
|
| 678 | 715 | if frame.totalSize == 0 { |
|
| 679 | 716 | // Leaf function: no frame to tear down, emit ret directly. |
|
| 680 | - | emit(e, encode::ret()); |
|
| 717 | + | try emit(e, encode::ret()); |
|
| 681 | 718 | return; |
|
| 682 | 719 | } |
|
| 683 | - | recordBranch(e, frame.epilogueBlock, BranchKind::Jump); |
|
| 720 | + | try recordBranch(e, frame.epilogueBlock, BranchKind::Jump); |
|
| 684 | 721 | } |
|
| 685 | 722 | ||
| 686 | 723 | /// Emit function epilogue. |
|
| 687 | 724 | /// Restore saved registers and release the frame. Restore FP only for dynamic frames. |
|
| 688 | - | export fn emitEpilogue(e: &mut Emitter, frame: &Frame) { |
|
| 725 | + | export fn emitEpilogue(e: &mut Emitter, frame: &Frame) throws (super::Error) { |
|
| 689 | 726 | // Record epilogue block address for return jumps. |
|
| 690 | - | recordBlock(e, frame.epilogueBlock); |
|
| 727 | + | try recordBlock(e, frame.epilogueBlock); |
|
| 691 | 728 | ||
| 692 | 729 | // Fast path: leaf function with no locals. |
|
| 693 | 730 | if frame.totalSize == 0 { |
|
| 694 | - | emit(e, encode::ret()); |
|
| 731 | + | try emit(e, encode::ret()); |
|
| 695 | 732 | return; |
|
| 696 | 733 | } |
|
| 697 | 734 | let totalSize = frame.totalSize; |
|
| 698 | 735 | ||
| 699 | 736 | // Restore SP to post-prologue value. Only needed when dynamic stack |
|
| 700 | 737 | // allocation may have moved SP. |
|
| 701 | 738 | if frame.isDynamic { |
|
| 702 | - | emitAddImm(e, super::SP, super::FP, 0 - totalSize); |
|
| 739 | + | try emitAddImm(e, super::SP, super::FP, 0 - totalSize); |
|
| 703 | 740 | } |
|
| 704 | 741 | // Restore callee-saved registers. |
|
| 705 | 742 | for i in 0..frame.savedRegsLen { |
|
| 706 | 743 | let sr = frame.savedRegs[i]; |
|
| 707 | - | emitLd(e, sr.reg, super::SP, sr.offset); |
|
| 744 | + | try emitLd(e, sr.reg, super::SP, sr.offset); |
|
| 708 | 745 | } |
|
| 709 | 746 | // Restore FP only if the prologue saved and changed it. |
|
| 710 | 747 | if frame.isDynamic { |
|
| 711 | - | emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 748 | + | try emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 712 | 749 | } |
|
| 713 | 750 | // Restore return address. |
|
| 714 | 751 | if not frame.isLeaf { |
|
| 715 | - | emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 752 | + | try emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 716 | 753 | } |
|
| 717 | 754 | // Deallocate stack frame. |
|
| 718 | - | emitAddImm(e, super::SP, super::SP, totalSize); |
|
| 719 | - | emit(e, encode::ret()); |
|
| 755 | + | try emitAddImm(e, super::SP, super::SP, totalSize); |
|
| 756 | + | try emit(e, encode::ret()); |
|
| 720 | 757 | } |
|
| 721 | 758 | ||
| 722 | 759 | ////////////////// |
|
| 723 | 760 | // Code Access // |
|
| 724 | 761 | ////////////////// |
|
| 725 | 762 | ||
| 726 | 763 | /// Get emitted code as a slice. |
|
| 727 | 764 | export fn getCode(e: &Emitter) -> *[u32] { |
|
| 728 | - | return &e.code[..e.codeLen]; |
|
| 765 | + | return &e.code[..]; |
|
| 729 | 766 | } |
|
| 730 | 767 | ||
| 731 | 768 | /// Record a debug entry mapping the current PC to a source location. |
|
| 732 | 769 | /// Deduplicates consecutive entries with the same location. |
|
| 733 | - | export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) { |
|
| 734 | - | let pc = e.codeLen * super::INSTR_SIZE as u32; |
|
| 770 | + | export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) throws (super::Error) { |
|
| 771 | + | let pc = e.code.len * super::INSTR_SIZE as u32; |
|
| 735 | 772 | ||
| 736 | 773 | // Skip if this is the same location as the previous entry. |
|
| 737 | - | if e.debugEntriesLen > 0 { |
|
| 738 | - | let prev = &e.debugEntries[e.debugEntriesLen - 1]; |
|
| 774 | + | if e.debugEntries.len > 0 { |
|
| 775 | + | let prev = &e.debugEntries[e.debugEntries.len - 1]; |
|
| 739 | 776 | if prev.offset == loc.offset and prev.moduleId == loc.moduleId { |
|
| 740 | 777 | return; |
|
| 741 | 778 | } |
|
| 742 | 779 | } |
|
| 743 | - | assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full"; |
|
| 744 | - | set e.debugEntries[e.debugEntriesLen] = types::DebugEntry { |
|
| 780 | + | try e.debugEntries.append(types::DebugEntry { |
|
| 745 | 781 | pc, |
|
| 746 | 782 | moduleId: loc.moduleId, |
|
| 747 | 783 | offset: loc.offset, |
|
| 784 | + | }, e.allocator) catch { |
|
| 785 | + | throw super::Error::Allocation; |
|
| 748 | 786 | }; |
|
| 749 | - | set e.debugEntriesLen += 1; |
|
| 750 | 787 | } |
|
| 751 | 788 | ||
| 752 | 789 | /// Get debug entries as a slice. |
|
| 753 | 790 | export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] { |
|
| 754 | - | return &e.debugEntries[..e.debugEntriesLen]; |
|
| 791 | + | return &e.debugEntries[..]; |
|
| 755 | 792 | } |
lib/std/arch/rv64/isel.rad
+241 -231
| 145 | 145 | } |
|
| 146 | 146 | ||
| 147 | 147 | /// Get the source register for an SSA register. |
|
| 148 | 148 | /// If the register is spilled, loads the value from the spill slot into the |
|
| 149 | 149 | /// scratch register and returns it. Otherwise returns the physical register. |
|
| 150 | - | unsafe fn getSrcReg(s: &mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg { |
|
| 150 | + | unsafe fn getSrcReg(s: &mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg throws (super::Error) { |
|
| 151 | 151 | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) { |
|
| 152 | - | emit::emitLd(s.e, scratch, spillBase(s), spillOffset(s, slot)); |
|
| 152 | + | try emit::emitLd(s.e, scratch, spillBase(s), spillOffset(s, slot)); |
|
| 153 | 153 | return scratch; |
|
| 154 | 154 | } |
|
| 155 | 155 | return getReg(s, ssa); |
|
| 156 | 156 | } |
|
| 157 | 157 | ||
| 158 | 158 | /// Resolve an IL value to the physical register holding it. |
|
| 159 | 159 | /// For non-spilled register values, returns the physical register directly. |
|
| 160 | 160 | /// For immediates, symbols, and spilled registers, materializes into `scratch`. |
|
| 161 | - | unsafe fn resolveVal(s: &mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg { |
|
| 161 | + | unsafe fn resolveVal(s: &mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg throws (super::Error) { |
|
| 162 | 162 | match val { |
|
| 163 | 163 | case il::Val::Reg(r) => { |
|
| 164 | - | return getSrcReg(s, r, scratch); |
|
| 164 | + | return try getSrcReg(s, r, scratch); |
|
| 165 | 165 | }, |
|
| 166 | 166 | case il::Val::Imm(imm) => { |
|
| 167 | 167 | if imm == 0 { |
|
| 168 | 168 | return super::ZERO; |
|
| 169 | 169 | } |
|
| 170 | - | emit::loadImm(s.e, scratch, imm); |
|
| 170 | + | try emit::loadImm(s.e, scratch, imm); |
|
| 171 | 171 | return scratch; |
|
| 172 | 172 | }, |
|
| 173 | 173 | case il::Val::DataSym(name) => { |
|
| 174 | - | emit::recordDataAddrLoad(s.e, name, scratch); |
|
| 174 | + | try emit::recordDataAddrLoad(s.e, name, scratch); |
|
| 175 | 175 | return scratch; |
|
| 176 | 176 | }, |
|
| 177 | 177 | case il::Val::FnAddr(name) => { |
|
| 178 | - | emit::recordAddrLoad(s.e, name, scratch); |
|
| 178 | + | try emit::recordAddrLoad(s.e, name, scratch); |
|
| 179 | 179 | return scratch; |
|
| 180 | 180 | }, |
|
| 181 | 181 | case il::Val::Undef => { |
|
| 182 | 182 | return scratch; |
|
| 183 | 183 | } |
|
| 184 | 184 | } |
|
| 185 | 185 | } |
|
| 186 | 186 | ||
| 187 | 187 | /// Load an IL value into a specific physical register. |
|
| 188 | 188 | /// Like [`resolveVal`], but ensures the value ends up in `rd`. |
|
| 189 | - | unsafe fn loadVal(s: &mut Selector, rd: gen::Reg, val: il::Val) -> gen::Reg { |
|
| 190 | - | let rs = resolveVal(s, rd, val); |
|
| 191 | - | emitMv(s, rd, rs); |
|
| 189 | + | unsafe fn loadVal(s: &mut Selector, rd: gen::Reg, val: il::Val) -> gen::Reg throws (super::Error) { |
|
| 190 | + | let rs = try resolveVal(s, rd, val); |
|
| 191 | + | try emitMv(s, rd, rs); |
|
| 192 | 192 | return rd; |
|
| 193 | 193 | } |
|
| 194 | 194 | ||
| 195 | 195 | /// Emit a move instruction if source and destination differ. |
|
| 196 | - | unsafe fn emitMv(s: &mut Selector, rd: gen::Reg, rs: gen::Reg) { |
|
| 196 | + | unsafe fn emitMv(s: &mut Selector, rd: gen::Reg, rs: gen::Reg) throws (super::Error) { |
|
| 197 | 197 | if *rd <> *rs { |
|
| 198 | - | emit::emit(s.e, encode::mv(rd, rs)); |
|
| 198 | + | try emit::emit(s.e, encode::mv(rd, rs)); |
|
| 199 | 199 | } |
|
| 200 | 200 | } |
|
| 201 | 201 | ||
| 202 | 202 | /// Emit zero-extension from a sub-word type to the full register width. |
|
| 203 | - | fn emitZext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) { |
|
| 203 | + | fn emitZext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) throws (super::Error) { |
|
| 204 | 204 | match typ { |
|
| 205 | - | case il::Type::W8 => emit::emit(e, encode::andi(rd, rs, MASK_W8)), |
|
| 205 | + | case il::Type::W8 => try emit::emit(e, encode::andi(rd, rs, MASK_W8)), |
|
| 206 | 206 | case il::Type::W16 => { |
|
| 207 | - | emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 208 | - | emit::emit(e, encode::srli(rd, rd, SHIFT_W16)); |
|
| 207 | + | try emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 208 | + | try emit::emit(e, encode::srli(rd, rd, SHIFT_W16)); |
|
| 209 | 209 | }, |
|
| 210 | 210 | case il::Type::W32 => { |
|
| 211 | - | emit::emit(e, encode::slli(rd, rs, SHIFT_W32)); |
|
| 212 | - | emit::emit(e, encode::srli(rd, rd, SHIFT_W32)); |
|
| 211 | + | try emit::emit(e, encode::slli(rd, rs, SHIFT_W32)); |
|
| 212 | + | try emit::emit(e, encode::srli(rd, rd, SHIFT_W32)); |
|
| 213 | 213 | }, |
|
| 214 | 214 | case il::Type::W64 => {} |
|
| 215 | 215 | } |
|
| 216 | 216 | } |
|
| 217 | 217 | ||
| 218 | 218 | /// Emit sign-extension from a sub-word type to the full register width. |
|
| 219 | - | fn emitSext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) { |
|
| 219 | + | fn emitSext(e: &mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) throws (super::Error) { |
|
| 220 | 220 | match typ { |
|
| 221 | 221 | case il::Type::W8 => { |
|
| 222 | - | emit::emit(e, encode::slli(rd, rs, SHIFT_W8)); |
|
| 223 | - | emit::emit(e, encode::srai(rd, rd, SHIFT_W8)); |
|
| 222 | + | try emit::emit(e, encode::slli(rd, rs, SHIFT_W8)); |
|
| 223 | + | try emit::emit(e, encode::srai(rd, rd, SHIFT_W8)); |
|
| 224 | 224 | }, |
|
| 225 | 225 | case il::Type::W16 => { |
|
| 226 | - | emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 227 | - | emit::emit(e, encode::srai(rd, rd, SHIFT_W16)); |
|
| 226 | + | try emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 227 | + | try emit::emit(e, encode::srai(rd, rd, SHIFT_W16)); |
|
| 228 | 228 | }, |
|
| 229 | 229 | case il::Type::W32 => { |
|
| 230 | - | emit::emit(e, encode::addiw(rd, rs, 0)); |
|
| 230 | + | try emit::emit(e, encode::addiw(rd, rs, 0)); |
|
| 231 | 231 | }, |
|
| 232 | 232 | case il::Type::W64 => {} |
|
| 233 | 233 | } |
|
| 234 | 234 | } |
|
| 235 | 235 |
| 238 | 238 | unsafe fn resolveAndTrapIfZero( |
|
| 239 | 239 | s: &mut Selector, |
|
| 240 | 240 | b: il::Val, |
|
| 241 | 241 | typ: il::Type, |
|
| 242 | 242 | signed: bool |
|
| 243 | - | ) -> gen::Reg { |
|
| 243 | + | ) -> gen::Reg throws (super::Error) { |
|
| 244 | 244 | let mut divisor = b; |
|
| 245 | 245 | if let case il::Val::Imm(imm) = b { |
|
| 246 | 246 | set divisor = il::Val::Imm(canonicalCmpImm(imm, typ, signed)); |
|
| 247 | 247 | } |
|
| 248 | - | let rs2 = resolveVal(s, super::SCRATCH2, divisor); |
|
| 248 | + | let rs2 = try resolveVal(s, super::SCRATCH2, divisor); |
|
| 249 | 249 | if not isExtendedImm(divisor, typ, signed) { |
|
| 250 | - | emitCmpExt(s.e, rs2, rs2, typ, signed); |
|
| 250 | + | try emitCmpExt(s.e, rs2, rs2, typ, signed); |
|
| 251 | 251 | } |
|
| 252 | 252 | let mut knownNonZero = false; |
|
| 253 | 253 | if let case il::Val::Imm(imm) = divisor { |
|
| 254 | 254 | set knownNonZero = imm <> 0; |
|
| 255 | 255 | } |
|
| 256 | 256 | if not knownNonZero { |
|
| 257 | - | emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2)); |
|
| 258 | - | emit::emit(s.e, encode::ebreak()); |
|
| 257 | + | try emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2)); |
|
| 258 | + | try emit::emit(s.e, encode::ebreak()); |
|
| 259 | 259 | } |
|
| 260 | 260 | return rs2; |
|
| 261 | 261 | } |
|
| 262 | 262 | ||
| 263 | 263 | //////////////////////// |
| 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 | + | let aligned = mem::alignUp(offset as u32, alignment); |
|
| 288 | + | let end = aligned as u64 + sz as u64; |
|
| 289 | + | if end > I32_MAX as u64 { |
|
| 290 | + | throw super::Error::Capacity; |
|
| 291 | + | } |
|
| 292 | + | set offset = end as i32; |
|
| 289 | 293 | } else { |
|
| 290 | 294 | set isDynamic = true; |
|
| 291 | 295 | } |
|
| 292 | 296 | }, |
|
| 293 | 297 | else => {}, |
| 300 | 304 | /// Select instructions for a function. |
|
| 301 | 305 | export unsafe fn selectFn( |
|
| 302 | 306 | e: &mut emit::Emitter, |
|
| 303 | 307 | ralloc: ®alloc::AllocResult, |
|
| 304 | 308 | func: *unsafe il::Fn |
|
| 305 | - | ) { |
|
| 309 | + | ) throws (super::Error) { |
|
| 306 | 310 | // Reset block offsets for this function. |
|
| 307 | 311 | labels::resetBlocks(&mut e.labels); |
|
| 308 | 312 | // Pre-scan for constant-sized reserves to promote to fixed frame slots. |
|
| 309 | - | let reserveInfo = computeReserveInfo(func); |
|
| 313 | + | let reserveInfo = try computeReserveInfo(func); |
|
| 314 | + | if reserveInfo.size > I32_MAX as i32 - ralloc.spill.frameSize { |
|
| 315 | + | throw super::Error::Capacity; |
|
| 316 | + | } |
|
| 310 | 317 | let isLeaf = func.isLeaf; |
|
| 311 | 318 | // Compute frame layout from spill slots, reserve slots, and used callee-saved registers. |
|
| 312 | - | let frame = emit::computeFrame( |
|
| 319 | + | let frame = try emit::computeFrame( |
|
| 313 | 320 | ralloc.spill.frameSize + reserveInfo.size, |
|
| 314 | 321 | ralloc.usedCalleeSaved, |
|
| 315 | 322 | func.blocks.len, |
|
| 316 | 323 | isLeaf, |
|
| 317 | 324 | reserveInfo.isDynamic |
| 324 | 331 | reserveOffset: 0, pendingSpill: nil, |
|
| 325 | 332 | nextSynthBlock: func.blocks.len + 1, |
|
| 326 | 333 | isDynamic: frame.isDynamic, |
|
| 327 | 334 | }; |
|
| 328 | 335 | // Record function name for printing. |
|
| 329 | - | emit::recordFunc(s.e, func.name); |
|
| 336 | + | try emit::recordFunc(s.e, func.name); |
|
| 330 | 337 | // Record function code offset for call patching. |
|
| 331 | - | emit::recordFuncOffset(s.e, func.name); |
|
| 338 | + | try emit::recordFuncOffset(s.e, func.name); |
|
| 332 | 339 | // Emit prologue. |
|
| 333 | - | emit::emitPrologue(s.e, &frame); |
|
| 340 | + | try emit::emitPrologue(s.e, &frame); |
|
| 334 | 341 | ||
| 335 | 342 | // Move function params from arg registers to assigned registers. |
|
| 336 | 343 | // Cross-call params may have been assigned to callee-saved registers |
|
| 337 | 344 | // instead of their natural arg registers. Spilled params are stored |
|
| 338 | 345 | // directly to their spill slots. |
| 341 | 348 | let param = funcParam.value; |
|
| 342 | 349 | let argReg = super::ARG_REGS[i]; |
|
| 343 | 350 | ||
| 344 | 351 | if let slot = regalloc::spill::spillSlot(&ralloc.spill, param) { |
|
| 345 | 352 | // Spilled parameter: store arg register to spill slot. |
|
| 346 | - | emit::emitSd(s.e, argReg, spillBase(&s), spillOffset(&s, slot)); |
|
| 353 | + | try emit::emitSd(s.e, argReg, spillBase(&s), spillOffset(&s, slot)); |
|
| 347 | 354 | } else if let assigned = ralloc.assignments[param.n] { |
|
| 348 | - | emitMv(&mut s, assigned, argReg); |
|
| 355 | + | try emitMv(&mut s, assigned, argReg); |
|
| 349 | 356 | } |
|
| 350 | 357 | } |
|
| 351 | 358 | } |
|
| 352 | 359 | ||
| 353 | 360 | // Emit each block. |
|
| 354 | 361 | for i in 0..func.blocks.len { |
|
| 355 | - | selectBlock(&mut s, i, &func.blocks[i], &frame, func); |
|
| 362 | + | try selectBlock(&mut s, i, &func.blocks[i], &frame, func); |
|
| 356 | 363 | } |
|
| 357 | 364 | // Emit epilogue. |
|
| 358 | - | emit::emitEpilogue(s.e, &frame); |
|
| 365 | + | try emit::emitEpilogue(s.e, &frame); |
|
| 359 | 366 | // Patch local branches now that all blocks are emitted. |
|
| 360 | - | emit::patchLocalBranches(s.e); |
|
| 367 | + | try emit::patchLocalBranches(s.e); |
|
| 361 | 368 | } |
|
| 362 | 369 | ||
| 363 | 370 | /// Select instructions for a block. |
|
| 364 | - | unsafe fn selectBlock(s: &mut Selector, blockIdx: u32, block: *unsafe il::Block, frame: &emit::Frame, func: *unsafe il::Fn) { |
|
| 371 | + | unsafe fn selectBlock(s: &mut Selector, blockIdx: u32, block: *unsafe il::Block, frame: &emit::Frame, func: *unsafe il::Fn) throws (super::Error) { |
|
| 365 | 372 | // Record block address for branch patching. |
|
| 366 | - | emit::recordBlock(s.e, blockIdx); |
|
| 373 | + | try emit::recordBlock(s.e, blockIdx); |
|
| 367 | 374 | ||
| 368 | 375 | // Block parameters are handled at jump sites (in `Jmp`/`Br`). |
|
| 369 | 376 | // By the time we enter the block, the arguments have already been |
|
| 370 | 377 | // moved to the parameter registers by the predecessor's terminator. |
|
| 371 | 378 | ||
| 372 | 379 | // Process each instruction, auto-committing any pending spill after each. |
|
| 373 | 380 | let hasLocs = block.locs.len > 0; |
|
| 374 | 381 | for instr, i in block.instrs { |
|
| 375 | 382 | // Record debug location before emitting machine instructions. |
|
| 376 | 383 | if hasLocs { |
|
| 377 | - | emit::recordSrcLoc(s.e, block.locs[i]); |
|
| 384 | + | try emit::recordSrcLoc(s.e, block.locs[i]); |
|
| 378 | 385 | } |
|
| 379 | 386 | set s.pendingSpill = nil; |
|
| 380 | - | selectInstr(s, blockIdx, instr, frame, func); |
|
| 387 | + | try selectInstr(s, blockIdx, instr, frame, func); |
|
| 381 | 388 | ||
| 382 | 389 | // Flush the pending spill store, if any. |
|
| 383 | 390 | if let p = s.pendingSpill { |
|
| 384 | 391 | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) { |
|
| 385 | - | emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot)); |
|
| 392 | + | try emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot)); |
|
| 386 | 393 | } |
|
| 387 | 394 | set s.pendingSpill = nil; |
|
| 388 | 395 | } |
|
| 389 | 396 | } |
|
| 390 | 397 | } |
|
| 391 | 398 | ||
| 392 | 399 | /// Select instructions for a single IL instruction. |
|
| 393 | - | unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) { |
|
| 400 | + | unsafe fn selectInstr(s: &mut Selector, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: *unsafe il::Fn) throws (super::Error) { |
|
| 394 | 401 | match instr { |
|
| 395 | 402 | case il::Instr::BinOp { op, typ, dst, a, b } => { |
|
| 396 | 403 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 397 | - | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 398 | - | selectAluBinOp(s, op, typ, rd, rs1, b); |
|
| 404 | + | let rs1 = try resolveVal(s, super::SCRATCH1, a); |
|
| 405 | + | try selectAluBinOp(s, op, typ, rd, rs1, b); |
|
| 399 | 406 | }, |
|
| 400 | 407 | case il::Instr::UnOp { op, typ, dst, a } => { |
|
| 401 | 408 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 402 | - | let rs = resolveVal(s, super::SCRATCH1, a); |
|
| 403 | - | selectAluUnOp(s, op, typ, rd, rs); |
|
| 409 | + | let rs = try resolveVal(s, super::SCRATCH1, a); |
|
| 410 | + | try selectAluUnOp(s, op, typ, rd, rs); |
|
| 404 | 411 | }, |
|
| 405 | 412 | case il::Instr::Load { typ, dst, src, offset } => { |
|
| 406 | 413 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 407 | - | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 408 | - | emit::emitLoad(s.e, rd, base, offset, typ); |
|
| 414 | + | let base = try getSrcReg(s, src, super::SCRATCH2); |
|
| 415 | + | try emit::emitLoad(s.e, rd, base, offset, typ); |
|
| 409 | 416 | }, |
|
| 410 | 417 | case il::Instr::Sload { typ, dst, src, offset } => { |
|
| 411 | 418 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 412 | - | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 413 | - | emit::emitSload(s.e, rd, base, offset, typ); |
|
| 419 | + | let base = try getSrcReg(s, src, super::SCRATCH2); |
|
| 420 | + | try emit::emitSload(s.e, rd, base, offset, typ); |
|
| 414 | 421 | }, |
|
| 415 | 422 | case il::Instr::Store { typ, src, dst, offset } => { |
|
| 416 | - | let base = getSrcReg(s, dst, super::SCRATCH2); |
|
| 417 | - | let rs = resolveVal(s, super::SCRATCH1, src); |
|
| 418 | - | emit::emitStore(s.e, rs, base, offset, typ); |
|
| 423 | + | let base = try getSrcReg(s, dst, super::SCRATCH2); |
|
| 424 | + | let rs = try resolveVal(s, super::SCRATCH1, src); |
|
| 425 | + | try emit::emitStore(s.e, rs, base, offset, typ); |
|
| 419 | 426 | }, |
|
| 420 | 427 | case il::Instr::Copy { dst, val } => { |
|
| 421 | 428 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 422 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 423 | - | emitMv(s, rd, rs); |
|
| 429 | + | let rs = try resolveVal(s, super::SCRATCH1, val); |
|
| 430 | + | try emitMv(s, rd, rs); |
|
| 424 | 431 | }, |
|
| 425 | 432 | case il::Instr::Reserve { dst, size, alignment } => { |
|
| 426 | 433 | match size { |
|
| 427 | 434 | case il::Val::Imm(sz) => { |
|
| 428 | 435 | // Constant-sized reserve: use pre-allocated frame slot. |
| 430 | 437 | let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32); |
|
| 431 | 438 | let base = spillBase(s); |
|
| 432 | 439 | let offset = s.ralloc.spill.frameSize + aligned |
|
| 433 | 440 | - (s.frameSize if s.isDynamic else 0); |
|
| 434 | 441 | ||
| 435 | - | emit::emitAddImm(s.e, rd, base, offset); |
|
| 442 | + | try emit::emitAddImm(s.e, rd, base, offset); |
|
| 436 | 443 | set s.reserveOffset = aligned + (sz as i32); |
|
| 437 | 444 | }, |
|
| 438 | 445 | case il::Val::Reg(r) => { |
|
| 439 | 446 | // Dynamic-sized reserve: runtime SP adjustment. |
|
| 440 | 447 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 441 | - | let rs = getSrcReg(s, r, super::SCRATCH2); |
|
| 448 | + | let rs = try getSrcReg(s, r, super::SCRATCH2); |
|
| 442 | 449 | ||
| 443 | - | emit::emit(s.e, encode::sub(super::SP, super::SP, rs)); |
|
| 450 | + | try emit::emit(s.e, encode::sub(super::SP, super::SP, rs)); |
|
| 444 | 451 | ||
| 445 | 452 | if alignment > 1 { |
|
| 446 | 453 | let mask = 0 - alignment as i32; |
|
| 447 | 454 | assert encode::isSmallImm(mask); |
|
| 448 | 455 | ||
| 449 | - | emit::emit(s.e, encode::andi(super::SP, super::SP, mask)); |
|
| 456 | + | try emit::emit(s.e, encode::andi(super::SP, super::SP, mask)); |
|
| 450 | 457 | } |
|
| 451 | - | emit::emit(s.e, encode::mv(rd, super::SP)); |
|
| 458 | + | try emit::emit(s.e, encode::mv(rd, super::SP)); |
|
| 452 | 459 | }, |
|
| 453 | 460 | else => |
|
| 454 | 461 | panic "selectInstr: invalid reserve operand", |
|
| 455 | 462 | } |
|
| 456 | 463 | }, |
| 476 | 483 | panic "selectInstr: blit dst not spilled"; |
|
| 477 | 484 | }; |
|
| 478 | 485 | let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) else { |
|
| 479 | 486 | panic "selectInstr: blit src not spilled"; |
|
| 480 | 487 | }; |
|
| 481 | - | emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot)); |
|
| 488 | + | try emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot)); |
|
| 482 | 489 | set srcReload = spillOffset(s, srcSlot); |
|
| 483 | 490 | } else { |
|
| 484 | - | set rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 485 | - | set rsrc = getSrcReg(s, src, super::SCRATCH2); |
|
| 491 | + | set rdst = try getSrcReg(s, dst, super::SCRATCH2); |
|
| 492 | + | set rsrc = try getSrcReg(s, src, super::SCRATCH2); |
|
| 486 | 493 | } |
|
| 487 | 494 | let mut offset: i32 = 0; |
|
| 488 | 495 | let mut remaining = staticSize as i32; |
|
| 489 | 496 | ||
| 490 | 497 | // For large blits where both pointers are in real registers, |
| 493 | 500 | let canLoop = not bothSpilled |
|
| 494 | 501 | and *rsrc <> *super::SCRATCH1 and *rsrc <> *super::SCRATCH2 |
|
| 495 | 502 | and *rdst <> *super::SCRATCH1 and *rdst <> *super::SCRATCH2; |
|
| 496 | 503 | ||
| 497 | 504 | if canLoop and dwordBytes >= super::BLIT_LOOP_THRESHOLD { |
|
| 498 | - | emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes); |
|
| 505 | + | try emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes); |
|
| 499 | 506 | ||
| 500 | - | let loopStart = s.e.codeLen; |
|
| 507 | + | let loopStart = s.e.code.len; |
|
| 501 | 508 | ||
| 502 | - | emit::emitLd(s.e, super::SCRATCH2, rsrc, 0); |
|
| 503 | - | emit::emitSd(s.e, super::SCRATCH2, rdst, 0); |
|
| 504 | - | emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE)); |
|
| 509 | + | try emit::emitLd(s.e, super::SCRATCH2, rsrc, 0); |
|
| 510 | + | try emit::emitSd(s.e, super::SCRATCH2, rdst, 0); |
|
| 511 | + | try emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE)); |
|
| 505 | 512 | ||
| 506 | 513 | if *rdst <> *rsrc { |
|
| 507 | - | emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE)); |
|
| 514 | + | try emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE)); |
|
| 508 | 515 | } |
|
| 509 | - | let brOff = (loopStart as i32 - s.e.codeLen as i32) * super::INSTR_SIZE; |
|
| 516 | + | let brOff = (loopStart as i32 - s.e.code.len as i32) * super::INSTR_SIZE; |
|
| 510 | 517 | ||
| 511 | - | emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff)); |
|
| 518 | + | try emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff)); |
|
| 512 | 519 | set remaining -= dwordBytes; |
|
| 513 | 520 | } |
|
| 514 | 521 | ||
| 515 | 522 | // Copy remaining: 8 bytes, then 4 bytes, then 1 byte at a time. |
|
| 516 | 523 | // Before each load/store pair, check whether the offset is |
|
| 517 | 524 | // about to exceed the 12-bit signed immediate range. When |
|
| 518 | 525 | // it does, advance the base registers by the accumulated |
|
| 519 | 526 | // offset and reset to zero. |
|
| 520 | 527 | while remaining >= super::DWORD_SIZE { |
|
| 521 | 528 | if offset > super::MAX_IMM - super::DWORD_SIZE { |
|
| 522 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 529 | + | try emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 523 | 530 | if *rdst <> *rsrc { |
|
| 524 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 531 | + | try emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 525 | 532 | } |
|
| 526 | 533 | set offset = 0; |
|
| 527 | 534 | } |
|
| 528 | 535 | if let off = srcReload { |
|
| 529 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 530 | - | emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 536 | + | try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 537 | + | try emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 531 | 538 | } else { |
|
| 532 | - | emit::emitLd(s.e, super::SCRATCH1, rsrc, offset); |
|
| 539 | + | try emit::emitLd(s.e, super::SCRATCH1, rsrc, offset); |
|
| 533 | 540 | } |
|
| 534 | - | emit::emitSd(s.e, super::SCRATCH1, rdst, offset); |
|
| 541 | + | try emit::emitSd(s.e, super::SCRATCH1, rdst, offset); |
|
| 535 | 542 | set offset += super::DWORD_SIZE; |
|
| 536 | 543 | set remaining -= super::DWORD_SIZE; |
|
| 537 | 544 | } |
|
| 538 | 545 | if remaining >= super::WORD_SIZE { |
|
| 539 | 546 | if offset > super::MAX_IMM - super::WORD_SIZE { |
|
| 540 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 547 | + | try emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 541 | 548 | if *rdst <> *rsrc { |
|
| 542 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 549 | + | try emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 543 | 550 | } |
|
| 544 | 551 | set offset = 0; |
|
| 545 | 552 | } |
|
| 546 | 553 | if let off = srcReload { |
|
| 547 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 548 | - | emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 554 | + | try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 555 | + | try emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 549 | 556 | } else { |
|
| 550 | - | emit::emitLw(s.e, super::SCRATCH1, rsrc, offset); |
|
| 557 | + | try emit::emitLw(s.e, super::SCRATCH1, rsrc, offset); |
|
| 551 | 558 | } |
|
| 552 | - | emit::emitSw(s.e, super::SCRATCH1, rdst, offset); |
|
| 559 | + | try emit::emitSw(s.e, super::SCRATCH1, rdst, offset); |
|
| 553 | 560 | set offset += super::WORD_SIZE; |
|
| 554 | 561 | set remaining -= super::WORD_SIZE; |
|
| 555 | 562 | } |
|
| 556 | 563 | while remaining > 0 { |
|
| 557 | 564 | if offset > super::MAX_IMM - 1 { |
|
| 558 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 565 | + | try emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 559 | 566 | if *rdst <> *rsrc { |
|
| 560 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 567 | + | try emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 561 | 568 | } |
|
| 562 | 569 | set offset = 0; |
|
| 563 | 570 | } |
|
| 564 | 571 | if let off = srcReload { |
|
| 565 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 566 | - | emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 572 | + | try emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 573 | + | try emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 567 | 574 | } else { |
|
| 568 | - | emit::emitLb(s.e, super::SCRATCH1, rsrc, offset); |
|
| 575 | + | try emit::emitLb(s.e, super::SCRATCH1, rsrc, offset); |
|
| 569 | 576 | } |
|
| 570 | - | emit::emitSb(s.e, super::SCRATCH1, rdst, offset); |
|
| 577 | + | try emit::emitSb(s.e, super::SCRATCH1, rdst, offset); |
|
| 571 | 578 | set offset += 1; |
|
| 572 | 579 | set remaining -= 1; |
|
| 573 | 580 | } |
|
| 574 | 581 | // Restore base registers if they were advanced (never happens |
|
| 575 | 582 | // in the both-spilled case since size <= MAX_IMM). |
|
| 576 | 583 | if not bothSpilled { |
|
| 577 | 584 | let advanced = staticSize as i32 - offset; |
|
| 578 | 585 | if advanced <> 0 { |
|
| 579 | - | emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced); |
|
| 586 | + | try emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced); |
|
| 580 | 587 | if *rdst <> *rsrc { |
|
| 581 | - | emit::emitAddImm(s.e, rdst, rdst, 0 - advanced); |
|
| 588 | + | try emit::emitAddImm(s.e, rdst, rdst, 0 - advanced); |
|
| 582 | 589 | } |
|
| 583 | 590 | } |
|
| 584 | 591 | } |
|
| 585 | 592 | }, |
|
| 586 | 593 | case il::Instr::Zext { typ, dst, val } => { |
|
| 587 | 594 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 588 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 589 | - | emitZext(s.e, rd, rs, typ); |
|
| 595 | + | let rs = try resolveVal(s, super::SCRATCH1, val); |
|
| 596 | + | try emitZext(s.e, rd, rs, typ); |
|
| 590 | 597 | }, |
|
| 591 | 598 | case il::Instr::Sext { typ, dst, val } => { |
|
| 592 | 599 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 593 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 594 | - | emitSext(s.e, rd, rs, typ); |
|
| 600 | + | let rs = try resolveVal(s, super::SCRATCH1, val); |
|
| 601 | + | try emitSext(s.e, rd, rs, typ); |
|
| 595 | 602 | }, |
|
| 596 | 603 | case il::Instr::Ret { val } => { |
|
| 597 | 604 | if let v = val { |
|
| 598 | - | let rs = resolveVal(s, super::SCRATCH1, v); |
|
| 599 | - | emitMv(s, super::A0, rs); |
|
| 605 | + | let rs = try resolveVal(s, super::SCRATCH1, v); |
|
| 606 | + | try emitMv(s, super::A0, rs); |
|
| 600 | 607 | } |
|
| 601 | 608 | // Skip the jump to epilogue if this RET is in the last block, |
|
| 602 | 609 | // since the epilogue immediately follows. |
|
| 603 | 610 | if frame.totalSize <> 0 and blockIdx + 1 == frame.epilogueBlock { |
|
| 604 | 611 | // Epilogue is the next block; fallthrough is sufficient. |
|
| 605 | 612 | } else { |
|
| 606 | - | emit::emitReturn(s.e, frame); |
|
| 613 | + | try emit::emitReturn(s.e, frame); |
|
| 607 | 614 | } |
|
| 608 | 615 | }, |
|
| 609 | 616 | case il::Instr::Jmp { target, args } => { |
|
| 610 | 617 | // Move arguments to target block's parameter registers. |
|
| 611 | - | emitBlockArgs(s, func, target, args); |
|
| 618 | + | try emitBlockArgs(s, func, target, args); |
|
| 612 | 619 | // Skip branch if target is the next block (fallthrough). |
|
| 613 | 620 | if target <> blockIdx + 1 { |
|
| 614 | - | emit::recordBranch(s.e, target, emit::BranchKind::Jump); |
|
| 621 | + | try emit::recordBranch(s.e, target, emit::BranchKind::Jump); |
|
| 615 | 622 | } |
|
| 616 | 623 | }, |
|
| 617 | 624 | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 618 | 625 | // Use zero register directly for immediate `0` operands. |
|
| 619 | 626 | let aIsZero = isZeroImm(a); |
|
| 620 | 627 | let bIsZero = isZeroImm(b); |
|
| 621 | 628 | ||
| 622 | - | let rs1 = super::ZERO if aIsZero else resolveVal(s, super::SCRATCH1, a); |
|
| 623 | - | let rs2 = super::ZERO if bIsZero else resolveVal(s, super::SCRATCH2, b); |
|
| 629 | + | let rs1 = super::ZERO if aIsZero else try resolveVal(s, super::SCRATCH1, a); |
|
| 630 | + | let rs2 = super::ZERO if bIsZero else try resolveVal(s, super::SCRATCH2, b); |
|
| 624 | 631 | ||
| 625 | 632 | // Normalize sub-word operands so that both registers have the same |
|
| 626 | 633 | // canonical representation. Without this, eg. `-1 : i8 ` loaded as |
|
| 627 | 634 | // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare |
|
| 628 | 635 | // unequal even though they are the same 8-bit pattern. |
| 636 | 643 | if let case il::CmpOp::Slt = op { |
|
| 637 | 644 | set signed = true; |
|
| 638 | 645 | } |
|
| 639 | 646 | let useSext = cmpUsesSext(typ, signed); |
|
| 640 | 647 | if not aIsZero and not isExtendedImm(a, typ, useSext) { |
|
| 641 | - | emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 648 | + | try emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 642 | 649 | } |
|
| 643 | 650 | if not bIsZero and not isExtendedImm(b, typ, useSext) { |
|
| 644 | - | emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 651 | + | try emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 645 | 652 | } |
|
| 646 | 653 | // Block-argument moves must only execute on the taken path. |
|
| 647 | 654 | // When `thenArgs` is non-empty, invert the branch so that the |
|
| 648 | 655 | // then-moves land on the fall-through (taken) side. |
|
| 649 | 656 | // |
| 652 | 659 | // conditional branch to skip to the *other* target and letting |
|
| 653 | 660 | // execution fall through. |
|
| 654 | 661 | if thenArgs.len > 0 and elseArgs.len > 0 { |
|
| 655 | 662 | panic "selectInstr: both `then` and `else` have block arguments"; |
|
| 656 | 663 | } else if thenArgs.len > 0 { |
|
| 657 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 658 | - | emitBlockArgs(s, func, thenTarget, thenArgs); |
|
| 664 | + | try emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 665 | + | try emitBlockArgs(s, func, thenTarget, thenArgs); |
|
| 659 | 666 | // Skip trailing jump if then is the next block (fallthrough). |
|
| 660 | 667 | if thenTarget <> blockIdx + 1 { |
|
| 661 | - | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump); |
|
| 668 | + | try emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump); |
|
| 662 | 669 | } |
|
| 663 | 670 | } else if thenTarget == blockIdx + 1 and elseArgs.len == 0 { |
|
| 664 | 671 | // Then is the next block and no else args: invert the |
|
| 665 | 672 | // condition to branch to else and fall through to then. |
|
| 666 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 673 | + | try emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 667 | 674 | } else { |
|
| 668 | - | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 }); |
|
| 669 | - | emitBlockArgs(s, func, elseTarget, elseArgs); |
|
| 675 | + | try emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 }); |
|
| 676 | + | try emitBlockArgs(s, func, elseTarget, elseArgs); |
|
| 670 | 677 | // Skip trailing jump if else is the next block (fallthrough). |
|
| 671 | 678 | if elseTarget <> blockIdx + 1 { |
|
| 672 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump); |
|
| 679 | + | try emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump); |
|
| 673 | 680 | } |
|
| 674 | 681 | } |
|
| 675 | 682 | }, |
|
| 676 | 683 | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 677 | - | let rs1 = resolveVal(s, super::SCRATCH1, val); |
|
| 684 | + | let rs1 = try resolveVal(s, super::SCRATCH1, val); |
|
| 678 | 685 | // When a case has block args, invert the branch to skip past |
|
| 679 | 686 | // the arg moves. |
|
| 680 | 687 | for c in cases { |
|
| 681 | - | emit::loadImm(s.e, super::SCRATCH2, c.value); |
|
| 688 | + | try emit::loadImm(s.e, super::SCRATCH2, c.value); |
|
| 682 | 689 | ||
| 683 | 690 | if c.args.len > 0 { |
|
| 684 | 691 | let skip = s.nextSynthBlock; |
|
| 685 | 692 | set s.nextSynthBlock = skip + 1; |
|
| 686 | 693 | ||
| 687 | - | emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond { |
|
| 694 | + | try emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond { |
|
| 688 | 695 | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 689 | 696 | }); |
|
| 690 | - | emitBlockArgs(s, func, c.target, c.args); |
|
| 691 | - | emit::recordBranch(s.e, c.target, emit::BranchKind::Jump); |
|
| 692 | - | emit::recordBlock(s.e, skip); |
|
| 697 | + | try emitBlockArgs(s, func, c.target, c.args); |
|
| 698 | + | try emit::recordBranch(s.e, c.target, emit::BranchKind::Jump); |
|
| 699 | + | try emit::recordBlock(s.e, skip); |
|
| 693 | 700 | } else { |
|
| 694 | - | emit::recordBranch(s.e, c.target, emit::BranchKind::Cond { |
|
| 701 | + | try emit::recordBranch(s.e, c.target, emit::BranchKind::Cond { |
|
| 695 | 702 | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 696 | 703 | }); |
|
| 697 | 704 | } |
|
| 698 | 705 | } |
|
| 699 | 706 | // Fall through to default. |
|
| 700 | - | emitBlockArgs(s, func, defaultTarget, defaultArgs); |
|
| 701 | - | emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 707 | + | try emitBlockArgs(s, func, defaultTarget, defaultArgs); |
|
| 708 | + | try emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 702 | 709 | }, |
|
| 703 | 710 | case il::Instr::Unreachable => { |
|
| 704 | - | emit::emit(s.e, encode::ebreak()); |
|
| 711 | + | try emit::emit(s.e, encode::ebreak()); |
|
| 705 | 712 | }, |
|
| 706 | 713 | case il::Instr::Call { retTy, dst, func, args } => { |
|
| 707 | 714 | // For indirect calls, save target to scratch register before arg |
|
| 708 | 715 | // setup can clobber it. |
|
| 709 | 716 | if let case il::Val::Reg(r) = func { |
|
| 710 | - | let target = getSrcReg(s, r, super::SCRATCH2); |
|
| 711 | - | emitMv(s, super::SCRATCH2, target); |
|
| 717 | + | let target = try getSrcReg(s, r, super::SCRATCH2); |
|
| 718 | + | try emitMv(s, super::SCRATCH2, target); |
|
| 712 | 719 | } |
|
| 713 | 720 | // Move arguments to A0-A7 using parallel move resolution. |
|
| 714 | - | assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments"; |
|
| 715 | - | emitParallelMoves(s, &super::ARG_REGS[..], args); |
|
| 721 | + | if args.len > super::ARG_REGS.len { |
|
| 722 | + | throw super::Error::Capacity; |
|
| 723 | + | } |
|
| 724 | + | try emitParallelMoves(s, &super::ARG_REGS[..], args); |
|
| 716 | 725 | ||
| 717 | 726 | // Emit call. |
|
| 718 | 727 | match func { |
|
| 719 | 728 | case il::Val::FnAddr(name) => { |
|
| 720 | - | emit::recordCall(s.e, name); |
|
| 729 | + | try emit::recordCall(s.e, name); |
|
| 721 | 730 | }, |
|
| 722 | 731 | case il::Val::Reg(_) => { |
|
| 723 | - | emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0)); |
|
| 732 | + | try emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0)); |
|
| 724 | 733 | }, |
|
| 725 | 734 | else => { |
|
| 726 | 735 | panic "selectInstr: invalid call target"; |
|
| 727 | 736 | } |
|
| 728 | 737 | } |
|
| 729 | 738 | // Move result from A0. |
|
| 730 | 739 | if let d = dst { |
|
| 731 | 740 | let rd = getDstReg(s, d, super::SCRATCH1); |
|
| 732 | - | emitMv(s, rd, super::A0); |
|
| 741 | + | try emitMv(s, rd, super::A0); |
|
| 733 | 742 | } |
|
| 734 | 743 | }, |
|
| 735 | 744 | case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => { |
|
| 736 | 745 | // Move arguments using parallel move. |
|
| 737 | 746 | // TODO: Can't use slice literals here because the lowerer doesn't |
|
| 738 | 747 | // support constant-evaluating struct/union values in them. |
|
| 739 | 748 | let ecallDsts: [gen::Reg; 5] = [super::A7, super::A0, super::A1, super::A2, super::A3]; |
|
| 740 | 749 | let ecallArgs: [il::Val; 5] = [num, a0, a1, a2, a3]; |
|
| 741 | 750 | ||
| 742 | - | emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]); |
|
| 743 | - | emit::emit(s.e, encode::ecall()); |
|
| 751 | + | try emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]); |
|
| 752 | + | try emit::emit(s.e, encode::ecall()); |
|
| 744 | 753 | ||
| 745 | 754 | // Result in A0. |
|
| 746 | 755 | let ecallRd = getDstReg(s, dst, super::SCRATCH1); |
|
| 747 | - | emitMv(s, ecallRd, super::A0); |
|
| 756 | + | try emitMv(s, ecallRd, super::A0); |
|
| 748 | 757 | }, |
|
| 749 | 758 | case il::Instr::Ebreak => { |
|
| 750 | - | emit::emit(s.e, encode::ebreak()); |
|
| 759 | + | try emit::emit(s.e, encode::ebreak()); |
|
| 751 | 760 | }, |
|
| 752 | 761 | case il::Instr::MemoryFence => { |
|
| 753 | - | emit::emit(s.e, encode::fence()); |
|
| 762 | + | try emit::emit(s.e, encode::fence()); |
|
| 754 | 763 | }, |
|
| 755 | 764 | } |
|
| 756 | 765 | } |
|
| 757 | 766 | ||
| 758 | 767 | /// Choose the cheapest canonical representation that preserves the comparison. |
| 767 | 776 | e: &mut emit::Emitter, |
|
| 768 | 777 | rd: gen::Reg, |
|
| 769 | 778 | rs: gen::Reg, |
|
| 770 | 779 | typ: il::Type, |
|
| 771 | 780 | useSext: bool |
|
| 772 | - | ) { |
|
| 781 | + | ) throws (super::Error) { |
|
| 773 | 782 | if useSext { |
|
| 774 | - | emitSext(e, rd, rs, typ); |
|
| 783 | + | try emitSext(e, rd, rs, typ); |
|
| 775 | 784 | } else { |
|
| 776 | - | emitZext(e, rd, rs, typ); |
|
| 785 | + | try emitZext(e, rd, rs, typ); |
|
| 777 | 786 | } |
|
| 778 | 787 | } |
|
| 779 | 788 | ||
| 780 | 789 | /// Truncate and extend an immediate exactly as its register operand would be. |
|
| 781 | 790 | fn canonicalCmpImm(imm: i64, typ: il::Type, useSext: bool) -> i64 { |
| 832 | 841 | return false; |
|
| 833 | 842 | } |
|
| 834 | 843 | ||
| 835 | 844 | /// Select a binary ALU operation, dispatching to the appropriate |
|
| 836 | 845 | /// instruction pattern based on the operation kind and type. |
|
| 837 | - | unsafe fn selectAluBinOp(s: &mut Selector, op: il::BinOp, typ: il::Type, rd: gen::Reg, rs1: gen::Reg, b: il::Val) { |
|
| 846 | + | unsafe fn selectAluBinOp(s: &mut Selector, op: il::BinOp, typ: il::Type, rd: gen::Reg, rs1: gen::Reg, b: il::Val) throws (super::Error) { |
|
| 838 | 847 | match op { |
|
| 839 | 848 | case il::BinOp::Add => { |
|
| 840 | 849 | if typ == il::Type::W32 { |
|
| 841 | 850 | // Inline W32 ADD with immediate optimization. |
|
| 842 | 851 | if let case il::Val::Imm(imm) = b { |
|
| 843 | 852 | if encode::isSmallImm64(imm) { |
|
| 844 | - | emit::emit(s.e, encode::addiw(rd, rs1, imm as i32)); |
|
| 853 | + | try emit::emit(s.e, encode::addiw(rd, rs1, imm as i32)); |
|
| 845 | 854 | return; |
|
| 846 | 855 | } |
|
| 847 | 856 | } |
|
| 848 | - | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 849 | - | emit::emit(s.e, encode::addw(rd, rs1, rs2)); |
|
| 857 | + | let rs2 = try resolveVal(s, super::SCRATCH2, b); |
|
| 858 | + | try emit::emit(s.e, encode::addw(rd, rs1, rs2)); |
|
| 850 | 859 | } else { |
|
| 851 | - | selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2); |
|
| 860 | + | try selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2); |
|
| 852 | 861 | } |
|
| 853 | 862 | } |
|
| 854 | 863 | case il::BinOp::Sub => { |
|
| 855 | 864 | // Optimize subtraction by small immediate: use ADDI with negated value. |
|
| 856 | 865 | if let case il::Val::Imm(imm) = b { |
|
| 857 | 866 | let neg = -imm; |
|
| 858 | 867 | if neg >= super::MIN_IMM as i64 and neg <= super::MAX_IMM as i64 { |
|
| 859 | - | emit::emit(s.e, |
|
| 868 | + | try emit::emit(s.e, |
|
| 860 | 869 | encode::addiw(rd, rs1, neg as i32) |
|
| 861 | 870 | if typ == il::Type::W32 else |
|
| 862 | 871 | encode::addi(rd, rs1, neg as i32)); |
|
| 863 | 872 | return; |
|
| 864 | 873 | } |
|
| 865 | 874 | } |
|
| 866 | - | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 875 | + | let rs2 = try resolveVal(s, super::SCRATCH2, b); |
|
| 867 | 876 | ||
| 868 | - | emit::emit(s.e, |
|
| 877 | + | try emit::emit(s.e, |
|
| 869 | 878 | encode::subw(rd, rs1, rs2) |
|
| 870 | 879 | if typ == il::Type::W32 else |
|
| 871 | 880 | encode::sub(rd, rs1, rs2)); |
|
| 872 | 881 | } |
|
| 873 | 882 | case il::BinOp::Mul => { |
|
| 874 | 883 | // Strength-reduce multiplication by known constants. |
|
| 875 | 884 | if let case il::Val::Imm(imm) = b { |
|
| 876 | 885 | if imm == 0 { |
|
| 877 | - | emit::emit(s.e, encode::mv(rd, super::ZERO)); |
|
| 886 | + | try emit::emit(s.e, encode::mv(rd, super::ZERO)); |
|
| 878 | 887 | return; |
|
| 879 | 888 | } else if imm == 1 { |
|
| 880 | - | emitMv(s, rd, rs1); |
|
| 889 | + | try emitMv(s, rd, rs1); |
|
| 881 | 890 | return; |
|
| 882 | 891 | } else if imm == 2 { |
|
| 883 | - | emit::emit(s.e, encode::slli(rd, rs1, 1)); |
|
| 892 | + | try emit::emit(s.e, encode::slli(rd, rs1, 1)); |
|
| 884 | 893 | return; |
|
| 885 | 894 | } else if imm == 4 { |
|
| 886 | - | emit::emit(s.e, encode::slli(rd, rs1, 2)); |
|
| 895 | + | try emit::emit(s.e, encode::slli(rd, rs1, 2)); |
|
| 887 | 896 | return; |
|
| 888 | 897 | } else if imm == 8 { |
|
| 889 | - | emit::emit(s.e, encode::slli(rd, rs1, 3)); |
|
| 898 | + | try emit::emit(s.e, encode::slli(rd, rs1, 3)); |
|
| 890 | 899 | return; |
|
| 891 | 900 | } |
|
| 892 | 901 | } |
|
| 893 | - | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 894 | - | emit::emit(s.e, |
|
| 902 | + | let rs2 = try resolveVal(s, super::SCRATCH2, b); |
|
| 903 | + | try emit::emit(s.e, |
|
| 895 | 904 | encode::mulw(rd, rs1, rs2) |
|
| 896 | 905 | if typ == il::Type::W32 else |
|
| 897 | 906 | encode::mul(rd, rs1, rs2)); |
|
| 898 | 907 | } |
|
| 899 | 908 | case il::BinOp::Sdiv => { |
|
| 900 | - | let rs2 = resolveAndTrapIfZero(s, b, typ, true); |
|
| 901 | - | emit::emit(s.e, |
|
| 909 | + | let rs2 = try resolveAndTrapIfZero(s, b, typ, true); |
|
| 910 | + | try emit::emit(s.e, |
|
| 902 | 911 | encode::divw(rd, rs1, rs2) |
|
| 903 | 912 | if typ == il::Type::W32 else |
|
| 904 | 913 | encode::div(rd, rs1, rs2)); |
|
| 905 | 914 | } |
|
| 906 | 915 | case il::BinOp::Udiv => { |
|
| 907 | - | let rs2 = resolveAndTrapIfZero(s, b, typ, false); |
|
| 908 | - | emit::emit(s.e, |
|
| 916 | + | let rs2 = try resolveAndTrapIfZero(s, b, typ, false); |
|
| 917 | + | try emit::emit(s.e, |
|
| 909 | 918 | encode::divuw(rd, rs1, rs2) |
|
| 910 | 919 | if typ == il::Type::W32 else |
|
| 911 | 920 | encode::divu(rd, rs1, rs2)); |
|
| 912 | 921 | } |
|
| 913 | 922 | case il::BinOp::Srem => { |
|
| 914 | - | let rs2 = resolveAndTrapIfZero(s, b, typ, true); |
|
| 915 | - | emit::emit(s.e, |
|
| 923 | + | let rs2 = try resolveAndTrapIfZero(s, b, typ, true); |
|
| 924 | + | try emit::emit(s.e, |
|
| 916 | 925 | encode::remw(rd, rs1, rs2) |
|
| 917 | 926 | if typ == il::Type::W32 else |
|
| 918 | 927 | encode::rem(rd, rs1, rs2)); |
|
| 919 | 928 | } |
|
| 920 | 929 | case il::BinOp::Urem => { |
|
| 921 | - | let rs2 = resolveAndTrapIfZero(s, b, typ, false); |
|
| 922 | - | emit::emit(s.e, |
|
| 930 | + | let rs2 = try resolveAndTrapIfZero(s, b, typ, false); |
|
| 931 | + | try emit::emit(s.e, |
|
| 923 | 932 | encode::remuw(rd, rs1, rs2) |
|
| 924 | 933 | if typ == il::Type::W32 else |
|
| 925 | 934 | encode::remu(rd, rs1, rs2)); |
|
| 926 | 935 | } |
|
| 927 | 936 | case il::BinOp::And => |
|
| 928 | - | selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2), |
|
| 937 | + | try selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2), |
|
| 929 | 938 | case il::BinOp::Or => |
|
| 930 | - | selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2), |
|
| 939 | + | try selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2), |
|
| 931 | 940 | case il::BinOp::Xor => |
|
| 932 | - | selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2), |
|
| 941 | + | try selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2), |
|
| 933 | 942 | case il::BinOp::Shl => |
|
| 934 | - | selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2), |
|
| 943 | + | try selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2), |
|
| 935 | 944 | case il::BinOp::Sshr => |
|
| 936 | - | selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2), |
|
| 945 | + | try selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2), |
|
| 937 | 946 | case il::BinOp::Ushr => |
|
| 938 | - | selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2), |
|
| 947 | + | try selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2), |
|
| 939 | 948 | case il::BinOp::Eq, il::BinOp::Ne => { |
|
| 940 | - | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 949 | + | let rs2 = try resolveVal(s, super::SCRATCH2, b); |
|
| 941 | 950 | let useSext = cmpUsesSext(typ, false); |
|
| 942 | - | emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 951 | + | try emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 943 | 952 | if not isExtendedImm(b, typ, useSext) { |
|
| 944 | - | emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 953 | + | try emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 945 | 954 | } |
|
| 946 | - | emit::emit(s.e, encode::xor(rd, rs1, rs2)); |
|
| 955 | + | try emit::emit(s.e, encode::xor(rd, rs1, rs2)); |
|
| 947 | 956 | if let case il::BinOp::Eq = op { |
|
| 948 | - | emit::emit(s.e, encode::sltiu(rd, rd, 1)); |
|
| 957 | + | try emit::emit(s.e, encode::sltiu(rd, rd, 1)); |
|
| 949 | 958 | } else { |
|
| 950 | - | emit::emit(s.e, encode::sltu(rd, super::ZERO, rd)); |
|
| 959 | + | try emit::emit(s.e, encode::sltu(rd, super::ZERO, rd)); |
|
| 951 | 960 | } |
|
| 952 | 961 | } |
|
| 953 | 962 | case il::BinOp::Slt => |
|
| 954 | - | selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, false, super::SCRATCH2), |
|
| 963 | + | try selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, false), |
|
| 955 | 964 | case il::BinOp::Ult => |
|
| 956 | - | selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, false, super::SCRATCH2), |
|
| 965 | + | try selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, false), |
|
| 957 | 966 | case il::BinOp::Sge => |
|
| 958 | - | selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, true, super::SCRATCH2), |
|
| 967 | + | try selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, true), |
|
| 959 | 968 | case il::BinOp::Uge => |
|
| 960 | - | selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, true, super::SCRATCH2), |
|
| 969 | + | try selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, true), |
|
| 961 | 970 | } |
|
| 962 | 971 | } |
|
| 963 | 972 | ||
| 964 | 973 | /// Select a unary ALU operation. |
|
| 965 | - | unsafe fn selectAluUnOp(s: &mut Selector, op: il::UnOp, typ: il::Type, rd: gen::Reg, rs: gen::Reg) { |
|
| 974 | + | unsafe fn selectAluUnOp(s: &mut Selector, op: il::UnOp, typ: il::Type, rd: gen::Reg, rs: gen::Reg) throws (super::Error) { |
|
| 966 | 975 | match op { |
|
| 967 | 976 | case il::UnOp::Neg => { |
|
| 968 | 977 | if typ == il::Type::W32 { |
|
| 969 | - | emit::emit(s.e, encode::subw(rd, super::ZERO, rs)); |
|
| 978 | + | try emit::emit(s.e, encode::subw(rd, super::ZERO, rs)); |
|
| 970 | 979 | } else { |
|
| 971 | - | emit::emit(s.e, encode::neg(rd, rs)); |
|
| 980 | + | try emit::emit(s.e, encode::neg(rd, rs)); |
|
| 972 | 981 | } |
|
| 973 | 982 | } |
|
| 974 | 983 | case il::UnOp::Not => |
|
| 975 | - | emit::emit(s.e, encode::not_(rd, rs)), |
|
| 984 | + | try emit::emit(s.e, encode::not_(rd, rs)), |
|
| 976 | 985 | } |
|
| 977 | 986 | } |
|
| 978 | 987 | ||
| 979 | 988 | /// Select binary operation with immediate optimization. |
|
| 980 | - | unsafe fn selectBinOp(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: BinOp, scratch: gen::Reg) { |
|
| 989 | + | unsafe fn selectBinOp(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: BinOp, scratch: gen::Reg) throws (super::Error) { |
|
| 981 | 990 | // Try immediate optimization first. |
|
| 982 | 991 | if let case il::Val::Imm(imm) = b { |
|
| 983 | 992 | if encode::isSmallImm64(imm) { |
|
| 984 | 993 | let simm = imm as i32; |
|
| 985 | 994 | match op { |
|
| 986 | - | case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, simm)), |
|
| 987 | - | case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, simm)), |
|
| 988 | - | case BinOp::Or => emit::emit(s.e, encode::ori(rd, rs1, simm)), |
|
| 989 | - | case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, simm)), |
|
| 995 | + | case BinOp::Add => try emit::emit(s.e, encode::addi(rd, rs1, simm)), |
|
| 996 | + | case BinOp::And => try emit::emit(s.e, encode::andi(rd, rs1, simm)), |
|
| 997 | + | case BinOp::Or => try emit::emit(s.e, encode::ori(rd, rs1, simm)), |
|
| 998 | + | case BinOp::Xor => try emit::emit(s.e, encode::xori(rd, rs1, simm)), |
|
| 990 | 999 | } |
|
| 991 | 1000 | return; |
|
| 992 | 1001 | } |
|
| 993 | 1002 | } |
|
| 994 | 1003 | // Fallback: load into register. |
|
| 995 | - | let rs2 = resolveVal(s, scratch, b); |
|
| 1004 | + | let rs2 = try resolveVal(s, scratch, b); |
|
| 996 | 1005 | match op { |
|
| 997 | - | case BinOp::Add => emit::emit(s.e, encode::add(rd, rs1, rs2)), |
|
| 998 | - | case BinOp::And => emit::emit(s.e, encode::and_(rd, rs1, rs2)), |
|
| 999 | - | case BinOp::Or => emit::emit(s.e, encode::or_(rd, rs1, rs2)), |
|
| 1000 | - | case BinOp::Xor => emit::emit(s.e, encode::xor(rd, rs1, rs2)), |
|
| 1006 | + | case BinOp::Add => try emit::emit(s.e, encode::add(rd, rs1, rs2)), |
|
| 1007 | + | case BinOp::And => try emit::emit(s.e, encode::and_(rd, rs1, rs2)), |
|
| 1008 | + | case BinOp::Or => try emit::emit(s.e, encode::or_(rd, rs1, rs2)), |
|
| 1009 | + | case BinOp::Xor => try emit::emit(s.e, encode::xor(rd, rs1, rs2)), |
|
| 1001 | 1010 | } |
|
| 1002 | 1011 | } |
|
| 1003 | 1012 | ||
| 1004 | 1013 | /// Select shift operation with immediate optimization. |
|
| 1005 | 1014 | /// For 32-bit operations, uses the `*w` variants that operate on the lower 32 bits |
|
| 1006 | 1015 | /// and sign-extend the result. |
|
| 1007 | - | unsafe fn selectShift(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: gen::Reg) { |
|
| 1016 | + | unsafe fn selectShift(s: &mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: gen::Reg) throws (super::Error) { |
|
| 1008 | 1017 | let isW32: bool = typ == il::Type::W32; |
|
| 1009 | 1018 | ||
| 1010 | 1019 | // Try immediate optimization first. |
|
| 1011 | 1020 | if let case il::Val::Imm(shamt) = b { |
|
| 1012 | 1021 | // Keep immediate forms only for encodable shift amounts. |
|
| 1013 | 1022 | // Otherwise fall back to register shifts, which naturally mask the count. |
|
| 1014 | 1023 | if shamt >= 0 and ((isW32 and shamt < 32) or (not isW32 and shamt < 64)) { |
|
| 1015 | 1024 | let sa = shamt as i32; |
|
| 1016 | 1025 | if isW32 { |
|
| 1017 | 1026 | match op { |
|
| 1018 | - | case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, sa)), |
|
| 1019 | - | case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, sa)), |
|
| 1020 | - | case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, sa)), |
|
| 1027 | + | case ShiftOp::Sll => try emit::emit(s.e, encode::slliw(rd, rs1, sa)), |
|
| 1028 | + | case ShiftOp::Srl => try emit::emit(s.e, encode::srliw(rd, rs1, sa)), |
|
| 1029 | + | case ShiftOp::Sra => try emit::emit(s.e, encode::sraiw(rd, rs1, sa)), |
|
| 1021 | 1030 | } |
|
| 1022 | 1031 | } else { |
|
| 1023 | 1032 | match op { |
|
| 1024 | - | case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, sa)), |
|
| 1025 | - | case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, sa)), |
|
| 1026 | - | case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, sa)), |
|
| 1033 | + | case ShiftOp::Sll => try emit::emit(s.e, encode::slli(rd, rs1, sa)), |
|
| 1034 | + | case ShiftOp::Srl => try emit::emit(s.e, encode::srli(rd, rs1, sa)), |
|
| 1035 | + | case ShiftOp::Sra => try emit::emit(s.e, encode::srai(rd, rs1, sa)), |
|
| 1027 | 1036 | } |
|
| 1028 | 1037 | } |
|
| 1029 | 1038 | return; |
|
| 1030 | 1039 | } |
|
| 1031 | 1040 | } |
|
| 1032 | 1041 | // Fallback: load into register. |
|
| 1033 | - | let rs2 = resolveVal(s, scratch, b); |
|
| 1042 | + | let rs2 = try resolveVal(s, scratch, b); |
|
| 1034 | 1043 | if isW32 { |
|
| 1035 | 1044 | match op { |
|
| 1036 | - | case ShiftOp::Sll => emit::emit(s.e, encode::sllw(rd, rs1, rs2)), |
|
| 1037 | - | case ShiftOp::Srl => emit::emit(s.e, encode::srlw(rd, rs1, rs2)), |
|
| 1038 | - | case ShiftOp::Sra => emit::emit(s.e, encode::sraw(rd, rs1, rs2)), |
|
| 1045 | + | case ShiftOp::Sll => try emit::emit(s.e, encode::sllw(rd, rs1, rs2)), |
|
| 1046 | + | case ShiftOp::Srl => try emit::emit(s.e, encode::srlw(rd, rs1, rs2)), |
|
| 1047 | + | case ShiftOp::Sra => try emit::emit(s.e, encode::sraw(rd, rs1, rs2)), |
|
| 1039 | 1048 | } |
|
| 1040 | 1049 | } else { |
|
| 1041 | 1050 | match op { |
|
| 1042 | - | case ShiftOp::Sll => emit::emit(s.e, encode::sll(rd, rs1, rs2)), |
|
| 1043 | - | case ShiftOp::Srl => emit::emit(s.e, encode::srl(rd, rs1, rs2)), |
|
| 1044 | - | case ShiftOp::Sra => emit::emit(s.e, encode::sra(rd, rs1, rs2)), |
|
| 1051 | + | case ShiftOp::Sll => try emit::emit(s.e, encode::sll(rd, rs1, rs2)), |
|
| 1052 | + | case ShiftOp::Srl => try emit::emit(s.e, encode::srl(rd, rs1, rs2)), |
|
| 1053 | + | case ShiftOp::Sra => try emit::emit(s.e, encode::sra(rd, rs1, rs2)), |
|
| 1045 | 1054 | } |
|
| 1046 | 1055 | } |
|
| 1047 | 1056 | } |
|
| 1048 | 1057 | ||
| 1049 | 1058 | /// Resolve parallel moves from IL values to physical destination registers. |
| 1055 | 1064 | /// 1. Identifies "ready" moves. |
|
| 1056 | 1065 | /// 2. Executes ready moves. |
|
| 1057 | 1066 | /// 3. Breaks cycles using scratch register. |
|
| 1058 | 1067 | /// |
|
| 1059 | 1068 | /// Entries with `ZERO` destination are skipped, as they are handled by caller. |
|
| 1060 | - | unsafe fn emitParallelMoves(s: &mut Selector, dsts: &[gen::Reg], args: &[il::Val]) { |
|
| 1069 | + | unsafe fn emitParallelMoves(s: &mut Selector, dsts: &[gen::Reg], args: &[il::Val]) throws (super::Error) { |
|
| 1061 | 1070 | let n: u32 = args.len; |
|
| 1062 | 1071 | if n == 0 { |
|
| 1063 | 1072 | return; |
|
| 1064 | 1073 | } |
|
| 1065 | 1074 | assert n <= MAX_BLOCK_ARGS, "emitParallelMoves: too many arguments"; |
| 1126 | 1135 | } |
|
| 1127 | 1136 | } |
|
| 1128 | 1137 | if isReady { |
|
| 1129 | 1138 | // Execute this move. |
|
| 1130 | 1139 | if isRegMove[i] { |
|
| 1131 | - | emitMv(s, dst, srcRegs[i]); |
|
| 1140 | + | try emitMv(s, dst, srcRegs[i]); |
|
| 1132 | 1141 | } else { |
|
| 1133 | 1142 | // Load immediate, symbol, or spilled value. |
|
| 1134 | - | loadVal(s, dst, args[i]); |
|
| 1143 | + | try loadVal(s, dst, args[i]); |
|
| 1135 | 1144 | } |
|
| 1136 | 1145 | set found = true; |
|
| 1137 | 1146 | set pending[i] = false; |
|
| 1138 | 1147 | set numPending -= 1; |
|
| 1139 | 1148 |
| 1147 | 1156 | // Break it by saving one source to scratch. |
|
| 1148 | 1157 | for i in 0..n { |
|
| 1149 | 1158 | if pending[i] and isRegMove[i] { |
|
| 1150 | 1159 | let src = srcRegs[i]; |
|
| 1151 | 1160 | // Save this source to scratch. |
|
| 1152 | - | emitMv(s, super::SCRATCH1, src); |
|
| 1161 | + | try emitMv(s, super::SCRATCH1, src); |
|
| 1153 | 1162 | // Update all pending moves that use this source. |
|
| 1154 | 1163 | for j in 0..n { |
|
| 1155 | 1164 | if pending[j] and isRegMove[j] and srcRegs[j] == src { |
|
| 1156 | 1165 | set srcRegs[j] = super::SCRATCH1; |
|
| 1157 | 1166 | } |
| 1166 | 1175 | /// Emit moves from block arguments to target block's parameter registers. |
|
| 1167 | 1176 | /// |
|
| 1168 | 1177 | /// Handles spilled destinations directly, then delegates to [`emitParallelMoves`] |
|
| 1169 | 1178 | /// for the remaining register-to-register parallel move resolution. Edges that |
|
| 1170 | 1179 | /// would overwrite an unconsumed spill source are unsupported. |
|
| 1171 | - | unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) { |
|
| 1180 | + | unsafe fn emitBlockArgs(s: &mut Selector, func: *unsafe il::Fn, target: u32, args: &[il::Val]) throws (super::Error) { |
|
| 1172 | 1181 | if args.len == 0 { |
|
| 1173 | 1182 | return; |
|
| 1174 | 1183 | } |
|
| 1175 | 1184 | let block = &func.blocks[target]; |
|
| 1176 | 1185 | assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch"; |
|
| 1177 | - | assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments"; |
|
| 1186 | + | if args.len > MAX_BLOCK_ARGS { |
|
| 1187 | + | throw super::Error::Capacity; |
|
| 1188 | + | } |
|
| 1178 | 1189 | ||
| 1179 | 1190 | // The parallel-move resolver only handles register destinations. Keep eager |
|
| 1180 | 1191 | // stores for independent spill slots, but reject dependencies that would |
|
| 1181 | 1192 | // require stack staging rather than silently miscompiling them. |
|
| 1182 | 1193 | for arg, i in args { |
| 1221 | 1232 | // These don't participate in the parallel move algorithm. |
|
| 1222 | 1233 | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, param) { |
|
| 1223 | 1234 | if let case il::Val::Undef = arg { |
|
| 1224 | 1235 | // Undefined values don't need any move. |
|
| 1225 | 1236 | } else { |
|
| 1226 | - | let rs = resolveVal(s, super::SCRATCH1, arg); |
|
| 1227 | - | emit::emitSd(s.e, rs, spillBase(s), spillOffset(s, slot)); |
|
| 1237 | + | let rs = try resolveVal(s, super::SCRATCH1, arg); |
|
| 1238 | + | try emit::emitSd(s.e, rs, spillBase(s), spillOffset(s, slot)); |
|
| 1228 | 1239 | } |
|
| 1229 | 1240 | } else { |
|
| 1230 | 1241 | set dsts[i] = getReg(s, param); |
|
| 1231 | 1242 | } |
|
| 1232 | 1243 | } |
|
| 1233 | - | emitParallelMoves(s, &dsts[..], args); |
|
| 1244 | + | try emitParallelMoves(s, &dsts[..], args); |
|
| 1234 | 1245 | } |
|
| 1235 | 1246 | ||
| 1236 | 1247 | /// Select a comparison with immediate optimization. |
|
| 1237 | 1248 | unsafe fn selectCmp( |
|
| 1238 | 1249 | s: &mut Selector, |
|
| 1239 | 1250 | typ: il::Type, |
|
| 1240 | 1251 | rd: gen::Reg, |
|
| 1241 | 1252 | rs1: gen::Reg, |
|
| 1242 | 1253 | b: il::Val, |
|
| 1243 | 1254 | op: CmpOp, |
|
| 1244 | - | invert: bool, |
|
| 1245 | - | scratch: gen::Reg |
|
| 1246 | - | ) { |
|
| 1255 | + | invert: bool |
|
| 1256 | + | ) throws (super::Error) { |
|
| 1247 | 1257 | let mut signed = false; |
|
| 1248 | 1258 | if let case CmpOp::Slt = op { |
|
| 1249 | 1259 | set signed = true; |
|
| 1250 | 1260 | } |
|
| 1251 | 1261 | let useSext = cmpUsesSext(typ, signed); |
|
| 1252 | - | emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 1262 | + | try emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 1253 | 1263 | ||
| 1254 | 1264 | // Canonicalizing the immediate can expose an immediate instruction even |
|
| 1255 | 1265 | // when the IL value used a different representation for the same width. |
|
| 1256 | 1266 | let mut rhs = b; |
|
| 1257 | 1267 | if let case il::Val::Imm(imm) = b { |
|
| 1258 | 1268 | let canonical = canonicalCmpImm(imm, typ, useSext); |
|
| 1259 | 1269 | set rhs = il::Val::Imm(canonical); |
|
| 1260 | 1270 | if encode::isSmallImm64(canonical) { |
|
| 1261 | 1271 | let simm = canonical as i32; |
|
| 1262 | 1272 | match op { |
|
| 1263 | - | case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, simm)), |
|
| 1264 | - | case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, simm)), |
|
| 1273 | + | case CmpOp::Slt => try emit::emit(s.e, encode::slti(rd, rs1, simm)), |
|
| 1274 | + | case CmpOp::Ult => try emit::emit(s.e, encode::sltiu(rd, rs1, simm)), |
|
| 1265 | 1275 | } |
|
| 1266 | 1276 | if invert { |
|
| 1267 | - | emit::emit(s.e, encode::xori(rd, rd, 1)); |
|
| 1277 | + | try emit::emit(s.e, encode::xori(rd, rd, 1)); |
|
| 1268 | 1278 | } |
|
| 1269 | 1279 | return; |
|
| 1270 | 1280 | } |
|
| 1271 | 1281 | } |
|
| 1272 | 1282 | ||
| 1273 | - | let rs2 = resolveVal(s, scratch, rhs); |
|
| 1283 | + | let rs2 = try resolveVal(s, super::SCRATCH2, rhs); |
|
| 1274 | 1284 | if not isExtendedImm(rhs, typ, useSext) { |
|
| 1275 | - | emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 1285 | + | try emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 1276 | 1286 | } |
|
| 1277 | 1287 | match op { |
|
| 1278 | - | case CmpOp::Slt => emit::emit(s.e, encode::slt(rd, rs1, rs2)), |
|
| 1279 | - | case CmpOp::Ult => emit::emit(s.e, encode::sltu(rd, rs1, rs2)), |
|
| 1288 | + | case CmpOp::Slt => try emit::emit(s.e, encode::slt(rd, rs1, rs2)), |
|
| 1289 | + | case CmpOp::Ult => try emit::emit(s.e, encode::sltu(rd, rs1, rs2)), |
|
| 1280 | 1290 | } |
|
| 1281 | 1291 | if invert { |
|
| 1282 | - | emit::emit(s.e, encode::xori(rd, rd, 1)); |
|
| 1292 | + | try emit::emit(s.e, encode::xori(rd, rd, 1)); |
|
| 1283 | 1293 | } |
|
| 1284 | 1294 | } |
lib/std/arch/rv64/tests.rad
+122 -2
| 4 | 4 | //! by comparing against known-good values. |
|
| 5 | 5 | ||
| 6 | 6 | use std::testing; |
|
| 7 | 7 | use std::lang::alloc; |
|
| 8 | 8 | use std::lang::il; |
|
| 9 | + | use std::lang::gen::data; |
|
| 9 | 10 | use std::lang::gen::bitset; |
|
| 10 | 11 | use std::lang::gen::regalloc; |
|
| 11 | 12 | use std::lang::gen::regalloc::liveness; |
|
| 13 | + | use std::arch::rv64; |
|
| 12 | 14 | use std::collections::dict; |
|
| 13 | 15 | ||
| 14 | 16 | use super::encode; |
|
| 15 | 17 | use super::asm; |
|
| 16 | 18 | ||
| 19 | + | /// Reusable emitter storage. |
|
| 17 | 20 | static ASSEMBLY_ARENA_STORAGE: [u8; 16777216] = [0; 16777216]; |
|
| 18 | 21 | static ASSEMBLY_TEXT_STORAGE: [u32; 2] = [0; 2]; |
|
| 19 | 22 | ||
| 20 | 23 | /// Scratch storage for register limit checks. |
|
| 21 | 24 | static REGISTER_SCRATCH: [u8; 65536] = [0; 65536]; |
| 73 | 76 | section: asm::Section::Text, |
|
| 74 | 77 | offset: super::INSTR_SIZE, |
|
| 75 | 78 | isExported: true, |
|
| 76 | 79 | }; |
|
| 77 | 80 | ||
| 78 | - | let mut generator = super::beginProgram( |
|
| 81 | + | let mut generator = try! super::beginProgram( |
|
| 79 | 82 | super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false }, |
|
| 80 | 83 | &mut arena |
|
| 81 | 84 | ); |
|
| 82 | - | super::addAssembly( |
|
| 85 | + | try! super::addAssembly( |
|
| 83 | 86 | &mut generator, |
|
| 84 | 87 | asm::Program { |
|
| 85 | 88 | text: &ASSEMBLY_TEXT_STORAGE[..], |
|
| 86 | 89 | data: &[], |
|
| 87 | 90 | symbols: symbolSlice, |
| 611 | 614 | try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch { |
|
| 612 | 615 | set failed = true; |
|
| 613 | 616 | }; |
|
| 614 | 617 | assert failed; |
|
| 615 | 618 | } |
|
| 619 | + | ||
| 620 | + | /// Unresolved calls preserve their placeholder instructions and report a symbol error. |
|
| 621 | + | @test unsafe fn testMissingCallSymbol() throws (testing::TestError) { |
|
| 622 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 623 | + | let mut e = try! rv64::emit::emitter(&mut arena, false); |
|
| 624 | + | try! rv64::emit::recordCall(&mut e, "missing"); |
|
| 625 | + | let mut failed = false; |
|
| 626 | + | try rv64::emit::patchCalls(&mut e) catch error { |
|
| 627 | + | assert error == rv64::Error::Symbol; |
|
| 628 | + | set failed = true; |
|
| 629 | + | }; |
|
| 630 | + | assert failed; |
|
| 631 | + | assert e.code[0] == encode::nop(); |
|
| 632 | + | assert e.code[1] == encode::nop(); |
|
| 633 | + | } |
|
| 634 | + | ||
| 635 | + | /// Local jumps outside their immediate range preserve the reserved instruction. |
|
| 636 | + | @test unsafe fn testLocalJumpRange() throws (testing::TestError) { |
|
| 637 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 638 | + | let mut e = try! rv64::emit::emitter(&mut arena, false); |
|
| 639 | + | try! rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump); |
|
| 640 | + | set e.labels.blockOffsets[0] = 0x200000; |
|
| 641 | + | set e.labels.blockCount = 1; |
|
| 642 | + | let mut failed = false; |
|
| 643 | + | try rv64::emit::patchLocalBranches(&mut e) catch error { |
|
| 644 | + | assert error == rv64::Error::Relocation; |
|
| 645 | + | set failed = true; |
|
| 646 | + | }; |
|
| 647 | + | assert failed; |
|
| 648 | + | assert e.code[0] == encode::nop(); |
|
| 649 | + | } |
|
| 650 | + | ||
| 651 | + | /// Assembly jumps reject targets beyond the J-type displacement range. |
|
| 652 | + | @test unsafe fn testAssemblyJumpRange() throws (testing::TestError) { |
|
| 653 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 654 | + | let mut e = try! rv64::emit::emitter(&mut arena, false); |
|
| 655 | + | try! rv64::emit::emit(&mut e, encode::nop()); |
|
| 656 | + | try! rv64::emit::recordJumpAt(&mut e, "far", rv64::ZERO, 0); |
|
| 657 | + | try! rv64::emit::recordFuncOffsetAt(&mut e, "far", 0x80000); |
|
| 658 | + | let mut failed = false; |
|
| 659 | + | try rv64::emit::patchJumps(&mut e) catch error { |
|
| 660 | + | assert error == rv64::Error::Relocation; |
|
| 661 | + | set failed = true; |
|
| 662 | + | }; |
|
| 663 | + | assert failed; |
|
| 664 | + | assert e.code[0] == encode::nop(); |
|
| 665 | + | } |
|
| 666 | + | ||
| 667 | + | /// Missing data symbols preserve address-load placeholders. |
|
| 668 | + | @test unsafe fn testMissingDataSymbol() throws (testing::TestError) { |
|
| 669 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 670 | + | let mut e = try! rv64::emit::emitter(&mut arena, false); |
|
| 671 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 672 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 673 | + | try! rv64::emit::recordDataAddrLoad(&mut e, "missing", rv64::A0); |
|
| 674 | + | let mut failed = false; |
|
| 675 | + | try rv64::emit::patchAddrLoads(&mut e, &map) catch error { |
|
| 676 | + | assert error == rv64::Error::Symbol; |
|
| 677 | + | set failed = true; |
|
| 678 | + | }; |
|
| 679 | + | assert failed; |
|
| 680 | + | assert e.code[0] == encode::nop(); |
|
| 681 | + | assert e.code[1] == encode::nop(); |
|
| 682 | + | } |
|
| 683 | + | ||
| 684 | + | /// Data addresses outside the supported signed address range are rejected. |
|
| 685 | + | @test unsafe fn testDataAddressRange() throws (testing::TestError) { |
|
| 686 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 687 | + | let mut e = try! rv64::emit::emitter(&mut arena, false); |
|
| 688 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 689 | + | static symbols: [data::DataSym; 1] = [data::DataSym { name: "far", addr: 0x80000000 }]; |
|
| 690 | + | let map = try! data::buildMap(&symbols[..], &mut entries[..]); |
|
| 691 | + | try! rv64::emit::recordDataAddrLoad(&mut e, "far", rv64::A0); |
|
| 692 | + | let mut failed = false; |
|
| 693 | + | try rv64::emit::patchAddrLoads(&mut e, &map) catch error { |
|
| 694 | + | assert error == rv64::Error::Relocation; |
|
| 695 | + | set failed = true; |
|
| 696 | + | }; |
|
| 697 | + | assert failed; |
|
| 698 | + | assert e.code[0] == encode::nop(); |
|
| 699 | + | assert e.code[1] == encode::nop(); |
|
| 700 | + | } |
|
| 701 | + | ||
| 702 | + | /// Data initializers report unresolved symbols before writing the slot. |
|
| 703 | + | @test unsafe fn testDataEmissionSymbol() throws (testing::TestError) { |
|
| 704 | + | let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]); |
|
| 705 | + | let e = try! rv64::emit::emitter(&mut arena, false); |
|
| 706 | + | static entries: [dict::Entry; 4] = undefined; |
|
| 707 | + | let map = try! data::buildMap(&[], &mut entries[..]); |
|
| 708 | + | let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false, |
|
| 709 | + | values: &[il::DataValue { item: il::DataItem::Sym("missing"), count: 1 }] }]; |
|
| 710 | + | let mut bytes = [0xaa as u8; 8]; |
|
| 711 | + | let mut failed = false; |
|
| 712 | + | try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error { |
|
| 713 | + | assert error == data::Error::Symbol; |
|
| 714 | + | set failed = true; |
|
| 715 | + | }; |
|
| 716 | + | assert failed; |
|
| 717 | + | assert bytes[0] == 0xaa; |
|
| 718 | + | } |
|
| 719 | + | ||
| 720 | + | /// Initialized and zero-filled data start at the requested alignment. |
|
| 721 | + | @test unsafe fn testDataAlignment() throws (testing::TestError) { |
|
| 722 | + | for zeroInit in [false, true] { |
|
| 723 | + | let items = [il::Data { |
|
| 724 | + | name: "aligned", size: 8, alignment: 8, readOnly: true, |
|
| 725 | + | isZeroInit: zeroInit, values: &[], |
|
| 726 | + | }]; |
|
| 727 | + | let mut symbols: [data::DataSym; 1] = undefined; |
|
| 728 | + | let mut count: u32 = 0; |
|
| 729 | + | let size = try! data::layoutSectionAtOffset( |
|
| 730 | + | &items[..], &mut symbols[..], &mut count, 16, 1, true |
|
| 731 | + | ); |
|
| 732 | + | assert size == 16 and count == 1; |
|
| 733 | + | assert symbols[0].addr == 24; |
|
| 734 | + | } |
|
| 735 | + | } |
lib/std/collections/dict.rad
+13 -4
| 3 | 3 | //! Uses DJB2 hashing with linear probing. The table size must be a power |
|
| 4 | 4 | //! of two and is provided by the caller via arena-allocated storage. |
|
| 5 | 5 | ||
| 6 | 6 | use std::mem; |
|
| 7 | 7 | ||
| 8 | + | /// Dictionary insertion failure. |
|
| 9 | + | export union Error: Copy { |
|
| 10 | + | /// The table has reached its supported load. |
|
| 11 | + | Capacity, |
|
| 12 | + | } |
|
| 13 | + | ||
| 8 | 14 | /// Hash map entry. |
|
| 9 | 15 | export record Entry: Copy { |
|
| 10 | 16 | /// Key. |
|
| 11 | 17 | key: *[u8], |
|
| 12 | 18 | /// Associated value. |
| 20 | 26 | /// Number of occupied entries. |
|
| 21 | 27 | count: u32, |
|
| 22 | 28 | } |
|
| 23 | 29 | ||
| 24 | 30 | /// Create a dict backed by the given entry storage. |
|
| 25 | - | /// The storage length must be a power of two. |
|
| 31 | + | /// The storage length must be a power of two and at least two. |
|
| 26 | 32 | export fn init(entries: *mut [Entry]) -> Dict { |
|
| 27 | 33 | for i in 0..entries.len { |
|
| 28 | 34 | set entries[i] = Entry { key: &[], value: 0 }; |
|
| 29 | 35 | } |
|
| 30 | 36 | return Dict { entries, count: 0 }; |
|
| 31 | 37 | } |
|
| 32 | 38 | ||
| 33 | - | /// Insert or update a key-value pair. Panics if the table exceeds 50% load. |
|
| 34 | - | export fn insert(m: &mut Dict, key: *[u8], value: i32) { |
|
| 39 | + | /// Insert or update a key-value pair. Throws if the table exceeds 50% load. |
|
| 40 | + | /// The key must not be empty. |
|
| 41 | + | export fn insert(m: &mut Dict, key: *[u8], value: i32) throws (Error) { |
|
| 35 | 42 | let mask = m.entries.len - 1; |
|
| 36 | 43 | let mut idx = hash(key) & mask; |
|
| 37 | 44 | ||
| 38 | 45 | loop { |
|
| 39 | 46 | let entry = &m.entries[idx]; |
|
| 40 | 47 | if entry.key.len == 0 { |
|
| 41 | - | assert m.count < m.entries.len / 2, "dict::insert: table full"; |
|
| 48 | + | if m.count >= m.entries.len / 2 { |
|
| 49 | + | throw Error::Capacity; |
|
| 50 | + | } |
|
| 42 | 51 | set m.entries[idx] = Entry { key, value }; |
|
| 43 | 52 | set m.count += 1; |
|
| 44 | 53 | return; |
|
| 45 | 54 | } |
|
| 46 | 55 | if mem::eq(entry.key, key) { |
lib/std/lang/gen/data.rad
+63 -32
| 4 | 4 | //! serializing initialized data into binary sections. |
|
| 5 | 5 | ||
| 6 | 6 | use std::mem; |
|
| 7 | 7 | use std::collections::dict; |
|
| 8 | 8 | use std::lang::il; |
|
| 9 | + | use std::lang::parser; |
|
| 9 | 10 | use std::lang::gen::labels; |
|
| 10 | 11 | ||
| 11 | 12 | /// Maximum number of data symbols. |
|
| 12 | 13 | export constant MAX_DATA_SYMS: u32 = 8192; |
|
| 13 | 14 | ||
| 14 | 15 | /// Size of the data symbol hash table. Must be a power of two |
|
| 15 | 16 | /// and at least twice the size of [`MAX_DATA_SYMS`]. |
|
| 16 | 17 | export constant DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2; |
|
| 17 | 18 | ||
| 19 | + | /// Data section layout or emission failure. |
|
| 20 | + | export union Error: Copy { |
|
| 21 | + | /// A required symbol is missing. |
|
| 22 | + | Symbol, |
|
| 23 | + | /// A symbol table or output buffer is full. |
|
| 24 | + | Capacity, |
|
| 25 | + | /// A size or address exceeds the supported range. |
|
| 26 | + | Overflow, |
|
| 27 | + | } |
|
| 28 | + | ||
| 18 | 29 | /// Data symbol entry mapping name to address. |
|
| 19 | 30 | export record DataSym: Copy { |
|
| 20 | 31 | /// Symbol name. |
|
| 21 | 32 | name: *[u8], |
|
| 22 | 33 | /// Absolute address, including data base address. |
| 39 | 50 | items: &[il::Data], |
|
| 40 | 51 | syms: &mut [DataSym], |
|
| 41 | 52 | count: &mut u32, |
|
| 42 | 53 | base: u32, |
|
| 43 | 54 | readOnly: bool |
|
| 44 | - | ) -> u32 { |
|
| 45 | - | return layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 55 | + | ) -> u32 throws (Error) { |
|
| 56 | + | return try layoutSectionAtOffset(items, syms, count, base, 0, readOnly); |
|
| 46 | 57 | } |
|
| 47 | 58 | ||
| 48 | 59 | /// Lay out data symbols for a single section starting at [`startOffset`]. |
|
| 49 | 60 | export fn layoutSectionAtOffset( |
|
| 50 | 61 | items: &[il::Data], |
|
| 51 | 62 | syms: &mut [DataSym], |
|
| 52 | 63 | count: &mut u32, |
|
| 53 | 64 | base: u32, |
|
| 54 | 65 | startOffset: u32, |
|
| 55 | 66 | readOnly: bool |
|
| 56 | - | ) -> u32 { |
|
| 67 | + | ) -> u32 throws (Error) { |
|
| 57 | 68 | let mut offset: u32 = startOffset; |
|
| 58 | 69 | ||
| 59 | 70 | // Data requiring sidecar image bytes first. |
|
| 60 | - | for i in 0..items.len { |
|
| 61 | - | let data = items[i]; |
|
| 62 | - | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 63 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 64 | - | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 65 | - | set *count += 1; |
|
| 66 | - | set offset += data.size; |
|
| 67 | - | } |
|
| 68 | - | } |
|
| 69 | 71 | // Zero-initialized data after. |
|
| 70 | - | for i in 0..items.len { |
|
| 71 | - | let data = items[i]; |
|
| 72 | - | if data.readOnly == readOnly and data.isZeroInit { |
|
| 73 | - | set offset = mem::alignUp(offset, data.alignment); |
|
| 74 | - | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 75 | - | set *count += 1; |
|
| 76 | - | set offset += data.size; |
|
| 72 | + | for zeroInit in [false, true] { |
|
| 73 | + | for data in items { |
|
| 74 | + | if data.readOnly == readOnly and data.isZeroInit == zeroInit { |
|
| 75 | + | let aligned = try alignOffset(offset, data.alignment); |
|
| 76 | + | if base as u64 + aligned as u64 + data.size as u64 > parser::U32_MAX as u64 { |
|
| 77 | + | throw Error::Overflow; |
|
| 78 | + | } |
|
| 79 | + | if *count >= syms.len { |
|
| 80 | + | throw Error::Capacity; |
|
| 81 | + | } |
|
| 82 | + | set syms[*count] = DataSym { name: data.name, addr: base + aligned }; |
|
| 83 | + | set *count += 1; |
|
| 84 | + | set offset = aligned + data.size; |
|
| 85 | + | } |
|
| 77 | 86 | } |
|
| 78 | 87 | } |
|
| 79 | 88 | return offset; |
|
| 80 | 89 | } |
|
| 81 | 90 | ||
| 91 | + | /// Align a section offset without unsigned overflow. |
|
| 92 | + | fn alignOffset(offset: u32, alignment: u32) -> u32 throws (Error) { |
|
| 93 | + | let aligned = mem::checkedAlignUp(offset, alignment) else { |
|
| 94 | + | throw Error::Overflow; |
|
| 95 | + | }; |
|
| 96 | + | return aligned; |
|
| 97 | + | } |
|
| 98 | + | ||
| 82 | 99 | /// Emit data bytes for a single section (read-only or read-write) into `buf`. |
|
| 83 | 100 | /// Iterates data requiring sidecar image bytes, serializing each data item. |
|
| 84 | 101 | /// Returns the total number of bytes written. |
|
| 85 | 102 | export unsafe fn emitSection( |
|
| 86 | 103 | items: &[il::Data], |
|
| 87 | 104 | dataSymMap: &DataSymMap, |
|
| 88 | 105 | fnLabels: &labels::Labels, |
|
| 89 | 106 | codeBase: u32, |
|
| 90 | 107 | buf: &mut [u8], |
|
| 91 | 108 | readOnly: bool |
|
| 92 | - | ) -> u32 { |
|
| 93 | - | return emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0); |
|
| 109 | + | ) -> u32 throws (Error) { |
|
| 110 | + | return try emitSectionAtOffset(items, dataSymMap, fnLabels, codeBase, buf, readOnly, 0); |
|
| 94 | 111 | } |
|
| 95 | 112 | ||
| 96 | 113 | /// Emit data bytes for a single section starting at `startOffset`. |
|
| 97 | 114 | export unsafe fn emitSectionAtOffset( |
|
| 98 | 115 | items: &[il::Data], |
| 100 | 117 | fnLabels: &labels::Labels, |
|
| 101 | 118 | codeBase: u32, |
|
| 102 | 119 | buf: &mut [u8], |
|
| 103 | 120 | readOnly: bool, |
|
| 104 | 121 | startOffset: u32 |
|
| 105 | - | ) -> u32 { |
|
| 122 | + | ) -> u32 throws (Error) { |
|
| 123 | + | if startOffset > buf.len { |
|
| 124 | + | throw Error::Capacity; |
|
| 125 | + | } |
|
| 106 | 126 | let mut offset: u32 = startOffset; |
|
| 107 | 127 | ||
| 108 | 128 | for i in 0..items.len { |
|
| 109 | 129 | let data = items[i]; |
|
| 110 | 130 | 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"; |
|
| 131 | + | let aligned = try alignOffset(offset, data.alignment); |
|
| 132 | + | if aligned > buf.len or data.size > buf.len - aligned { |
|
| 133 | + | throw Error::Capacity; |
|
| 134 | + | } |
|
| 135 | + | set offset = aligned; |
|
| 113 | 136 | for j in 0..data.values.len { |
|
| 114 | 137 | let v = &data.values[j]; |
|
| 115 | 138 | for _ in 0..v.count { |
|
| 116 | 139 | match v.item { |
|
| 117 | 140 | case il::DataItem::Val { typ, val } => { |
| 120 | 143 | ||
| 121 | 144 | set offset += size; |
|
| 122 | 145 | }, |
|
| 123 | 146 | case il::DataItem::Sym(name) => { |
|
| 124 | 147 | let addr = lookupAddr(dataSymMap, name) else { |
|
| 125 | - | panic "emitSectionAtOffset: data symbol not found"; |
|
| 148 | + | throw Error::Symbol; |
|
| 126 | 149 | }; |
|
| 127 | 150 | let addr64: u64 = addr as u64; |
|
| 128 | - | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 151 | + | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, @sizeOf(u64))); |
|
| 129 | 152 | ||
| 130 | 153 | set offset += @sizeOf(u64); |
|
| 131 | 154 | }, |
|
| 132 | 155 | case il::DataItem::Fn(name) => { |
|
| 133 | - | let addr = codeBase + labels::funcOffset(fnLabels, name) as u32; |
|
| 134 | - | let addr64: u64 = addr as u64; |
|
| 135 | - | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, 8)); |
|
| 156 | + | let functionOffset = dict::get(&fnLabels.funcs, name) else { |
|
| 157 | + | throw Error::Symbol; |
|
| 158 | + | }; |
|
| 159 | + | if codeBase as u64 + functionOffset as u64 > parser::U32_MAX as u64 { |
|
| 160 | + | throw Error::Overflow; |
|
| 161 | + | } |
|
| 162 | + | let addr64 = codeBase as u64 + functionOffset as u64; |
|
| 163 | + | try! mem::copy(&mut buf[offset..], @sliceOf(&addr64 as &u8, @sizeOf(u64))); |
|
| 136 | 164 | ||
| 137 | 165 | set offset += @sizeOf(*u8); |
|
| 138 | 166 | }, |
|
| 139 | 167 | case il::DataItem::Str(s) => { |
|
| 140 | 168 | try! mem::copy(&mut buf[offset..], s); |
| 151 | 179 | } |
|
| 152 | 180 | return offset; |
|
| 153 | 181 | } |
|
| 154 | 182 | ||
| 155 | 183 | /// 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 { |
|
| 184 | + | /// The table length must be a power of two and at least two. |
|
| 185 | + | /// Throws if the symbol count exceeds half the table length. |
|
| 186 | + | export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap throws (Error) { |
|
| 158 | 187 | let mut d = dict::init(entries); |
|
| 159 | 188 | for i in 0..syms.len { |
|
| 160 | - | dict::insert(&mut d, syms[i].name, syms[i].addr as i32); |
|
| 189 | + | try dict::insert(&mut d, syms[i].name, syms[i].addr as i32) catch { |
|
| 190 | + | throw Error::Capacity; |
|
| 191 | + | }; |
|
| 161 | 192 | } |
|
| 162 | 193 | return DataSymMap { dict: d, syms }; |
|
| 163 | 194 | } |
|
| 164 | 195 | ||
| 165 | 196 | /// Resolve a data symbol to its final absolute address using the hash map. |
lib/std/lang/gen/labels.rad
+10 -2
| 9 | 9 | /// Maximum number of functions. |
|
| 10 | 10 | export constant MAX_FUNCS: u32 = 8192; |
|
| 11 | 11 | /// Size of the function hash table. Must be a power of two. |
|
| 12 | 12 | export constant FUNC_TABLE_SIZE: u32 = MAX_FUNCS * 2; |
|
| 13 | 13 | ||
| 14 | + | /// Block label storage failure. |
|
| 15 | + | export union Error: Copy { |
|
| 16 | + | /// The block index exceeds the label storage. |
|
| 17 | + | Capacity, |
|
| 18 | + | } |
|
| 19 | + | ||
| 14 | 20 | /// Label tracking for code emission. |
|
| 15 | 21 | export record Labels { |
|
| 16 | 22 | /// Block offsets indexed by block index. |
|
| 17 | 23 | /// Per-function, reset each function. |
|
| 18 | 24 | blockOffsets: *mut [i32], |
| 35 | 41 | export fn resetBlocks(l: &mut Labels) { |
|
| 36 | 42 | set l.blockCount = 0; |
|
| 37 | 43 | } |
|
| 38 | 44 | ||
| 39 | 45 | /// Record a block's code offset by its index. O(1). |
|
| 40 | - | export fn recordBlock(l: &mut Labels, blockIdx: u32, offset: i32) { |
|
| 41 | - | assert blockIdx < l.blockOffsets.len, "recordBlock: block index out of range"; |
|
| 46 | + | export fn recordBlock(l: &mut Labels, blockIdx: u32, offset: i32) throws (Error) { |
|
| 47 | + | if blockIdx >= l.blockOffsets.len { |
|
| 48 | + | throw Error::Capacity; |
|
| 49 | + | } |
|
| 42 | 50 | set l.blockOffsets[blockIdx] = offset; |
|
| 43 | 51 | set l.blockCount += 1; |
|
| 44 | 52 | } |
|
| 45 | 53 | ||
| 46 | 54 | /// Look up a block's byte offset by index. O(1). |
lib/std/lang/lower.rad
+9 -6
| 109 | 109 | //////////////////// |
|
| 110 | 110 | ||
| 111 | 111 | /// Lowering errors are typically unrecoverable since they indicate bugs in |
|
| 112 | 112 | /// the resolver or malformed AST that should have been caught earlier. |
|
| 113 | 113 | export union LowerError: Copy { |
|
| 114 | + | /// The function sink reported an output failure. |
|
| 115 | + | OutputFailed, |
|
| 114 | 116 | /// A node's symbol was not set before lowering. |
|
| 115 | 117 | MissingSymbol(*ast::Node), |
|
| 116 | 118 | /// A node's type was not set before lowering. |
|
| 117 | 119 | MissingType(*ast::Node), |
|
| 118 | 120 | /// A node's constant value was not set before lowering. |
| 167 | 169 | } |
|
| 168 | 170 | ||
| 169 | 171 | /// Print a LowerError for debugging. |
|
| 170 | 172 | export fn printError(err: LowerError) { |
|
| 171 | 173 | match err { |
|
| 174 | + | case LowerError::OutputFailed => io::print("OutputFailed"), |
|
| 172 | 175 | case LowerError::MissingSymbol(_) => io::print("MissingSymbol"), |
|
| 173 | 176 | case LowerError::MissingType(_) => io::print("MissingType"), |
|
| 174 | 177 | case LowerError::MissingConst(_) => io::print("MissingConst"), |
|
| 175 | 178 | case LowerError::ExpectedOptional => io::print("ExpectedOptional"), |
|
| 176 | 179 | case LowerError::ExpectedRecord => io::print("ExpectedRecord"), |
| 268 | 271 | /// Function sink used by lowerers that consume functions as they are produced. |
|
| 269 | 272 | export record FnSink: Copy { |
|
| 270 | 273 | /// Opaque context. It must remain valid for every sink callback. |
|
| 271 | 274 | ctx: *unsafe mut opaque, |
|
| 272 | 275 | /// Callback invoked for each lowered function. |
|
| 273 | - | emitFn: unsafe fn(*unsafe mut opaque, *unsafe il::Fn, FnRole), |
|
| 276 | + | emitFn: unsafe fn(*unsafe mut opaque, *unsafe il::Fn, FnRole) throws (LowerError), |
|
| 274 | 277 | } |
|
| 275 | 278 | ||
| 276 | 279 | /// Destination for functions produced by the lowerer. |
|
| 277 | 280 | export union FnOutput: Copy { |
|
| 278 | 281 | /// Store lowered functions in the provided slice. |
| 356 | 359 | ||
| 357 | 360 | return tag; |
|
| 358 | 361 | } |
|
| 359 | 362 | ||
| 360 | 363 | /// Emit one function to the active output. |
|
| 361 | - | unsafe fn emitFunction(self: &mut Lowerer, func: *unsafe il::Fn, role: FnRole) { |
|
| 364 | + | unsafe fn emitFunction(self: &mut Lowerer, func: *unsafe il::Fn, role: FnRole) throws (LowerError) { |
|
| 362 | 365 | match self.output { |
|
| 363 | 366 | case FnOutput::Accumulate(accumulated) => { |
|
| 364 | 367 | let mut fns = accumulated; |
|
| 365 | 368 | try! fns.append(func, self.allocator); |
|
| 366 | 369 | set self.output = FnOutput::Accumulate(fns); |
|
| 367 | 370 | } |
|
| 368 | 371 | case FnOutput::Stream(sink) => { |
|
| 369 | - | sink.emitFn(sink.ctx, func, role); |
|
| 372 | + | try sink.emitFn(sink.ctx, func, role); |
|
| 370 | 373 | } |
|
| 371 | 374 | } |
|
| 372 | 375 | } |
|
| 373 | 376 | ||
| 374 | 377 | /// Get the function role for a top-level function declaration. |
| 831 | 834 | for node in stmtsList { |
|
| 832 | 835 | match node.value { |
|
| 833 | 836 | case ast::NodeValue::FnDecl(decl) => { |
|
| 834 | 837 | if let f = try lowerFnDecl(low, node, decl) { |
|
| 835 | 838 | let role = lowerFnRole(isRoot, decl.attrs); |
|
| 836 | - | emitFunction(low, f, role); |
|
| 839 | + | try emitFunction(low, f, role); |
|
| 837 | 840 | } |
|
| 838 | 841 | } |
|
| 839 | 842 | case ast::NodeValue::ConstDecl(decl) => { |
|
| 840 | 843 | try lowerDataDecl(low, node, decl.value, true); |
|
| 841 | 844 | } |
| 845 | 848 | case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 846 | 849 | try lowerInstanceDecl(low, node, traitName, targetType, methods); |
|
| 847 | 850 | } |
|
| 848 | 851 | case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, .. } => { |
|
| 849 | 852 | if let f = try lowerMethodDecl(low, node, name, receiverName, sig, body) { |
|
| 850 | - | emitFunction(low, f, FnRole::Normal); |
|
| 853 | + | try emitFunction(low, f, FnRole::Normal); |
|
| 851 | 854 | } |
|
| 852 | 855 | } |
|
| 853 | 856 | else => {}, |
|
| 854 | 857 | } |
|
| 855 | 858 | } |
| 1114 | 1117 | throw LowerError::ExpectedIdentifier; |
|
| 1115 | 1118 | }; |
|
| 1116 | 1119 | let qualName = instanceMethodName(self, nil, typeName, mName); |
|
| 1117 | 1120 | let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body) |
|
| 1118 | 1121 | else continue; |
|
| 1119 | - | emitFunction(self, func, FnRole::Normal); |
|
| 1122 | + | try emitFunction(self, func, FnRole::Normal); |
|
| 1120 | 1123 | ||
| 1121 | 1124 | let method = resolver::findTraitMethod(traitInfo, mName) |
|
| 1122 | 1125 | else panic "lowerInstanceDecl: method not found in trait"; |
|
| 1123 | 1126 | ||
| 1124 | 1127 | set methodNames[method.index] = qualName; |