Carry slice element pointers through loops

8c7552bb010a226d2686724e93e8e5d064ec01cdf46523185eebb9937588f7e4
Alexis Sellier committed ago 1 parent 5573da2c
lib/std/lang/gen/regalloc/assign.rad +1 -2
229 229
    return nil;
230 230
}
231 231
232 232
/// Find first free register in pool, allocate it, return it.
233 233
unsafe fn findFreeInPool(usedRegs: &mut bitset::Bitset, current: &mut RegMap, ssaReg: u32, pool: *[gen::Reg]) -> ?gen::Reg {
234 -
    for i in 0..pool.len {
235 -
        let r = pool[i];
234 +
    for r in pool {
236 235
        if not bitset::contains(usedRegs, *r as u32) {
237 236
            bitset::put(usedRegs, *r as u32);
238 237
            rmapSet(current, ssaReg, r);
239 238
            return r;
240 239
        }
lib/std/lang/gen/regalloc/liveness.rad +4 -5
82 82
    for b in 0..blockCount {
83 83
        let block = &func.blocks[b];
84 84
        for p in block.params {
85 85
            countRegisters(p.value, &mut registerCount);
86 86
        }
87 -
        for i in 0..block.instrs.len {
88 -
            il::forEachReg(block.instrs[i], countRegistersCallback, &mut registerCount as &mut opaque);
89 -
            if let dst = il::instrDst(block.instrs[i]) {
87 +
        for instr in block.instrs {
88 +
            il::forEachReg(instr, countRegistersCallback, &mut registerCount as &mut opaque);
89 +
            if let dst = il::instrDst(instr) {
90 90
                countRegisters(dst, &mut registerCount);
91 91
            }
92 92
        }
93 93
    }
94 94
    if registerCount > MAX_SSA_REGS as u64 {
158 158
/// Compute local defs and uses for a single block.
159 159
unsafe fn computeLocalDefsUses(block: *unsafe il::Block, defs: *unsafe mut bitset::Bitset, uses: *unsafe mut bitset::Bitset) {
160 160
    for p in block.params {
161 161
        bitset::put(defs, p.value.n);
162 162
    }
163 -
    for i in 0..block.instrs.len {
164 -
        let instr = block.instrs[i];
163 +
    for instr in block.instrs {
165 164
        let mut ctx = DefsUses { defs, uses };
166 165
        il::forEachReg(instr, addUseCallback, &mut ctx as &mut opaque);
167 166
168 167
        if let dst = il::instrDst(instr) {
169 168
            bitset::put(defs, dst.n);
lib/std/lang/lower.rad +125 -25
560 560
export record LoopCtx: Copy {
561 561
    /// Where `break` should transfer control (the loop's exit block).
562 562
    breakTarget: BlockId,
563 563
    /// Where `continue` should transfer control.
564 564
    continueTarget: ?BlockId,
565 +
    /// Checked slice whose exact loop index selects the current element.
566 +
    indexedSlice: ?IndexedSliceLoop,
567 +
}
568 +
569 +
/// Loop state for a checked slice indexed by its length-bounded range.
570 +
record IndexedSliceLoop: Copy {
571 +
    /// Slice binding resolved for the range bound.
572 +
    sourceSym: *unsafe mut resolver::Symbol,
573 +
    /// Range index binding.
574 +
    indexSym: *unsafe mut resolver::Symbol,
575 +
    /// Address of the current element.
576 +
    ptrVar: Var,
577 +
    /// Element type used by the subscript.
578 +
    item: *resolver::Type,
565 579
}
566 580
567 581
/// Logical operator.
568 582
union LogicalOp: Copy { And, Or }
569 583
574 588
        valVar: Var,
575 589
        indexVar: ?Var,
576 590
        endVal: il::Val,
577 591
        valType: il::Type,
578 592
        unsigned: bool,
593 +
        indexedSlice: ?IndexedSliceLoop,
579 594
    },
580 595
    /// Collection iterator: `for elem in slice`.
581 596
    Collection {
582 597
        valVar: ?Var,
583 -
        idxVar: Var,
584 -
        dataReg: il::Reg,
585 -
        lengthVal: il::Val,
598 +
        bound: CollectionBound,
586 599
        elemType: *resolver::Type,
587 600
    },
588 601
}
589 602
603 +
/// State used to bound collection iteration.
604 +
union CollectionBound: Copy {
605 +
    /// An index bounds loops that expose it or have no element pointer.
606 +
    Index { idxVar: Var, ptrVar: ?Var, lengthVal: il::Val },
607 +
    /// The element address bounds loops without an index binding.
608 +
    Pointer { ptrVar: Var, endVal: il::Val },
609 +
}
610 +
590 611
//////////////////////////////
591 612
// Pattern Matching Support //
592 613
//////////////////////////////
593 614
594 615
// Match expressions are lowered by evaluating the subject once, then emitting
2678 2699
    assert self.loopDepth < self.loopStack.len, "enterLoop: loop depth overflow";
2679 2700
    let slot = &mut self.loopStack[self.loopDepth];
2680 2701
2681 2702
    set slot.breakTarget = breakBlock;
2682 2703
    set slot.continueTarget = continueBlock;
2704 +
    set slot.indexedSlice = nil;
2683 2705
    set self.loopDepth += 1;
2684 2706
}
2685 2707
2686 2708
/// Exit the current loop context.
2687 2709
fn exitLoop(self: &mut FnLowerer) {
5176 5198
/// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute
5177 5199
/// the element address.
5178 5200
unsafe fn lowerElemPtr(
5179 5201
    self: &mut FnLowerer, container: *ast::Node, index: *ast::Node
5180 5202
) -> ElemPtrResult throws (LowerError) {
5203 +
    if let case ast::NodeValue::Ident(_) = container.value {
5204 +
        if let case ast::NodeValue::Ident(_) = index.value {
5205 +
            let sourceSym = resolver::symbolFor(self.low.resolver, container);
5206 +
            let indexSym = resolver::symbolFor(self.low.resolver, index);
5207 +
            for depth in 0..self.loopDepth {
5208 +
                let indexed = self.loopStack[depth].indexedSlice else continue;
5209 +
                if sourceSym == indexed.sourceSym and indexSym == indexed.indexSym {
5210 +
                    let ptr = emitValToReg(self, try useVar(self, indexed.ptrVar));
5211 +
                    return ElemPtrResult { elemReg: ptr, elemType: *indexed.item };
5212 +
                }
5213 +
            }
5214 +
        }
5215 +
    }
5181 5216
    let containerTy = try typeOf(self, container);
5182 5217
    let subjectTy = resolver::autoDeref(containerTy);
5183 5218
    let baseVal = try lowerExpr(self, container);
5184 5219
    let indexVal = try lowerExpr(self, index);
5185 5220
    let baseReg = emitValToReg(self, baseVal);
5613 5648
5614 5649
    try switchToAndSeal(self, endBlock);
5615 5650
    exitLoop(self);
5616 5651
}
5617 5652
5618 -
/// Emit an increment of a variable by `1`.
5619 -
unsafe fn emitIncrement(self: &mut FnLowerer, v: Var, typ: il::Type) throws (LowerError) {
5653 +
/// Emit an increment of a variable by a fixed step.
5654 +
unsafe fn emitIncrement(self: &mut FnLowerer, v: Var, typ: il::Type, step: u32) throws (LowerError) {
5620 5655
    let cur = try useVar(self, v);
5621 -
    let next = nextReg(self);
5622 -
5623 -
    emit(self, il::Instr::BinOp { op: il::BinOp::Add, typ, dst: next, a: cur, b: il::Val::Imm(1) });
5624 -
    defVar(self, v, il::Val::Reg(next));
5656 +
    let next = emitTypedBinOp(self, il::BinOp::Add, typ, cur, il::Val::Imm(step as i64));
5657 +
    defVar(self, v, next);
5625 5658
}
5626 5659
5627 5660
/// Common for-loop lowering for both range and collection iterators.
5628 5661
///
5629 5662
/// The step block is created lazily (after the loop body) so that it gets
5635 5668
    let loopBlock = try createBlock(self, "loop");
5636 5669
    let bodyBlock = try createBlock(self, "body");
5637 5670
    let endBlock = try createBlock(self, "merge");
5638 5671
5639 5672
    enterLoop(self, endBlock, nil);
5673 +
    if let case ForIter::Range { indexedSlice, .. } = *iter {
5674 +
        let ctx = currentLoop(self) else panic "lowerForLoop: missing loop context";
5675 +
        set ctx.indexedSlice = indexedSlice;
5676 +
    }
5640 5677
    try switchAndJumpTo(self, loopBlock);
5641 5678
5642 5679
    // Emit condition check.
5643 5680
    match *iter {
5644 5681
        case ForIter::Range { valVar, endVal, valType, unsigned, .. } => {
5645 5682
            let curVal = try useVar(self, valVar);
5646 5683
            let cmp = il::CmpOp::Ult if unsigned else il::CmpOp::Slt;
5647 5684
            try emitBrCmp(self, cmp, valType, curVal, endVal, bodyBlock, endBlock);
5648 5685
        }
5649 -
        case ForIter::Collection { idxVar, lengthVal, .. } => {
5650 -
            let curIdx = try useVar(self, idxVar);
5651 -
            try emitBrCmp(self, il::CmpOp::Slt, il::Type::W32, curIdx, lengthVal, bodyBlock, endBlock);
5686 +
        case ForIter::Collection { bound, .. } => match bound {
5687 +
            case CollectionBound::Index { idxVar, lengthVal, .. } => {
5688 +
                let curIdx = try useVar(self, idxVar);
5689 +
                try emitBrCmp(self, il::CmpOp::Slt, il::Type::W32, curIdx, lengthVal, bodyBlock, endBlock);
5690 +
            },
5691 +
            case CollectionBound::Pointer { ptrVar, endVal } => {
5692 +
                let curPtr = try useVar(self, ptrVar);
5693 +
                try emitBrCmp(self, il::CmpOp::Ne, il::Type::W64, curPtr, endVal, bodyBlock, endBlock);
5694 +
            },
5652 5695
        }
5653 5696
    }
5654 5697
    // Switch to loop body.
5655 5698
    try switchToAndSeal(self, bodyBlock);
5656 5699
5657 5700
    // Emit element binding, only for collections.
5658 5701
    // Reads the element at the current index.
5659 -
    if let case ForIter::Collection { valVar, idxVar, dataReg, elemType, .. } = *iter {
5702 +
    let mut elementPtr: ?Var = nil;
5703 +
    if let case ForIter::Collection { valVar, bound, elemType } = *iter {
5704 +
        match bound {
5705 +
            case CollectionBound::Index { ptrVar, .. } => set elementPtr = ptrVar,
5706 +
            case CollectionBound::Pointer { ptrVar, .. } => set elementPtr = ptrVar,
5707 +
        }
5660 5708
        if let v = valVar {
5661 -
            let curIdx = try useVar(self, idxVar);
5662 -
            let elemReg = emitElem(self, resolver::getTypeLayout(*elemType).size, dataReg, curIdx);
5709 +
            let ptr = elementPtr else panic "lowerForLoop: missing collection pointer";
5710 +
            let elemReg = emitValToReg(self, try useVar(self, ptr));
5663 5711
            let val = emitRead(self, elemReg, 0, *elemType);
5664 5712
5665 5713
            defVar(self, v, val);
5666 5714
        }
5667 5715
    }
5677 5725
    }
5678 5726
    // Otherwise, emit increment directly in the current block,
5679 5727
    // saving the jump to a separate step block.
5680 5728
    if not blockHasTerminator(self) {
5681 5729
        match *iter {
5682 -
            case ForIter::Range { valVar, valType, indexVar, .. } => {
5683 -
                try emitIncrement(self, valVar, valType);
5730 +
            case ForIter::Range { valVar, valType, indexVar, indexedSlice, .. } => {
5731 +
                try emitIncrement(self, valVar, valType, 1);
5684 5732
                if let idxVar = indexVar {
5685 -
                    try emitIncrement(self, idxVar, il::Type::W32);
5733 +
                    try emitIncrement(self, idxVar, il::Type::W32, 1);
5734 +
                }
5735 +
                if let indexed = indexedSlice {
5736 +
                    let stride = resolver::getTypeLayout(*indexed.item).size;
5737 +
                    try emitIncrement(self, indexed.ptrVar, il::Type::W64, stride);
5686 5738
                }
5687 5739
            }
5688 -
            case ForIter::Collection { idxVar, .. } => {
5689 -
                try emitIncrement(self, idxVar, il::Type::W32);
5740 +
            case ForIter::Collection { bound, elemType, .. } => {
5741 +
                if let case CollectionBound::Index { idxVar, .. } = bound {
5742 +
                    try emitIncrement(self, idxVar, il::Type::W32, 1);
5743 +
                }
5744 +
                if let ptr = elementPtr {
5745 +
                    let stride = resolver::getTypeLayout(*elemType).size;
5746 +
                    try emitIncrement(self, ptr, il::Type::W64, stride);
5747 +
                }
5690 5748
            }
5691 5749
        }
5692 5750
        try emitJmp(self, loopBlock);
5693 5751
    }
5694 5752
    exitLoop(self);
5704 5762
    let savedVarsLen = enterVarScope(self);
5705 5763
    let info = resolver::forLoopInfoFor(self.low.resolver, node) else {
5706 5764
        throw LowerError::MissingMetadata;
5707 5765
    };
5708 5766
    match info {
5709 -
        case resolver::ForLoopInfo::Range { valType, range, bindingName, indexName } => {
5767 +
        case resolver::ForLoopInfo::Range { valType, range, bindingName, indexName, indexedSlice } => {
5710 5768
            let endExpr = range.end else {
5711 5769
                throw LowerError::MissingMetadata;
5712 5770
            };
5713 5771
            let mut startVal = il::Val::Imm(0);
5714 5772
            if let start = range.start {
5720 5778
5721 5779
            let mut indexVar: ?Var = nil;
5722 5780
            if indexName <> nil { // Optional index always starts at zero.
5723 5781
                set indexVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0));
5724 5782
            }
5783 +
            let mut indexedLoop: ?IndexedSliceLoop = nil;
5784 +
            if let info = indexedSlice {
5785 +
                let sourceSym = resolver::symbolFor(self.low.resolver, info.source) else {
5786 +
                    throw LowerError::MissingMetadata;
5787 +
                };
5788 +
                let indexSym = resolver::symbolFor(self.low.resolver, f.binding) else {
5789 +
                    throw LowerError::MissingMetadata;
5790 +
                };
5791 +
                let sliceVal = try lowerExpr(self, info.source);
5792 +
                let sliceReg = emitValToReg(self, sliceVal);
5793 +
                let dataReg = loadSlicePtr(self, sliceReg);
5794 +
                let ptrVar = newVar(self, nil, il::Type::W64, false, il::Val::Reg(dataReg));
5795 +
                set indexedLoop = IndexedSliceLoop {
5796 +
                    sourceSym, indexSym, ptrVar, item: info.item,
5797 +
                };
5798 +
            }
5725 5799
            let iter = ForIter::Range {
5726 5800
                valVar, indexVar, endVal, valType: iterType,
5727 5801
                unsigned: isUnsignedType(*valType),
5802 +
                indexedSlice: indexedLoop,
5728 5803
            };
5729 5804
5730 5805
            try lowerForLoop(self, &iter, f.body);
5731 5806
        }
5732 5807
        case resolver::ForLoopInfo::Collection { elemType, length, bindingName, indexName } => {
5739 5814
                set lengthVal = il::Val::Imm(len as i64);
5740 5815
            } else { // Slice (length must be loaded).
5741 5816
                set lengthVal = loadSliceLen(self, containerReg);
5742 5817
                set dataReg = loadSlicePtr(self, containerReg);
5743 5818
            }
5744 -
            // Declare index value binidng.
5745 -
            let idxVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0));
5746 -
5747 5819
            // Declare element value binding.
5820 +
            let mut ptrVar: ?Var = nil;
5748 5821
            let mut valVar: ?Var = nil;
5749 5822
            if bindingName <> nil {
5823 +
                set ptrVar = newVar(self, nil, il::Type::W64, false, il::Val::Reg(dataReg));
5750 5824
                set valVar = newVar(
5751 5825
                    self,
5752 5826
                    bindingName,
5753 5827
                    ilType(self.low, *elemType),
5754 5828
                    false,
5755 5829
                    il::Val::Undef
5756 5830
                );
5757 5831
            }
5758 -
            let iter = ForIter::Collection { valVar, idxVar, dataReg, lengthVal, elemType };
5832 +
            let stride = resolver::getTypeLayout(*elemType).size;
5833 +
            let mut bound: CollectionBound = undefined;
5834 +
            // A nonzero u32 stride reaches the end address after exactly
5835 +
            // length steps because length times stride fits in u64.
5836 +
            if indexName == nil and stride > 0 and ptrVar <> nil {
5837 +
                let mut endVal: il::Val = undefined;
5838 +
                if let len = length {
5839 +
                    let bytes = len as u64 * stride as u64;
5840 +
                    set endVal = emitTypedBinOp(
5841 +
                        self, il::BinOp::Add, il::Type::W64,
5842 +
                        il::Val::Reg(dataReg), il::Val::Imm(bytes as i64)
5843 +
                    );
5844 +
                } else {
5845 +
                    let dst = nextReg(self);
5846 +
                    emit(self, il::Instr::Zext { typ: il::Type::W32, dst, val: lengthVal });
5847 +
                    set endVal = il::Val::Reg(emitElem(self, stride, dataReg, il::Val::Reg(dst)));
5848 +
                }
5849 +
                let ptr = ptrVar else panic "lowerFor: missing collection pointer";
5850 +
                set bound = CollectionBound::Pointer {
5851 +
                    ptrVar: ptr,
5852 +
                    endVal,
5853 +
                };
5854 +
            } else {
5855 +
                let idxVar = newVar(self, indexName, il::Type::W32, false, il::Val::Imm(0));
5856 +
                set bound = CollectionBound::Index { idxVar, ptrVar, lengthVal };
5857 +
            }
5858 +
            let iter = ForIter::Collection { valVar, bound, elemType };
5759 5859
5760 5860
            try lowerForLoop(self, &iter, f.body);
5761 5861
        }
5762 5862
    }
5763 5863
    exitVarScope(self, savedVarsLen);
lib/std/lang/resolver.rad +58 -5
253 253
    mutable: bool,
254 254
    /// Static capacity if container is an array.
255 255
    capacity: ?u32,
256 256
}
257 257
258 +
/// Checked slice whose length bounds an indexed range loop.
259 +
export record IndexedSliceInfo: Copy {
260 +
    /// Identifier that provides the slice header.
261 +
    source: *ast::Node,
262 +
    /// Type of each slice element.
263 +
    item: *Type,
264 +
}
265 +
258 266
/// Pre-computed metadata for `for` loop iteration.
259 267
/// Used by the lowerer to avoid re-analyzing the iterable type.
260 268
export union ForLoopInfo: Copy {
261 269
    /// Iterating over a range expression (e.g., `for i in 0..n`).
262 270
    Range {
263 271
        valType: *Type,
264 272
        range: ast::Range,
265 273
        bindingName: ?*[u8],
266 -
        indexName: ?*[u8]
274 +
        indexName: ?*[u8],
275 +
        indexedSlice: ?IndexedSliceInfo,
267 276
    },
268 277
    /// Iterating over an array or slice. For arrays, the length field is set.
269 278
    Collection {
270 279
        elemType: *Type,
271 280
        length: ?u32,
4698 4707
    try visitOptional(self, loopNode.elseBranch, Type::Void);
4699 4708
4700 4709
    return setNodeType(self, node, Type::Void);
4701 4710
}
4702 4711
4712 +
/// Find a stable checked slice whose length bounds a range index.
4713 +
unsafe fn indexedSliceForRange(self: &Resolver, binding: *ast::Node, range: ast::Range) -> ?IndexedSliceInfo {
4714 +
    let case ast::NodeValue::Ident(_) = binding.value else {
4715 +
        return nil;
4716 +
    };
4717 +
    let start = range.start else {
4718 +
        return nil;
4719 +
    };
4720 +
    let case ast::NodeValue::Number(zero) = start.value else {
4721 +
        return nil;
4722 +
    };
4723 +
    if zero.magnitude <> 0 {
4724 +
        return nil;
4725 +
    }
4726 +
    let end = range.end else {
4727 +
        return nil;
4728 +
    };
4729 +
    let case ast::NodeValue::FieldAccess(access) = end.value else {
4730 +
        return nil;
4731 +
    };
4732 +
    let case ast::NodeValue::Ident(field) = access.child.value else {
4733 +
        return nil;
4734 +
    };
4735 +
    if not mem::eq(field, LEN_FIELD) {
4736 +
        return nil;
4737 +
    }
4738 +
    let case ast::NodeValue::Ident(_) = access.parent.value else {
4739 +
        return nil;
4740 +
    };
4741 +
    let source = access.parent;
4742 +
    let sym = symbolFor(self, source) else {
4743 +
        return nil;
4744 +
    };
4745 +
    let case SymbolData::Value { mutable: false, type: ty, .. } = sym.data else {
4746 +
        return nil;
4747 +
    };
4748 +
    let case Type::Slice { class: types::PointerClass::Ref, item, mutable: false } = ty else {
4749 +
        return nil;
4750 +
    };
4751 +
    return IndexedSliceInfo { source, item };
4752 +
}
4753 +
4703 4754
/// Analyze a `for` loop, binding iteration variables.
4704 4755
unsafe fn resolveFor(self: &mut Resolver, node: *ast::Node, forStmt: ast::For) -> Type
4705 4756
    throws (ResolveError)
4706 4757
{
4707 4758
    let iterableTy = try infer(self, forStmt.iterable);
4717 4768
            set indexName = name;
4718 4769
        }
4719 4770
    }
4720 4771
    // Extract item type and store pre-computed loop metadata for the lowerer.
4721 4772
    let mut itemTy: Type = undefined;
4773 +
    let mut hasElementPointer = bindingName <> nil;
4722 4774
    match iterableTy {
4723 4775
        case Type::Slice { item, class, .. } => {
4724 4776
            if class == types::PointerClass::Unsafe {
4725 4777
                try requireUnsafe(self, forStmt.iterable);
4726 4778
            }
4738 4790
            let case ast::NodeValue::Range(range) = forStmt.iterable.value else {
4739 4791
                throw emitError(self, forStmt.iterable, ErrorKind::ExpectedIterable);
4740 4792
            };
4741 4793
            set itemTy = *valType;
4742 4794
4795 +
            let indexedSlice = indexedSliceForRange(self, forStmt.binding, range);
4743 4796
            setForLoopInfo(self, node, ForLoopInfo::Range {
4744 -
                valType, range, bindingName, indexName
4797 +
                valType, range, bindingName, indexName, indexedSlice
4745 4798
            });
4799 +
            set hasElementPointer = indexedSlice <> nil;
4746 4800
        }
4747 4801
        case Type::Array(arrayInfo) => {
4748 4802
            set itemTy = *arrayInfo.item;
4749 4803
            setForLoopInfo(self, node, ForLoopInfo::Collection {
4750 4804
                elemType: arrayInfo.item,
4759 4813
    try bindForLoopPattern(self, forStmt.binding, itemTy, false);
4760 4814
4761 4815
    if let pat = forStmt.index {
4762 4816
        try bindForLoopPattern(self, pat, Type::U32, false);
4763 4817
    }
4764 -
    // The lowerer always creates at least one internal variable for iteration,
4765 -
    // even when the binding is a placeholder or no explicit index is given.
4818 +
    // Reserve space for an iterator variable and a possible element pointer.
4766 4819
    if let owner = self.currentFnNode {
4767 -
        set self.nodeData.entries[owner.id].localCount += 1;
4820 +
        set self.nodeData.entries[owner.id].localCount += 2 if hasElementPointer else 1;
4768 4821
    }
4769 4822
    try visitLoop(self, forStmt.body);
4770 4823
    exitScope(self);
4771 4824
4772 4825
    try visitOptional(self, forStmt.elseBranch, Type::Void);
test/tests/average.ril +12 -11
1 1
fn w32 $average(w64 %0) {
2 2
  @entry0
3 3
    load w32 %1 %0 8;
4 4
    load w64 %2 %0 0;
5 -
    jmp @loop1(0, 0);
6 -
  @loop1(w32 %3, w32 %7)
7 -
    br.slt w32 %3 %1 @body2 @merge3;
8 -
  @body2
5 +
    zext w32 %3 %1;
9 6
    mul w64 %4 %3 4;
10 7
    add w64 %5 %2 %4;
11 -
    load w32 %6 %5 0;
12 -
    add w32 %8 %7 %6;
13 -
    add w32 %9 %3 1;
14 -
    jmp @loop1(%9, %8);
8 +
    jmp @loop1(%2, 0);
9 +
  @loop1(w64 %6, w32 %8)
10 +
    br.ne w64 %6 %5 @body2 @merge3;
11 +
  @body2
12 +
    load w32 %7 %6 0;
13 +
    add w32 %9 %8 %7;
14 +
    add w64 %10 %6 4;
15 +
    jmp @loop1(%10, %9);
15 16
  @merge3
16 -
    load w32 %11 %0 8;
17 -
    udiv w32 %12 %7 %11;
18 -
    ret %12;
17 +
    load w32 %12 %0 8;
18 +
    udiv w32 %13 %8 %12;
19 +
    ret %13;
19 20
}
test/tests/const.array.ril +10 -12
6 6
}
7 7
8 8
fn w32 $main() {
9 9
  @entry0
10 10
    copy %0 $NUMBERS;
11 -
    jmp @loop1(0, 0);
12 -
  @loop1(w32 %1, w32 %5)
13 -
    br.slt w32 %1 4 @body2 @merge3;
11 +
    add w64 %1 %0 16;
12 +
    jmp @loop1(%0, 0);
13 +
  @loop1(w64 %2, w32 %4)
14 +
    br.ne w64 %2 %1 @body2 @merge3;
14 15
  @body2
15 -
    mul w64 %2 %1 4;
16 -
    add w64 %3 %0 %2;
17 -
    sload w32 %4 %3 0;
18 -
    add w32 %6 %5 %4;
19 -
    add w32 %7 %1 1;
20 -
    jmp @loop1(%7, %6);
16 +
    sload w32 %3 %2 0;
17 +
    add w32 %5 %4 %3;
18 +
    add w64 %6 %2 4;
19 +
    jmp @loop1(%6, %5);
21 20
  @merge3
22 -
    sub w32 %8 %5 10;
23 -
    ret %8;
21 +
    sub w32 %7 %4 10;
22 +
    ret %7;
24 23
}
25 -
test/tests/loop.for.array.ril +9 -10
1 1
fn w32 $forArrayIndexed(w64 %0) {
2 2
  @entry0
3 -
    jmp @loop1(0, 0);
4 -
  @loop1(w32 %1, w32 %5)
3 +
    jmp @loop1(0, %0, 0);
4 +
  @loop1(w32 %1, w64 %2, w32 %4)
5 5
    br.slt w32 %1 3 @body2 @merge3;
6 6
  @body2
7 -
    mul w64 %2 %1 4;
8 -
    add w64 %3 %0 %2;
9 -
    sload w32 %4 %3 0;
10 -
    add w32 %6 %4 %1;
11 -
    add w32 %7 %5 %6;
12 -
    add w32 %8 %1 1;
13 -
    jmp @loop1(%8, %7);
7 +
    sload w32 %3 %2 0;
8 +
    add w32 %5 %3 %1;
9 +
    add w32 %6 %4 %5;
10 +
    add w32 %7 %1 1;
11 +
    add w64 %8 %2 4;
12 +
    jmp @loop1(%7, %8, %6);
14 13
  @merge3
15 -
    ret %5;
14 +
    ret %4;
16 15
}
test/tests/loop.for.continue.ril +13 -14
17 17
    jmp @loop1(%5, %6);
18 18
}
19 19
20 20
fn w32 $forContinueArray(w64 %0) {
21 21
  @entry0
22 -
    jmp @loop1(0, 0);
23 -
  @loop1(w32 %1, w32 %5)
24 -
    br.slt w32 %1 4 @body2 @merge3;
22 +
    add w64 %1 %0 16;
23 +
    jmp @loop1(%0, 0);
24 +
  @loop1(w64 %2, w32 %4)
25 +
    br.ne w64 %2 %1 @body2 @merge3;
25 26
  @body2
26 -
    mul w64 %2 %1 4;
27 -
    add w64 %3 %0 %2;
28 -
    sload w32 %4 %3 0;
29 -
    br.eq w32 %4 0 @then4 @merge5;
27 +
    sload w32 %3 %2 0;
28 +
    br.eq w32 %3 0 @then4 @merge5;
30 29
  @merge3
31 -
    ret %5;
30 +
    ret %4;
32 31
  @then4
33 -
    jmp @step6(%5);
32 +
    jmp @step6(%4);
34 33
  @merge5
35 -
    add w32 %6 %5 %4;
36 -
    jmp @step6(%6);
37 -
  @step6(w32 %9)
38 -
    add w32 %8 %1 1;
39 -
    jmp @loop1(%8, %9);
34 +
    add w32 %5 %4 %3;
35 +
    jmp @step6(%5);
36 +
  @step6(w32 %8)
37 +
    add w64 %7 %2 4;
38 +
    jmp @loop1(%7, %8);
40 39
}
test/tests/loop.for.pointer.rad added +66 -0
1 +
//! returns: 0
2 +
3 +
/// Zero-size collection element.
4 +
record Empty: Copy {}
5 +
6 +
/// Aggregate with a stride greater than one machine word.
7 +
record Item: Copy {
8 +
    /// First value.
9 +
    first: u64,
10 +
    /// Second value.
11 +
    second: u64,
12 +
}
13 +
14 +
/// Check collection iteration through empty, short, and aggregate slices.
15 +
@default fn main() -> u32 {
16 +
    let emptyStorage: [u32; 0] = [];
17 +
    let empty: &[u32] = &emptyStorage[..];
18 +
    let mut count: u32 = 0;
19 +
    for value in empty {
20 +
        set count += value;
21 +
    }
22 +
    assert count == 0;
23 +
24 +
    let emptyValues: [Empty; 3] = [Empty {}, Empty {}, Empty {}];
25 +
    let mut visits: u32 = 0;
26 +
    for value in emptyValues {
27 +
        assert value == Empty {};
28 +
        set visits += 1;
29 +
    }
30 +
    assert visits == 3;
31 +
32 +
    let values: [u32; 4] = [3, 0, 5, 7];
33 +
    let slice: &[u32] = &values[..];
34 +
    let mut nonzero: u32 = 0;
35 +
    for value in slice {
36 +
        if value == 0 {
37 +
            continue;
38 +
        }
39 +
        set nonzero += value;
40 +
    }
41 +
    assert nonzero == 15;
42 +
43 +
    let mut sum: u32 = 0;
44 +
    for value, index in slice {
45 +
        if value == 0 {
46 +
            continue;
47 +
        }
48 +
        set sum += value + index;
49 +
    }
50 +
    assert sum == 20;
51 +
52 +
    let items: [Item; 3] = [
53 +
        Item { first: 1, second: 2 },
54 +
        Item { first: 3, second: 4 },
55 +
        Item { first: 5, second: 6 },
56 +
    ];
57 +
    let mut total: u64 = 0;
58 +
    for item in items {
59 +
        set total += item.second;
60 +
        if item.first == 3 {
61 +
            break;
62 +
        }
63 +
    }
64 +
    assert total == 6;
65 +
    return 0;
66 +
}
test/tests/loop.for.slice.indexed.offset.bounds.rad added +12 -0
1 +
//! returns: 133
2 +
3 +
/// An offset index keeps its own bounds check.
4 +
@default fn main() -> u32 {
5 +
    let values: [u32; 1] = [1];
6 +
    let slice: &[u32] = &values[..];
7 +
    for i in 0..slice.len {
8 +
        let value = slice[i + 1];
9 +
        assert value > 0;
10 +
    }
11 +
    return 0;
12 +
}
test/tests/loop.for.slice.indexed.other.bounds.rad added +14 -0
1 +
//! returns: 133
2 +
3 +
/// A different slice keeps its own bounds check.
4 +
@default fn main() -> u32 {
5 +
    let values: [u32; 2] = [1, 2];
6 +
    let short: [u32; 1] = [3];
7 +
    let first: &[u32] = &values[..];
8 +
    let second: &[u32] = &short[..];
9 +
    for i in 0..first.len {
10 +
        let value = second[i];
11 +
        assert value > 0;
12 +
    }
13 +
    return 0;
14 +
}
test/tests/loop.for.slice.indexed.rad added +8 -0
1 +
/// Sum a checked slice with the index bounded by its length.
2 +
fn sumIndexed(items: &[u32]) -> u32 {
3 +
    let mut sum: u32 = 0;
4 +
    for i in 0..items.len {
5 +
        set sum += items[i];
6 +
    }
7 +
    return sum;
8 +
}
test/tests/loop.for.slice.indexed.ril added +16 -0
1 +
fn w32 $sumIndexed(w64 %0) {
2 +
  @entry0
3 +
    load w32 %1 %0 8;
4 +
    load w64 %2 %0 0;
5 +
    jmp @loop1(0, 0, %2);
6 +
  @loop1(w32 %3, w32 %4, w64 %5)
7 +
    br.ult w32 %3 %1 @body2 @merge3;
8 +
  @body2
9 +
    load w32 %6 %5 0;
10 +
    add w32 %7 %4 %6;
11 +
    add w32 %8 %3 1;
12 +
    add w64 %9 %5 4;
13 +
    jmp @loop1(%8, %7, %9);
14 +
  @merge3
15 +
    ret %4;
16 +
}
test/tests/loop.for.slice.indexed.runtime.rad added +55 -0
1 +
//! returns: 0
2 +
3 +
/// Aggregate with a sixteen-byte element stride.
4 +
record Pair: Copy {
5 +
    /// First value.
6 +
    first: u64,
7 +
    /// Second value.
8 +
    second: u64,
9 +
}
10 +
11 +
/// Exercise exact indexed traversal through checked slices.
12 +
@default fn main() -> u32 {
13 +
    let emptyStorage: [u32; 0] = [];
14 +
    let empty: &[u32] = &emptyStorage[..];
15 +
    let mut count: u32 = 0;
16 +
    for i in 0..empty.len {
17 +
        set count += empty[i];
18 +
    }
19 +
    assert count == 0;
20 +
21 +
    let values: [u32; 3] = [2, 3, 5];
22 +
    let slice: &[u32] = &values[..];
23 +
    let mut sum: u32 = 0;
24 +
    for i in 0..slice.len {
25 +
        if i == 1 {
26 +
            continue;
27 +
        }
28 +
        set sum += slice[i];
29 +
    }
30 +
    assert sum == 7;
31 +
32 +
    let pairs: [Pair; 3] = [
33 +
        Pair { first: 1, second: 2 },
34 +
        Pair { first: 3, second: 4 },
35 +
        Pair { first: 5, second: 6 },
36 +
    ];
37 +
    let pairSlice: &[Pair] = &pairs[..];
38 +
    let mut total: u64 = 0;
39 +
    for i in 0..pairSlice.len {
40 +
        set total += pairSlice[i].second;
41 +
        if i == 1 {
42 +
            break;
43 +
        }
44 +
    }
45 +
    assert total == 6;
46 +
47 +
    let mut nested: u32 = 0;
48 +
    for i in 0..slice.len {
49 +
        for j in 0..2 {
50 +
            set nested += slice[i] + j;
51 +
        }
52 +
    }
53 +
    assert nested == 23;
54 +
    return 0;
55 +
}
test/tests/loop.for.slice.indexed.shadow.bounds.rad added +13 -0
1 +
//! returns: 133
2 +
3 +
/// A shadowed index keeps its own bounds check.
4 +
@default fn main() -> u32 {
5 +
    let values: [u32; 1] = [1];
6 +
    let slice: &[u32] = &values[..];
7 +
    for i in 0..slice.len {
8 +
        let i: u32 = 1;
9 +
        let value = slice[i];
10 +
        assert value > 0;
11 +
    }
12 +
    return 0;
13 +
}
test/tests/loop.for.slice.indexed.source.shadow.bounds.rad added +14 -0
1 +
//! returns: 133
2 +
3 +
/// A shadowed slice keeps its own bounds check.
4 +
@default fn main() -> u32 {
5 +
    let values: [u32; 2] = [1, 2];
6 +
    let short: [u32; 1] = [3];
7 +
    let slice: &[u32] = &values[..];
8 +
    for i in 0..slice.len {
9 +
        let slice: &[u32] = &short[..];
10 +
        let value = slice[i];
11 +
        assert value > 0;
12 +
    }
13 +
    return 0;
14 +
}
test/tests/loop.for.slice.pointer.bound.rad added +8 -0
1 +
/// Sum a checked slice without an index binding.
2 +
fn sumValues(values: &[u32]) -> u32 {
3 +
    let mut sum: u32 = 0;
4 +
    for value in values {
5 +
        set sum += value;
6 +
    }
7 +
    return sum;
8 +
}
test/tests/loop.for.slice.pointer.bound.ril added +18 -0
1 +
fn w32 $sumValues(w64 %0) {
2 +
  @entry0
3 +
    load w32 %1 %0 8;
4 +
    load w64 %2 %0 0;
5 +
    zext w32 %3 %1;
6 +
    mul w64 %4 %3 4;
7 +
    add w64 %5 %2 %4;
8 +
    jmp @loop1(%2, 0);
9 +
  @loop1(w64 %6, w32 %8)
10 +
    br.ne w64 %6 %5 @body2 @merge3;
11 +
  @body2
12 +
    load w32 %7 %6 0;
13 +
    add w32 %9 %8 %7;
14 +
    add w64 %10 %6 4;
15 +
    jmp @loop1(%10, %9);
16 +
  @merge3
17 +
    ret %8;
18 +
}
test/tests/loop.for.slice.ril +9 -10
1 1
fn w32 $forSliceIndexed(w64 %0) {
2 2
  @entry0
3 3
    load w32 %1 %0 8;
4 4
    load w64 %2 %0 0;
5 -
    jmp @loop1(0, 0);
6 -
  @loop1(w32 %3, w32 %7)
5 +
    jmp @loop1(0, %2, 0);
6 +
  @loop1(w32 %3, w64 %4, w32 %6)
7 7
    br.slt w32 %3 %1 @body2 @merge3;
8 8
  @body2
9 -
    mul w64 %4 %3 4;
10 -
    add w64 %5 %2 %4;
11 -
    sload w32 %6 %5 0;
12 -
    add w32 %8 %6 %3;
13 -
    add w32 %9 %7 %8;
14 -
    add w32 %10 %3 1;
15 -
    jmp @loop1(%10, %9);
9 +
    sload w32 %5 %4 0;
10 +
    add w32 %7 %5 %3;
11 +
    add w32 %8 %6 %7;
12 +
    add w32 %9 %3 1;
13 +
    add w64 %10 %4 4;
14 +
    jmp @loop1(%9, %10, %8);
16 15
  @merge3
17 -
    ret %7;
16 +
    ret %6;
18 17
}