compiler: Construct published instructions from checked operands
c8d0ba8937dc35adaf707bc491e3e055c1bb4c560dfea3a92a719579b7eb94b3
1 parent
25d7de6e
lib/std/arch/rv64/bounds.rad
+51 -0
| 80 | 80 | } |
|
| 81 | 81 | ||
| 82 | 82 | /// Reusable emitter allocation storage. |
|
| 83 | 83 | static MEMORY: [u8; 16777216] = [0; 16777216]; |
|
| 84 | 84 | ||
| 85 | + | /// Checked IL construction reaches instruction selection without a raw IL import. |
|
| 86 | + | @test unsafe fn constructedInstructions() throws (testing::TestError) { |
|
| 87 | + | let mut arena = alloc::new(&mut SCRATCH[..]); |
|
| 88 | + | let mut code = alloc::new(&mut MEMORY[..]); |
|
| 89 | + | let mut e = try! emit::emitter(&mut code, false); |
|
| 90 | + | use arena as storage in { |
|
| 91 | + | try! selectConstructed(&storage, &mut e); |
|
| 92 | + | } |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// Construct, allocate, and select a function that returns a constant. |
|
| 96 | + | fn selectConstructed 'view (storage: &Session 'view, e: &mut emit::Emitter) |
|
| 97 | + | throws (alloc::AllocError) |
|
| 98 | + | { |
|
| 99 | + | let copy = try storage.new(il::published::Code 'view::Fixed( |
|
| 100 | + | il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(7) })); |
|
| 101 | + | let ret = try storage.new(il::published::Code 'view::Fixed( |
|
| 102 | + | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) })); |
|
| 103 | + | let first = try il::published::buildInstruction(copy, nil, storage); |
|
| 104 | + | let instructions = try storage.fill(first, 2); |
|
| 105 | + | set instructions[1] = try il::published::buildInstruction(ret, nil, storage); |
|
| 106 | + | let noParams: [il::Param; 0] = []; |
|
| 107 | + | let params: &'view [il::Param] = try storage.copy(&noParams[..]); |
|
| 108 | + | let noLocations: [il::SrcLoc; 0] = []; |
|
| 109 | + | let locations = try storage.copy(&noLocations[..]); |
|
| 110 | + | let blocks = try storage.fill(il::published::Block 'view { identity: nil, params, |
|
| 111 | + | instructions: &instructions[..], locations, loopDepth: 0 }, 1); |
|
| 112 | + | let function = il::published::Function 'view { name: "p::constructed", isLeaf: true, |
|
| 113 | + | params, blocks: &blocks[..] }; |
|
| 114 | + | let config = super::targetConfig(); |
|
| 115 | + | let allocation = try regalloc::allocate(&function, &config, storage); |
|
| 116 | + | isel::selectFn(e, &allocation, &function); |
|
| 117 | + | assert e.error == nil; |
|
| 118 | + | let assigned = allocation.assignments[0] else panic; |
|
| 119 | + | let expected = encode::addi(super::SCRATCH1, super::ZERO, 7); |
|
| 120 | + | let move = encode::mv(assigned, super::SCRATCH1); |
|
| 121 | + | let output: 'output = &*e in { |
|
| 122 | + | let mut found = false; |
|
| 123 | + | let mut moved = *assigned == *super::SCRATCH1; |
|
| 124 | + | for word in emit::getCode(output) { |
|
| 125 | + | if word == expected { |
|
| 126 | + | set found = true; |
|
| 127 | + | } |
|
| 128 | + | if word == move { |
|
| 129 | + | set moved = true; |
|
| 130 | + | } |
|
| 131 | + | } |
|
| 132 | + | assert found and moved; |
|
| 133 | + | } |
|
| 134 | + | } |
|
| 135 | + | ||
| 85 | 136 | /// Selection owns call arguments and instructions used to size the stack frame. |
|
| 86 | 137 | @test unsafe fn publishedCalls() throws (testing::TestError) { |
|
| 87 | 138 | for indirect in [false, true] { |
|
| 88 | 139 | let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
|
| 89 | 140 | let mut args = [il::Val::Imm(7), il::Val::Reg(il::Reg { n: 1 })]; |
lib/std/lang/il/published.rad
+102 -30
| 21 | 21 | blocks: &'view [Block 'view], |
|
| 22 | 22 | } |
|
| 23 | 23 | ||
| 24 | 24 | /// Read-only block tables and their original identity. |
|
| 25 | 25 | export record Block: 'view + Copy { |
|
| 26 | - | /// Original block pointer, used only for identity comparisons. |
|
| 27 | - | identity: *unsafe il::Block, |
|
| 26 | + | /// Original block pointer for imported code, used only for identity comparisons. |
|
| 27 | + | identity: ?*unsafe il::Block, |
|
| 28 | 28 | /// Block parameter definitions. |
|
| 29 | 29 | params: &'view [il::Param], |
|
| 30 | 30 | /// Published instruction descriptors. |
|
| 31 | 31 | instructions: &'view [Instruction 'view], |
|
| 32 | 32 | /// Source locations in instruction order, or an empty slice without debug data. |
| 35 | 35 | loopDepth: u32, |
|
| 36 | 36 | } |
|
| 37 | 37 | ||
| 38 | 38 | /// An instruction and its immutable register-allocation inputs. |
|
| 39 | 39 | export opaque record Instruction: 'view + Copy { |
|
| 40 | - | /// Original instruction pointer, used only for identity comparisons. |
|
| 41 | - | identity: *unsafe il::Instr, |
|
| 40 | + | /// Original instruction pointer for imported code, used only for identity comparisons. |
|
| 41 | + | identity: ?*unsafe il::Instr, |
|
| 42 | 42 | /// Instruction operands with owned variable-length tables. |
|
| 43 | 43 | code: &'view Code 'view, |
|
| 44 | 44 | /// Destination register, when the instruction defines a value. |
|
| 45 | 45 | destination: ?il::Reg, |
|
| 46 | 46 | /// Whether the instruction has call-clobber semantics. |
| 156 | 156 | } |
|
| 157 | 157 | return Block 'view { identity: block as *unsafe il::Block, params, |
|
| 158 | 158 | instructions: &views[..], locations, loopDepth: block.loopDepth }; |
|
| 159 | 159 | } |
|
| 160 | 160 | ||
| 161 | - | /// Collect source registers and successor positions for one instruction. |
|
| 161 | + | /// Import an instruction and construct its checked analysis tables. |
|
| 162 | 162 | unsafe fn publishInstruction 'view (source: &il::Instr, storage: &Session 'view) |
|
| 163 | 163 | -> Instruction 'view throws (alloc::AllocError) |
|
| 164 | 164 | { |
|
| 165 | + | let code = try storage.new(try publishCode(source, storage)); |
|
| 166 | + | return try buildInstruction(code, source as *unsafe il::Instr, storage); |
|
| 167 | + | } |
|
| 168 | + | ||
| 169 | + | /// Construct instruction metadata from immutable, region-owned operands. |
|
| 170 | + | /// The source identity is retained as a value and is never dereferenced. |
|
| 171 | + | export fn buildInstruction 'view ( |
|
| 172 | + | code: &'view Code 'view, |
|
| 173 | + | identity: ?*unsafe il::Instr, |
|
| 174 | + | storage: &Session 'view |
|
| 175 | + | ) -> Instruction 'view throws (alloc::AllocError) { |
|
| 165 | 176 | let noTargets: [u32; 0] = []; |
|
| 166 | 177 | let mut targets: &'view [u32] = try storage.copy(&noTargets[..]); |
|
| 167 | - | match source { |
|
| 168 | - | case il::Instr::Jmp { target, .. } => { |
|
| 178 | + | let mut count: u64 = 0; |
|
| 179 | + | let mut destination: ?il::Reg = nil; |
|
| 180 | + | let mut isCall = false; |
|
| 181 | + | match code { |
|
| 182 | + | case Code::Fixed(fixed) => { |
|
| 183 | + | let cursor = il::registers(fixed); |
|
| 184 | + | assert not cursor.arguments, "buildInstruction: expected inline operands"; |
|
| 185 | + | set count = cursor.count as u64; |
|
| 186 | + | set destination = il::instrDst(*fixed); |
|
| 187 | + | set isCall = il::isCall(*fixed); |
|
| 188 | + | }, |
|
| 189 | + | case Code::Call { dst, func, args, .. } => { |
|
| 190 | + | let prefix = [*func]; |
|
| 191 | + | set count = registerCount(&prefix[..]) + registerCount(*args); |
|
| 192 | + | set destination = *dst; |
|
| 193 | + | set isCall = true; |
|
| 194 | + | }, |
|
| 195 | + | case Code::Jmp { target, args } => { |
|
| 196 | + | set count = registerCount(*args); |
|
| 169 | 197 | set targets = try storage.fill(*target, 1); |
|
| 170 | 198 | }, |
|
| 171 | - | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 199 | + | case Code::Br { a, b, thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
|
| 200 | + | let prefix = [*a, *b]; |
|
| 201 | + | set count = registerCount(&prefix[..]) + registerCount(*thenArgs) + registerCount(*elseArgs); |
|
| 172 | 202 | let edges = try storage.fill(*thenTarget, 2); |
|
| 173 | 203 | set edges[1] = *elseTarget; |
|
| 174 | 204 | set targets = &edges[..]; |
|
| 175 | 205 | }, |
|
| 176 | - | case il::Instr::Switch { defaultTarget, cases, .. } => { |
|
| 177 | - | let cases: &[il::SwitchCase] = *cases; |
|
| 206 | + | case Code::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 207 | + | let prefix = [*val]; |
|
| 208 | + | set count = registerCount(&prefix[..]) + registerCount(*defaultArgs); |
|
| 178 | 209 | if cases.len == MAX_ELEMENTS { |
|
| 179 | 210 | throw alloc::AllocError::OutOfMemory; |
|
| 180 | 211 | } |
|
| 181 | 212 | let edges = try storage.fill(*defaultTarget, cases.len + 1); |
|
| 182 | - | for item, i in cases { |
|
| 213 | + | for item, i in *cases { |
|
| 183 | 214 | set edges[i + 1] = item.target; |
|
| 215 | + | set count += registerCount(item.args); |
|
| 184 | 216 | } |
|
| 185 | 217 | set targets = &edges[..]; |
|
| 186 | 218 | }, |
|
| 187 | - | else => {}, |
|
| 188 | 219 | } |
|
| 189 | - | let mut cursor = il::registers(source); |
|
| 190 | - | let mut count: u32 = 0; |
|
| 191 | - | while let reg = il::nextReg(&mut cursor, source) { |
|
| 192 | - | if count == MAX_ELEMENTS { |
|
| 193 | - | throw alloc::AllocError::OutOfMemory; |
|
| 194 | - | } |
|
| 195 | - | set count += 1; |
|
| 220 | + | if count > MAX_ELEMENTS as u64 { |
|
| 221 | + | throw alloc::AllocError::OutOfMemory; |
|
| 196 | 222 | } |
|
| 197 | - | let operands = try storage.fill(il::Reg { n: 0 }, count); |
|
| 198 | - | set cursor = il::registers(source); |
|
| 199 | - | for i in 0..count { |
|
| 200 | - | let reg = il::nextReg(&mut cursor, source) |
|
| 201 | - | else panic "publishInstruction: operand count changed"; |
|
| 202 | - | set operands[i] = reg; |
|
| 223 | + | let operands = try storage.fill(il::Reg { n: 0 }, count as u32); |
|
| 224 | + | let mut written: u32 = 0; |
|
| 225 | + | match code { |
|
| 226 | + | case Code::Fixed(fixed) => { |
|
| 227 | + | let cursor = il::registers(fixed); |
|
| 228 | + | for i in 0..cursor.count { |
|
| 229 | + | set operands[i] = cursor.fixed[i]; |
|
| 230 | + | } |
|
| 231 | + | set written = cursor.count; |
|
| 232 | + | }, |
|
| 233 | + | case Code::Call { func, args, .. } => { |
|
| 234 | + | let prefix = [*func]; |
|
| 235 | + | set written = copyRegisters(&prefix[..], operands, 0); |
|
| 236 | + | set written = copyRegisters(*args, operands, written); |
|
| 237 | + | }, |
|
| 238 | + | case Code::Jmp { args, .. } => set written = copyRegisters(*args, operands, 0), |
|
| 239 | + | case Code::Br { a, b, thenArgs, elseArgs, .. } => { |
|
| 240 | + | let prefix = [*a, *b]; |
|
| 241 | + | set written = copyRegisters(&prefix[..], operands, 0); |
|
| 242 | + | set written = copyRegisters(*thenArgs, operands, written); |
|
| 243 | + | set written = copyRegisters(*elseArgs, operands, written); |
|
| 244 | + | }, |
|
| 245 | + | case Code::Switch { val, defaultArgs, cases, .. } => { |
|
| 246 | + | let prefix = [*val]; |
|
| 247 | + | set written = copyRegisters(&prefix[..], operands, 0); |
|
| 248 | + | set written = copyRegisters(*defaultArgs, operands, written); |
|
| 249 | + | for item in *cases { |
|
| 250 | + | set written = copyRegisters(item.args, operands, written); |
|
| 251 | + | } |
|
| 252 | + | }, |
|
| 203 | 253 | } |
|
| 204 | - | let code = try storage.new(try publishCode(source, storage)); |
|
| 205 | - | return Instruction 'view { identity: source as *unsafe il::Instr, code, |
|
| 206 | - | destination: il::instrDst(*source), isCall: il::isCall(*source), |
|
| 254 | + | assert written == operands.len, "buildInstruction: operand count changed"; |
|
| 255 | + | return Instruction 'view { identity, code, destination, isCall, |
|
| 207 | 256 | operands: &operands[..], targets }; |
|
| 208 | 257 | } |
|
| 209 | 258 | ||
| 259 | + | /// Count source registers in one operand group, including repeated uses. |
|
| 260 | + | fn registerCount(values: &[il::Val]) -> u64 { |
|
| 261 | + | let mut count: u64 = 0; |
|
| 262 | + | for value in values { |
|
| 263 | + | if let case il::Val::Reg(_) = value { |
|
| 264 | + | set count += 1; |
|
| 265 | + | } |
|
| 266 | + | } |
|
| 267 | + | return count; |
|
| 268 | + | } |
|
| 269 | + | ||
| 270 | + | /// Write source registers at an offset and return the next free position. |
|
| 271 | + | fn copyRegisters(values: &[il::Val], output: &mut [il::Reg], offset: u32) -> u32 { |
|
| 272 | + | let mut next = offset; |
|
| 273 | + | for value in values { |
|
| 274 | + | if let case il::Val::Reg(reg) = value { |
|
| 275 | + | set output[next] = reg; |
|
| 276 | + | set next += 1; |
|
| 277 | + | } |
|
| 278 | + | } |
|
| 279 | + | return next; |
|
| 280 | + | } |
|
| 281 | + | ||
| 210 | 282 | /// Copy the variable-length operands of an instruction into the session. |
|
| 211 | 283 | unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view) |
|
| 212 | 284 | -> Code 'view throws (alloc::AllocError) |
|
| 213 | 285 | { |
|
| 214 | 286 | match *source { |
| 247 | 319 | /// Return the instruction data with session-owned operand tables. |
|
| 248 | 320 | export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view { |
|
| 249 | 321 | return instruction.code; |
|
| 250 | 322 | } |
|
| 251 | 323 | ||
| 252 | - | /// Return the original instruction pointer for identity comparisons. |
|
| 253 | - | export fn identity 'view (instruction: &Instruction 'view) -> *unsafe il::Instr { |
|
| 324 | + | /// Return the original instruction pointer when the instruction was imported. |
|
| 325 | + | export fn identity 'view (instruction: &Instruction 'view) -> ?*unsafe il::Instr { |
|
| 254 | 326 | return instruction.identity; |
|
| 255 | 327 | } |
|
| 256 | 328 | ||
| 257 | 329 | /// Return an instruction's destination register. |
|
| 258 | 330 | export fn destination 'view (instruction: &Instruction 'view) -> ?il::Reg { |
lib/std/lang/il/publishedTests.rad
+32 -2
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use std::lang::alloc; |
|
| 5 | 5 | use std::lang::il; |
|
| 6 | 6 | use std::lang::il::published; |
|
| 7 | 7 | ||
| 8 | + | /// Checked instruction construction preserves argument order and repeated uses. |
|
| 9 | + | @test fn testCheckedInstruction() throws (testing::TestError) { |
|
| 10 | + | static DATA: [u8; 4096] = [0; 4096]; |
|
| 11 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 12 | + | use arena as storage in { |
|
| 13 | + | let instruction = try! buildCall(&storage); |
|
| 14 | + | assert published::identity(&instruction) == nil; |
|
| 15 | + | assert published::isCall(&instruction); |
|
| 16 | + | let destination = published::destination(&instruction) else panic; |
|
| 17 | + | assert destination.n == 9; |
|
| 18 | + | let regs = published::registers(&instruction); |
|
| 19 | + | assert regs.len == 3 and regs[0].n == 2 and regs[1].n == 7 and regs[2].n == 7; |
|
| 20 | + | assert published::successors(&instruction).len == 0; |
|
| 21 | + | } |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Construct a call whose argument source ends with this stack frame. |
|
| 25 | + | fn buildCall 'view (storage: &Session 'view) |
|
| 26 | + | -> published::Instruction 'view throws (alloc::AllocError) |
|
| 27 | + | { |
|
| 28 | + | let values = [il::Val::Imm(4), il::Val::Reg(il::Reg { n: 7 }), |
|
| 29 | + | il::Val::Reg(il::Reg { n: 7 })]; |
|
| 30 | + | let args = try storage.copy(&values[..]); |
|
| 31 | + | let code = try storage.new(published::Code 'view::Call { retTy: il::Type::W64, |
|
| 32 | + | dst: il::Reg { n: 9 }, func: il::Val::Reg(il::Reg { n: 2 }), args }); |
|
| 33 | + | return try published::buildInstruction(code, nil, storage); |
|
| 34 | + | } |
|
| 35 | + | ||
| 8 | 36 | /// Publish operands from source tables that end with this stack frame. |
|
| 9 | 37 | unsafe fn transientInput 'view (storage: &Session 'view) -> published::Function 'view |
|
| 10 | 38 | throws (alloc::AllocError) |
|
| 11 | 39 | { |
|
| 12 | 40 | let params = [il::Param { value: il::Reg { n: 5 }, type: il::Type::W64 }]; |
| 79 | 107 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 80 | 108 | let input: 'input = &function in { |
|
| 81 | 109 | use arena as storage in { |
|
| 82 | 110 | let view = try! published::publish(input, &storage); |
|
| 83 | 111 | assert view.blocks.len == 1; |
|
| 84 | - | assert view.blocks[0].identity == &blocks[0]; |
|
| 112 | + | let blockIdentity = view.blocks[0].identity else panic; |
|
| 113 | + | assert blockIdentity == &blocks[0]; |
|
| 85 | 114 | let instruction = &view.blocks[0].instructions[0]; |
|
| 86 | - | assert published::identity(instruction) == &instructions[0]; |
|
| 115 | + | let identity = published::identity(instruction) else panic; |
|
| 116 | + | assert identity == &instructions[0]; |
|
| 87 | 117 | set arguments[1] = il::Val::Reg(il::Reg { n: 99 }); |
|
| 88 | 118 | set cases[0].target = 42; |
|
| 89 | 119 | set instructions[0] = il::Instr::Ret { val: nil }; |
|
| 90 | 120 | let registers = published::registers(instruction); |
|
| 91 | 121 | assert registers.len == 3; |