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