refactor(lower): own control-flow edge arguments
2d2cb3d60788723d97b746954950095d053885da83013ff049260dad63ad2a31
Keep jump, branch, and switch arguments in the function lowerer's owned block state while SSA construction mutates them. Publish raw IL aliases only after each block is complete, so edge rewrites and growth stay in safe Radiance.
1 parent
84158aa3
lib/std/lang/lower.rad
+356 -151
| 528 | 528 | ||
| 529 | 529 | /// A handle to a basic block within the current function. |
|
| 530 | 530 | /// Block handles are stable, they don't change as more blocks are added. |
|
| 531 | 531 | export record BlockId: Copy(u32); |
|
| 532 | 532 | ||
| 533 | + | /// Construction-time switch case with safely owned edge arguments. |
|
| 534 | + | record LowerSwitchCase { |
|
| 535 | + | /// Constant value selecting this case. |
|
| 536 | + | value: i64, |
|
| 537 | + | /// Destination block. |
|
| 538 | + | target: BlockId, |
|
| 539 | + | /// Mutable argument capacity retained until final IL publication. |
|
| 540 | + | args: *mut [il::Val], |
|
| 541 | + | } |
|
| 542 | + | ||
| 543 | + | /// Construction-time terminator edges with safely owned argument storage. |
|
| 544 | + | union LowerTerminator { |
|
| 545 | + | /// No jump, branch, or switch has been recorded. |
|
| 546 | + | None, |
|
| 547 | + | /// One unconditional edge. |
|
| 548 | + | Jmp { |
|
| 549 | + | /// Destination block. |
|
| 550 | + | target: BlockId, |
|
| 551 | + | /// Mutable argument capacity retained until final IL publication. |
|
| 552 | + | args: *mut [il::Val], |
|
| 553 | + | }, |
|
| 554 | + | /// Two conditional edges. |
|
| 555 | + | Br { |
|
| 556 | + | /// Destination when the comparison succeeds. |
|
| 557 | + | thenTarget: BlockId, |
|
| 558 | + | /// Mutable argument capacity for the successful edge. |
|
| 559 | + | thenArgs: *mut [il::Val], |
|
| 560 | + | /// Destination when the comparison fails. |
|
| 561 | + | elseTarget: BlockId, |
|
| 562 | + | /// Mutable argument capacity for the failed edge. |
|
| 563 | + | elseArgs: *mut [il::Val], |
|
| 564 | + | }, |
|
| 565 | + | /// Default and case edges for a switch. |
|
| 566 | + | Switch { |
|
| 567 | + | /// Destination when no case matches. |
|
| 568 | + | defaultTarget: BlockId, |
|
| 569 | + | /// Mutable argument capacity for the default edge. |
|
| 570 | + | defaultArgs: *mut [il::Val], |
|
| 571 | + | /// Cases and their mutable argument capacities. |
|
| 572 | + | cases: *mut [LowerSwitchCase], |
|
| 573 | + | }, |
|
| 574 | + | } |
|
| 575 | + | ||
| 533 | 576 | /// Internal block state during construction. |
|
| 534 | 577 | /// |
|
| 535 | 578 | /// The key invariants: |
|
| 536 | 579 | /// |
|
| 537 | 580 | /// - A block is "open" if it has no terminator; instructions can be added. |
| 549 | 592 | /// whose predecessor arguments must be resolved. |
|
| 550 | 593 | paramVars: *mut [u32], |
|
| 551 | 594 | /// Instructions accumulated so far. The last instruction should eventually |
|
| 552 | 595 | /// be a terminator. |
|
| 553 | 596 | instrs: *mut [il::Instr], |
|
| 597 | + | /// Safely owned edge data for the final jump, branch, or switch instruction. |
|
| 598 | + | terminator: LowerTerminator, |
|
| 554 | 599 | /// Debug source locations, one per instruction. Only populated when |
|
| 555 | 600 | /// debug info is enabled. |
|
| 556 | 601 | locs: *mut [il::SrcLoc], |
|
| 557 | 602 | /// Predecessor block ids. Used for SSA construction to propagate values |
|
| 558 | 603 | /// from predecessors when a variable is used before being defined locally. |
| 2101 | 2146 | for i in 0..self.blockData.len { |
|
| 2102 | 2147 | let blk = &mut self.blockData[i]; |
|
| 2103 | 2148 | if blk.vars[*v] == from { |
|
| 2104 | 2149 | set blk.vars[*v] = to; |
|
| 2105 | 2150 | } |
|
| 2106 | - | if blk.instrs.len > 0 { |
|
| 2107 | - | let ix = blk.instrs.len - 1; |
|
| 2108 | - | match &mut blk.instrs[ix] { |
|
| 2109 | - | case il::Instr::Jmp { args, .. } => { |
|
| 2110 | - | unsafe { |
|
| 2111 | - | rewriteValInSlice(*args, from, to); |
|
| 2112 | - | } |
|
| 2113 | - | }, |
|
| 2114 | - | case il::Instr::Br { thenArgs, elseArgs, .. } => { |
|
| 2115 | - | unsafe { |
|
| 2116 | - | rewriteValInSlice(*thenArgs, from, to); |
|
| 2117 | - | rewriteValInSlice(*elseArgs, from, to); |
|
| 2118 | - | } |
|
| 2119 | - | } |
|
| 2120 | - | case il::Instr::Switch { defaultArgs, cases, .. } => { |
|
| 2121 | - | unsafe { |
|
| 2122 | - | rewriteValInSlice(*defaultArgs, from, to); |
|
| 2123 | - | rewriteSwitchArgs(*cases, from, to); |
|
| 2124 | - | } |
|
| 2125 | - | } |
|
| 2126 | - | else => {} |
|
| 2127 | - | } |
|
| 2151 | + | rewriteTerminatorArgs(&mut blk.terminator, from, to); |
|
| 2152 | + | } |
|
| 2153 | + | } |
|
| 2154 | + | ||
| 2155 | + | /// Rewrite provisional values in safely owned terminator edge arguments. |
|
| 2156 | + | fn rewriteTerminatorArgs( |
|
| 2157 | + | terminator: &mut LowerTerminator, |
|
| 2158 | + | from: il::Val, |
|
| 2159 | + | to: il::Val, |
|
| 2160 | + | ) { |
|
| 2161 | + | match terminator { |
|
| 2162 | + | case LowerTerminator::None => {} |
|
| 2163 | + | case LowerTerminator::Jmp { args, .. } => { |
|
| 2164 | + | rewriteValInSlice(&mut args[..], from, to); |
|
| 2165 | + | } |
|
| 2166 | + | case LowerTerminator::Br { thenArgs, elseArgs, .. } => { |
|
| 2167 | + | rewriteValInSlice(&mut thenArgs[..], from, to); |
|
| 2168 | + | rewriteValInSlice(&mut elseArgs[..], from, to); |
|
| 2169 | + | } |
|
| 2170 | + | case LowerTerminator::Switch { defaultArgs, cases, .. } => { |
|
| 2171 | + | rewriteValInSlice(&mut defaultArgs[..], from, to); |
|
| 2172 | + | rewriteSwitchArgs(&mut cases[..], from, to); |
|
| 2128 | 2173 | } |
|
| 2129 | 2174 | } |
|
| 2130 | 2175 | } |
|
| 2131 | 2176 | ||
| 2132 | - | /// Replace a provisional value in each switch case's argument sequence. |
|
| 2133 | - | fn rewriteSwitchArgs(cases: &mut [il::SwitchCase], from: il::Val, to: il::Val) { |
|
| 2177 | + | /// Replace a provisional value in each construction-time switch argument sequence. |
|
| 2178 | + | fn rewriteSwitchArgs(cases: &mut [LowerSwitchCase], from: il::Val, to: il::Val) { |
|
| 2134 | 2179 | for i in 0..cases.len { |
|
| 2135 | - | let branch = &mut cases[i]; |
|
| 2136 | - | unsafe { |
|
| 2137 | - | rewriteValInSlice(branch.args, from, to); |
|
| 2138 | - | } |
|
| 2180 | + | rewriteValInSlice(cases[i].args, from, to); |
|
| 2139 | 2181 | } |
|
| 2140 | 2182 | } |
|
| 2141 | 2183 | ||
| 2142 | 2184 | /// Replace all occurrences of `from` with `to` in an args slice. |
|
| 2143 | 2185 | fn rewriteValInSlice(args: &mut [il::Val], from: il::Val, to: il::Val) { |
| 2193 | 2235 | blocks.append(BlockData { |
|
| 2194 | 2236 | label, |
|
| 2195 | 2237 | params: &mut [], |
|
| 2196 | 2238 | paramVars: &mut [], |
|
| 2197 | 2239 | instrs: &mut [], |
|
| 2240 | + | terminator: LowerTerminator::None, |
|
| 2198 | 2241 | locs: &mut [], |
|
| 2199 | 2242 | preds: &mut [], |
|
| 2200 | 2243 | vars, |
|
| 2201 | 2244 | sealState: Sealed::No, |
|
| 2202 | 2245 | loopDepth, |
| 2298 | 2341 | block.locs.append(srcLoc, allocator); |
|
| 2299 | 2342 | } |
|
| 2300 | 2343 | block.instrs.append(*instr, allocator); |
|
| 2301 | 2344 | } |
|
| 2302 | 2345 | ||
| 2303 | - | /// Emit an unconditional jump to `target`. |
|
| 2304 | - | fn emitJmp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2346 | + | /// Record a terminator instruction and retain its mutable edges in safe storage. |
|
| 2347 | + | fn recordTerminator 'arena 'phase 'function ( |
|
| 2348 | + | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 2349 | + | instr: il::Instr, |
|
| 2350 | + | terminator: LowerTerminator, |
|
| 2351 | + | ) where 'arena: 'phase, 'phase: 'function { |
|
| 2352 | + | let block = currentBlock(self); |
|
| 2353 | + | emit(self, instr); |
|
| 2354 | + | set self.blockData[*block].terminator = terminator; |
|
| 2355 | + | } |
|
| 2356 | + | ||
| 2357 | + | /// Emit a jump placeholder while retaining its mutable edge arguments. |
|
| 2358 | + | fn emitJmpTerminator 'arena 'phase 'function ( |
|
| 2359 | + | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 2360 | + | target: BlockId, |
|
| 2361 | + | args: *mut [il::Val], |
|
| 2362 | + | ) where 'arena: 'phase, 'phase: 'function { |
|
| 2363 | + | unsafe { |
|
| 2364 | + | recordTerminator( |
|
| 2365 | + | self, |
|
| 2366 | + | il::Instr::Jmp { target: *target, args: &mut [] }, |
|
| 2367 | + | LowerTerminator::Jmp { target, args }, |
|
| 2368 | + | ); |
|
| 2369 | + | } |
|
| 2370 | + | } |
|
| 2371 | + | ||
| 2372 | + | /// Emit a branch placeholder while retaining its mutable edge arguments. |
|
| 2373 | + | fn emitBrTerminator 'arena 'phase 'function ( |
|
| 2374 | + | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 2375 | + | op: il::CmpOp, |
|
| 2376 | + | typ: il::Type, |
|
| 2377 | + | a: il::Val, |
|
| 2378 | + | b: il::Val, |
|
| 2379 | + | thenTarget: BlockId, |
|
| 2380 | + | elseTarget: BlockId, |
|
| 2381 | + | terminator: LowerTerminator, |
|
| 2382 | + | ) where 'arena: 'phase, 'phase: 'function { |
|
| 2305 | 2383 | unsafe { |
|
| 2306 | - | emit(self, il::Instr::Jmp { target: *target, args: &mut [] }); |
|
| 2384 | + | recordTerminator( |
|
| 2385 | + | self, |
|
| 2386 | + | il::Instr::Br { |
|
| 2387 | + | op, typ, a, b, |
|
| 2388 | + | thenTarget: *thenTarget, thenArgs: &mut [], |
|
| 2389 | + | elseTarget: *elseTarget, elseArgs: &mut [], |
|
| 2390 | + | }, |
|
| 2391 | + | terminator, |
|
| 2392 | + | ); |
|
| 2307 | 2393 | } |
|
| 2394 | + | } |
|
| 2395 | + | ||
| 2396 | + | /// Emit a switch placeholder while retaining its mutable edge arguments and cases. |
|
| 2397 | + | fn emitSwitchTerminator 'arena 'phase 'function ( |
|
| 2398 | + | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 2399 | + | val: il::Val, |
|
| 2400 | + | defaultTarget: BlockId, |
|
| 2401 | + | terminator: LowerTerminator, |
|
| 2402 | + | ) where 'arena: 'phase, 'phase: 'function { |
|
| 2403 | + | unsafe { |
|
| 2404 | + | recordTerminator( |
|
| 2405 | + | self, |
|
| 2406 | + | il::Instr::Switch { |
|
| 2407 | + | val, |
|
| 2408 | + | defaultTarget: *defaultTarget, |
|
| 2409 | + | defaultArgs: &mut [], |
|
| 2410 | + | cases: &mut [], |
|
| 2411 | + | }, |
|
| 2412 | + | terminator, |
|
| 2413 | + | ); |
|
| 2414 | + | } |
|
| 2415 | + | } |
|
| 2416 | + | ||
| 2417 | + | /// Emit an unconditional jump to `target`. |
|
| 2418 | + | fn emitJmp 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2419 | + | emitJmpTerminator(self, target, &mut []); |
|
| 2308 | 2420 | addPredecessor(self, target, currentBlock(self)); |
|
| 2309 | 2421 | } |
|
| 2310 | 2422 | ||
| 2311 | 2423 | /// Emit an unconditional jump to `target` with a single argument. |
|
| 2312 | 2424 | fn emitJmpWithArg 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId, arg: il::Val) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2313 | - | unsafe { |
|
| 2314 | - | let args = try allocVal(self, arg); |
|
| 2315 | - | emit(self, il::Instr::Jmp { target: *target, args }); |
|
| 2316 | - | } |
|
| 2425 | + | let args = try allocVal(self, arg); |
|
| 2426 | + | emitJmpTerminator(self, target, args); |
|
| 2317 | 2427 | addPredecessor(self, target, currentBlock(self)); |
|
| 2318 | 2428 | } |
|
| 2319 | 2429 | ||
| 2320 | 2430 | /// Emit an unconditional jump to `target` and switch to it. |
|
| 2321 | 2431 | fn switchAndJumpTo 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, target: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
| 2324 | 2434 | } |
|
| 2325 | 2435 | ||
| 2326 | 2436 | /// Emit a conditional branch based on `cond`. |
|
| 2327 | 2437 | fn emitBr 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: il::Reg, thenBlock: BlockId, elseBlock: BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2328 | 2438 | assert thenBlock <> elseBlock; |
|
| 2329 | - | unsafe { |
|
| 2330 | - | emit(self, il::Instr::Br { |
|
| 2331 | - | op: il::CmpOp::Ne, |
|
| 2332 | - | typ: il::Type::W32, |
|
| 2333 | - | a: il::Val::Reg(cond), |
|
| 2334 | - | b: il::Val::Imm(0), |
|
| 2335 | - | thenTarget: *thenBlock, |
|
| 2336 | - | thenArgs: &mut [], |
|
| 2337 | - | elseTarget: *elseBlock, |
|
| 2338 | - | elseArgs: &mut [], |
|
| 2339 | - | }); |
|
| 2340 | - | } |
|
| 2439 | + | emitBrTerminator( |
|
| 2440 | + | self, |
|
| 2441 | + | il::CmpOp::Ne, |
|
| 2442 | + | il::Type::W32, |
|
| 2443 | + | il::Val::Reg(cond), |
|
| 2444 | + | il::Val::Imm(0), |
|
| 2445 | + | thenBlock, elseBlock, |
|
| 2446 | + | LowerTerminator::Br { |
|
| 2447 | + | thenTarget: thenBlock, thenArgs: &mut [], |
|
| 2448 | + | elseTarget: elseBlock, elseArgs: &mut [], |
|
| 2449 | + | }, |
|
| 2450 | + | ); |
|
| 2341 | 2451 | addPredecessor(self, thenBlock, currentBlock(self)); |
|
| 2342 | 2452 | addPredecessor(self, elseBlock, currentBlock(self)); |
|
| 2343 | 2453 | } |
|
| 2344 | 2454 | ||
| 2345 | 2455 | /// Emit a compare-and-branch instruction with the given comparison op. |
| 2351 | 2461 | b: il::Val, |
|
| 2352 | 2462 | thenBlock: BlockId, |
|
| 2353 | 2463 | elseBlock: BlockId |
|
| 2354 | 2464 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2355 | 2465 | assert thenBlock <> elseBlock; |
|
| 2356 | - | unsafe { |
|
| 2357 | - | emit(self, il::Instr::Br { |
|
| 2358 | - | op, typ, a, b, |
|
| 2359 | - | thenTarget: *thenBlock, thenArgs: &mut [], |
|
| 2360 | - | elseTarget: *elseBlock, elseArgs: &mut [], |
|
| 2361 | - | }); |
|
| 2362 | - | } |
|
| 2466 | + | emitBrTerminator( |
|
| 2467 | + | self, |
|
| 2468 | + | op, typ, a, b, |
|
| 2469 | + | thenBlock, elseBlock, |
|
| 2470 | + | LowerTerminator::Br { |
|
| 2471 | + | thenTarget: thenBlock, thenArgs: &mut [], |
|
| 2472 | + | elseTarget: elseBlock, elseArgs: &mut [], |
|
| 2473 | + | }, |
|
| 2474 | + | ); |
|
| 2363 | 2475 | addPredecessor(self, thenBlock, currentBlock(self)); |
|
| 2364 | 2476 | addPredecessor(self, elseBlock, currentBlock(self)); |
|
| 2365 | 2477 | } |
|
| 2366 | 2478 | ||
| 2367 | 2479 | /// Emit a guard that traps with `ebreak` when a comparison is false. |
| 2804 | 2916 | } |
|
| 2805 | 2917 | } |
|
| 2806 | 2918 | preds.append(*pred, allocator); |
|
| 2807 | 2919 | } |
|
| 2808 | 2920 | ||
| 2921 | + | /// Return whether construction-time edge data must be published. |
|
| 2922 | + | fn hasLowerTerminator(terminator: &LowerTerminator) -> bool { |
|
| 2923 | + | match terminator { |
|
| 2924 | + | case LowerTerminator::None => return false, |
|
| 2925 | + | case LowerTerminator::Jmp { .. }, |
|
| 2926 | + | LowerTerminator::Br { .. }, |
|
| 2927 | + | LowerTerminator::Switch { .. } => |
|
| 2928 | + | return true, |
|
| 2929 | + | } |
|
| 2930 | + | } |
|
| 2931 | + | ||
| 2932 | + | /// Publish one block's safely owned edge arguments into its final IL terminator. |
|
| 2933 | + | unsafe fn publishTerminator( |
|
| 2934 | + | instr: il::Instr, |
|
| 2935 | + | terminator: &mut LowerTerminator, |
|
| 2936 | + | allocator: alloc::Allocator, |
|
| 2937 | + | ) -> il::Instr { |
|
| 2938 | + | match terminator { |
|
| 2939 | + | case LowerTerminator::None => return instr, |
|
| 2940 | + | case LowerTerminator::Jmp { args, .. } => { |
|
| 2941 | + | let case il::Instr::Jmp { target, .. } = instr |
|
| 2942 | + | else panic "publishTerminator: expected jump terminator"; |
|
| 2943 | + | return il::Instr::Jmp { |
|
| 2944 | + | target, |
|
| 2945 | + | args: (&mut args[..]) as *unsafe mut [il::Val], |
|
| 2946 | + | }; |
|
| 2947 | + | } |
|
| 2948 | + | case LowerTerminator::Br { thenArgs, elseArgs, .. } => { |
|
| 2949 | + | let case il::Instr::Br { |
|
| 2950 | + | op, typ, a, b, thenTarget, elseTarget, .. |
|
| 2951 | + | } = instr |
|
| 2952 | + | else panic "publishTerminator: expected branch terminator"; |
|
| 2953 | + | return il::Instr::Br { |
|
| 2954 | + | op, typ, a, b, |
|
| 2955 | + | thenTarget, |
|
| 2956 | + | thenArgs: (&mut thenArgs[..]) as *unsafe mut [il::Val], |
|
| 2957 | + | elseTarget, |
|
| 2958 | + | elseArgs: (&mut elseArgs[..]) as *unsafe mut [il::Val], |
|
| 2959 | + | }; |
|
| 2960 | + | } |
|
| 2961 | + | case LowerTerminator::Switch { defaultArgs, cases, .. } => { |
|
| 2962 | + | let case il::Instr::Switch { val, defaultTarget, .. } = instr |
|
| 2963 | + | else panic "publishTerminator: expected switch terminator"; |
|
| 2964 | + | let mut publishedCases: *mut [il::SwitchCase] = &mut []; |
|
| 2965 | + | for i in 0..cases.len { |
|
| 2966 | + | let branch = &mut cases[i]; |
|
| 2967 | + | publishedCases.append(il::SwitchCase { |
|
| 2968 | + | value: branch.value, |
|
| 2969 | + | target: *branch.target, |
|
| 2970 | + | args: (&mut branch.args[..]) as *unsafe mut [il::Val], |
|
| 2971 | + | }, allocator); |
|
| 2972 | + | } |
|
| 2973 | + | return il::Instr::Switch { |
|
| 2974 | + | val, |
|
| 2975 | + | defaultTarget, |
|
| 2976 | + | defaultArgs: (&mut defaultArgs[..]) as *unsafe mut [il::Val], |
|
| 2977 | + | cases: (&mut publishedCases[..]) as *unsafe mut [il::SwitchCase], |
|
| 2978 | + | }; |
|
| 2979 | + | } |
|
| 2980 | + | } |
|
| 2981 | + | } |
|
| 2982 | + | ||
| 2809 | 2983 | /// Finalize all blocks and return the block array. |
|
| 2810 | 2984 | unsafe fn finalizeBlocks 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) -> *unsafe [il::Block] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2811 | 2985 | let mut blocks: *mut [il::Block] = &mut []; |
|
| 2812 | - | let allocator = alloc::arenaAllocator(self.arena); |
|
| 2986 | + | let allocator = self.allocator; |
|
| 2813 | 2987 | ||
| 2814 | 2988 | for i in 0..self.blockData.len { |
|
| 2815 | - | let data = &mut self.blockData[i]; |
|
| 2989 | + | if hasLowerTerminator(&self.blockData[i].terminator) { |
|
| 2990 | + | let instrIdx = self.blockData[i].instrs.len - 1; |
|
| 2991 | + | let instr = self.blockData[i].instrs[instrIdx]; |
|
| 2992 | + | let published = publishTerminator( |
|
| 2993 | + | instr, |
|
| 2994 | + | &mut self.blockData[i].terminator, |
|
| 2995 | + | allocator, |
|
| 2996 | + | ); |
|
| 2997 | + | set self.blockData[i].instrs[instrIdx] = published; |
|
| 2998 | + | } |
|
| 2816 | 2999 | ||
| 3000 | + | let data = &mut self.blockData[i]; |
|
| 2817 | 3001 | blocks.append(il::Block { |
|
| 2818 | 3002 | label: data.label, |
|
| 2819 | 3003 | params: &data.params[..], |
|
| 2820 | 3004 | instrs: (&mut data.instrs[..]) as *unsafe mut [il::Instr], |
|
| 2821 | 3005 | locs: &data.locs[..], |
| 2886 | 3070 | } |
|
| 2887 | 3071 | return result; |
|
| 2888 | 3072 | } |
|
| 2889 | 3073 | ||
| 2890 | 3074 | /// Allocate a single-value slice in the lowering arena. |
|
| 2891 | - | unsafe fn allocVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) -> *unsafe mut [il::Val] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 3075 | + | fn allocVal 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, val: il::Val) -> *mut [il::Val] throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 2892 | 3076 | let values = [val]; |
|
| 2893 | - | return try copyVals(self, &values[..]); |
|
| 3077 | + | return valueStorage(&values[..], values.len, self.allocator); |
|
| 2894 | 3078 | } |
|
| 2895 | 3079 | ||
| 2896 | 3080 | //////////////////////// |
|
| 2897 | 3081 | // SSA Var Management // |
|
| 2898 | 3082 | //////////////////////// |
| 3194 | 3378 | let pred = BlockId(self.blockData[*block].preds[index]); |
|
| 3195 | 3379 | // This may recursively trigger more block arg resolution if the |
|
| 3196 | 3380 | // predecessor also needs to look up the variable from its predecessors. |
|
| 3197 | 3381 | let val = try useVarInBlock(self, pred, v); |
|
| 3198 | 3382 | assert val <> il::Val::Undef, "createBlockParam: predecessor provides undef value for block parameter"; |
|
| 3199 | - | patchTerminatorArg(self, pred, *block, paramIdx, val); |
|
| 3383 | + | patchTerminatorArg(self, pred, block, paramIdx, val); |
|
| 3200 | 3384 | } |
|
| 3201 | 3385 | } |
|
| 3202 | 3386 | ||
| 3203 | 3387 | /// Check if a block parameter is trivial, i.e. all predecessors provide |
|
| 3204 | 3388 | /// the same value. Returns the trivial value if so. |
| 3237 | 3421 | /// Patch a single terminator argument for a specific edge. This is used during |
|
| 3238 | 3422 | /// SSA construction to pass variable values along control flow edges. |
|
| 3239 | 3423 | fn patchTerminatorArg 'arena 'phase 'function ( |
|
| 3240 | 3424 | self: &mut FnLowerer 'arena 'phase 'function, |
|
| 3241 | 3425 | from: BlockId, // The predecessor block containing the terminator to patch. |
|
| 3242 | - | target: u32, // The index of the target block we're passing the value to. |
|
| 3426 | + | target: BlockId, // The target block receiving the value. |
|
| 3243 | 3427 | paramIdx: u32, // The index of the block parameter to set. |
|
| 3244 | 3428 | val: il::Val // The value to pass as the argument. |
|
| 3245 | 3429 | ) where 'arena: 'phase, 'phase: 'function { |
|
| 3246 | 3430 | let allocator = self.allocator; |
|
| 3247 | - | // Get mutable block data by block id. |
|
| 3248 | 3431 | let data = &mut self.blockData[*from]; |
|
| 3249 | - | let ix = data.instrs.len - 1; // The terminator is always the last instruction. |
|
| 3250 | - | patchEdgeArgs(&mut data.instrs[ix], target, paramIdx, val, allocator); |
|
| 3432 | + | patchEdgeArgs(&mut data.terminator, target, paramIdx, val, allocator); |
|
| 3251 | 3433 | } |
|
| 3252 | 3434 | ||
| 3253 | - | /// Patch all edges in a terminator that pass a value to the target block. |
|
| 3254 | - | fn patchEdgeArgs(instr: &mut il::Instr, target: u32, paramIdx: u32, val: il::Val, allocator: alloc::Allocator) { |
|
| 3255 | - | // TODO: We shouldn't need to use a mutable subscript here, given that the |
|
| 3256 | - | // fields are already mutable. |
|
| 3257 | - | match instr { |
|
| 3258 | - | case il::Instr::Jmp { args, .. } => { |
|
| 3259 | - | unsafe { |
|
| 3260 | - | set *args = growArgs(*args, paramIdx + 1, allocator); |
|
| 3261 | - | set args[paramIdx] = val; |
|
| 3262 | - | } |
|
| 3263 | - | } |
|
| 3264 | - | case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
|
| 3265 | - | // Nb. both branches could target the same block (e.g. `if cond { x } else { x }`). |
|
| 3435 | + | /// Patch all construction-time edges that pass a value to the target block. |
|
| 3436 | + | fn patchEdgeArgs( |
|
| 3437 | + | terminator: &mut LowerTerminator, |
|
| 3438 | + | target: BlockId, |
|
| 3439 | + | paramIdx: u32, |
|
| 3440 | + | val: il::Val, |
|
| 3441 | + | allocator: alloc::Allocator, |
|
| 3442 | + | ) { |
|
| 3443 | + | let capacity = paramIdx + 1; |
|
| 3444 | + | match terminator { |
|
| 3445 | + | case LowerTerminator::None => |
|
| 3446 | + | panic "patchEdgeArgs: predecessor has no edge terminator", |
|
| 3447 | + | case LowerTerminator::Jmp { args, .. } => { |
|
| 3448 | + | growArgs(args, capacity, allocator); |
|
| 3449 | + | set args[paramIdx] = val; |
|
| 3450 | + | } |
|
| 3451 | + | case LowerTerminator::Br { thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 3452 | + | // Both branches may target the same block. |
|
| 3266 | 3453 | if *thenTarget == target { |
|
| 3267 | - | unsafe { |
|
| 3268 | - | set *thenArgs = growArgs(*thenArgs, paramIdx + 1, allocator); |
|
| 3269 | - | set thenArgs[paramIdx] = val; |
|
| 3270 | - | } |
|
| 3454 | + | growArgs(thenArgs, capacity, allocator); |
|
| 3455 | + | set thenArgs[paramIdx] = val; |
|
| 3271 | 3456 | } |
|
| 3272 | 3457 | if *elseTarget == target { |
|
| 3273 | - | unsafe { |
|
| 3274 | - | set *elseArgs = growArgs(*elseArgs, paramIdx + 1, allocator); |
|
| 3275 | - | set elseArgs[paramIdx] = val; |
|
| 3276 | - | } |
|
| 3458 | + | growArgs(elseArgs, capacity, allocator); |
|
| 3459 | + | set elseArgs[paramIdx] = val; |
|
| 3277 | 3460 | } |
|
| 3278 | 3461 | } |
|
| 3279 | - | case il::Instr::Switch { defaultTarget, defaultArgs, cases, .. } => { |
|
| 3462 | + | case LowerTerminator::Switch { defaultTarget, defaultArgs, cases } => { |
|
| 3280 | 3463 | if *defaultTarget == target { |
|
| 3281 | - | unsafe { |
|
| 3282 | - | set *defaultArgs = growArgs(*defaultArgs, paramIdx + 1, allocator); |
|
| 3283 | - | set defaultArgs[paramIdx] = val; |
|
| 3284 | - | } |
|
| 3464 | + | growArgs(defaultArgs, capacity, allocator); |
|
| 3465 | + | set defaultArgs[paramIdx] = val; |
|
| 3285 | 3466 | } |
|
| 3286 | - | unsafe { |
|
| 3287 | - | patchSwitchArgs(*cases, target, paramIdx, val, allocator); |
|
| 3288 | - | } |
|
| 3289 | - | } |
|
| 3290 | - | else => { |
|
| 3291 | - | // Other terminators (e.g. `Ret`, `Unreachable`) don't have successor blocks. |
|
| 3467 | + | patchSwitchArgs(&mut cases[..], target, paramIdx, val, allocator); |
|
| 3292 | 3468 | } |
|
| 3293 | 3469 | } |
|
| 3294 | 3470 | } |
|
| 3295 | 3471 | ||
| 3296 | - | /// Patch every switch case that passes a value to the target block. |
|
| 3297 | - | fn patchSwitchArgs(cases: &mut [il::SwitchCase], target: u32, paramIdx: u32, val: il::Val, allocator: alloc::Allocator) { |
|
| 3472 | + | /// Patch every construction-time switch case targeting the given block. |
|
| 3473 | + | fn patchSwitchArgs( |
|
| 3474 | + | cases: &mut [LowerSwitchCase], |
|
| 3475 | + | target: BlockId, |
|
| 3476 | + | paramIdx: u32, |
|
| 3477 | + | val: il::Val, |
|
| 3478 | + | allocator: alloc::Allocator, |
|
| 3479 | + | ) { |
|
| 3298 | 3480 | let capacity = paramIdx + 1; |
|
| 3299 | 3481 | for i in 0..cases.len { |
|
| 3300 | 3482 | let branch = &mut cases[i]; |
|
| 3301 | 3483 | if branch.target == target { |
|
| 3302 | - | unsafe { |
|
| 3303 | - | set branch.args = growArgs(branch.args, capacity, allocator); |
|
| 3304 | - | set branch.args[paramIdx] = val; |
|
| 3305 | - | } |
|
| 3484 | + | growArgs(&mut branch.args, capacity, allocator); |
|
| 3485 | + | set branch.args[paramIdx] = val; |
|
| 3306 | 3486 | } |
|
| 3307 | 3487 | } |
|
| 3308 | 3488 | } |
|
| 3309 | 3489 | ||
| 3310 | - | /// Grow an args array to hold at least the given capacity. |
|
| 3311 | - | unsafe fn growArgs(args: *unsafe mut [il::Val], capacity: u32, allocator: alloc::Allocator) -> *unsafe mut [il::Val] { |
|
| 3490 | + | /// Grow an argument array to hold the requested capacity. |
|
| 3491 | + | fn growArgs(args: &mut *mut [il::Val], capacity: u32, allocator: alloc::Allocator) { |
|
| 3312 | 3492 | if args.len >= capacity { |
|
| 3313 | - | return args; |
|
| 3493 | + | return; |
|
| 3314 | 3494 | } |
|
| 3315 | - | let newArgs = valueStorage(args, capacity, allocator); |
|
| 3316 | - | return (&mut newArgs[..]) as *unsafe mut [il::Val]; |
|
| 3495 | + | set *args = valueStorage(&args[..], capacity, allocator); |
|
| 3317 | 3496 | } |
|
| 3318 | 3497 | ||
| 3319 | 3498 | /// Select a receiver or explicit parameter name from its AST declaration. |
|
| 3320 | 3499 | fn paramName(params: &[*ast::Node], receiver: ?*ast::Node, index: u32) -> *[u8] throws (LowerError) { |
|
| 3321 | 3500 | let mut position = index; |
| 3816 | 3995 | } |
|
| 3817 | 3996 | } |
|
| 3818 | 3997 | return try finalizeBlocks(self); |
|
| 3819 | 3998 | } |
|
| 3820 | 3999 | ||
| 3821 | - | /// Append an initialized switch case to owned storage. |
|
| 4000 | + | /// Append a construction-time switch case with safely owned arguments. |
|
| 3822 | 4001 | fn appendSwitchCase( |
|
| 3823 | - | cases: &mut *mut [il::SwitchCase], |
|
| 4002 | + | cases: &mut *mut [LowerSwitchCase], |
|
| 3824 | 4003 | value: i64, |
|
| 3825 | 4004 | target: BlockId, |
|
| 3826 | - | args: *unsafe mut [il::Val], |
|
| 4005 | + | args: *mut [il::Val], |
|
| 3827 | 4006 | allocator: alloc::Allocator, |
|
| 3828 | 4007 | ) { |
|
| 3829 | - | cases.append(il::SwitchCase { value, target: *target, args }, allocator); |
|
| 4008 | + | cases.append(LowerSwitchCase { value, target, args }, allocator); |
|
| 3830 | 4009 | } |
|
| 3831 | 4010 | ||
| 3832 | 4011 | /// Lower a scalar match as a switch instruction. |
|
| 3833 | 4012 | unsafe fn lowerMatchSwitch 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, prongs: *[*ast::Node], subject: &MatchSubject, mergeBlock: &mut ?BlockId) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 3834 | 4013 | let mut blocks: *mut [BlockId] = &mut []; |
|
| 3835 | - | let mut cases: *mut [il::SwitchCase] = &mut []; |
|
| 4014 | + | let mut cases: *mut [LowerSwitchCase] = &mut []; |
|
| 3836 | 4015 | let mut defaultIdx: u32 = 0; |
|
| 3837 | 4016 | let entry = currentBlock(self); |
|
| 3838 | 4017 | ||
| 3839 | 4018 | for p, i in prongs { |
|
| 3840 | 4019 | let case ast::NodeValue::MatchProng(prong) = p.value |
| 3855 | 4034 | } |
|
| 3856 | 4035 | } |
|
| 3857 | 4036 | } |
|
| 3858 | 4037 | addPredecessor(self, blocks[i], entry); |
|
| 3859 | 4038 | } |
|
| 3860 | - | emit(self, il::Instr::Switch { |
|
| 3861 | - | val: subject.val, |
|
| 3862 | - | defaultTarget: *blocks[defaultIdx], |
|
| 3863 | - | defaultArgs: &mut [], |
|
| 3864 | - | cases: (&mut cases[..]) as *unsafe mut [il::SwitchCase] |
|
| 3865 | - | }); |
|
| 4039 | + | emitSwitchTerminator( |
|
| 4040 | + | self, |
|
| 4041 | + | subject.val, |
|
| 4042 | + | blocks[defaultIdx], |
|
| 4043 | + | LowerTerminator::Switch { |
|
| 4044 | + | defaultTarget: blocks[defaultIdx], |
|
| 4045 | + | defaultArgs: &mut [], |
|
| 4046 | + | cases, |
|
| 4047 | + | }, |
|
| 4048 | + | ); |
|
| 3866 | 4049 | ||
| 3867 | 4050 | for p, i in prongs { |
|
| 3868 | 4051 | let case ast::NodeValue::MatchProng(prong) = p.value |
|
| 3869 | 4052 | else throw LowerError::UnexpectedNodeValue(p); |
|
| 3870 | 4053 |
| 4866 | 5049 | ||
| 4867 | 5050 | let falseArgs = try allocVal(self, il::Val::Imm(0)); |
|
| 4868 | 5051 | let trueArgs = try allocVal(self, il::Val::Imm(1)); |
|
| 4869 | 5052 | ||
| 4870 | 5053 | // Check if tags differ. |
|
| 4871 | - | emit(self, il::Instr::Br { |
|
| 4872 | - | op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB, |
|
| 4873 | - | thenTarget: *nilCheck, thenArgs: &mut [], |
|
| 4874 | - | elseTarget: *mergeBlock, elseArgs: falseArgs, |
|
| 4875 | - | }); |
|
| 5054 | + | emitBrTerminator( |
|
| 5055 | + | self, |
|
| 5056 | + | il::CmpOp::Eq, il::Type::W8, tagA, tagB, |
|
| 5057 | + | nilCheck, mergeBlock, |
|
| 5058 | + | LowerTerminator::Br { |
|
| 5059 | + | thenTarget: nilCheck, thenArgs: &mut [], |
|
| 5060 | + | elseTarget: mergeBlock, elseArgs: falseArgs, |
|
| 5061 | + | }, |
|
| 5062 | + | ); |
|
| 4876 | 5063 | addPredecessor(self, nilCheck, currentBlock(self)); |
|
| 4877 | 5064 | addPredecessor(self, mergeBlock, currentBlock(self)); |
|
| 4878 | 5065 | ||
| 4879 | 5066 | // Check if both are `nil`. |
|
| 4880 | 5067 | try switchToAndSeal(self, nilCheck); |
|
| 4881 | - | emit(self, il::Instr::Br { |
|
| 4882 | - | op: il::CmpOp::Ne, typ: il::Type::W8, a: tagA, b: il::Val::Imm(0), |
|
| 4883 | - | thenTarget: *payloadCmp, thenArgs: &mut [], |
|
| 4884 | - | elseTarget: *mergeBlock, elseArgs: trueArgs, |
|
| 4885 | - | }); |
|
| 5068 | + | emitBrTerminator( |
|
| 5069 | + | self, |
|
| 5070 | + | il::CmpOp::Ne, il::Type::W8, tagA, il::Val::Imm(0), |
|
| 5071 | + | payloadCmp, mergeBlock, |
|
| 5072 | + | LowerTerminator::Br { |
|
| 5073 | + | thenTarget: payloadCmp, thenArgs: &mut [], |
|
| 5074 | + | elseTarget: mergeBlock, elseArgs: trueArgs, |
|
| 5075 | + | }, |
|
| 5076 | + | ); |
|
| 4886 | 5077 | addPredecessor(self, payloadCmp, currentBlock(self)); |
|
| 4887 | 5078 | addPredecessor(self, mergeBlock, currentBlock(self)); |
|
| 4888 | 5079 | ||
| 4889 | 5080 | // Both are non-`nil`, compare payloads. |
|
| 4890 | 5081 | try switchToAndSeal(self, payloadCmp); |
| 4946 | 5137 | // Compare tags: if they differ, jump to merge with `false`; otherwise check payloads. |
|
| 4947 | 5138 | let falseArgs = try allocVal(self, il::Val::Imm(0)); |
|
| 4948 | 5139 | ||
| 4949 | 5140 | assert tagBlock <> mergeBlock; |
|
| 4950 | 5141 | ||
| 4951 | - | // TODO: Use the helper once the compiler supports more than eight function params. |
|
| 4952 | - | emit(self, il::Instr::Br { |
|
| 4953 | - | op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB, |
|
| 4954 | - | thenTarget: *tagBlock, thenArgs: &mut [], |
|
| 4955 | - | elseTarget: *mergeBlock, elseArgs: falseArgs, |
|
| 4956 | - | }); |
|
| 5142 | + | emitBrTerminator( |
|
| 5143 | + | self, |
|
| 5144 | + | il::CmpOp::Eq, il::Type::W8, tagA, tagB, |
|
| 5145 | + | tagBlock, mergeBlock, |
|
| 5146 | + | LowerTerminator::Br { |
|
| 5147 | + | thenTarget: tagBlock, thenArgs: &mut [], |
|
| 5148 | + | elseTarget: mergeBlock, elseArgs: falseArgs, |
|
| 5149 | + | }, |
|
| 5150 | + | ); |
|
| 4957 | 5151 | addPredecessor(self, tagBlock, currentBlock(self)); |
|
| 4958 | 5152 | addPredecessor(self, mergeBlock, currentBlock(self)); |
|
| 4959 | 5153 | ||
| 4960 | 5154 | // Create comparison blocks for each non-void variant and build switch cases. |
|
| 4961 | 5155 | // Void variants jump directly to merge with `true`. |
|
| 4962 | - | let trueArgs = try allocVal(self, il::Val::Imm(1)); |
|
| 4963 | - | let mut cases: *mut [il::SwitchCase] = &mut []; |
|
| 5156 | + | let mut cases: *mut [LowerSwitchCase] = &mut []; |
|
| 5157 | + | let mut caseBlocks: *mut [BlockId] = &mut []; |
|
| 4964 | 5158 | for variant, i in unionInfo.variants { |
|
| 4965 | 5159 | if variant.valueType == resolver::Type::Void { |
|
| 5160 | + | let trueArgs = try allocVal(self, il::Val::Imm(1)); |
|
| 5161 | + | caseBlocks.append(mergeBlock, alloc::arenaAllocator(self.arena)); |
|
| 4966 | 5162 | appendSwitchCase(&mut cases, i as i64, mergeBlock, trueArgs, alloc::arenaAllocator(self.arena)); |
|
| 4967 | 5163 | } else { |
|
| 4968 | 5164 | let payloadBlock = try createBlock(self, "eq#payload"); |
|
| 5165 | + | caseBlocks.append(payloadBlock, alloc::arenaAllocator(self.arena)); |
|
| 4969 | 5166 | appendSwitchCase(&mut cases, i as i64, payloadBlock, &mut [], alloc::arenaAllocator(self.arena)); |
|
| 4970 | 5167 | } |
|
| 4971 | 5168 | } |
|
| 4972 | 5169 | ||
| 4973 | 5170 | // Emit switch in @tag block. Default arm is unreachable since we cover all variants. |
|
| 4974 | 5171 | let unreachableBlock = try createBlock(self, "eq#unreachable"); |
|
| 4975 | 5172 | try switchToAndSeal(self, tagBlock); |
|
| 4976 | - | emit(self, il::Instr::Switch { |
|
| 4977 | - | val: tagA, |
|
| 4978 | - | defaultTarget: *unreachableBlock, |
|
| 4979 | - | defaultArgs: &mut [], |
|
| 4980 | - | cases: (&mut cases[..]) as *unsafe mut [il::SwitchCase] |
|
| 4981 | - | }); |
|
| 5173 | + | emitSwitchTerminator( |
|
| 5174 | + | self, |
|
| 5175 | + | tagA, |
|
| 5176 | + | unreachableBlock, |
|
| 5177 | + | LowerTerminator::Switch { |
|
| 5178 | + | defaultTarget: unreachableBlock, |
|
| 5179 | + | defaultArgs: &mut [], |
|
| 5180 | + | cases, |
|
| 5181 | + | }, |
|
| 5182 | + | ); |
|
| 4982 | 5183 | ||
| 4983 | 5184 | // Add predecessor edges for switch targets. |
|
| 4984 | 5185 | addPredecessor(self, unreachableBlock, tagBlock); |
|
| 4985 | - | for c in &cases[..] { |
|
| 4986 | - | addPredecessor(self, BlockId(c.target), tagBlock); |
|
| 5186 | + | for caseBlock in &caseBlocks[..] { |
|
| 5187 | + | addPredecessor(self, caseBlock, tagBlock); |
|
| 4987 | 5188 | } |
|
| 4988 | 5189 | let valOffset = unionInfo.valOffset as i32; |
|
| 4989 | 5190 | ||
| 4990 | 5191 | // Emit payload comparison blocks for non-void variants. |
|
| 4991 | 5192 | for variant, i in unionInfo.variants { |
|
| 4992 | - | let caseBlock = BlockId(cases[i].target); |
|
| 5193 | + | let caseBlock = caseBlocks[i]; |
|
| 4993 | 5194 | if caseBlock <> mergeBlock { |
|
| 4994 | 5195 | try switchToAndSeal(self, caseBlock); |
|
| 4995 | 5196 | let payloadEq = try emitEqAtOffset( |
|
| 4996 | 5197 | self, a, b, offset + valOffset, variant.valueType |
|
| 4997 | 5198 | ); |
| 6753 | 6954 | ) throws (LowerError) where 'arena: 'phase, 'phase: 'function { |
|
| 6754 | 6955 | let entry = currentBlock(self); |
|
| 6755 | 6956 | ||
| 6756 | 6957 | // First pass: create blocks, resolve error types, and build switch cases. |
|
| 6757 | 6958 | let mut targets: [?CatchTarget; MAX_CATCH_CLAUSES] = [nil; MAX_CATCH_CLAUSES]; |
|
| 6758 | - | let mut cases: *mut [il::SwitchCase] = &mut []; |
|
| 6959 | + | let mut cases: *mut [LowerSwitchCase] = &mut []; |
|
| 6759 | 6960 | let mut defaultIdx: ?u32 = nil; |
|
| 6760 | 6961 | ||
| 6761 | 6962 | for clauseNode, i in catches { |
|
| 6762 | 6963 | let case ast::NodeValue::CatchClause(clause) = clauseNode.value |
|
| 6763 | 6964 | else panic "lowerMultiCatch: expected CatchClause"; |
| 6785 | 6986 | let block = try createBlock(self, "unreachable"); |
|
| 6786 | 6987 | addPredecessor(self, block, entry); |
|
| 6787 | 6988 | set selectedDefault = block; |
|
| 6788 | 6989 | } |
|
| 6789 | 6990 | let defaultTarget = selectedDefault else panic "lowerMultiCatch: missing default destination"; |
|
| 6790 | - | emit(self, il::Instr::Switch { |
|
| 6791 | - | val: il::Val::Reg(tagReg), |
|
| 6792 | - | defaultTarget: *defaultTarget, |
|
| 6793 | - | defaultArgs: &mut [], |
|
| 6794 | - | cases: (&mut cases[..]) as *unsafe mut [il::SwitchCase] |
|
| 6795 | - | }); |
|
| 6991 | + | emitSwitchTerminator( |
|
| 6992 | + | self, |
|
| 6993 | + | il::Val::Reg(tagReg), |
|
| 6994 | + | defaultTarget, |
|
| 6995 | + | LowerTerminator::Switch { |
|
| 6996 | + | defaultTarget, |
|
| 6997 | + | defaultArgs: &mut [], |
|
| 6998 | + | cases, |
|
| 6999 | + | }, |
|
| 7000 | + | ); |
|
| 6796 | 7001 | ||
| 6797 | 7002 | // Second pass: emit each catch clause body. |
|
| 6798 | 7003 | for clauseNode, i in catches { |
|
| 6799 | 7004 | let case ast::NodeValue::CatchClause(clause) = clauseNode.value |
|
| 6800 | 7005 | else panic "lowerMultiCatch: expected CatchClause"; |