compiler: Borrow lexical variable metadata safely

0c14982741a160baebf8ea8094b9841c3b5b734c5d9c498dc6e3cf1a87243d70
Alexis Sellier committed ago 1 parent cbcd09d4
lib/std/lang/lower.rad +73 -53
487 487
    /// When true, the SSA value is a pointer to a stack slot and reads/writes
488 488
    /// must go through memory instead of using the cached SSA value directly.
489 489
    addressTaken: bool,
490 490
}
491 491
492 +
/// Checked metadata storage and the active lexical variable prefix.
493 +
record Variables: 'function {
494 +
    /// Slots reserved from the resolver's local-variable bound.
495 +
    items: &'function mut [VarData],
496 +
    /// Number of active variable bindings.
497 +
    len: u32,
498 +
}
499 +
492 500
/// Links a function parameter to its corresponding variable for the entry block.
493 501
/// After creating the entry block, we iterate through these to define initial values.
494 502
record FnParamBinding: Copy {
495 503
    /// The variable that receives this parameter's value.
496 504
    var: Var,
704 712
    // ~ SSA variable tracking ~ //
705 713
706 714
    /// Metadata (name, type, mutability) for each variable. Indexed by variable
707 715
    /// id. Doesn't change after declaration. For the SSA value of a variable in
708 716
    /// a specific block, see [`BlockData::vars`].
709 -
    vars: *unsafe mut [VarData],
717 +
    vars: Variables 'function,
710 718
    /// Parameter-to-variable bindings, initialized in the entry block.
711 719
    params: [FnParamBinding; resolver::MAX_FN_PARAMS],
712 720
    /// Number of initialized parameter bindings.
713 721
    paramsLen: u32,
714 722
968 976
    set self.pkgName = pkgName;
969 977
    set self.currentMod = nil;
970 978
    set self.packageDataStart = self.data.len;
971 979
}
972 980
981 +
/// Allocate initialized variable metadata for one function.
982 +
unsafe fn variableStorage(arena: &mut alloc::Arena, count: u32) -> *unsafe mut [VarData] {
983 +
    let entries = try! alloc::allocRawSlice(arena, @sizeOf(VarData), @alignOf(VarData), count)
984 +
        as *unsafe mut [VarData];
985 +
    for i in 0..count {
986 +
        set entries[i] = VarData { name: nil, type: il::Type::W64, mutable: false, addressTaken: false };
987 +
    }
988 +
    return entries;
989 +
}
990 +
973 991
/// Create a new function lowerer for a given function type and name.
974 992
unsafe fn fnLowerer 'arena 'phase 'function (
975 993
    self: &'function mut Lowerer 'arena 'phase,
976 994
    node: *ast::Node,
977 995
    fnType: *resolver::FnType,
978 996
    qualName: *[u8],
979 -
    functionArena: &'function mut alloc::Arena
997 +
    functionArena: &'function mut alloc::Arena,
998 +
    variables: &'function mut [VarData]
980 999
) -> FnLowerer 'arena 'phase 'function where 'arena: 'phase, 'phase: 'function {
981 1000
    let localCount = resolver::nodeData(self.resolver, node).localCount;
982 1001
    let mut fnLow = FnLowerer 'arena 'phase 'function {
983 1002
        low: self,
984 1003
        arena: functionArena,
985 1004
        fnType,
986 1005
        localCount,
987 1006
        fnName: qualName,
988 -
        vars: &mut [],
1007 +
        vars: Variables 'function { items: variables, len: 0 },
989 1008
        params: [FnParamBinding { var: Var(0), reg: il::Reg { n: 0 } }; resolver::MAX_FN_PARAMS],
990 1009
        paramsLen: 0,
991 1010
        blockData: &mut [],
992 1011
        entryBlock: nil,
993 1012
        currentBlock: nil,
1042 1061
1043 1062
    // Register function symbol for cross-package call resolution.
1044 1063
    if let sym = data.sym {
1045 1064
        registerSymbolName(self, sym, qualName);
1046 1065
    }
1047 -
    let parent: 'function = &mut *self, arena = &mut *functionArena where 'phase: 'function in {
1048 -
        let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena);
1066 +
    let variableSlots = variableStorage(functionArena, data.localCount);
1067 +
    let parent: 'function = &mut *self, arena = &mut *functionArena, variables = &mut variableSlots[..] where 'phase: 'function in {
1068 +
        let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena, variables);
1049 1069
1050 1070
        // If the function returns an aggregate or is throwing, prepend a hidden
1051 1071
        // return parameter. The caller allocates the buffer and passes it
1052 1072
        // as the first argument; the callee writes the return value into it.
1053 1073
        if requiresReturnParam(fnType) and not isExtern {
1223 1243
        throw LowerError::ExpectedFunction;
1224 1244
    };
1225 1245
    let sym = data.sym else throw LowerError::MissingSymbol(node);
1226 1246
    registerSymbolName(self, sym, qualName);
1227 1247
1228 -
    let parent: 'function = &mut *self, arena = &mut *functionArena where 'phase: 'function in {
1229 -
        let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena);
1248 +
    let variableSlots = variableStorage(functionArena, data.localCount);
1249 +
    let parent: 'function = &mut *self, arena = &mut *functionArena, variables = &mut variableSlots[..] where 'phase: 'function in {
1250 +
        let mut fnLow = fnLowerer(parent, node, fnType, qualName, arena, variables);
1230 1251
        if requiresReturnParam(fnType) {
1231 1252
            set fnLow.returnReg = nextReg(&mut fnLow);
1232 1253
        }
1233 1254
        let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName);
1234 1255
        let func = try! alloc::allocRaw(fnLow.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *unsafe mut il::Fn;
2885 2906
    type: il::Type,
2886 2907
    mutable: bool,
2887 2908
    val: il::Val
2888 2909
) -> Var where 'arena: 'phase, 'phase: 'function {
2889 2910
    let id = self.vars.len;
2890 -
    self.vars.append(
2891 -
        VarData { name, type, mutable, addressTaken: false },
2892 -
        alloc::arenaAllocator(self.arena)
2893 -
    );
2894 -
2911 +
    assert id < self.vars.items.len, "newVar: variable metadata capacity exceeded";
2912 +
    set self.vars.items[id] = VarData { name, type, mutable, addressTaken: false };
2913 +
    set self.vars.len += 1;
2895 2914
    let v = Var(id);
2896 2915
    if self.currentBlock <> nil {
2897 2916
        defVar(self, v, val);
2898 2917
    }
2899 2918
    return v;
2953 2972
    return try createBlockParam(self, block, v);
2954 2973
}
2955 2974
2956 2975
/// Look up a variable by name in the current scope.
2957 2976
/// Searches from most recently declared to first, enabling shadowing.
2958 -
unsafe fn lookupVarByName 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, name: *[u8]) -> ?Var where 'arena: 'phase, 'phase: 'function {
2959 -
    let mut id = self.vars.len;
2977 +
fn lookupVarByName 'function (variables: &Variables 'function, name: *[u8]) -> ?Var {
2978 +
    let mut id = variables.len;
2960 2979
    while id > 0 {
2961 2980
        set id -= 1;
2962 -
        if let varName = self.vars[id].name {
2981 +
        if let varName = variables.items[id].name {
2963 2982
            // Names are interned strings, so pointer comparison suffices.
2964 2983
            if varName == name {
2965 2984
                return Var(id);
2966 2985
            }
2967 2986
        }
2968 2987
    }
2969 2988
    return nil;
2970 2989
}
2971 2990
2972 2991
/// Look up a local variable bound to an identifier node.
2973 -
unsafe fn lookupLocalVar 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, node: *ast::Node) -> ?Var where 'arena: 'phase, 'phase: 'function {
2992 +
fn lookupLocalVar 'function (variables: &Variables 'function, node: *ast::Node) -> ?Var {
2974 2993
    let case ast::NodeValue::Ident(name) = node.value else {
2975 2994
        return nil;
2976 2995
    };
2977 -
    return lookupVarByName(self, name);
2996 +
    return lookupVarByName(variables, name);
2978 2997
}
2979 2998
2980 2999
/// Save current lexical variable scope depth.
2981 -
unsafe fn enterVarScope 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function) -> u32 where 'arena: 'phase, 'phase: 'function {
2982 -
    return self.vars.len;
3000 +
fn enterVarScope 'function (variables: &Variables 'function) -> u32 {
3001 +
    return variables.len;
2983 3002
}
2984 3003
2985 3004
/// Restore lexical variable scope depth.
2986 -
unsafe fn exitVarScope 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, savedVarsLen: u32) where 'arena: 'phase, 'phase: 'function {
2987 -
    set self.vars = @sliceOf(self.vars.ptr, savedVarsLen, self.vars.cap);
3005 +
fn exitVarScope 'function (variables: &mut Variables 'function, savedVarsLen: u32) {
3006 +
    assert savedVarsLen <= variables.len, "exitVarScope: invalid saved scope";
3007 +
    set variables.len = savedVarsLen;
2988 3008
}
2989 3009
2990 3010
/// Get the metadata for a variable.
2991 -
unsafe fn getVar 'arena 'phase 'function (self: &FnLowerer 'arena 'phase 'function, v: Var) -> *unsafe VarData where 'arena: 'phase, 'phase: 'function {
2992 -
    assert *v < self.vars.len;
2993 -
    return &self.vars[*v];
3011 +
fn getVar 'function (variables: &Variables 'function, v: Var) -> VarData {
3012 +
    assert *v < variables.len;
3013 +
    return variables.items[*v];
2994 3014
}
2995 3015
2996 3016
/// Create a block parameter to merge a variable's value from multiple
2997 3017
/// control flow paths.
2998 3018
///
3012 3032
unsafe fn createBlockParam 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, block: BlockId, v: Var) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3013 3033
    // Entry block must not have block parameters.
3014 3034
    assert block <> self.entryBlock, "createBlockParam: entry block must not have block parameters";
3015 3035
    // Allocate a register to hold the merged value.
3016 3036
    let reg = nextReg(self);
3017 -
    let type = getVar(self, v).type;
3037 +
    let type = getVar(&self.vars, v).type;
3018 3038
3019 3039
    // Create block parameter and add it to the block.
3020 3040
    let param = il::Param { value: reg, type };
3021 3041
    let blk = getBlockMut(self, block);
3022 3042
    let paramIdx = blk.paramVars.len;
3800 3820
    let firstArm = try createBlock(self, "arm");
3801 3821
    try emitJmp(self, firstArm);
3802 3822
    try switchToAndSeal(self, firstArm);
3803 3823
3804 3824
    for prongNode, i in prongs {
3805 -
        let prongScope = enterVarScope(self);
3825 +
        let prongScope = enterVarScope(&self.vars);
3806 3826
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
3807 3827
            else panic "lowerMatch: expected match prong";
3808 3828
3809 3829
        let isLastArm = i + 1 == prongs.len;
3810 3830
        let hasGuard = prong.guard <> nil;
3865 3885
        }
3866 3886
        // Lower prong body and jump to merge if unterminated.
3867 3887
        try switchToAndSeal(self, bodyBlock);
3868 3888
        try lowerNode(self, prong.body);
3869 3889
        try emitMergeIfUnterminated(self, &mut mergeBlock);
3870 -
        exitVarScope(self, prongScope);
3890 +
        exitVarScope(&mut self.vars, prongScope);
3871 3891
3872 3892
        // Switch to next arm, unless last arm without guard.
3873 3893
        if not isLastArm or hasGuard {
3874 3894
            try switchToAndSeal(self, nextArm);
3875 3895
            if isLastArm {
3885 3905
    }
3886 3906
}
3887 3907
3888 3908
/// Lower an `if let` statement.
3889 3909
unsafe fn lowerIfLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, cond: ast::IfLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
3890 -
    let savedVarsLen = enterVarScope(self);
3910 +
    let savedVarsLen = enterVarScope(&self.vars);
3891 3911
    let subject = try lowerMatchSubject(self, cond.pattern.scrutinee);
3892 3912
    let mut thenBlock: BlockId = undefined;
3893 3913
    if cond.pattern.guard == nil {
3894 3914
        set thenBlock = try createBlock(self, "then");
3895 3915
    }
3903 3923
    try lowerNode(self, cond.thenBranch);
3904 3924
    try emitMergeIfUnterminated(self, &mut mergeBlock);
3905 3925
    // Pattern bindings are visible only in the success branch. Restore the
3906 3926
    // outer variable scope before lowering `else`, where a same-named outer
3907 3927
    // variable may be referenced.
3908 -
    exitVarScope(self, savedVarsLen);
3928 +
    exitVarScope(&mut self.vars, savedVarsLen);
3909 3929
3910 3930
    // Lower else branch.
3911 3931
    try switchToAndSeal(self, elseBlock);
3912 3932
    if let elseBranch = cond.elseBranch {
3913 3933
        try lowerNode(self, elseBranch);
3994 4014
        "success",
3995 4015
        elseBlock,
3996 4016
    );
3997 4017
    let mut bindingVar: ?Var = nil;
3998 4018
    if let case ast::PatternKind::Binding = letElse.pattern.kind {
3999 -
        set bindingVar = lookupLocalVar(self, letElse.pattern.pattern);
4019 +
        set bindingVar = lookupLocalVar(&self.vars, letElse.pattern.pattern);
4000 4020
    }
4001 4021
    let mergeBlock = try createBlock(self, "merge");
4002 4022
    try emitJmp(self, mergeBlock);
4003 4023
4004 4024
    try switchToAndSeal(self, elseBlock);
4016 4036
    try switchToAndSeal(self, mergeBlock);
4017 4037
}
4018 4038
4019 4039
/// Lower a `while let` loop as a match-driven loop.
4020 4040
unsafe fn lowerWhileLet 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, w: ast::WhileLet) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4021 -
    let savedVarsLen = enterVarScope(self);
4041 +
    let savedVarsLen = enterVarScope(&self.vars);
4022 4042
    // Create control flow blocks: loop header, body (created lazily when
4023 4043
    // there's a guard), and exit.
4024 4044
    let whileBlock = try createBlock(self, "while");
4025 4045
    let mut bodyBlock: BlockId = undefined;
4026 4046
    if w.pattern.guard == nil {
4040 4060
    try lowerBlock(self, w.body);
4041 4061
    try emitJmpAndSeal(self, whileBlock);
4042 4062
4043 4063
    exitLoop(self);
4044 4064
    try switchToAndSeal(self, endBlock);
4045 -
    exitVarScope(self, savedVarsLen);
4065 +
    exitVarScope(&mut self.vars, savedVarsLen);
4046 4066
}
4047 4067
4048 4068
///////////////////
4049 4069
// Node Lowering //
4050 4070
///////////////////
4054 4074
    if self.low.options.debug {
4055 4075
        set self.srcLoc.offset = node.span.offset;
4056 4076
    }
4057 4077
    match node.value {
4058 4078
        case ast::NodeValue::RegionBlock { bindings, body, .. } => {
4059 -
            let savedVarsLen = enterVarScope(self);
4079 +
            let savedVarsLen = enterVarScope(&self.vars);
4060 4080
            let values = try allocVals(self, bindings.len);
4061 4081
            for bindingNode, i in bindings {
4062 4082
                let case ast::NodeValue::RegionBinding(binding) = bindingNode.value
4063 4083
                    else throw LowerError::ExpectedIdentifier;
4064 4084
                set values[i] = try lowerExpr(self, binding.value);
4065 4085
                if blockHasTerminator(self) {
4066 -
                    exitVarScope(self, savedVarsLen);
4086 +
                    exitVarScope(&mut self.vars, savedVarsLen);
4067 4087
                    return;
4068 4088
                }
4069 4089
            }
4070 4090
            for bindingNode, i in bindings {
4071 4091
                let case ast::NodeValue::RegionBinding(binding) = bindingNode.value
4072 4092
                    else throw LowerError::ExpectedIdentifier;
4073 4093
                try bindLetValue(self, bindingNode, ast::borrowBinding(binding), values[i]);
4074 4094
            }
4075 4095
            try lowerBlock(self, body);
4076 -
            exitVarScope(self, savedVarsLen);
4096 +
            exitVarScope(&mut self.vars, savedVarsLen);
4077 4097
        }
4078 4098
        case ast::NodeValue::Block(_) => {
4079 4099
            try lowerBlock(self, node);
4080 4100
        }
4081 4101
        case ast::NodeValue::Return { value } => {
4162 4182
/// Lower a code block.
4163 4183
unsafe fn lowerBlock 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4164 4184
    let case ast::NodeValue::Block(blk) = node.value else {
4165 4185
        throw LowerError::ExpectedBlock(node);
4166 4186
    };
4167 -
    let savedVarsLen = enterVarScope(self);
4187 +
    let savedVarsLen = enterVarScope(&self.vars);
4168 4188
    for stmt in blk.statements {
4169 4189
        try lowerNode(self, stmt);
4170 4190
4171 4191
        // If the statement diverges, further statements are unreachable.
4172 4192
        if blockHasTerminator(self) {
4173 -
            exitVarScope(self, savedVarsLen);
4193 +
            exitVarScope(&mut self.vars, savedVarsLen);
4174 4194
            return;
4175 4195
        }
4176 4196
    }
4177 -
    exitVarScope(self, savedVarsLen);
4197 +
    exitVarScope(&mut self.vars, savedVarsLen);
4178 4198
}
4179 4199
4180 4200
///////////////////////////////////////
4181 4201
// Record and Aggregate Type Helpers //
4182 4202
///////////////////////////////////////
5189 5209
        };
5190 5210
        return il::Val::Reg(emitDataAddr(self, sym));
5191 5211
    }
5192 5212
    // Handle variable address: `&x`
5193 5213
    if let case ast::NodeValue::Ident(_) = addr.target.value {
5194 -
        if let v = lookupLocalVar(self, addr.target) {
5214 +
        if let v = lookupLocalVar(&self.vars, addr.target) {
5195 5215
            let val = try useVar(self, v);
5196 5216
            let typ = try typeOf(self, addr.target);
5197 5217
            // For aggregates, the value is already a pointer.
5198 5218
            if isAggregateType(typ) {
5199 5219
                return val;
5200 5220
            }
5201 5221
            // For scalars, if we've already materialized a stack slot for this
5202 5222
            // variable, the SSA value is that slot pointer.
5203 -
            if self.vars[*v].addressTaken {
5223 +
            if self.vars.items[*v].addressTaken {
5204 5224
                // Already address-taken; return existing stack pointer.
5205 5225
                return val;
5206 5226
            }
5207 5227
            // Materialize a stack slot using the declaration's resolved
5208 5228
            // layout so `align(N)` on locals is honored.
5209 5229
            let layout = resolver::getLayout(self.low.resolver, addr.target, typ);
5210 5230
            let slot = emitReserveLayout(self, layout);
5211 5231
            try emitStore(self, slot, 0, typ, val);
5212 5232
            let stackVal = il::Val::Reg(slot);
5213 5233
5214 -
            set self.vars[*v].addressTaken = true;
5234 +
            set self.vars.items[*v].addressTaken = true;
5215 5235
            defVar(self, v, stackVal);
5216 5236
5217 5237
            return stackVal;
5218 5238
        }
5219 5239
        // Fall back to symbol lookup for constants/statics.
5402 5422
                let layout = resolver::getLayout(self.low.resolver, node, typ);
5403 5423
                let slot = emitReserveLayout(self, layout);
5404 5424
                try emitStore(self, slot, 0, typ, varVal);
5405 5425
5406 5426
                let v = newVar(self, name, ilType, l.mutable, il::Val::Reg(slot));
5407 -
                set self.vars[*v].addressTaken = true;
5427 +
                set self.vars.items[*v].addressTaken = true;
5408 5428
5409 5429
                return;
5410 5430
            }
5411 5431
        }
5412 5432
    }
5565 5585
    let rhs = try lowerExpr(self, a.right);
5566 5586
5567 5587
    match a.left.value {
5568 5588
        case ast::NodeValue::Ident(_) => {
5569 5589
            // First try local variable lookup.
5570 -
            if let v = lookupLocalVar(self, a.left) {
5571 -
                if not getVar(self, v).mutable {
5590 +
            if let v = lookupLocalVar(&self.vars, a.left) {
5591 +
                if not getVar(&self.vars, v).mutable {
5572 5592
                    throw LowerError::ImmutableAssignment;
5573 5593
                }
5574 5594
                let leftTy = try typeOf(self, a.left);
5575 -
                if isAggregateType(leftTy) or getVar(self, v).addressTaken {
5595 +
                if isAggregateType(leftTy) or getVar(&self.vars, v).addressTaken {
5576 5596
                    // Aggregates and address-taken scalars are represented as
5577 5597
                    // pointers to stack memory. Store through the pointer.
5578 5598
                    let val = try useVar(self, v);
5579 5599
                    let dst = emitValToReg(self, val);
5580 5600
5821 5841
    switchToBlock(self, endBlock);
5822 5842
}
5823 5843
5824 5844
/// Lower a `for` loop over a range, array, or slice.
5825 5845
unsafe fn lowerFor 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function, node: *ast::Node, f: ast::For) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5826 -
    let savedVarsLen = enterVarScope(self);
5846 +
    let savedVarsLen = enterVarScope(&self.vars);
5827 5847
    let info = resolver::forLoopInfoFor(self.low.resolver, node) else {
5828 5848
        throw LowerError::MissingMetadata;
5829 5849
    };
5830 5850
    match info {
5831 5851
        case resolver::ForLoopInfo::Range { valType, range, bindingName, indexName } => {
5880 5900
            let iter = ForIter::Collection { valVar, idxVar, dataReg, lengthVal, elemType };
5881 5901
5882 5902
            try lowerForLoop(self, &iter, f.body);
5883 5903
        }
5884 5904
    }
5885 -
    exitVarScope(self, savedVarsLen);
5905 +
    exitVarScope(&mut self.vars, savedVarsLen);
5886 5906
}
5887 5907
5888 5908
/// Lower a break statement.
5889 5909
unsafe fn lowerBreak 'arena 'phase 'function (self: &mut FnLowerer 'arena 'phase 'function) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
5890 5910
    let ctx = currentLoop(self) else {
6511 6531
        if first.typeNode <> nil or t.catches.len > 1 {
6512 6532
            // Typed multi-catch: switch on global error tag.
6513 6533
            try lowerMultiCatch(self, t.catches, calleeInfo, base, tagReg, &mut mergeBlock);
6514 6534
        } else {
6515 6535
            // Single untyped catch clause.
6516 -
            let savedVarsLen = enterVarScope(self);
6536 +
            let savedVarsLen = enterVarScope(&self.vars);
6517 6537
            if let binding = first.binding {
6518 6538
                let case ast::NodeValue::Ident(name) = binding.value else {
6519 6539
                    throw LowerError::ExpectedIdentifier;
6520 6540
                };
6521 6541
                let errTy = *calleeInfo.throwList[0];
6522 6542
                let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET);
6523 6543
                let _ = newVar(self, name, ilType(self.low, errTy), false, errVal);
6524 6544
            }
6525 6545
            try lowerBlock(self, first.body);
6526 6546
            try emitMergeIfUnterminated(self, &mut mergeBlock);
6527 -
            exitVarScope(self, savedVarsLen);
6547 +
            exitVarScope(&mut self.vars, savedVarsLen);
6528 6548
        }
6529 6549
    } else if t.shouldPanic {
6530 6550
        // `try!` -- panic on error, emit unreachable since control won't continue.
6531 6551
        // TODO: We should have some kind of `panic` instruction?
6532 6552
        emit(self, il::Instr::Unreachable);
6628 6648
    for clauseNode, i in catches {
6629 6649
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value
6630 6650
            else panic "lowerMultiCatch: expected CatchClause";
6631 6651
6632 6652
        try switchToAndSeal(self, blocks[i]);
6633 -
        let savedVarsLen = enterVarScope(self);
6653 +
        let savedVarsLen = enterVarScope(&self.vars);
6634 6654
6635 6655
        if let binding = clause.binding {
6636 6656
            let case ast::NodeValue::Ident(name) = binding.value else {
6637 6657
                throw LowerError::ExpectedIdentifier;
6638 6658
            };
6642 6662
            newVar(self, name, ilType(self.low, errTy), false, errVal);
6643 6663
        }
6644 6664
        try lowerBlock(self, clause.body);
6645 6665
        try emitMergeIfUnterminated(self, mergeBlock);
6646 6666
6647 -
        exitVarScope(self, savedVarsLen);
6667 +
        exitVarScope(&mut self.vars, savedVarsLen);
6648 6668
    }
6649 6669
6650 6670
    // Emit unreachable block if no catch-all.
6651 6671
    if defaultIdx == nil {
6652 6672
        try switchToAndSeal(self, defaultTarget);
7449 7469
7450 7470
    match node.value {
7451 7471
        case ast::NodeValue::Ident(_) => {
7452 7472
            // First try local variable lookup.
7453 7473
            // Otherwise fall back to global symbol lookup.
7454 -
            if let v = lookupLocalVar(self, node) {
7474 +
            if let v = lookupLocalVar(&self.vars, node) {
7455 7475
                set val = try useVar(self, v);
7456 -
                if self.vars[*v].addressTaken {
7476 +
                if self.vars.items[*v].addressTaken {
7457 7477
                    let typ = try typeOf(self, node);
7458 7478
                    let ptr = emitValToReg(self, val);
7459 7479
                    set val = emitRead(self, ptr, 0, typ);
7460 7480
                }
7461 7481
            } else {
test/tests/lower.variable.scopes.rad added +40 -0
1 +
//! returns: 0
2 +
3 +
/// Exercise nested bindings and variable-slot reuse across control flow.
4 +
fn scopes(flag: bool) -> u32 {
5 +
    let mut value: u32 = 3;
6 +
    if flag {
7 +
        let value: u32 = 7;
8 +
        assert value == 7;
9 +
        if value == 7 {
10 +
            let value: u32 = 11;
11 +
            assert value == 11;
12 +
        }
13 +
        assert value == 7;
14 +
    } else {
15 +
        let value: u32 = 13;
16 +
        assert value == 13;
17 +
    }
18 +
    assert value == 3;
19 +
    for item, index in [1 as u32, 2, 3] {
20 +
        let value = item + index;
21 +
        assert value == 2 * index + 1;
22 +
    }
23 +
    let mut index: u32 = 0;
24 +
    while index < 3 {
25 +
        let value = index;
26 +
        assert value < 3;
27 +
        set index += 1;
28 +
    }
29 +
    let address: 'write = &mut value in {
30 +
        set *address = 17;
31 +
    }
32 +
    return value;
33 +
}
34 +
35 +
/// Check both branch paths and independent function contexts.
36 +
@default fn main() -> i32 {
37 +
    assert scopes(true) == 17;
38 +
    assert scopes(false) == 17;
39 +
    return 0;
40 +
}