compiler: Check block sealing and parameter binding
9066f1a303ecda6243adbe4355d997e66fa2f5b68a81c3a8c89f4b44e70a6369
1 parent
8b5ccb06
lib/std/lang/lower.rad
+30 -19
| 2206 | 2206 | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 2207 | 2207 | labelBase: *[u8], |
|
| 2208 | 2208 | param: il::Param |
|
| 2209 | 2209 | ) -> BlockId throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2210 | 2210 | let block = try createBlock(self, labelBase); |
|
| 2211 | - | let blk = getBlockMut(self, block); |
|
| 2212 | - | blk.params.append(param, alloc::arenaAllocator(self.arena)); |
|
| 2211 | + | let allocator = alloc::arenaAllocator(self.arena); |
|
| 2212 | + | self.blockData[*block].params.append(param, allocator); |
|
| 2213 | 2213 | ||
| 2214 | 2214 | return block; |
|
| 2215 | 2215 | } |
|
| 2216 | 2216 | ||
| 2217 | 2217 | /// Switch to building a different block. |
| 2224 | 2224 | /// |
|
| 2225 | 2225 | /// Sealing enables SSA construction to resolve variable uses by looking up |
|
| 2226 | 2226 | /// values from predecessors and inserting block parameters as needed. It |
|
| 2227 | 2227 | /// does not prevent instructions from being added to the block. |
|
| 2228 | 2228 | unsafe fn sealBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2229 | - | let blk = getBlockMut(self, block); |
|
| 2230 | - | let case Sealed::No = blk.sealState else { |
|
| 2231 | - | return; // Already sealed. |
|
| 2232 | - | }; |
|
| 2233 | - | // Keep the current parameter count. Resolution can add more parameters. |
|
| 2234 | - | let paramCount = blk.paramVars.len; |
|
| 2235 | - | set blk.sealState = Sealed::Yes; |
|
| 2229 | + | let paramCount = markBlockSealed(&mut self.blockData[*block]) else return; |
|
| 2236 | 2230 | ||
| 2237 | 2231 | // Complete each parameter that was created before sealing. |
|
| 2238 | 2232 | for paramIdx in 0..paramCount { |
|
| 2239 | 2233 | let varId = self.blockData[*block].paramVars[paramIdx]; |
|
| 2240 | 2234 | try resolveBlockArgs(self, block, Var(varId), paramIdx); |
|
| 2241 | 2235 | } |
|
| 2242 | 2236 | } |
|
| 2243 | 2237 | ||
| 2238 | + | /// Mark a block sealed and return its pending parameter count, or nil if already sealed. |
|
| 2239 | + | fn markBlockSealed(block: &mut BlockData) -> ?u32 { |
|
| 2240 | + | let case Sealed::No = block.sealState else { |
|
| 2241 | + | return nil; // Already sealed. |
|
| 2242 | + | }; |
|
| 2243 | + | // Keep the current parameter count. Resolution can add more parameters. |
|
| 2244 | + | let paramCount = block.paramVars.len; |
|
| 2245 | + | set block.sealState = Sealed::Yes; |
|
| 2246 | + | return paramCount; |
|
| 2247 | + | } |
|
| 2248 | + | ||
| 2244 | 2249 | /// Seal a block and switch to it. |
|
| 2245 | 2250 | unsafe fn switchToAndSeal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2246 | 2251 | try sealBlock(self, block); |
|
| 2247 | 2252 | switchToBlock(self, block); |
|
| 2248 | 2253 | } |
| 3110 | 3115 | let reg = nextReg(self); |
|
| 3111 | 3116 | let type = getVar(&self.vars, v).type; |
|
| 3112 | 3117 | ||
| 3113 | 3118 | // Create block parameter and add it to the block. |
|
| 3114 | 3119 | let param = il::Param { value: reg, type }; |
|
| 3115 | - | let blk = getBlockMut(self, block); |
|
| 3116 | - | let paramIdx = blk.paramVars.len; |
|
| 3117 | - | blk.params.append(param, alloc::arenaAllocator(self.arena)); |
|
| 3118 | - | blk.paramVars.append(*v, alloc::arenaAllocator(self.arena)); // Associate variable with parameter. |
|
| 3119 | - | ||
| 3120 | - | // Record that this variable's value in this block is now the parameter register. |
|
| 3121 | - | // This must happen before the predecessor loop to handle self-referential loops. |
|
| 3122 | - | set blk.vars[*v] = il::Val::Reg(reg); |
|
| 3120 | + | let allocator = alloc::arenaAllocator(self.arena); |
|
| 3121 | + | let paramIdx = bindBlockParam(&mut self.blockData[*block], param, v, allocator); |
|
| 3123 | 3122 | ||
| 3124 | - | if blk.sealState == Sealed::Yes { |
|
| 3123 | + | if self.blockData[*block].sealState == Sealed::Yes { |
|
| 3125 | 3124 | // Block sealed: check for trivial phi before committing. If all |
|
| 3126 | 3125 | // predecessors provide the same value, we can remove the param we |
|
| 3127 | 3126 | // just created and use that value directly. |
|
| 3128 | 3127 | if let trivial = try getTrivialPhiVal(self, block, v) { |
|
| 3129 | 3128 | let provisional = il::Val::Reg(reg); |
|
| 3130 | 3129 | removeLastBlockParam(self, block); |
|
| 3131 | 3130 | rewriteCachedVarValue(self, v, provisional, trivial); |
|
| 3132 | - | set getBlockMut(self, block).vars[*v] = trivial; |
|
| 3131 | + | set self.blockData[*block].vars[*v] = trivial; |
|
| 3133 | 3132 | return trivial; |
|
| 3134 | 3133 | } |
|
| 3135 | 3134 | // Non-trivial phi: patch predecessors to pass their values. |
|
| 3136 | 3135 | try resolveBlockArgs(self, block, v, paramIdx); |
|
| 3137 | 3136 | } |
|
| 3138 | 3137 | return il::Val::Reg(reg); |
|
| 3139 | 3138 | } |
|
| 3140 | 3139 | ||
| 3140 | + | /// Register a block parameter and its provisional variable value. |
|
| 3141 | + | fn bindBlockParam(block: &mut BlockData, param: il::Param, v: Var, allocator: alloc::Allocator) -> u32 { |
|
| 3142 | + | let paramIdx = block.paramVars.len; |
|
| 3143 | + | block.params.append(param, allocator); |
|
| 3144 | + | block.paramVars.append(*v, allocator); // Associate variable with parameter. |
|
| 3145 | + | ||
| 3146 | + | // Record that this variable's value in this block is now the parameter register. |
|
| 3147 | + | // This must happen before the predecessor loop to handle self-referential loops. |
|
| 3148 | + | set block.vars[*v] = il::Val::Reg(param.value); |
|
| 3149 | + | return paramIdx; |
|
| 3150 | + | } |
|
| 3151 | + | ||
| 3141 | 3152 | /// Complete the block parameter at `paramIdx`. Look up the variable's value in |
|
| 3142 | 3153 | /// each predecessor and patch its terminator with the edge argument. |
|
| 3143 | 3154 | /// |
|
| 3144 | 3155 | /// This is the block-parameter equivalent of adding operands to a phi-function in |
|
| 3145 | 3156 | /// traditional SSA. Where a phi-function merges values at the join point: |
test/tests/ssa.predecessor.storage.rad
+16 -0
| 36 | 36 | set count += 1; |
|
| 37 | 37 | } |
|
| 38 | 38 | return first + second + count; |
|
| 39 | 39 | } |
|
| 40 | 40 | ||
| 41 | + | /// Seal nested loop headers with stable and changing carried values. |
|
| 42 | + | fn nested(limit: u32) -> u32 { |
|
| 43 | + | let mut count: u32 = 0; |
|
| 44 | + | let mut stable: u32 = 23; |
|
| 45 | + | for outer in 0..limit { |
|
| 46 | + | for inner in 0..outer + 1 { |
|
| 47 | + | set count += 1; |
|
| 48 | + | if inner % 2 == 0 { |
|
| 49 | + | set stable = 23; |
|
| 50 | + | } |
|
| 51 | + | } |
|
| 52 | + | } |
|
| 53 | + | return count + stable; |
|
| 54 | + | } |
|
| 55 | + | ||
| 41 | 56 | /// Compare both branch paths over empty and repeated loop iterations. |
|
| 42 | 57 | @default fn main() -> u32 { |
|
| 43 | 58 | for limit in 0..32 { |
|
| 44 | 59 | assert unchanged(limit) == 18 + limit; |
|
| 60 | + | assert nested(limit) == 23 + limit * (limit + 1) / 2; |
|
| 45 | 61 | for choose in [false, true] { |
|
| 46 | 62 | let mut total: u32 = 3; |
|
| 47 | 63 | let mut carry: u32 = 5; |
|
| 48 | 64 | for index in 0..limit { |
|
| 49 | 65 | set carry += 7 if choose and index % 2 == 0 else 13; |