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&regHasUpvalue != 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  }