compiler: Borrow lexical variable metadata safely
0c14982741a160baebf8ea8094b9841c3b5b734c5d9c498dc6e3cf1a87243d70
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 | + | } |