rv64: Reject unsafe spilled block arguments
c08a46cac1c8b2f94ac06c0de40e91c3bd866eb391aac24f729bc71d937b7e3a
RV64 block-argument emission wrote spilled destinations eagerly during parallel assignment. A destination spill slot could overwrite an old value still needed as a later source, silently corrupting live values. Detect overlapping spill assignments before emitting the edge and stop code generation for this unsupported case. Safe spilled block arguments continue to use eager stores without reserving frame staging space. Assisted-by: Codex:gpt-5.6-sol
1 parent
7fec2230
lib/std/arch/rv64/isel.rad
+36 -1
| 1146 | 1146 | } |
|
| 1147 | 1147 | ||
| 1148 | 1148 | /// Emit moves from block arguments to target block's parameter registers. |
|
| 1149 | 1149 | /// |
|
| 1150 | 1150 | /// Handles spilled destinations directly, then delegates to [`emitParallelMoves`] |
|
| 1151 | - | /// for the remaining register-to-register parallel move resolution. |
|
| 1151 | + | /// for the remaining register-to-register parallel move resolution. Edges that |
|
| 1152 | + | /// would overwrite an unconsumed spill source are unsupported. |
|
| 1152 | 1153 | fn emitBlockArgs(s: *mut Selector, func: *il::Fn, target: u32, args: *mut [il::Val]) { |
|
| 1153 | 1154 | if args.len == 0 { |
|
| 1154 | 1155 | return; |
|
| 1155 | 1156 | } |
|
| 1156 | 1157 | let block = &func.blocks[target]; |
|
| 1157 | 1158 | assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch"; |
|
| 1158 | 1159 | assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments"; |
|
| 1159 | 1160 | ||
| 1161 | + | // The parallel-move resolver only handles register destinations. Keep eager |
|
| 1162 | + | // stores for independent spill slots, but reject dependencies that would |
|
| 1163 | + | // require stack staging rather than silently miscompiling them. |
|
| 1164 | + | for arg, i in args { |
|
| 1165 | + | if let dstSlot = regalloc::spill::spillSlot(&s.ralloc.spill, block.params[i].value) { |
|
| 1166 | + | let mut changesSlot = true; |
|
| 1167 | + | if let case il::Val::Reg(src) = arg { |
|
| 1168 | + | if let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) { |
|
| 1169 | + | if srcSlot == dstSlot { |
|
| 1170 | + | set changesSlot = false; |
|
| 1171 | + | } |
|
| 1172 | + | } |
|
| 1173 | + | } |
|
| 1174 | + | if changesSlot { |
|
| 1175 | + | for source, j in args { |
|
| 1176 | + | if let case il::Val::Reg(src) = source { |
|
| 1177 | + | if let sourceSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) { |
|
| 1178 | + | if sourceSlot == dstSlot { |
|
| 1179 | + | if let sourceDstSlot = regalloc::spill::spillSlot( |
|
| 1180 | + | &s.ralloc.spill, block.params[j].value |
|
| 1181 | + | ) { |
|
| 1182 | + | assert sourceDstSlot == dstSlot or j < i, |
|
| 1183 | + | "emitBlockArgs: overlapping spilled block arguments are unsupported"; |
|
| 1184 | + | } else { |
|
| 1185 | + | panic "emitBlockArgs: overlapping spilled block arguments are unsupported"; |
|
| 1186 | + | } |
|
| 1187 | + | } |
|
| 1188 | + | } |
|
| 1189 | + | } |
|
| 1190 | + | } |
|
| 1191 | + | } |
|
| 1192 | + | } |
|
| 1193 | + | } |
|
| 1194 | + | ||
| 1160 | 1195 | // Destination registers for each arg. |
|
| 1161 | 1196 | // Zero means the destination is spilled or skipped. |
|
| 1162 | 1197 | let mut dsts: [gen::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS]; |
|
| 1163 | 1198 | ||
| 1164 | 1199 | for arg, i in args { |