compiler: Select inline RV64 instructions in checked code
d3ace8fb8614a750cc1c60a9843aaf65cb01217caf6e9cbe875405b4a3ce5297
1 parent
81619cc0
lib/std/arch/rv64/bounds.rad
+89 -0
| 14 | 14 | ||
| 15 | 15 | /// Reusable emitter allocation storage. |
|
| 16 | 16 | static MEMORY: [u8; 16777216] = [0; 16777216]; |
|
| 17 | 17 | /// Function and liveness test storage. |
|
| 18 | 18 | static SCRATCH: [u8; 65536] = [0; 65536]; |
|
| 19 | + | /// Code output with guard words for instruction selection checks. |
|
| 20 | + | static SELECTION_WORDS: [u32; 130] = [0; 130]; |
|
| 19 | 21 | ||
| 20 | 22 | /// Dictionary storage for bounded map tests. |
|
| 21 | 23 | unsafe static ENTRIES: [dict::Entry; 4] = undefined; |
|
| 22 | 24 | ||
| 23 | 25 | /// Build a non-debug generator with a fixed code address. |
| 196 | 198 | throw testing::TestError::Failed; |
|
| 197 | 199 | }; |
|
| 198 | 200 | try testing::expect(length == 8 and bytes[0] == 1); |
|
| 199 | 201 | } |
|
| 200 | 202 | ||
| 203 | + | /// Inline instruction selection respects every shorter output capacity. |
|
| 204 | + | @test unsafe fn inlineSelectionCapacity() throws (testing::TestError) { |
|
| 205 | + | let dst = il::Reg { n: 3 }; |
|
| 206 | + | let first = il::Reg { n: 0 }; |
|
| 207 | + | let second = il::Reg { n: 1 }; |
|
| 208 | + | let a = il::Val::Reg(first); |
|
| 209 | + | let b = il::Val::Reg(second); |
|
| 210 | + | let instructions = [ |
|
| 211 | + | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst, a, b }, |
|
| 212 | + | il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W32, dst, a }, |
|
| 213 | + | il::Instr::Load { typ: il::Type::W8, dst, src: first, offset: 4096 }, |
|
| 214 | + | il::Instr::Sload { typ: il::Type::W16, dst, src: first, offset: -4096 }, |
|
| 215 | + | il::Instr::Store { typ: il::Type::W32, src: a, dst: second, offset: 4096 }, |
|
| 216 | + | il::Instr::Copy { dst, val: il::Val::Imm(0x123456789abcdef) }, |
|
| 217 | + | il::Instr::Reserve { dst, size: il::Val::Imm(32), alignment: 16 }, |
|
| 218 | + | il::Instr::Reserve { dst, size: a, alignment: 16 }, |
|
| 219 | + | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(0) }, |
|
| 220 | + | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(8) }, |
|
| 221 | + | il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(40) }, |
|
| 222 | + | il::Instr::Zext { typ: il::Type::W16, dst, val: a }, |
|
| 223 | + | il::Instr::Sext { typ: il::Type::W8, dst, val: a }, |
|
| 224 | + | il::Instr::Ret { val: a }, |
|
| 225 | + | il::Instr::Unreachable, |
|
| 226 | + | il::Instr::Ecall { dst, num: a, a0: b, a1: a, a2: b, a3: a }, |
|
| 227 | + | il::Instr::DeviceRead { typ: il::Type::W64, dst, handle: a, offset: b }, |
|
| 228 | + | il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: a }, |
|
| 229 | + | il::Instr::Ebreak, |
|
| 230 | + | il::Instr::MemoryFence, |
|
| 231 | + | ]; |
|
| 232 | + | for instr in instructions { |
|
| 233 | + | try checkSelectionCapacity(instr); |
|
| 234 | + | } |
|
| 235 | + | } |
|
| 236 | + | ||
| 237 | + | /// Verify exact-fit output and canaries around every shorter code buffer. |
|
| 238 | + | unsafe fn checkSelectionCapacity(instr: il::Instr) throws (testing::TestError) { |
|
| 239 | + | let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }]; |
|
| 240 | + | let mut count: u32 = 2; |
|
| 241 | + | if let case il::Instr::Ret { .. } = instr { |
|
| 242 | + | set count = 1; |
|
| 243 | + | } |
|
| 244 | + | let params = [ |
|
| 245 | + | il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }, |
|
| 246 | + | il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }, |
|
| 247 | + | ]; |
|
| 248 | + | let func = il::Fn { |
|
| 249 | + | name: "p::inline", params: ¶ms[..], returnType: il::Type::W64, |
|
| 250 | + | isExtern: false, isLeaf: not il::isCall(instr), |
|
| 251 | + | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..count], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 252 | + | }; |
|
| 253 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 254 | + | let mut gen = generator(&mut arena); |
|
| 255 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 256 | + | super::generateFunction(&mut gen, &func, &mut scratch); |
|
| 257 | + | assert gen.e.error == nil; |
|
| 258 | + | let length = gen.e.codeLen; |
|
| 259 | + | let mut expected: [u32; 128] = [0; 128]; |
|
| 260 | + | assert length > 0 and length <= expected.len; |
|
| 261 | + | for i in 0..length { |
|
| 262 | + | set expected[i] = gen.e.code[i]; |
|
| 263 | + | } |
|
| 264 | + | for capacity in 0..(length + 1) { |
|
| 265 | + | let words = &mut SELECTION_WORDS[..]; |
|
| 266 | + | for i in 0..words.len { |
|
| 267 | + | set words[i] = 0xdeadbeef; |
|
| 268 | + | } |
|
| 269 | + | alloc::reset(&mut arena); |
|
| 270 | + | set gen = generator(&mut arena); |
|
| 271 | + | set gen.e.code = &mut words[1..capacity + 1]; |
|
| 272 | + | super::generateFunction(&mut gen, &func, &mut scratch); |
|
| 273 | + | assert gen.e.codeLen <= capacity; |
|
| 274 | + | assert words[0] == 0xdeadbeef; |
|
| 275 | + | for i in (capacity + 1)..words.len { |
|
| 276 | + | assert words[i] == 0xdeadbeef; |
|
| 277 | + | } |
|
| 278 | + | if capacity == length { |
|
| 279 | + | assert gen.e.error == nil; |
|
| 280 | + | assert gen.e.codeLen == length; |
|
| 281 | + | for i in 0..length { |
|
| 282 | + | assert words[i + 1] == expected[i]; |
|
| 283 | + | } |
|
| 284 | + | } else { |
|
| 285 | + | assert gen.e.error == super::Error::Capacity; |
|
| 286 | + | } |
|
| 287 | + | } |
|
| 288 | + | } |
|
| 289 | + | ||
| 201 | 290 | /// Name the failed invariant before returning to the test runner. |
|
| 202 | 291 | fn check(condition: bool, name: *[u8]) throws (testing::TestError) { |
|
| 203 | 292 | if not condition { |
|
| 204 | 293 | io::printLn(name); |
|
| 205 | 294 | throw testing::TestError::Failed; |
lib/std/arch/rv64/isel.rad
+139 -129
| 398 | 398 | // Record debug location before emitting machine instructions. |
|
| 399 | 399 | if hasLocs { |
|
| 400 | 400 | emit::recordSrcLoc(s.e, block.locs[i]); |
|
| 401 | 401 | } |
|
| 402 | 402 | set s.pendingSpill = nil; |
|
| 403 | - | selectInstr(s, blockIdx, instr, frame, func); |
|
| 403 | + | selectInstr(s, blockIdx, &instr, frame, func); |
|
| 404 | 404 | ||
| 405 | 405 | // Flush the pending spill store, if any. |
|
| 406 | 406 | if let p = s.pendingSpill { |
|
| 407 | 407 | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) { |
|
| 408 | 408 | emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot)); |
| 411 | 411 | } |
|
| 412 | 412 | } |
|
| 413 | 413 | } |
|
| 414 | 414 | ||
| 415 | 415 | /// Select instructions for a single IL instruction. |
|
| 416 | - | unsafe fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: il::Instr, frame: &emit::Frame, func: &il::Fn) where 'scratch: 'selection { |
|
| 417 | - | match instr { |
|
| 418 | - | case il::Instr::BinOp { op, typ, dst, a, b } => { |
|
| 419 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 420 | - | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 421 | - | selectAluBinOp(s, op, typ, rd, rs1, b); |
|
| 422 | - | }, |
|
| 423 | - | case il::Instr::UnOp { op, typ, dst, a } => { |
|
| 424 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 425 | - | let rs = resolveVal(s, super::SCRATCH1, a); |
|
| 426 | - | selectAluUnOp(s, op, typ, rd, rs); |
|
| 427 | - | }, |
|
| 428 | - | case il::Instr::Load { typ, dst, src, offset } => { |
|
| 429 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 430 | - | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 431 | - | emit::emitLoad(s.e, rd, base, offset, typ); |
|
| 432 | - | }, |
|
| 433 | - | case il::Instr::Sload { typ, dst, src, offset } => { |
|
| 434 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 435 | - | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 436 | - | emit::emitSload(s.e, rd, base, offset, typ); |
|
| 437 | - | }, |
|
| 438 | - | case il::Instr::Store { typ, src, dst, offset } => { |
|
| 439 | - | let base = getSrcReg(s, dst, super::SCRATCH2); |
|
| 440 | - | let rs = resolveVal(s, super::SCRATCH1, src); |
|
| 441 | - | emit::emitStore(s.e, rs, base, offset, typ); |
|
| 442 | - | }, |
|
| 443 | - | case il::Instr::Copy { dst, val } => { |
|
| 444 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 445 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 446 | - | emitMv(s, rd, rs); |
|
| 447 | - | }, |
|
| 448 | - | case il::Instr::Reserve { dst, size, alignment } => { |
|
| 449 | - | match size { |
|
| 450 | - | case il::Val::Imm(sz) => { |
|
| 451 | - | // Constant-sized reserve: use pre-allocated frame slot. |
|
| 452 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 453 | - | let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32); |
|
| 454 | - | let base = spillBase(s); |
|
| 455 | - | let offset = s.ralloc.spill.frameSize + aligned |
|
| 456 | - | - (s.frameSize if s.isDynamic else 0); |
|
| 457 | - | ||
| 458 | - | emit::emitAddImm(s.e, rd, base, offset); |
|
| 459 | - | set s.reserveOffset = aligned + (sz as i32); |
|
| 460 | - | }, |
|
| 461 | - | case il::Val::Reg(r) => { |
|
| 462 | - | // Dynamic-sized reserve: runtime SP adjustment. |
|
| 463 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 464 | - | let rs = getSrcReg(s, r, super::SCRATCH2); |
|
| 465 | - | ||
| 466 | - | emit::emit(s.e, encode::sub(super::SP, super::SP, rs)); |
|
| 467 | - | ||
| 468 | - | if alignment > 1 { |
|
| 469 | - | let mask = 0 - alignment as i32; |
|
| 470 | - | assert encode::isSmallImm(mask); |
|
| 471 | - | ||
| 472 | - | emit::emit(s.e, encode::andi(super::SP, super::SP, mask)); |
|
| 473 | - | } |
|
| 474 | - | emit::emit(s.e, encode::mv(rd, super::SP)); |
|
| 475 | - | }, |
|
| 476 | - | else => |
|
| 477 | - | panic "selectInstr: invalid reserve operand", |
|
| 478 | - | } |
|
| 479 | - | }, |
|
| 480 | - | case il::Instr::Blit { dst, src, size } => { |
|
| 481 | - | let case il::Val::Imm(staticSize) = size else { |
|
| 482 | - | set s.e.error = super::Error::Capacity; return; |
|
| 483 | - | }; |
|
| 484 | - | if staticSize < 0 or staticSize > 0x7fffffff { |
|
| 485 | - | set s.e.error = super::Error::Capacity; return; |
|
| 486 | - | } |
|
| 487 | - | if staticSize == 0 { |
|
| 488 | - | return; |
|
| 489 | - | } |
|
| 490 | - | let rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 491 | - | let rsrc = getSrcReg(s, src, super::SCRATCH1); |
|
| 492 | - | // Blit addresses have byte alignment. Small copies need no loop state. |
|
| 493 | - | if staticSize < super::BLIT_LOOP_THRESHOLD as i64 { |
|
| 494 | - | for offset in 0..staticSize as u32 { |
|
| 495 | - | emit::emitLb(s.e, super::ADDR_SCRATCH, rsrc, offset as i32); |
|
| 496 | - | emit::emitSb(s.e, super::ADDR_SCRATCH, rdst, offset as i32); |
|
| 497 | - | } |
|
| 498 | - | } else { |
|
| 499 | - | // Private cursors preserve both input pointers. Save the count |
|
| 500 | - | // register because it can hold a live allocated value. |
|
| 501 | - | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, rsrc, 0)); |
|
| 502 | - | emit::emit(s.e, encode::addi(super::SCRATCH2, rdst, 0)); |
|
| 503 | - | emit::emit(s.e, encode::addi(super::SP, super::SP, -16)); |
|
| 504 | - | emit::emitSd(s.e, super::T3, super::SP, 0); |
|
| 505 | - | emit::loadImm(s.e, super::T3, staticSize); |
|
| 506 | - | let start = s.e.codeLen; |
|
| 507 | - | emit::emitLb(s.e, super::SCRATCH1, super::ADDR_SCRATCH, 0); |
|
| 508 | - | emit::emitSb(s.e, super::SCRATCH1, super::SCRATCH2, 0); |
|
| 509 | - | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, super::ADDR_SCRATCH, 1)); |
|
| 510 | - | emit::emit(s.e, encode::addi(super::SCRATCH2, super::SCRATCH2, 1)); |
|
| 511 | - | emit::emit(s.e, encode::addi(super::T3, super::T3, -1)); |
|
| 512 | - | let offset = (start as i32 - s.e.codeLen as i32) * super::INSTR_SIZE; |
|
| 513 | - | emit::emit(s.e, encode::bne(super::T3, super::ZERO, offset)); |
|
| 514 | - | emit::emitLd(s.e, super::T3, super::SP, 0); |
|
| 515 | - | emit::emit(s.e, encode::addi(super::SP, super::SP, 16)); |
|
| 516 | - | } |
|
| 517 | - | }, |
|
| 518 | - | case il::Instr::Zext { typ, dst, val } => { |
|
| 519 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 520 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 521 | - | emitZext(s.e, rd, rs, typ); |
|
| 522 | - | }, |
|
| 523 | - | case il::Instr::Sext { typ, dst, val } => { |
|
| 524 | - | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 525 | - | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 526 | - | emitSext(s.e, rd, rs, typ); |
|
| 527 | - | }, |
|
| 528 | - | case il::Instr::Ret { val } => { |
|
| 529 | - | if let v = val { |
|
| 530 | - | let rs = resolveVal(s, super::SCRATCH1, v); |
|
| 531 | - | emitMv(s, super::A0, rs); |
|
| 532 | - | } |
|
| 533 | - | // Skip the jump to epilogue if this RET is in the last block, |
|
| 534 | - | // since the epilogue immediately follows. |
|
| 535 | - | if frame.totalSize <> 0 and blockIdx + 1 == frame.epilogueBlock { |
|
| 536 | - | // Epilogue is the next block; fallthrough is sufficient. |
|
| 537 | - | } else { |
|
| 538 | - | emit::emitReturn(s.e, frame); |
|
| 539 | - | } |
|
| 540 | - | }, |
|
| 416 | + | unsafe fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::Instr, frame: &emit::Frame, func: &il::Fn) where 'scratch: 'selection { |
|
| 417 | + | match *instr { |
|
| 541 | 418 | case il::Instr::Jmp { target, args } => { |
|
| 542 | 419 | // Move arguments to target block's parameter registers. |
|
| 543 | 420 | emitBlockArgs(s, func.blocks[target].params, args); |
|
| 544 | 421 | // Skip branch if target is the next block (fallthrough). |
|
| 545 | 422 | if target <> blockIdx + 1 { |
| 630 | 507 | } |
|
| 631 | 508 | // Fall through to default. |
|
| 632 | 509 | emitBlockArgs(s, func.blocks[defaultTarget].params, defaultArgs); |
|
| 633 | 510 | emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 634 | 511 | }, |
|
| 635 | - | case il::Instr::Unreachable => { |
|
| 636 | - | emit::emit(s.e, encode::ebreak()); |
|
| 637 | - | }, |
|
| 638 | 512 | case il::Instr::Call { retTy, dst, func, args } => { |
|
| 639 | 513 | // For indirect calls, save target to scratch register before arg |
|
| 640 | 514 | // setup can clobber it. |
|
| 641 | 515 | if let case il::Val::Reg(r) = func { |
|
| 642 | 516 | let target = getSrcReg(s, r, super::SCRATCH2); |
| 665 | 539 | if let d = dst { |
|
| 666 | 540 | let rd = getDstReg(s, d, super::SCRATCH1); |
|
| 667 | 541 | emitMv(s, rd, super::A0); |
|
| 668 | 542 | } |
|
| 669 | 543 | }, |
|
| 544 | + | else => selectFixedInstr(s, blockIdx, instr, frame), |
|
| 545 | + | } |
|
| 546 | + | } |
|
| 547 | + | ||
| 548 | + | /// Select RV64 instructions for inline IL operands. |
|
| 549 | + | fn selectFixedInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::Instr, frame: &emit::Frame) where 'scratch: 'selection { |
|
| 550 | + | match *instr { |
|
| 551 | + | case il::Instr::BinOp { op, typ, dst, a, b } => { |
|
| 552 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 553 | + | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 554 | + | selectAluBinOp(s, op, typ, rd, rs1, b); |
|
| 555 | + | }, |
|
| 556 | + | case il::Instr::UnOp { op, typ, dst, a } => { |
|
| 557 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 558 | + | let rs = resolveVal(s, super::SCRATCH1, a); |
|
| 559 | + | selectAluUnOp(s, op, typ, rd, rs); |
|
| 560 | + | }, |
|
| 561 | + | case il::Instr::Load { typ, dst, src, offset } => { |
|
| 562 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 563 | + | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 564 | + | emit::emitLoad(s.e, rd, base, offset, typ); |
|
| 565 | + | }, |
|
| 566 | + | case il::Instr::Sload { typ, dst, src, offset } => { |
|
| 567 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 568 | + | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 569 | + | emit::emitSload(s.e, rd, base, offset, typ); |
|
| 570 | + | }, |
|
| 571 | + | case il::Instr::Store { typ, src, dst, offset } => { |
|
| 572 | + | let base = getSrcReg(s, dst, super::SCRATCH2); |
|
| 573 | + | let rs = resolveVal(s, super::SCRATCH1, src); |
|
| 574 | + | emit::emitStore(s.e, rs, base, offset, typ); |
|
| 575 | + | }, |
|
| 576 | + | case il::Instr::Copy { dst, val } => { |
|
| 577 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 578 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 579 | + | emitMv(s, rd, rs); |
|
| 580 | + | }, |
|
| 581 | + | case il::Instr::Reserve { dst, size, alignment } => { |
|
| 582 | + | match size { |
|
| 583 | + | case il::Val::Imm(sz) => { |
|
| 584 | + | // Constant-sized reserve: use pre-allocated frame slot. |
|
| 585 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 586 | + | let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32); |
|
| 587 | + | let base = spillBase(s); |
|
| 588 | + | let offset = s.ralloc.spill.frameSize + aligned |
|
| 589 | + | - (s.frameSize if s.isDynamic else 0); |
|
| 590 | + | ||
| 591 | + | emit::emitAddImm(s.e, rd, base, offset); |
|
| 592 | + | set s.reserveOffset = aligned + (sz as i32); |
|
| 593 | + | }, |
|
| 594 | + | case il::Val::Reg(r) => { |
|
| 595 | + | // Dynamic-sized reserve: runtime SP adjustment. |
|
| 596 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 597 | + | let rs = getSrcReg(s, r, super::SCRATCH2); |
|
| 598 | + | ||
| 599 | + | emit::emit(s.e, encode::sub(super::SP, super::SP, rs)); |
|
| 600 | + | ||
| 601 | + | if alignment > 1 { |
|
| 602 | + | let mask = 0 - alignment as i32; |
|
| 603 | + | assert encode::isSmallImm(mask); |
|
| 604 | + | ||
| 605 | + | emit::emit(s.e, encode::andi(super::SP, super::SP, mask)); |
|
| 606 | + | } |
|
| 607 | + | emit::emit(s.e, encode::mv(rd, super::SP)); |
|
| 608 | + | }, |
|
| 609 | + | else => |
|
| 610 | + | panic "selectFixedInstr: invalid reserve operand", |
|
| 611 | + | } |
|
| 612 | + | }, |
|
| 613 | + | case il::Instr::Blit { dst, src, size } => { |
|
| 614 | + | let case il::Val::Imm(staticSize) = size else { |
|
| 615 | + | set s.e.error = super::Error::Capacity; return; |
|
| 616 | + | }; |
|
| 617 | + | if staticSize < 0 or staticSize > 0x7fffffff { |
|
| 618 | + | set s.e.error = super::Error::Capacity; return; |
|
| 619 | + | } |
|
| 620 | + | if staticSize == 0 { |
|
| 621 | + | return; |
|
| 622 | + | } |
|
| 623 | + | let rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 624 | + | let rsrc = getSrcReg(s, src, super::SCRATCH1); |
|
| 625 | + | // Blit addresses have byte alignment. Small copies need no loop state. |
|
| 626 | + | if staticSize < super::BLIT_LOOP_THRESHOLD as i64 { |
|
| 627 | + | for offset in 0..staticSize as u32 { |
|
| 628 | + | emit::emitLb(s.e, super::ADDR_SCRATCH, rsrc, offset as i32); |
|
| 629 | + | emit::emitSb(s.e, super::ADDR_SCRATCH, rdst, offset as i32); |
|
| 630 | + | } |
|
| 631 | + | } else { |
|
| 632 | + | // Private cursors preserve both input pointers. Save the count |
|
| 633 | + | // register because it can hold a live allocated value. |
|
| 634 | + | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, rsrc, 0)); |
|
| 635 | + | emit::emit(s.e, encode::addi(super::SCRATCH2, rdst, 0)); |
|
| 636 | + | emit::emit(s.e, encode::addi(super::SP, super::SP, -16)); |
|
| 637 | + | emit::emitSd(s.e, super::T3, super::SP, 0); |
|
| 638 | + | emit::loadImm(s.e, super::T3, staticSize); |
|
| 639 | + | let start = s.e.codeLen; |
|
| 640 | + | emit::emitLb(s.e, super::SCRATCH1, super::ADDR_SCRATCH, 0); |
|
| 641 | + | emit::emitSb(s.e, super::SCRATCH1, super::SCRATCH2, 0); |
|
| 642 | + | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, super::ADDR_SCRATCH, 1)); |
|
| 643 | + | emit::emit(s.e, encode::addi(super::SCRATCH2, super::SCRATCH2, 1)); |
|
| 644 | + | emit::emit(s.e, encode::addi(super::T3, super::T3, -1)); |
|
| 645 | + | let offset = (start as i32 - s.e.codeLen as i32) * super::INSTR_SIZE; |
|
| 646 | + | emit::emit(s.e, encode::bne(super::T3, super::ZERO, offset)); |
|
| 647 | + | emit::emitLd(s.e, super::T3, super::SP, 0); |
|
| 648 | + | emit::emit(s.e, encode::addi(super::SP, super::SP, 16)); |
|
| 649 | + | } |
|
| 650 | + | }, |
|
| 651 | + | case il::Instr::Zext { typ, dst, val } => { |
|
| 652 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 653 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 654 | + | emitZext(s.e, rd, rs, typ); |
|
| 655 | + | }, |
|
| 656 | + | case il::Instr::Sext { typ, dst, val } => { |
|
| 657 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 658 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 659 | + | emitSext(s.e, rd, rs, typ); |
|
| 660 | + | }, |
|
| 661 | + | case il::Instr::Ret { val } => { |
|
| 662 | + | if let v = val { |
|
| 663 | + | let rs = resolveVal(s, super::SCRATCH1, v); |
|
| 664 | + | emitMv(s, super::A0, rs); |
|
| 665 | + | } |
|
| 666 | + | // Skip the jump to epilogue if this RET is in the last block, |
|
| 667 | + | // since the epilogue immediately follows. |
|
| 668 | + | if frame.totalSize <> 0 and blockIdx + 1 == frame.epilogueBlock { |
|
| 669 | + | // Epilogue is the next block; fallthrough is sufficient. |
|
| 670 | + | } else { |
|
| 671 | + | emit::emitReturn(s.e, frame); |
|
| 672 | + | } |
|
| 673 | + | }, |
|
| 674 | + | case il::Instr::Unreachable => { |
|
| 675 | + | emit::emit(s.e, encode::ebreak()); |
|
| 676 | + | }, |
|
| 670 | 677 | case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => { |
|
| 671 | 678 | // Move arguments using parallel move. |
|
| 672 | 679 | // TODO: Can't use slice literals here because the lowerer doesn't |
|
| 673 | 680 | // support constant-evaluating struct/union values in them. |
|
| 674 | 681 | let ecallDsts: [gen::Reg; 5] = [super::A7, super::A0, super::A1, super::A2, super::A3]; |
| 706 | 713 | emit::emit(s.e, encode::ebreak()); |
|
| 707 | 714 | }, |
|
| 708 | 715 | case il::Instr::MemoryFence => { |
|
| 709 | 716 | emit::emit(s.e, encode::fence()); |
|
| 710 | 717 | }, |
|
| 718 | + | case il::Instr::Call { .. }, il::Instr::Jmp { .. }, |
|
| 719 | + | il::Instr::Br { .. }, il::Instr::Switch { .. } => |
|
| 720 | + | panic "selectFixedInstr: expected inline operands", |
|
| 711 | 721 | } |
|
| 712 | 722 | } |
|
| 713 | 723 | ||
| 714 | 724 | /// Choose the cheapest canonical representation that preserves the comparison. |
|
| 715 | 725 | /// RV64 word operations naturally sign-extend, and sign-extension preserves |