github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/ir/builder.go (about) 1 package ir 2 3 import ( 4 "fmt" 5 6 "github.com/arnodel/golua/ops" 7 ) 8 9 type Name string 10 11 type RegData struct { 12 IsCell bool 13 IsConstant bool 14 refCount int 15 } 16 17 const regHasUpvalue uint = 1 18 19 type CodeBuilder struct { 20 chunkName string 21 registers []RegData 22 context lexicalContext 23 parent *CodeBuilder 24 upvalues []Register 25 upvalueDests []Register 26 upnames []string 27 code []Instruction 28 lines []int 29 labels []bool 30 constantPool *ConstantPool 31 } 32 33 func NewCodeBuilder(chunkName string, constantPool *ConstantPool) *CodeBuilder { 34 return &CodeBuilder{ 35 chunkName: chunkName, 36 context: lexicalContext{}.pushNew(), 37 constantPool: constantPool, 38 } 39 } 40 41 func (c *CodeBuilder) NewChild(chunkName string) *CodeBuilder { 42 return &CodeBuilder{ 43 chunkName: chunkName, 44 context: lexicalContext{}.pushNew(), 45 constantPool: c.constantPool, 46 parent: c, 47 } 48 } 49 50 func (c *CodeBuilder) Dump() { 51 fmt.Println("--context") 52 c.context.dump() 53 fmt.Println("--constants") 54 for i, k := range c.constantPool.Constants() { 55 fmt.Printf("k%d: %s\n", i, k) 56 } 57 fmt.Println("--code") 58 for _, instr := range c.code { 59 fmt.Println(instr) 60 } 61 } 62 63 func (c *CodeBuilder) DeclareUniqueGotoLabel(name Name, line int) (Label, error) { 64 _, prevLine, ok := c.getGotoLabel(name) 65 if ok { 66 if prevLine > 0 { 67 return 0, fmt.Errorf("label '%s' already defined at line %d", name, prevLine) 68 } 69 return 0, fmt.Errorf("label '%s' already defined", name) 70 } 71 return c.DeclareGotoLabel(name, line), nil 72 } 73 74 func (c *CodeBuilder) DeclareGotoLabelNoLine(name Name) Label { 75 return c.DeclareGotoLabel(name, 0) 76 } 77 78 func (c *CodeBuilder) DeclareGotoLabel(name Name, line int) Label { 79 lbl := c.GetNewLabel() 80 c.context.addLabel(name, lbl, line) 81 return lbl 82 } 83 84 func (c *CodeBuilder) getGotoLabel(name Name) (Label, int, bool) { 85 return c.context.getLabel(name) 86 } 87 88 func (c *CodeBuilder) EmitGotoLabel(name Name) error { 89 label, line, ok := c.getGotoLabel(name) 90 if !ok { 91 return fmt.Errorf("cannot emit undeclared label '%s'", name) 92 } 93 if c.labels[label] { 94 return fmt.Errorf("label '%s' used twice", name) 95 } 96 return c.EmitLabel(label, line) 97 } 98 99 func (c *CodeBuilder) GetNewLabel() Label { 100 lbl := Label(len(c.labels)) 101 c.labels = append(c.labels, false) 102 return lbl 103 } 104 105 func (c *CodeBuilder) EmitLabel(lbl Label, line int) error { 106 if c.labels[lbl] { 107 return fmt.Errorf("label '%s' emitted twice", lbl) 108 } 109 c.labels[lbl] = true 110 c.Emit(DeclareLabel{Label: lbl}, line) 111 return nil 112 } 113 114 func (c *CodeBuilder) EmitLabelNoLine(lbl Label) error { 115 return c.EmitLabel(lbl, 0) 116 } 117 118 func (c *CodeBuilder) GetRegister(name Name) (Register, bool) { 119 return c.getRegister(name, 0) 120 } 121 122 func (c *CodeBuilder) getRegister(name Name, tags uint) (reg Register, ok bool) { 123 reg, ok = c.context.getRegister(name, tags) 124 if ok || c.parent == nil { 125 return 126 } 127 reg, ok = c.parent.getRegister(name, regHasUpvalue) 128 if ok { 129 isConstant := c.parent.IsConstantReg(reg) 130 c.parent.registers[reg].IsCell = true 131 c.upvalues = append(c.upvalues, reg) 132 c.upnames = append(c.upnames, string(name)) 133 reg = c.GetFreeRegister() 134 c.upvalueDests = append(c.upvalueDests, reg) 135 c.registers[reg].IsCell = true 136 c.registers[reg].IsConstant = isConstant 137 c.context.addToRoot(name, reg) 138 } 139 return 140 } 141 142 func (c *CodeBuilder) GetFreeRegister() Register { 143 reg := Register(len(c.registers)) 144 c.registers = append(c.registers, RegData{}) 145 return reg 146 } 147 148 func (c *CodeBuilder) TakeRegister(reg Register) { 149 c.registers[reg].refCount++ 150 if c.registers[reg].refCount == 1 { 151 c.EmitNoLine(TakeRegister{Reg: reg}) 152 } 153 } 154 155 func (c *CodeBuilder) ReleaseRegister(reg Register) { 156 if c.registers[reg].refCount == 0 { 157 panic("cannot release register") 158 } 159 c.registers[reg].refCount-- 160 if c.registers[reg].refCount == 0 { 161 c.EmitNoLine(ReleaseRegister{Reg: reg}) 162 } 163 } 164 165 func (c *CodeBuilder) PushContext() { 166 c.context = c.context.pushNew() 167 } 168 169 func (c *CodeBuilder) PopContext() { 170 context, top := c.context.pop() 171 if top.reg == nil { 172 panic("Cannot pop empty context") 173 } 174 c.emitTruncate(context.top()) 175 c.context = context 176 c.emitClearReg(top) 177 for _, tr := range top.reg { 178 c.ReleaseRegister(tr.reg) 179 } 180 } 181 182 func (c *CodeBuilder) emitClearReg(m lexicalScope) { 183 for _, tr := range m.reg { 184 if tr.tags®HasUpvalue != 0 && tr.reg >= 0 { 185 c.EmitNoLine(ClearReg{Dst: tr.reg}) 186 } 187 } 188 } 189 190 // PushCloseAction emits a PushCloseStack instruction and updates the current 191 // lexical context accordingly 192 func (c *CodeBuilder) PushCloseAction(reg Register) { 193 c.context.addHeight(1) 194 c.EmitNoLine(PushCloseStack{Src: reg}) 195 } 196 197 // HasPendingCloseActions returns true if there are close actions in the current 198 // context. In this case tail calls are disabled in order to allow the close 199 // actions to take place after the call. 200 func (c *CodeBuilder) HasPendingCloseActions() bool { 201 return c.context.getHeight() > 0 202 } 203 204 func (c *CodeBuilder) emitTruncate(m lexicalScope) { 205 if m.height < c.context.top().height { 206 c.EmitNoLine(TruncateCloseStack{Height: m.height}) 207 } 208 } 209 210 func (c *CodeBuilder) EmitJump(lblName Name, line int) bool { 211 var ( 212 lc = c.context 213 top lexicalScope 214 ) 215 for len(lc) > 0 { 216 lc, top = lc.pop() 217 if lbl, line, ok := top.getLabel(lblName); ok { 218 c.emitTruncate(top) 219 c.Emit(Jump{Label: lbl}, line) 220 return true 221 } 222 c.emitClearReg(top) 223 } 224 return false 225 } 226 227 func (c *CodeBuilder) DeclareLocal(name Name, reg Register) { 228 c.TakeRegister(reg) 229 c.context.addToTop(name, reg) 230 } 231 232 func (c *CodeBuilder) MarkConstantReg(reg Register) { 233 c.registers[reg].IsConstant = true 234 } 235 236 func (c *CodeBuilder) IsConstantReg(reg Register) bool { 237 return c.registers[reg].IsConstant 238 } 239 240 func (c *CodeBuilder) EmitNoLine(instr Instruction) { 241 c.Emit(instr, 0) 242 } 243 244 func (c *CodeBuilder) Emit(instr Instruction, line int) { 245 c.code = append(c.code, instr) 246 c.lines = append(c.lines, line) 247 } 248 249 func (c *CodeBuilder) Close() (uint, []Register) { 250 return c.getConstantIndex(c.getCode()), c.upvalues 251 } 252 253 func (c *CodeBuilder) getConstantIndex(k Constant) uint { 254 return c.constantPool.GetConstantIndex(k) 255 } 256 257 func (c *CodeBuilder) getCode() *Code { 258 return &Code{ 259 Instructions: c.code, 260 Lines: c.lines, 261 Constants: c.constantPool.Constants(), 262 Registers: c.registers, 263 UpvalueDests: c.upvalueDests, 264 UpNames: c.upnames, 265 Name: c.chunkName, 266 } 267 } 268 269 func EmitConstant(c *CodeBuilder, k Constant, reg Register, line int) { 270 c.Emit(LoadConst{Dst: reg, Kidx: c.getConstantIndex(k)}, line) 271 } 272 273 func EmitMoveNoLine(c *CodeBuilder, dst Register, src Register) { 274 EmitMove(c, dst, src, 0) 275 } 276 277 func EmitMove(c *CodeBuilder, dst Register, src Register, line int) { 278 if dst != src { 279 c.Emit(Transform{Op: ops.OpId, Dst: dst, Src: src}, line) 280 } 281 }