compiler: Select instructions from owned regional IL
25d7de6e0dca5ab7787de21dbb32824da800579cf3178af6701b9188e21f90b1
1 parent
c69442cd
lib/std/arch/rv64.rad
+1 -1
| 311 | 311 | config: ®alloc::TargetConfig, |
|
| 312 | 312 | storage: &Session 'scratch |
|
| 313 | 313 | ) throws (alloc::AllocError) { |
|
| 314 | 314 | let view = try il::published::publish(func, storage); |
|
| 315 | 315 | let ralloc = try regalloc::allocate(&view, config, storage); |
|
| 316 | - | isel::selectFn(&mut generator.e, &ralloc, func, &view); |
|
| 316 | + | isel::selectFn(&mut generator.e, &ralloc, &view); |
|
| 317 | 317 | } |
|
| 318 | 318 | ||
| 319 | 319 | /// Record an alternate name for the next function emitted. |
|
| 320 | 320 | export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) { |
|
| 321 | 321 | let codeLen = generator.e.codeLen; |
lib/std/arch/rv64/bounds.rad
+51 -4
| 14 | 14 | use super::isel; |
|
| 15 | 15 | ||
| 16 | 16 | /// Branch forms whose taken edge supplies block parameters. |
|
| 17 | 17 | union ParameterEdge: Copy { Jump, Then, Else, Case, Default } |
|
| 18 | 18 | ||
| 19 | - | /// Selection uses published parameter definitions after source tables change. |
|
| 19 | + | /// Selection owns parameter definitions, branch instructions, and edge arguments. |
|
| 20 | 20 | @test unsafe fn publishedParameters() throws (testing::TestError) { |
|
| 21 | 21 | for edge in [ParameterEdge::Jump, ParameterEdge::Then, ParameterEdge::Else, |
|
| 22 | 22 | ParameterEdge::Case, ParameterEdge::Default] |
|
| 23 | 23 | { |
|
| 24 | 24 | let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
| 57 | 57 | let mut gen = generator(&mut code); |
|
| 58 | 58 | use arena as storage in { |
|
| 59 | 59 | let view = try! il::published::publish(&func, &storage); |
|
| 60 | 60 | let config = super::targetConfig(); |
|
| 61 | 61 | let allocation = try! regalloc::allocate(&view, &config, &storage); |
|
| 62 | - | isel::selectFn(&mut gen.e, &allocation, &func, &view); |
|
| 62 | + | isel::selectFn(&mut gen.e, &allocation, &view); |
|
| 63 | 63 | assert gen.e.error == nil; |
|
| 64 | 64 | let expected = try! storage.copy(&gen.e.code[..gen.e.codeLen]); |
|
| 65 | 65 | set params[0].value.n = 0xffffffff; |
|
| 66 | 66 | set targetParams[0].value.n = 0xffffffff; |
|
| 67 | + | set args[0] = il::Val::Imm(42); |
|
| 68 | + | set cases[0].value = 99; |
|
| 69 | + | set entry[0] = il::Instr::Unreachable; |
|
| 70 | + | set target[0] = il::Instr::Ret { val: il::Val::Imm(99) }; |
|
| 67 | 71 | alloc::reset(&mut code); |
|
| 68 | 72 | set gen = generator(&mut code); |
|
| 69 | - | isel::selectFn(&mut gen.e, &allocation, &func, &view); |
|
| 73 | + | isel::selectFn(&mut gen.e, &allocation, &view); |
|
| 70 | 74 | assert gen.e.error == nil and gen.e.codeLen == expected.len; |
|
| 71 | 75 | for word, i in expected { |
|
| 72 | 76 | assert gen.e.code[i] == word; |
|
| 73 | 77 | } |
|
| 74 | 78 | } |
| 76 | 80 | } |
|
| 77 | 81 | ||
| 78 | 82 | /// Reusable emitter allocation storage. |
|
| 79 | 83 | static MEMORY: [u8; 16777216] = [0; 16777216]; |
|
| 80 | 84 | ||
| 85 | + | /// Selection owns call arguments and instructions used to size the stack frame. |
|
| 86 | + | @test unsafe fn publishedCalls() throws (testing::TestError) { |
|
| 87 | + | for indirect in [false, true] { |
|
| 88 | + | let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
|
| 89 | + | let mut args = [il::Val::Imm(7), il::Val::Reg(il::Reg { n: 1 })]; |
|
| 90 | + | let callee = il::Val::Reg(il::Reg { n: 0 }) if indirect else il::Val::FnAddr("callee"); |
|
| 91 | + | let mut instructions = [ |
|
| 92 | + | il::Instr::Reserve { dst: il::Reg { n: 1 }, size: il::Val::Imm(16), alignment: 8 }, |
|
| 93 | + | il::Instr::Call { retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: callee, |
|
| 94 | + | args: &args[..] }, |
|
| 95 | + | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) }, |
|
| 96 | + | ]; |
|
| 97 | + | let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..], |
|
| 98 | + | locs: &[], preds: &[], loopDepth: 0 }]; |
|
| 99 | + | let mut func = il::Fn { name: "p::calls", params: ¶ms[..], returnType: il::Type::W64, |
|
| 100 | + | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
|
| 101 | + | let mut arena = alloc::new(&mut SCRATCH[..]); |
|
| 102 | + | let mut code = alloc::new(&mut MEMORY[..]); |
|
| 103 | + | let mut gen = generator(&mut code); |
|
| 104 | + | use arena as storage in { |
|
| 105 | + | let view = try! il::published::publish(&func, &storage); |
|
| 106 | + | let config = super::targetConfig(); |
|
| 107 | + | let allocation = try! regalloc::allocate(&view, &config, &storage); |
|
| 108 | + | isel::selectFn(&mut gen.e, &allocation, &view); |
|
| 109 | + | assert gen.e.error == nil; |
|
| 110 | + | let expected = try! storage.copy(&gen.e.code[..gen.e.codeLen]); |
|
| 111 | + | set args[0] = il::Val::Imm(99); |
|
| 112 | + | set args[1] = il::Val::Imm(100); |
|
| 113 | + | set func.isLeaf = true; |
|
| 114 | + | for i in 0..instructions.len { |
|
| 115 | + | set instructions[i] = il::Instr::Unreachable; |
|
| 116 | + | } |
|
| 117 | + | alloc::reset(&mut code); |
|
| 118 | + | set gen = generator(&mut code); |
|
| 119 | + | isel::selectFn(&mut gen.e, &allocation, &view); |
|
| 120 | + | assert gen.e.error == nil and gen.e.codeLen == expected.len; |
|
| 121 | + | for word, i in expected { |
|
| 122 | + | assert gen.e.code[i] == word; |
|
| 123 | + | } |
|
| 124 | + | } |
|
| 125 | + | } |
|
| 126 | + | } |
|
| 127 | + | ||
| 81 | 128 | /// Selection retains published locations and consecutive-location deduplication. |
|
| 82 | 129 | @test unsafe fn publishedLocations() throws (testing::TestError) { |
|
| 83 | 130 | // Storage for machine instructions, fixups, and source location entries. |
|
| 84 | 131 | static DEBUG_MEMORY: [u8; 33554432] = [0; 33554432]; |
|
| 85 | 132 | let mut locations = [il::SrcLoc { moduleId: 1, offset: 10 }, |
| 104 | 151 | let config = super::targetConfig(); |
|
| 105 | 152 | let allocation = try! regalloc::allocate(&view, &config, &storage); |
|
| 106 | 153 | for i in 0..locations.len { |
|
| 107 | 154 | set locations[i] = il::SrcLoc { moduleId: 9, offset: 99 }; |
|
| 108 | 155 | } |
|
| 109 | - | isel::selectFn(&mut emitter, &allocation, &func, &view); |
|
| 156 | + | isel::selectFn(&mut emitter, &allocation, &view); |
|
| 110 | 157 | assert emitter.error == nil; |
|
| 111 | 158 | let output: 'output = &emitter in { |
|
| 112 | 159 | let entries = emit::getDebugEntries(output); |
|
| 113 | 160 | assert entries.len == 2; |
|
| 114 | 161 | assert entries[0].moduleId == 1 and entries[0].offset == 10; |
lib/std/arch/rv64/isel.rad
+23 -23
| 272 | 272 | isDynamic: bool, |
|
| 273 | 273 | } |
|
| 274 | 274 | ||
| 275 | 275 | /// Add a block's reserve instructions to the accumulated frame requirements. |
|
| 276 | 276 | /// Returns the total size needed for all static reserves, respecting alignment. |
|
| 277 | - | fn computeReserveInfo(instrs: &[il::Instr], previous: ReserveInfo) -> ?ReserveInfo { |
|
| 277 | + | fn computeReserveInfo 'view (instrs: &[il::published::Instruction 'view], previous: ReserveInfo) -> ?ReserveInfo { |
|
| 278 | 278 | let mut offset = previous.size; |
|
| 279 | 279 | let mut isDynamic = previous.isDynamic; |
|
| 280 | 280 | for instr in instrs { |
|
| 281 | - | match instr { |
|
| 281 | + | let case il::published::Code::Fixed(fixed) = il::published::code(&instr) else continue; |
|
| 282 | + | match fixed { |
|
| 282 | 283 | case il::Instr::Reserve { size, alignment, .. } => { |
|
| 283 | 284 | if let case il::Val::Imm(sz) = size { |
|
| 284 | 285 | if alignment == 0 or (alignment & (alignment - 1)) <> 0 or sz < 0 { |
|
| 285 | 286 | return nil; |
|
| 286 | 287 | } |
| 297 | 298 | } |
|
| 298 | 299 | } |
|
| 299 | 300 | return ReserveInfo { size: offset, isDynamic }; |
|
| 300 | 301 | } |
|
| 301 | 302 | ||
| 302 | - | /// Select instructions with published parameter definitions and assignments. |
|
| 303 | - | /// Published inputs and assignments must describe the selected instruction tables. |
|
| 304 | - | export unsafe fn selectFn 'scratch ( |
|
| 303 | + | /// Select instructions from published IL and its register assignments. |
|
| 304 | + | export fn selectFn 'scratch ( |
|
| 305 | 305 | e: &mut emit::Emitter, |
|
| 306 | 306 | ralloc: ®alloc::AllocResult 'scratch, |
|
| 307 | - | func: &il::Fn, |
|
| 308 | 307 | view: &il::published::Function 'scratch |
|
| 309 | 308 | ) { |
|
| 310 | 309 | if e.error <> nil { |
|
| 311 | 310 | return; |
|
| 312 | 311 | } |
|
| 313 | 312 | // Reset block offsets for this function. |
|
| 314 | 313 | labels::resetBlocks(&mut e.labels); |
|
| 315 | 314 | // Pre-scan for constant-sized reserves to promote to fixed frame slots. |
|
| 316 | 315 | let mut reserveInfo = ReserveInfo { size: 0, isDynamic: false }; |
|
| 317 | - | for block in func.blocks { |
|
| 318 | - | let next = computeReserveInfo(&block.instrs[..], reserveInfo) else { |
|
| 316 | + | for block in view.blocks { |
|
| 317 | + | let next = computeReserveInfo(block.instructions, reserveInfo) else { |
|
| 319 | 318 | set e.error = super::Error::Capacity; |
|
| 320 | 319 | return; |
|
| 321 | 320 | }; |
|
| 322 | 321 | set reserveInfo = next; |
|
| 323 | 322 | } |
|
| 324 | 323 | if reserveInfo.size as u64 + ralloc.spill.frameSize as u64 > 0x7fffff00 { |
|
| 325 | 324 | set e.error = super::Error::Capacity; return; |
|
| 326 | 325 | } |
|
| 327 | - | let isLeaf = func.isLeaf; |
|
| 326 | + | let isLeaf = view.isLeaf; |
|
| 328 | 327 | // Compute frame layout from spill slots, reserve slots, and used callee-saved registers. |
|
| 329 | 328 | let frame = emit::computeFrame( |
|
| 330 | 329 | ralloc.spill.frameSize + reserveInfo.size, |
|
| 331 | 330 | ralloc.usedCalleeSaved, |
|
| 332 | 331 | view.blocks.len, |
| 342 | 341 | reserveOffset: 0, pendingSpill: nil, |
|
| 343 | 342 | nextSynthBlock: view.blocks.len + 1, |
|
| 344 | 343 | isDynamic: frame.isDynamic, |
|
| 345 | 344 | }; |
|
| 346 | 345 | // Record function name for printing. |
|
| 347 | - | emit::recordFunc(s.e, func.name); |
|
| 346 | + | emit::recordFunc(s.e, view.name); |
|
| 348 | 347 | // Record function code offset for call patching. |
|
| 349 | - | emit::recordFuncOffset(s.e, func.name); |
|
| 348 | + | emit::recordFuncOffset(s.e, view.name); |
|
| 350 | 349 | // Emit prologue. |
|
| 351 | 350 | emit::emitPrologue(s.e, &frame); |
|
| 352 | 351 | ||
| 353 | 352 | // Move function params from arg registers to assigned registers. |
|
| 354 | 353 | // Cross-call params may have been assigned to callee-saved registers |
| 371 | 370 | // Emit each block. |
|
| 372 | 371 | for i in 0..view.blocks.len { |
|
| 373 | 372 | if s.e.error <> nil { |
|
| 374 | 373 | return; |
|
| 375 | 374 | } |
|
| 376 | - | selectBlock(&mut s, i, &func.blocks[i], &frame, view); |
|
| 375 | + | selectBlock(&mut s, i, &frame, view); |
|
| 377 | 376 | } |
|
| 378 | 377 | // Emit epilogue. |
|
| 379 | 378 | emit::emitEpilogue(s.e, &frame); |
|
| 380 | 379 | // Patch local branches now that all blocks are emitted. |
|
| 381 | 380 | emit::patchLocalBranches(s.e); |
|
| 382 | 381 | } |
|
| 383 | 382 | } |
|
| 384 | 383 | ||
| 385 | 384 | /// Select instructions for a block. |
|
| 386 | - | unsafe fn selectBlock 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, block: &il::Block, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection { |
|
| 385 | + | fn selectBlock 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection { |
|
| 387 | 386 | // Record block address for branch patching. |
|
| 388 | 387 | emit::recordBlock(s.e, blockIdx); |
|
| 389 | 388 | ||
| 390 | 389 | // Block parameters are handled at jump sites (in `Jmp`/`Br`). |
|
| 391 | 390 | // By the time we enter the block, the arguments have already been |
|
| 392 | 391 | // moved to the parameter registers by the predecessor's terminator. |
|
| 393 | 392 | ||
| 394 | 393 | // Process each instruction, auto-committing any pending spill after each. |
|
| 395 | 394 | let locations = view.blocks[blockIdx].locations; |
|
| 396 | 395 | let hasLocs = locations.len > 0; |
|
| 397 | - | for instr, i in block.instrs { |
|
| 396 | + | for instr, i in view.blocks[blockIdx].instructions { |
|
| 398 | 397 | if s.e.error <> nil { |
|
| 399 | 398 | return; |
|
| 400 | 399 | } |
|
| 401 | 400 | // Record debug location before emitting machine instructions. |
|
| 402 | 401 | if hasLocs { |
|
| 403 | 402 | emit::recordSrcLoc(s.e, locations[i]); |
|
| 404 | 403 | } |
|
| 405 | 404 | set s.pendingSpill = nil; |
|
| 406 | - | selectInstr(s, blockIdx, &instr, frame, view); |
|
| 405 | + | let code = il::published::code(&instr); |
|
| 406 | + | selectInstr(s, blockIdx, code, frame, view); |
|
| 407 | 407 | ||
| 408 | 408 | // Flush the pending spill store, if any. |
|
| 409 | 409 | if let p = s.pendingSpill { |
|
| 410 | 410 | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) { |
|
| 411 | 411 | emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot)); |
| 414 | 414 | } |
|
| 415 | 415 | } |
|
| 416 | 416 | } |
|
| 417 | 417 | ||
| 418 | 418 | /// Select instructions for a single IL instruction. |
|
| 419 | - | unsafe fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::Instr, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection { |
|
| 419 | + | fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::published::Code 'scratch, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection { |
|
| 420 | 420 | match *instr { |
|
| 421 | - | case il::Instr::Jmp { target, args } => { |
|
| 421 | + | case il::published::Code::Jmp { target, args } => { |
|
| 422 | 422 | selectJump(s, blockIdx, target, view.blocks[target].params, args); |
|
| 423 | 423 | }, |
|
| 424 | - | case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
|
| 424 | + | case il::published::Code::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
|
| 425 | 425 | let params: &'scratch [il::Param] = view.blocks[thenTarget].params |
|
| 426 | 426 | if thenArgs.len > 0 and elseArgs.len == 0 |
|
| 427 | 427 | else view.blocks[elseTarget].params if elseArgs.len > 0 and thenArgs.len == 0 |
|
| 428 | 428 | else &view.params[..0]; |
|
| 429 | 429 | selectBranch(s, blockIdx, instr, params, thenArgs, elseArgs); |
|
| 430 | 430 | }, |
|
| 431 | - | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 431 | + | case il::published::Code::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 432 | 432 | let rs1 = resolveVal(s, super::SCRATCH1, val); |
|
| 433 | 433 | for c in cases { |
|
| 434 | 434 | let params: &'scratch [il::Param] = view.blocks[c.target].params |
|
| 435 | 435 | if c.args.len > 0 else &view.params[..0]; |
|
| 436 | 436 | selectSwitchCase(s, rs1, c.value, c.target, params, c.args); |
|
| 437 | 437 | } |
|
| 438 | 438 | // Fall through to default. |
|
| 439 | 439 | emitBlockArgs(s, view.blocks[defaultTarget].params, defaultArgs); |
|
| 440 | 440 | emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 441 | 441 | }, |
|
| 442 | - | case il::Instr::Call { dst, func, args, .. } => selectCall(s, &func, args, dst), |
|
| 443 | - | else => selectFixedInstr(s, blockIdx, instr, frame), |
|
| 442 | + | case il::published::Code::Call { dst, func, args, .. } => selectCall(s, &func, args, dst), |
|
| 443 | + | case il::published::Code::Fixed(fixed) => selectFixedInstr(s, blockIdx, &fixed, frame), |
|
| 444 | 444 | } |
|
| 445 | 445 | } |
|
| 446 | 446 | ||
| 447 | 447 | /// Compare one switch case and emit its argument moves only on a match. |
|
| 448 | 448 | fn selectSwitchCase 'scratch 'selection ( |
| 492 | 492 | ||
| 493 | 493 | /// Select a conditional branch with parameters for its argument-bearing edge. |
|
| 494 | 494 | fn selectBranch 'scratch 'selection ( |
|
| 495 | 495 | s: &mut Selector 'scratch 'selection, |
|
| 496 | 496 | blockIdx: u32, |
|
| 497 | - | instr: &il::Instr, |
|
| 497 | + | instr: &il::published::Code 'scratch, |
|
| 498 | 498 | params: &[il::Param], |
|
| 499 | 499 | thenArgs: &[il::Val], |
|
| 500 | 500 | elseArgs: &[il::Val] |
|
| 501 | 501 | ) where 'scratch: 'selection { |
|
| 502 | - | let case il::Instr::Br { op, typ, a, b, thenTarget, elseTarget, .. } = *instr |
|
| 502 | + | let case il::published::Code::Br { op, typ, a, b, thenTarget, elseTarget, .. } = *instr |
|
| 503 | 503 | else panic "selectBranch: expected conditional branch"; |
|
| 504 | 504 | // Use zero register directly for immediate `0` operands. |
|
| 505 | 505 | let aIsZero = isZeroImm(a); |
|
| 506 | 506 | let bIsZero = isZeroImm(b); |
|
| 507 | 507 |
lib/std/lang/il/published.rad
+123 -8
| 1 | - | //! Immutable, region-bound views of IL used by function analyses. |
|
| 1 | + | //! Immutable, region-bound IL for analysis and instruction selection. |
|
| 2 | 2 | //! |
|
| 3 | - | //! Publication owns analysis tables and retains original node pointers as |
|
| 4 | - | //! identities. Analysis reads do not access the source tables. |
|
| 3 | + | //! Publication owns instruction and analysis tables. Original node pointers |
|
| 4 | + | //! identify source nodes. Symbol names use immutable IL symbol storage. |
|
| 5 | 5 | ||
| 6 | 6 | use std::lang::alloc; |
|
| 7 | 7 | use std::lang::il; |
|
| 8 | 8 | ||
| 9 | 9 | /// Largest element count represented by an analysis slice. |
|
| 10 | 10 | constant MAX_ELEMENTS: u32 = 0xffffffff; |
|
| 11 | 11 | ||
| 12 | 12 | /// Published function tables within one analysis lifetime. |
|
| 13 | 13 | export record Function: 'view + Copy { |
|
| 14 | + | /// Immutable symbol name. |
|
| 15 | + | name: *[u8], |
|
| 16 | + | /// Whether the function makes no calls. |
|
| 17 | + | isLeaf: bool, |
|
| 14 | 18 | /// Function parameter definitions. |
|
| 15 | 19 | params: &'view [il::Param], |
|
| 16 | 20 | /// Blocks in source order. |
|
| 17 | 21 | blocks: &'view [Block 'view], |
|
| 18 | 22 | } |
| 29 | 33 | locations: &'view [il::SrcLoc], |
|
| 30 | 34 | /// Loop nesting depth for cost weighting. |
|
| 31 | 35 | loopDepth: u32, |
|
| 32 | 36 | } |
|
| 33 | 37 | ||
| 34 | - | /// An instruction's immutable register-allocation inputs. |
|
| 38 | + | /// An instruction and its immutable register-allocation inputs. |
|
| 35 | 39 | export opaque record Instruction: 'view + Copy { |
|
| 36 | 40 | /// Original instruction pointer, used only for identity comparisons. |
|
| 37 | 41 | identity: *unsafe il::Instr, |
|
| 42 | + | /// Instruction operands with owned variable-length tables. |
|
| 43 | + | code: &'view Code 'view, |
|
| 38 | 44 | /// Destination register, when the instruction defines a value. |
|
| 39 | 45 | destination: ?il::Reg, |
|
| 40 | 46 | /// Whether the instruction has call-clobber semantics. |
|
| 41 | 47 | isCall: bool, |
|
| 42 | 48 | /// Source registers in operand order, including repeated uses. |
|
| 43 | 49 | operands: &'view [il::Reg], |
|
| 44 | 50 | /// Successor block positions in branch order. |
|
| 45 | 51 | targets: &'view [u32], |
|
| 46 | 52 | } |
|
| 47 | 53 | ||
| 48 | - | /// Copy analysis tables from valid IL into session-owned immutable storage. |
|
| 54 | + | /// Instruction data with region-bound call and branch tables. |
|
| 55 | + | export union Code: 'view + Copy { |
|
| 56 | + | /// An instruction whose operands are all inline. |
|
| 57 | + | Fixed(il::Instr), |
|
| 58 | + | /// An unconditional branch and its argument values. |
|
| 59 | + | Jmp { |
|
| 60 | + | /// Destination block position. |
|
| 61 | + | target: u32, |
|
| 62 | + | /// Values supplied to the destination parameters. |
|
| 63 | + | args: &'view [il::Val], |
|
| 64 | + | }, |
|
| 65 | + | /// A comparison and its two outgoing edges. |
|
| 66 | + | Br { |
|
| 67 | + | /// Comparison operation. |
|
| 68 | + | op: il::CmpOp, |
|
| 69 | + | /// Comparison width. |
|
| 70 | + | typ: il::Type, |
|
| 71 | + | /// Left comparison operand. |
|
| 72 | + | a: il::Val, |
|
| 73 | + | /// Right comparison operand. |
|
| 74 | + | b: il::Val, |
|
| 75 | + | /// Destination when the comparison succeeds. |
|
| 76 | + | thenTarget: u32, |
|
| 77 | + | /// Values for the successful edge. |
|
| 78 | + | thenArgs: &'view [il::Val], |
|
| 79 | + | /// Destination when the comparison fails. |
|
| 80 | + | elseTarget: u32, |
|
| 81 | + | /// Values for the failed edge. |
|
| 82 | + | elseArgs: &'view [il::Val], |
|
| 83 | + | }, |
|
| 84 | + | /// A value-based branch table. |
|
| 85 | + | Switch { |
|
| 86 | + | /// Value to compare with the cases. |
|
| 87 | + | val: il::Val, |
|
| 88 | + | /// Destination when no case matches. |
|
| 89 | + | defaultTarget: u32, |
|
| 90 | + | /// Values supplied to the default destination. |
|
| 91 | + | defaultArgs: &'view [il::Val], |
|
| 92 | + | /// Cases in comparison order. |
|
| 93 | + | cases: &'view [SwitchCase 'view], |
|
| 94 | + | }, |
|
| 95 | + | /// A function call and its argument values. |
|
| 96 | + | Call { |
|
| 97 | + | /// Return value width. |
|
| 98 | + | retTy: il::Type, |
|
| 99 | + | /// Register for the return value, if used. |
|
| 100 | + | dst: ?il::Reg, |
|
| 101 | + | /// Function symbol or address. |
|
| 102 | + | func: il::Val, |
|
| 103 | + | /// Values supplied to the function. |
|
| 104 | + | args: &'view [il::Val], |
|
| 105 | + | }, |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | /// One switch comparison and its outgoing edge. |
|
| 109 | + | export record SwitchCase: 'view + Copy { |
|
| 110 | + | /// Value that selects this case. |
|
| 111 | + | value: i64, |
|
| 112 | + | /// Destination block position. |
|
| 113 | + | target: u32, |
|
| 114 | + | /// Values supplied to the destination parameters. |
|
| 115 | + | args: &'view [il::Val], |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Copy IL tables from valid IL into session-owned immutable storage. |
|
| 49 | 119 | /// All source tables and nested arrays must remain valid during this call. |
|
| 50 | 120 | /// Original node pointers are identity values, not published storage borrows. |
|
| 51 | 121 | export unsafe fn publish 'view (function: &il::Fn, storage: &Session 'view) |
|
| 52 | 122 | -> Function 'view throws (alloc::AllocError) |
|
| 53 | 123 | { |
|
| 54 | 124 | let params = try storage.copy(function.params); |
|
| 55 | 125 | let blocks: &[il::Block] = function.blocks; |
|
| 56 | 126 | if blocks.len == 0 { |
|
| 57 | 127 | let none: [Block 'view; 0] = []; |
|
| 58 | - | return Function 'view { params, blocks: try storage.copy(&none[..]) }; |
|
| 128 | + | return Function 'view { name: function.name, isLeaf: function.isLeaf, |
|
| 129 | + | params, blocks: try storage.copy(&none[..]) }; |
|
| 59 | 130 | } |
|
| 60 | 131 | let first = try publishBlock(&blocks[0], storage); |
|
| 61 | 132 | let views = try storage.fill(first, blocks.len); |
|
| 62 | 133 | for i in 1..blocks.len { |
|
| 63 | 134 | set views[i] = try publishBlock(&blocks[i], storage); |
|
| 64 | 135 | } |
|
| 65 | - | return Function 'view { params, blocks: &views[..] }; |
|
| 136 | + | return Function 'view { name: function.name, isLeaf: function.isLeaf, |
|
| 137 | + | params, blocks: &views[..] }; |
|
| 66 | 138 | } |
|
| 67 | 139 | ||
| 68 | 140 | /// Copy block definitions and each instruction's analysis tables. |
|
| 69 | 141 | unsafe fn publishBlock 'view (block: &il::Block, storage: &Session 'view) |
|
| 70 | 142 | -> Block 'view throws (alloc::AllocError) |
| 127 | 199 | for i in 0..count { |
|
| 128 | 200 | let reg = il::nextReg(&mut cursor, source) |
|
| 129 | 201 | else panic "publishInstruction: operand count changed"; |
|
| 130 | 202 | set operands[i] = reg; |
|
| 131 | 203 | } |
|
| 132 | - | return Instruction 'view { identity: source as *unsafe il::Instr, |
|
| 204 | + | let code = try storage.new(try publishCode(source, storage)); |
|
| 205 | + | return Instruction 'view { identity: source as *unsafe il::Instr, code, |
|
| 133 | 206 | destination: il::instrDst(*source), isCall: il::isCall(*source), |
|
| 134 | 207 | operands: &operands[..], targets }; |
|
| 135 | 208 | } |
|
| 136 | 209 | ||
| 210 | + | /// Copy the variable-length operands of an instruction into the session. |
|
| 211 | + | unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view) |
|
| 212 | + | -> Code 'view throws (alloc::AllocError) |
|
| 213 | + | { |
|
| 214 | + | match *source { |
|
| 215 | + | case il::Instr::Jmp { target, args } => { |
|
| 216 | + | return Code 'view::Jmp { target, args: try storage.copy(args) }; |
|
| 217 | + | }, |
|
| 218 | + | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 219 | + | return Code 'view::Br { op, typ, a, b, thenTarget, |
|
| 220 | + | thenArgs: try storage.copy(thenArgs), elseTarget, |
|
| 221 | + | elseArgs: try storage.copy(elseArgs) }; |
|
| 222 | + | }, |
|
| 223 | + | case il::Instr::Call { retTy, dst, func, args } => { |
|
| 224 | + | return Code 'view::Call { retTy, dst, func, args: try storage.copy(args) }; |
|
| 225 | + | }, |
|
| 226 | + | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 227 | + | let defaultArgs = try storage.copy(defaultArgs); |
|
| 228 | + | let cases: &[il::SwitchCase] = cases; |
|
| 229 | + | if cases.len == 0 { |
|
| 230 | + | let none: [SwitchCase 'view; 0] = []; |
|
| 231 | + | return Code 'view::Switch { val, defaultTarget, defaultArgs, |
|
| 232 | + | cases: try storage.copy(&none[..]) }; |
|
| 233 | + | } |
|
| 234 | + | let first = SwitchCase 'view { value: cases[0].value, target: cases[0].target, |
|
| 235 | + | args: try storage.copy(cases[0].args) }; |
|
| 236 | + | let views = try storage.fill(first, cases.len); |
|
| 237 | + | for i in 1..cases.len { |
|
| 238 | + | set views[i] = SwitchCase 'view { value: cases[i].value, target: cases[i].target, |
|
| 239 | + | args: try storage.copy(cases[i].args) }; |
|
| 240 | + | } |
|
| 241 | + | return Code 'view::Switch { val, defaultTarget, defaultArgs, cases: &views[..] }; |
|
| 242 | + | }, |
|
| 243 | + | else => return Code 'view::Fixed(*source), |
|
| 244 | + | } |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | /// Return the instruction data with session-owned operand tables. |
|
| 248 | + | export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view { |
|
| 249 | + | return instruction.code; |
|
| 250 | + | } |
|
| 251 | + | ||
| 137 | 252 | /// Return the original instruction pointer for identity comparisons. |
|
| 138 | 253 | export fn identity 'view (instruction: &Instruction 'view) -> *unsafe il::Instr { |
|
| 139 | 254 | return instruction.identity; |
|
| 140 | 255 | } |
|
| 141 | 256 |
lib/std/lang/il/publishedTests.rad
+27 -6
| 40 | 40 | assert not published::isCall(&block.instructions[0]); |
|
| 41 | 41 | let returned = published::registers(&block.instructions[1]); |
|
| 42 | 42 | assert returned.len == 1 and returned[0].n == 9; |
|
| 43 | 43 | assert published::destination(&block.instructions[1]) == nil; |
|
| 44 | 44 | assert published::successors(&block.instructions[1]).len == 0; |
|
| 45 | + | let case published::Code::Fixed(fixed) = published::code(&block.instructions[1]) |
|
| 46 | + | else panic; |
|
| 47 | + | let case il::Instr::Ret { val } = fixed else panic; |
|
| 48 | + | let value = val else panic; |
|
| 49 | + | let case il::Val::Reg(reg) = value else panic; |
|
| 50 | + | assert reg.n == 9; |
|
| 45 | 51 | } |
|
| 46 | 52 | } |
|
| 47 | 53 | ||
| 48 | 54 | /// Empty functions require no analysis storage. |
|
| 49 | 55 | @test unsafe fn testEmptyPublication() throws (testing::TestError) { |
| 86 | 92 | for reg in registers { |
|
| 87 | 93 | assert reg.n == 7; |
|
| 88 | 94 | } |
|
| 89 | 95 | let targets = published::successors(instruction); |
|
| 90 | 96 | assert targets.len == 2 and targets[0] == 0 and targets[1] == 0; |
|
| 97 | + | let case published::Code::Switch { cases, defaultArgs, .. } = published::code(instruction) |
|
| 98 | + | else panic; |
|
| 99 | + | assert defaultArgs.len == 0 and cases.len == 1; |
|
| 100 | + | assert cases[0].value == 1 and cases[0].target == 0; |
|
| 101 | + | assert cases[0].args.len == 3; |
|
| 102 | + | let case il::Val::Reg(reg) = cases[0].args[1] else panic; |
|
| 103 | + | assert reg.n == 7; |
|
| 91 | 104 | } |
|
| 92 | 105 | } |
|
| 93 | 106 | } |
|
| 94 | 107 | ||
| 95 | 108 | /// Allocation failures leave no borrowed source storage in published results. |
|
| 96 | 109 | @test unsafe fn testPublicationAllocationFailure() throws (testing::TestError) { |
|
| 97 | - | let args = [il::Val::Reg(il::Reg { n: 4 })]; |
|
| 98 | - | let locations = [il::SrcLoc { moduleId: 5, offset: 17 }]; |
|
| 110 | + | let mut args = [il::Val::Reg(il::Reg { n: 4 })]; |
|
| 111 | + | let locations = [il::SrcLoc { moduleId: 5, offset: 17 }, il::SrcLoc { moduleId: 5, offset: 18 }]; |
|
| 112 | + | let mut cases = [il::SwitchCase { value: 1, target: 0, args: &mut args[..] }, |
|
| 113 | + | il::SwitchCase { value: 2, target: 0, args: &mut args[..] }]; |
|
| 99 | 114 | let mut instructions = [il::Instr::Call { retTy: il::Type::W64, dst: nil, |
|
| 100 | - | func: il::Val::FnAddr("callee"), args: &args[..] }]; |
|
| 115 | + | func: il::Val::FnAddr("callee"), args: &args[..] }, |
|
| 116 | + | il::Instr::Switch { val: il::Val::Imm(1), defaultTarget: 0, |
|
| 117 | + | defaultArgs: &mut args[..], cases: &mut cases[..] }]; |
|
| 101 | 118 | let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..], |
|
| 102 | 119 | locs: &locations[..], preds: &[], loopDepth: 0 }]; |
|
| 103 | 120 | let function = il::Fn { name: "allocation", params: &[], returnType: il::Type::W64, |
|
| 104 | 121 | isExtern: false, isLeaf: false, blocks: &blocks[..] }; |
|
| 105 | 122 | let mut succeeded = false; |
|
| 106 | - | for capacity in 0..513 { |
|
| 107 | - | static DATA: [u8; 512] = [0; 512]; |
|
| 123 | + | for capacity in 0..2049 { |
|
| 124 | + | static DATA: [u8; 2048] = [0; 2048]; |
|
| 108 | 125 | let mut arena = alloc::new(&mut DATA[..capacity]); |
|
| 109 | 126 | let input: 'input = &function in { |
|
| 110 | 127 | use arena as storage in { |
|
| 111 | 128 | if let view = try? published::publish(input, &storage) { |
|
| 112 | 129 | let registers = published::registers(&view.blocks[0].instructions[0]); |
|
| 113 | 130 | assert registers.len == 1 and registers[0].n == 4; |
|
| 114 | - | assert view.blocks[0].locations.len == 1; |
|
| 131 | + | assert view.blocks[0].locations.len == 2; |
|
| 115 | 132 | assert view.blocks[0].locations[0].offset == 17; |
|
| 133 | + | let case published::Code::Switch { cases, defaultArgs, .. } = |
|
| 134 | + | *published::code(&view.blocks[0].instructions[1]) else panic; |
|
| 135 | + | assert cases.len == 2 and cases[1].value == 2; |
|
| 136 | + | assert cases[1].args.len == 1 and defaultArgs.len == 1; |
|
| 116 | 137 | set succeeded = true; |
|
| 117 | 138 | } else { |
|
| 118 | 139 | assert not succeeded; |
|
| 119 | 140 | } |
|
| 120 | 141 | } |