Use unlabeled records in more places

60a9450d88ee83d5edf2301eaad6ed76cf28b5f9ced7df3eee856333a4e48378
Alexis Sellier committed ago 1 parent e291fc36
lib/std/lang/lower.rad +56 -63
374 374
// a mapping from [`Var`] to current SSA value. When control flow merges,
375 375
// block parameters are inserted to merge values from different control flow paths.
376 376
377 377
/// A variable handle. Represents a source-level variable during lowering.
378 378
/// The same [`Var`] can have different SSA values in different blocks.
379 -
// TODO: Use `Var(u32)`.
380 -
pub record Var {
381 -
    /// Index into the function's variables array.
382 -
    id: u32,
383 -
}
379 +
pub record Var(u32);
384 380
385 381
/// Metadata for a source-level variable, stored once per function.
386 382
///
387 383
/// Each variable declaration in the source creates one [`VarData`] entry in the
388 384
/// function's `variables` array, indexed by `id`. This contains static
425 421
// instructions until terminated by a jump, branch, return, or unreachable.
426 422
// Blocks can be created before they're filled (forward references for jumps).
427 423
428 424
/// A handle to a basic block within the current function.
429 425
/// Block handles are stable, they don't change as more blocks are added.
430 -
pub record BlockId {
431 -
    /// Index into the function's block array.
432 -
    n: u32,
433 -
}
426 +
pub record BlockId(u32);
434 427
435 428
/// Internal block state during construction.
436 429
///
437 430
/// The key invariants:
438 431
///
1791 1784
/// The latter is necessary because recursive SSA resolution may have already
1792 1785
/// patched terminator arguments with the provisional value before it was
1793 1786
/// found to be trivial.
1794 1787
fn rewriteCachedVarValue(self: *mut FnLowerer, v: Var, from: il::Val, to: il::Val) {
1795 1788
    for i in 0..self.blockData.len {
1796 -
        let blk = getBlockMut(self, BlockId { n: i });
1797 -
        if blk.vars[v.id] == from {
1798 -
            blk.vars[v.id] = to;
1789 +
        let blk = getBlockMut(self, BlockId(i));
1790 +
        if blk.vars[*v] == from {
1791 +
            blk.vars[*v] = to;
1799 1792
        }
1800 1793
        if blk.instrs.len > 0 {
1801 1794
            let ix = blk.instrs.len - 1;
1802 1795
            match &mut blk.instrs[ix] {
1803 1796
                case il::Instr::Jmp { args, .. } =>
1848 1841
/// The block is initially unsealed (predecessors may be added later) and empty.
1849 1842
/// Returns a [`BlockId`] that can be used for jumps and branches. The block must
1850 1843
/// be switched to via [`switchToBlock`] before instructions can be emitted.
1851 1844
fn createBlock(self: *mut FnLowerer, labelBase: *[u8]) -> BlockId throws (LowerError) {
1852 1845
    let label = try nextLabel(self, labelBase);
1853 -
    let id = BlockId { n: self.blockData.len };
1846 +
    let id = BlockId(self.blockData.len);
1854 1847
    let varCount = self.fnType.localCount;
1855 1848
    let vars = try! alloc::allocSlice(self.low.arena, @sizeOf(?il::Val), @alignOf(?il::Val), varCount) as *mut [?il::Val];
1856 1849
1857 1850
    for i in 0..varCount {
1858 1851
        vars[i] = nil;
1903 1896
    };
1904 1897
    blk.sealState = Sealed::Yes;
1905 1898
1906 1899
    // Complete all incomplete block parameters.
1907 1900
    for varId in incompleteVars {
1908 -
        try resolveBlockArgs(self, block, Var { id: varId });
1901 +
        try resolveBlockArgs(self, block, Var(varId));
1909 1902
    }
1910 1903
}
1911 1904
1912 1905
/// Seal a block and switch to it.
1913 1906
fn switchToAndSeal(self: *mut FnLowerer, block: BlockId) throws (LowerError) {
1915 1908
    switchToBlock(self, block);
1916 1909
}
1917 1910
1918 1911
/// Get block data by block id.
1919 1912
fn getBlock(self: *FnLowerer, block: BlockId) -> *BlockData {
1920 -
    return &self.blockData[block.n];
1913 +
    return &self.blockData[*block];
1921 1914
}
1922 1915
1923 1916
/// Get mutable block data by block id.
1924 1917
fn getBlockMut(self: *mut FnLowerer, block: BlockId) -> *mut BlockData {
1925 -
    return &mut self.blockData[block.n];
1918 +
    return &mut self.blockData[*block];
1926 1919
}
1927 1920
1928 1921
/// Get the current block being built.
1929 1922
fn currentBlock(self: *FnLowerer) -> BlockId {
1930 1923
    let block = self.currentBlock else {
1957 1950
    block.instrs.append(instr, self.allocator);
1958 1951
}
1959 1952
1960 1953
/// Emit an unconditional jump to `target`.
1961 1954
fn emitJmp(self: *mut FnLowerer, target: BlockId) throws (LowerError) {
1962 -
    emit(self, il::Instr::Jmp { target: target.n, args: &mut [] });
1955 +
    emit(self, il::Instr::Jmp { target: *target, args: &mut [] });
1963 1956
    addPredecessor(self, target, currentBlock(self));
1964 1957
}
1965 1958
1966 1959
/// Emit an unconditional jump to `target` with a single argument.
1967 1960
fn emitJmpWithArg(self: *mut FnLowerer, target: BlockId, arg: il::Val) throws (LowerError) {
1968 1961
    let args = try allocVal(self, arg);
1969 -
    emit(self, il::Instr::Jmp { target: target.n, args });
1962 +
    emit(self, il::Instr::Jmp { target: *target, args });
1970 1963
    addPredecessor(self, target, currentBlock(self));
1971 1964
}
1972 1965
1973 1966
/// Emit an unconditional jump to `target` and switch to it.
1974 1967
fn switchAndJumpTo(self: *mut FnLowerer, target: BlockId) throws (LowerError) {
1982 1975
    emit(self, il::Instr::Br {
1983 1976
        op: il::CmpOp::Ne,
1984 1977
        typ: il::Type::W32,
1985 1978
        a: il::Val::Reg(cond),
1986 1979
        b: il::Val::Imm(0),
1987 -
        thenTarget: thenBlock.n,
1980 +
        thenTarget: *thenBlock,
1988 1981
        thenArgs: &mut [],
1989 -
        elseTarget: elseBlock.n,
1982 +
        elseTarget: *elseBlock,
1990 1983
        elseArgs: &mut [],
1991 1984
    });
1992 1985
    addPredecessor(self, thenBlock, currentBlock(self));
1993 1986
    addPredecessor(self, elseBlock, currentBlock(self));
1994 1987
}
2004 1997
    elseBlock: BlockId
2005 1998
) throws (LowerError) {
2006 1999
    assert thenBlock != elseBlock;
2007 2000
    emit(self, il::Instr::Br {
2008 2001
        op, typ, a, b,
2009 -
        thenTarget: thenBlock.n, thenArgs: &mut [],
2010 -
        elseTarget: elseBlock.n, elseArgs: &mut [],
2002 +
        thenTarget: *thenBlock, thenArgs: &mut [],
2003 +
        elseTarget: *elseBlock, elseArgs: &mut [],
2011 2004
    });
2012 2005
    addPredecessor(self, thenBlock, currentBlock(self));
2013 2006
    addPredecessor(self, elseBlock, currentBlock(self));
2014 2007
}
2015 2008
2380 2373
fn addPredecessor(self: *mut FnLowerer, target: BlockId, pred: BlockId) {
2381 2374
    let blk = getBlockMut(self, target);
2382 2375
    assert blk.sealState != Sealed::Yes, "addPredecessor: adding predecessor to sealed block";
2383 2376
    let preds = &mut blk.preds;
2384 2377
    for i in 0..preds.len {
2385 -
        if preds[i] == pred.n { // Avoid duplicate predecessor entries.
2378 +
        if preds[i] == *pred { // Avoid duplicate predecessor entries.
2386 2379
            return;
2387 2380
        }
2388 2381
    }
2389 -
    preds.append(pred.n, self.allocator);
2382 +
    preds.append(*pred, self.allocator);
2390 2383
}
2391 2384
2392 2385
/// Finalize all blocks and return the block array.
2393 2386
fn finalizeBlocks(self: *mut FnLowerer) -> *[il::Block] throws (LowerError) {
2394 2387
    let blocks = try! alloc::allocSlice(
2562 2555
    val: il::Val
2563 2556
) -> Var {
2564 2557
    let id = self.vars.len;
2565 2558
    self.vars.append(VarData { name, type, mutable, addressTaken: false }, self.allocator);
2566 2559
2567 -
    let v = Var { id };
2560 +
    let v = Var(id);
2568 2561
    if self.currentBlock != nil {
2569 2562
        defVar(self, v, val);
2570 2563
    }
2571 2564
    return v;
2572 2565
}
2574 2567
/// Define (write) a variable. Record the SSA value of a variable in the
2575 2568
/// current block. Called when a variable is assigned or initialized (`let`
2576 2569
/// bindings, assignments, loop updates). When [`useVar`] is later called,
2577 2570
/// it will retrieve this value.
2578 2571
fn defVar(self: *mut FnLowerer, v: Var, val: il::Val) {
2579 -
    assert v.id < self.vars.len;
2580 -
    getBlockMut(self, currentBlock(self)).vars[v.id] = val;
2572 +
    assert *v < self.vars.len;
2573 +
    getBlockMut(self, currentBlock(self)).vars[*v] = val;
2581 2574
}
2582 2575
2583 2576
/// Use (read) the current value of a variable in the current block.
2584 2577
/// May insert block parameters if the value must come from predecessors.
2585 2578
fn useVar(self: *mut FnLowerer, v: Var) -> il::Val throws (LowerError) {
2591 2584
/// Given a variable and a block where it's used, this function finds the
2592 2585
/// correct [`il::Val`] that holds the variable's value at that program point.
2593 2586
/// When control flow merges from multiple predecessors with different
2594 2587
/// definitions, it creates a block parameter to unify them.
2595 2588
fn useVarInBlock(self: *mut FnLowerer, block: BlockId, v: Var) -> il::Val throws (LowerError) {
2596 -
    assert v.id < self.vars.len;
2589 +
    assert *v < self.vars.len;
2597 2590
2598 2591
    let blk = getBlockMut(self, block);
2599 -
    if let val = blk.vars[v.id] {
2592 +
    if let val = blk.vars[*v] {
2600 2593
        return val;
2601 2594
    }
2602 2595
    // Entry block cannot have block parameters. If variable isn't defined
2603 2596
    // in entry, we return undefined.
2604 2597
    if block == self.entryBlock {
2610 2603
            throw LowerError::InvalidUse;
2611 2604
        }
2612 2605
        // Single predecessor means no merge needed, variable is implicitly
2613 2606
        // available without a block parameter.
2614 2607
        if blk.preds.len == 1 {
2615 -
            let pred = BlockId { n: blk.preds[0] };
2616 -
            if pred.n != block.n {
2608 +
            let pred = BlockId(blk.preds[0]);
2609 +
            if *pred != *block {
2617 2610
                let val = try useVarInBlock(self, pred, v);
2618 -
                blk.vars[v.id] = val; // Cache.
2611 +
                blk.vars[*v] = val; // Cache.
2619 2612
                return val;
2620 2613
            }
2621 2614
        }
2622 2615
    }
2623 2616
    // Multiple predecessors or unsealed block: need a block parameter to merge
2632 2625
    while id > 0 {
2633 2626
        id -= 1;
2634 2627
        if let varName = self.vars[id].name {
2635 2628
            // Names are interned strings, so pointer comparison suffices.
2636 2629
            if varName == name {
2637 -
                return Var { id };
2630 +
                return Var(id);
2638 2631
            }
2639 2632
        }
2640 2633
    }
2641 2634
    return nil;
2642 2635
}
2659 2652
    self.vars = @sliceOf(self.vars.ptr, savedVarsLen, self.vars.cap);
2660 2653
}
2661 2654
2662 2655
/// Get the metadata for a variable.
2663 2656
fn getVar(self: *FnLowerer, v: Var) -> *VarData {
2664 -
    assert v.id < self.vars.len;
2665 -
    return &self.vars[v.id];
2657 +
    assert *v < self.vars.len;
2658 +
    return &self.vars[*v];
2666 2659
}
2667 2660
2668 2661
/// Create a block parameter to merge a variable's value from multiple
2669 2662
/// control flow paths.
2670 2663
///
2690 2683
2691 2684
    // Create block parameter and add it to the block.
2692 2685
    let param = il::Param { value: reg, type };
2693 2686
    let blk = getBlockMut(self, block);
2694 2687
    blk.params.append(param, self.allocator);
2695 -
    blk.paramVars.append(v.id, self.allocator); // Associate variable with parameter.
2688 +
    blk.paramVars.append(*v, self.allocator); // Associate variable with parameter.
2696 2689
2697 2690
    // Record that this variable's value in this block is now the parameter register.
2698 2691
    // This must happen before the predecessor loop to handle self-referential loops.
2699 -
    blk.vars[v.id] = il::Val::Reg(reg);
2692 +
    blk.vars[*v] = il::Val::Reg(reg);
2700 2693
2701 2694
    match &mut blk.sealState {
2702 2695
        case Sealed::No { incompleteVars } => {
2703 2696
            // Block unsealed: defer until sealing.
2704 -
            incompleteVars.append(v.id, self.allocator);
2697 +
            incompleteVars.append(*v, self.allocator);
2705 2698
        },
2706 2699
        case Sealed::Yes => {
2707 2700
            // Block sealed: check for trivial phi before committing. If all
2708 2701
            // predecessors provide the same value, we can remove the param we
2709 2702
            // just created and use that value directly.
2710 2703
            if let trivial = try getTrivialPhiVal(self, block, v) {
2711 2704
                let provisional = il::Val::Reg(reg);
2712 2705
                removeLastBlockParam(self, block);
2713 2706
                rewriteCachedVarValue(self, v, provisional, trivial);
2714 -
                getBlockMut(self, block).vars[v.id] = trivial;
2707 +
                getBlockMut(self, block).vars[*v] = trivial;
2715 2708
                return trivial;
2716 2709
            }
2717 2710
            // Non-trivial phi: patch predecessors to pass their values.
2718 2711
            try resolveBlockArgs(self, block, v);
2719 2712
        },
2737 2730
2738 2731
    // Find the parameter index corresponding to this variable.
2739 2732
    // Each variable that needs merging gets its own block parameter slot.
2740 2733
    let mut paramIdx: u32 = 0;
2741 2734
    for i in 0..blk.paramVars.len {
2742 -
        if blk.paramVars[i] == v.id {
2735 +
        if blk.paramVars[i] == *v {
2743 2736
            paramIdx = i;
2744 2737
            break;
2745 2738
        }
2746 2739
    }
2747 2740
2748 2741
    // For each predecessor, recursively look up the variable's reaching definition
2749 2742
    // in that block, then patch the predecessor's terminator to pass that value
2750 2743
    // as an argument to this block's parameter.
2751 2744
    for predId in blk.preds {
2752 -
        let pred = BlockId { n: predId };
2745 +
        let pred = BlockId(predId);
2753 2746
        // This may recursively trigger more block arg resolution if the
2754 2747
        // predecessor also needs to look up the variable from its predecessors.
2755 2748
        let val = try useVarInBlock(self, pred, v);
2756 2749
        assert val != il::Val::Undef, "createBlockParam: predecessor provides undef value for block parameter";
2757 -
        patchTerminatorArg(self, pred, block.n, paramIdx, val);
2750 +
        patchTerminatorArg(self, pred, *block, paramIdx, val);
2758 2751
    }
2759 2752
}
2760 2753
2761 2754
/// Check if a block parameter is trivial, i.e. all predecessors provide
2762 2755
/// the same value. Returns the trivial value if so.
2763 2756
fn getTrivialPhiVal(self: *mut FnLowerer, block: BlockId, v: Var) -> ?il::Val throws (LowerError) {
2764 2757
    let blk = getBlock(self, block);
2765 2758
    // Get the block parameter register.
2766 -
    let paramReg = blk.vars[v.id];
2759 +
    let paramReg = blk.vars[*v];
2767 2760
    // Check if all predecessors provide the same value.
2768 2761
    let mut sameVal: ?il::Val = nil;
2769 2762
2770 2763
    for predId in blk.preds {
2771 -
        let pred = BlockId { n: predId };
2764 +
        let pred = BlockId(predId);
2772 2765
        let val = try useVarInBlock(self, pred, v);
2773 2766
2774 2767
        // Check if this is a self-reference.
2775 2768
        // This happens in cycles where the loop back-edge passes the phi to
2776 2769
        // itself. We skip self-references when checking for trivial phis.
3362 3355
                    let cv = resolver::constValueEntry(self.low.resolver, pat)
3363 3356
                        else throw LowerError::MissingConst(pat);
3364 3357
3365 3358
                    cases.append(il::SwitchCase {
3366 3359
                        value: constToScalar(cv),
3367 -
                        target: blocks[i].n,
3360 +
                        target: *blocks[i],
3368 3361
                        args: &mut []
3369 3362
                    }, self.allocator);
3370 3363
                }
3371 3364
            }
3372 3365
        }
3373 3366
        addPredecessor(self, blocks[i], entry);
3374 3367
    }
3375 3368
    emit(self, il::Instr::Switch {
3376 3369
        val: subject.val,
3377 -
        defaultTarget: blocks[defaultIdx].n,
3370 +
        defaultTarget: *blocks[defaultIdx],
3378 3371
        defaultArgs: &mut [],
3379 3372
        cases: &mut cases[..]
3380 3373
    });
3381 3374
3382 3375
    for p, i in prongs {
3517 3510
        }
3518 3511
3519 3512
        // Evaluate guard if present; can still fail to next arm.
3520 3513
        if let g = prong.guard {
3521 3514
            try emitCondBranch(self, g, bodyBlock, nextArm);
3522 -
        } else if currentBlock(self).n != bodyBlock.n {
3515 +
        } else if *currentBlock(self) != *bodyBlock {
3523 3516
            // Nested tests changed the current block. Create a new body block
3524 3517
            // after the nest blocks to maintain RPO ordering, and jump to it.
3525 3518
            bodyBlock = try createBlock(self, bodyLabel);
3526 3519
            try emitJmp(self, bodyBlock);
3527 3520
        }
3622 3615
    }
3623 3616
    // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`.
3624 3617
    if let g = pat.guard {
3625 3618
        try emitCondBranch(self, g, *successBlock, failBlock);
3626 3619
        try switchToAndSeal(self, *successBlock);
3627 -
    } else if currentBlock(self).n != targetBlock.n {
3620 +
    } else if *currentBlock(self) != *targetBlock {
3628 3621
        // Nested tests changed the current block. Create a new success block
3629 3622
        // after the nest blocks to maintain RPO ordering, and jump to it.
3630 3623
        *successBlock = try createBlock(self, successLabel);
3631 3624
3632 3625
        try emitJmp(self, *successBlock);
4300 4293
    let trueArgs = try allocVal(self, il::Val::Imm(1));
4301 4294
4302 4295
    // Check if tags differ.
4303 4296
    emit(self, il::Instr::Br {
4304 4297
        op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB,
4305 -
        thenTarget: nilCheck.n, thenArgs: &mut [],
4306 -
        elseTarget: mergeBlock.n, elseArgs: falseArgs,
4298 +
        thenTarget: *nilCheck, thenArgs: &mut [],
4299 +
        elseTarget: *mergeBlock, elseArgs: falseArgs,
4307 4300
    });
4308 4301
    addPredecessor(self, nilCheck, currentBlock(self));
4309 4302
    addPredecessor(self, mergeBlock, currentBlock(self));
4310 4303
4311 4304
    // Check if both are `nil`.
4312 4305
    try switchToAndSeal(self, nilCheck);
4313 4306
    emit(self, il::Instr::Br {
4314 4307
        op: il::CmpOp::Ne, typ: il::Type::W8, a: tagA, b: il::Val::Imm(0),
4315 -
        thenTarget: payloadCmp.n, thenArgs: &mut [],
4316 -
        elseTarget: mergeBlock.n, elseArgs: trueArgs,
4308 +
        thenTarget: *payloadCmp, thenArgs: &mut [],
4309 +
        elseTarget: *mergeBlock, elseArgs: trueArgs,
4317 4310
    });
4318 4311
    addPredecessor(self, payloadCmp, currentBlock(self));
4319 4312
    addPredecessor(self, mergeBlock, currentBlock(self));
4320 4313
4321 4314
    // Both are non-`nil`, compare payloads.
4381 4374
    assert tagBlock != mergeBlock;
4382 4375
4383 4376
    // TODO: Use the helper once the compiler supports more than eight function params.
4384 4377
    emit(self, il::Instr::Br {
4385 4378
        op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB,
4386 -
        thenTarget: tagBlock.n, thenArgs: &mut [],
4387 -
        elseTarget: mergeBlock.n, elseArgs: falseArgs,
4379 +
        thenTarget: *tagBlock, thenArgs: &mut [],
4380 +
        elseTarget: *mergeBlock, elseArgs: falseArgs,
4388 4381
    });
4389 4382
    addPredecessor(self, tagBlock, currentBlock(self));
4390 4383
    addPredecessor(self, mergeBlock, currentBlock(self));
4391 4384
4392 4385
    // Create comparison blocks for each non-void variant and build switch cases.
4399 4392
    let mut caseBlocks: [?BlockId; resolver::MAX_UNION_VARIANTS] = undefined;
4400 4393
    for variant, i in unionInfo.variants {
4401 4394
        if variant.valueType == resolver::Type::Void {
4402 4395
            cases[i] = il::SwitchCase {
4403 4396
                value: i as i64,
4404 -
                target: mergeBlock.n,
4397 +
                target: *mergeBlock,
4405 4398
                args: trueArgs
4406 4399
            };
4407 4400
            caseBlocks[i] = nil;
4408 4401
        } else {
4409 4402
            let payloadBlock = try createBlock(self, "eq#payload");
4410 4403
            cases[i] = il::SwitchCase {
4411 4404
                value: i as i64,
4412 -
                target: payloadBlock.n,
4405 +
                target: *payloadBlock,
4413 4406
                args: &mut []
4414 4407
            };
4415 4408
            caseBlocks[i] = payloadBlock;
4416 4409
        }
4417 4410
    }
4419 4412
    // Emit switch in @tag block. Default arm is unreachable since we cover all variants.
4420 4413
    let unreachableBlock = try createBlock(self, "eq#unreachable");
4421 4414
    try switchToAndSeal(self, tagBlock);
4422 4415
    emit(self, il::Instr::Switch {
4423 4416
        val: tagA,
4424 -
        defaultTarget: unreachableBlock.n,
4417 +
        defaultTarget: *unreachableBlock,
4425 4418
        defaultArgs: &mut [],
4426 4419
        cases
4427 4420
    });
4428 4421
4429 4422
    // Add predecessor edges for switch targets.
4794 4787
            if isAggregateType(typ) {
4795 4788
                return val;
4796 4789
            }
4797 4790
            // For scalars, if we've already materialized a stack slot for this
4798 4791
            // variable, the SSA value is that slot pointer.
4799 -
            if self.vars[v.id].addressTaken {
4792 +
            if self.vars[*v].addressTaken {
4800 4793
                // Already address-taken; return existing stack pointer.
4801 4794
                return val;
4802 4795
            }
4803 4796
            // Materialize a stack slot using the declaration's resolved
4804 4797
            // layout so `align(N)` on locals is honored.
4805 4798
            let layout = resolver::getLayout(self.low.resolver, addr.target, typ);
4806 4799
            let slot = emitReserveLayout(self, layout);
4807 4800
            try emitStore(self, slot, 0, typ, val);
4808 4801
            let stackVal = il::Val::Reg(slot);
4809 4802
4810 -
            self.vars[v.id].addressTaken = true;
4803 +
            self.vars[*v].addressTaken = true;
4811 4804
            defVar(self, v, stackVal);
4812 4805
4813 4806
            return stackVal;
4814 4807
        }
4815 4808
        // Fall back to symbol lookup for constants/statics.
5004 4997
                let layout = resolver::getLayout(self.low.resolver, node, typ);
5005 4998
                let slot = emitReserveLayout(self, layout);
5006 4999
                try emitStore(self, slot, 0, typ, varVal);
5007 5000
5008 5001
                let v = newVar(self, name, ilType, l.mutable, il::Val::Reg(slot));
5009 -
                self.vars[v.id].addressTaken = true;
5002 +
                self.vars[*v].addressTaken = true;
5010 5003
5011 5004
                return;
5012 5005
            }
5013 5006
        }
5014 5007
    }
6110 6103
            let errTy = try typeOf(self, typeNode);
6111 6104
            errTypes[i] = errTy;
6112 6105
6113 6106
            cases.append(il::SwitchCase {
6114 6107
                value: getOrAssignErrorTag(self.low, errTy) as i64,
6115 -
                target: blocks[i].n,
6108 +
                target: *blocks[i],
6116 6109
                args: &mut []
6117 6110
            }, self.allocator);
6118 6111
        } else {
6119 6112
            errTypes[i] = nil;
6120 6113
            defaultIdx = i;
6129 6122
        defaultTarget = try createBlock(self, "unreachable");
6130 6123
        addPredecessor(self, defaultTarget, entry);
6131 6124
    }
6132 6125
    emit(self, il::Instr::Switch {
6133 6126
        val: il::Val::Reg(tagReg),
6134 -
        defaultTarget: defaultTarget.n,
6127 +
        defaultTarget: *defaultTarget,
6135 6128
        defaultArgs: &mut [],
6136 6129
        cases
6137 6130
    });
6138 6131
6139 6132
    // Second pass: emit each catch clause body.
6826 6819
        case ast::NodeValue::Ident(_) => {
6827 6820
            // First try local variable lookup.
6828 6821
            // Otherwise fall back to global symbol lookup.
6829 6822
            if let v = lookupLocalVar(self, node) {
6830 6823
                val = try useVar(self, v);
6831 -
                if self.vars[v.id].addressTaken {
6824 +
                if self.vars[*v].addressTaken {
6832 6825
                    let typ = try typeOf(self, node);
6833 6826
                    let ptr = emitValToReg(self, val);
6834 6827
                    val = emitRead(self, ptr, 0, typ);
6835 6828
                }
6836 6829
            } else {
lib/std/sys/unix.rad +9 -14
1 1
//! Unix-specific system calls and utilities.
2 2
use std::intrinsics;
3 3
4 4
/// File access modes.
5 -
// TODO: Use unlabeled record (blocked on positional field access syntax).
6 -
pub record OpenFlags {
7 -
    value: i64,
8 -
}
5 +
pub record OpenFlags(i64);
9 6
10 7
/// Open file for reading only.
11 -
pub const O_RDONLY: OpenFlags = OpenFlags { value: 0 };
8 +
pub const O_RDONLY: OpenFlags = OpenFlags(0);
12 9
13 10
/// Open file for writing only.
14 -
pub const O_WRONLY: OpenFlags = OpenFlags { value: 1 };
11 +
pub const O_WRONLY: OpenFlags = OpenFlags(1);
15 12
16 13
/// Open file for reading and writing.
17 -
pub const O_RDWR: OpenFlags = OpenFlags { value: 2 };
14 +
pub const O_RDWR: OpenFlags = OpenFlags(2);
18 15
19 16
/// Create file if it doesn't exist.
20 -
pub const O_CREAT: OpenFlags = OpenFlags { value: 64 };
17 +
pub const O_CREAT: OpenFlags = OpenFlags(64);
21 18
22 19
/// Truncate file to zero length.
23 -
pub const O_TRUNC: OpenFlags = OpenFlags { value: 512 };
20 +
pub const O_TRUNC: OpenFlags = OpenFlags(512);
24 21
25 22
/// Standard file descriptor for stdin.
26 23
pub const STDIN: i64 = 0;
27 24
28 25
/// Standard file descriptor for stdout.
35 32
const AT_FDCWD: i64 = -100;
36 33
37 34
/// Opens a file at the given path and returns a file descriptor.
38 35
/// Returns a negative value on error.
39 36
pub fn open(path: *[u8], flags: OpenFlags) -> i64 {
40 -
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, flags.value, 0);
37 +
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, 0);
41 38
}
42 39
43 40
/// Opens a file at the given path with mode, returns a file descriptor.
44 41
pub fn openOpts(path: *[u8], flags: OpenFlags, mode: i64) -> i64 {
45 -
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, flags.value, mode);
42 +
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, mode);
46 43
}
47 44
48 45
/// Reads from a file descriptor into the provided buffer.
49 46
/// Returns the number of bytes read, or a negative value on error.
50 47
pub fn read(fd: i64, buf: *mut [u8]) -> i64 {
107 104
108 105
/// Writes the entire contents of a buffer to a file at the given path.
109 106
/// Creates the file if it doesn't exist, truncates if it does.
110 107
/// Returns `true` on success.
111 108
pub fn writeFile(path: *[u8], data: *[u8]) -> bool {
112 -
    let flags = OpenFlags {
113 -
        value: O_WRONLY.value | O_CREAT.value | O_TRUNC.value
114 -
    };
109 +
    let flags = OpenFlags(*O_WRONLY | *O_CREAT | *O_TRUNC);
115 110
    let fd = openOpts(path, flags, 420); // 0644 in octal.
116 111
    if fd < 0 {
117 112
        return false;
118 113
    }
119 114
    let mut written: u32 = 0;