ir_scope.mx raw
1 package main
2
3 import (
4 . "git.smesh.lol/moxie/pkg/types"
5 )
6
7 // checkUseAfterFree: no-op in arena model.
8 func checkUseAfterFree(f *SSAFunction) {}
9
10 // checkParamMutation rejects stores through function parameters.
11 // Parameters are borrowed references into the caller's arena. Writing
12 // through them places callee-arena data in caller-owned storage,
13 // which dangles when the callee's arena dies. Return values are the
14 // only way to pass data out.
15 func checkParamMutation(f *SSAFunction) {
16 if f == nil || f.Blocks == nil {
17 return
18 }
19 // Skip runtime - it manages arenas directly.
20 if f.Pkg != nil && f.Pkg.Pkg != nil {
21 path := f.Pkg.Pkg.Path
22 if path == "runtime" || path == "unsafe" {
23 return
24 }
25 }
26 // Pass 1: find allocs that hold parameter values.
27 // The SSA builder stores each param into an alloc:
28 // %alloc = alloca T
29 // store T %param, ptr %alloc
30 paramAllocs := map[SSAValue]bool{}
31 for _, p := range f.Params {
32 paramAllocs[SSAValue(p)] = true
33 }
34 paramHolders := map[SSAValue]bool{}
35 for _, b := range f.Blocks {
36 for _, instr := range b.Instrs {
37 s, ok := instr.(*SSAStore)
38 if !ok {
39 continue
40 }
41 if paramAllocs[s.Val] {
42 paramHolders[s.Addr] = true
43 }
44 }
45 }
46 // Pass 2: check all stores.
47 for _, b := range f.Blocks {
48 for _, instr := range b.Instrs {
49 s, ok := instr.(*SSAStore)
50 if !ok {
51 continue
52 }
53 if paramAllocs[s.Val] {
54 continue
55 }
56 if isParamDerivedEx(s.Addr, paramHolders, 0) {
57 name := paramRootName(s.Addr, paramHolders, 0)
58 push(cctx.compileErrors, f.name | ": store through borrowed parameter '" | name | "'")
59 }
60 }
61 }
62 }
63
64 func isParamDerivedEx(v SSAValue, holders map[SSAValue]bool, depth int32) (ok bool) {
65 if v == nil || depth > 20 {
66 return false
67 }
68 switch x := v.(type) {
69 case *SSAParameter:
70 return true
71 case *SSAFieldAddr:
72 return isParamDerivedEx(x.X, holders, depth+1)
73 case *SSAIndexAddr:
74 return isParamDerivedEx(x.X, holders, depth+1)
75 case *SSAUnOp:
76 if x.Op == OpMul {
77 if holders[x.X] {
78 return true
79 }
80 return isParamDerivedEx(x.X, holders, depth+1)
81 }
82 case *SSAChangeType:
83 return isParamDerivedEx(x.X, holders, depth+1)
84 case *SSAConvert:
85 return isParamDerivedEx(x.X, holders, depth+1)
86 case *SSAPhi:
87 for _, edge := range x.Edges {
88 if isParamDerivedEx(edge, holders, depth+1) {
89 return true
90 }
91 }
92 }
93 return false
94 }
95
96 func paramRootName(v SSAValue, holders map[SSAValue]bool, depth int32) (name string) {
97 if v == nil || depth > 20 {
98 return "?"
99 }
100 switch x := v.(type) {
101 case *SSAParameter:
102 return x.SSAName()
103 case *SSAFieldAddr:
104 return paramRootName(x.X, holders, depth+1)
105 case *SSAIndexAddr:
106 return paramRootName(x.X, holders, depth+1)
107 case *SSAUnOp:
108 if x.Op == OpMul {
109 return paramRootName(x.X, holders, depth+1)
110 }
111 case *SSAChangeType:
112 return paramRootName(x.X, holders, depth+1)
113 case *SSAConvert:
114 return paramRootName(x.X, holders, depth+1)
115 case *SSAPhi:
116 for _, edge := range x.Edges {
117 n := paramRootName(edge, holders, depth+1)
118 if n != "?" {
119 return n
120 }
121 }
122 }
123 return "?"
124 }
125
126 // markReturnEscapes promotes stack allocas to heap when their address can
127 // reach a return value.
128 func markReturnEscapes(f *SSAFunction) {
129 derived := map[SSAValue][]*SSAAlloc{}
130 stored := map[*SSAAlloc][]*SSAAlloc{}
131 mergeInto := func(dst []*SSAAlloc, srcs []*SSAAlloc) ([]*SSAAlloc, bool) {
132 didMerge := false
133 for _, s := range srcs {
134 if s == nil {
135 continue
136 }
137 found := false
138 for _, c := range dst {
139 if c == s {
140 found = true
141 break
142 }
143 }
144 if !found {
145 push(dst, s)
146 didMerge = true
147 }
148 }
149 return dst, didMerge
150 }
151 for _, b := range f.Blocks {
152 for _, instr := range b.Instrs {
153 if a, ok := instr.(*SSAAlloc); ok {
154 derived[SSAValue(a)] = []*SSAAlloc{a}
155 }
156 }
157 }
158 changed := true
159 for changed {
160 changed = false
161 for _, b := range f.Blocks {
162 for _, instr := range b.Instrs {
163 var dst SSAValue
164 var srcs []*SSAAlloc
165 switch i := instr.(type) {
166 case *SSASlice:
167 dst = i
168 srcs = derived[i.X]
169 case *SSAFieldAddr:
170 dst = i
171 srcs = derived[i.X]
172 case *SSAIndexAddr:
173 dst = i
174 srcs = derived[i.X]
175 case *SSAChangeType:
176 dst = i
177 srcs = derived[i.X]
178 case *SSAConvert:
179 dst = i
180 srcs = derived[i.X]
181 case *SSAMakeInterface:
182 dst = i
183 srcs = derived[i.X]
184 case *SSAPhi:
185 dst = i
186 for _, edge := range i.Edges {
187 srcs = srcs | derived[edge]
188 }
189 case *SSAUnOp:
190 if i.Op == OpMul {
191 dst = i
192 for _, a := range derived[i.X] {
193 srcs = srcs | stored[a]
194 }
195 }
196 case *SSAStore:
197 if d := derived[i.Val]; len(d) > 0 {
198 for _, a := range derived[i.Addr] {
199 ns, ch := mergeInto(stored[a], d)
200 if ch {
201 stored[a] = ns
202 changed = true
203 }
204 }
205 }
206 }
207 if dst != nil && len(srcs) > 0 {
208 nd, ch := mergeInto(derived[dst], srcs)
209 if ch {
210 derived[dst] = nd
211 changed = true
212 }
213 }
214 }
215 }
216 }
217 for _, b := range f.Blocks {
218 for _, instr := range b.Instrs {
219 r, ok := instr.(*SSAReturn)
220 if !ok {
221 continue
222 }
223 for _, res := range r.Results {
224 work := derived[res]
225 seen := map[*SSAAlloc]bool{}
226 for wi := 0; wi < len(work); wi++ {
227 a := work[wi]
228 if a == nil {
229 continue
230 }
231 if seen[a] {
232 continue
233 }
234 seen[a] = true
235 a.Heap = true
236 work = work | stored[a]
237 }
238 }
239 }
240 }
241 }
242
243 func allocEscapesViaCall(a *SSAAlloc, f *SSAFunction) (ok bool) {
244 escaped := map[SSAValue]bool{}
245 escaped[SSAValue(a)] = true
246 changed := true
247 for changed {
248 changed = false
249 for _, b := range f.Blocks {
250 for _, instr := range b.Instrs {
251 switch i := instr.(type) {
252 case *SSAFieldAddr:
253 if escaped[i.X] {
254 if v, ok2 := instr.(SSAValue); ok2 && !escaped[v] {
255 escaped[v] = true
256 changed = true
257 }
258 }
259 case *SSAMakeInterface:
260 if escaped[i.X] {
261 if v, ok2 := instr.(SSAValue); ok2 && !escaped[v] {
262 escaped[v] = true
263 changed = true
264 }
265 }
266 case *SSAStore:
267 if escaped[i.Val] || escaped[i.Addr] {
268 if escaped[i.Val] && !escaped[i.Addr] {
269 escaped[i.Addr] = true
270 changed = true
271 }
272 }
273 case *SSAPhi:
274 for _, edge := range i.Edges {
275 if escaped[edge] {
276 if v, ok2 := instr.(SSAValue); ok2 && !escaped[v] {
277 escaped[v] = true
278 changed = true
279 }
280 break
281 }
282 }
283 }
284 }
285 }
286 }
287 for _, b := range f.Blocks {
288 for _, instr := range b.Instrs {
289 switch i := instr.(type) {
290 case *SSACall:
291 for _, arg := range i.Call.Args {
292 if escaped[arg] {
293 return true
294 }
295 }
296 case *SSAMapUpdate:
297 if escaped[i.Key] || escaped[i.Value] || escaped[i.Map] {
298 return true
299 }
300 case *SSASend:
301 if escaped[i.X] {
302 return true
303 }
304 case *SSAInvoke:
305 if escaped[i.X] {
306 return true
307 }
308 for _, arg := range i.Args {
309 if escaped[arg] {
310 return true
311 }
312 }
313 }
314 }
315 }
316 return false
317 }
318
319 // Scope dealloc functions: all no-ops in arena model.
320 func (e *irEmitter) scopeTrackAlloc(ptrReg string) {}
321 func (e *irEmitter) scopeBeforeReturn(r *SSAReturn) {}
322 func (e *irEmitter) emitScopeFreesAt(scopeID int32) {}
323 func (e *irEmitter) emitScopeRelocateOnStore(s *SSAStore, val, valType string) {}
324