331529fc94
* add an Aerospike storage backend * go mod vendor * add Aerospike storage configuration docs * review fixes * bump aerospike client to v3.1.1 * rename the defaultHostname variable * relocate the docs page
1673 lines
44 KiB
Go
1673 lines
44 KiB
Go
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/yuin/gopher-lua/ast"
|
|
"math"
|
|
"reflect"
|
|
)
|
|
|
|
/* internal constants & structs {{{ */
|
|
|
|
const maxRegisters = 200
|
|
|
|
type expContextType int
|
|
|
|
const (
|
|
ecGlobal expContextType = iota
|
|
ecUpvalue
|
|
ecLocal
|
|
ecTable
|
|
ecVararg
|
|
ecMethod
|
|
ecNone
|
|
)
|
|
|
|
const regNotDefined = opMaxArgsA + 1
|
|
const labelNoJump = 0
|
|
|
|
type expcontext struct {
|
|
ctype expContextType
|
|
reg int
|
|
// varargopt >= 0: wants varargopt+1 results, i.e a = func()
|
|
// varargopt = -1: ignore results i.e func()
|
|
// varargopt = -2: receive all results i.e a = {func()}
|
|
varargopt int
|
|
}
|
|
|
|
type assigncontext struct {
|
|
ec *expcontext
|
|
keyrk int
|
|
valuerk int
|
|
keyks bool
|
|
needmove bool
|
|
}
|
|
|
|
type lblabels struct {
|
|
t int
|
|
f int
|
|
e int
|
|
b bool
|
|
}
|
|
|
|
type constLValueExpr struct {
|
|
ast.ExprBase
|
|
|
|
Value LValue
|
|
}
|
|
|
|
// }}}
|
|
|
|
/* utilities {{{ */
|
|
var _ecnone0 = &expcontext{ecNone, regNotDefined, 0}
|
|
var _ecnonem1 = &expcontext{ecNone, regNotDefined, -1}
|
|
var _ecnonem2 = &expcontext{ecNone, regNotDefined, -2}
|
|
var ecfuncdef = &expcontext{ecMethod, regNotDefined, 0}
|
|
|
|
func ecupdate(ec *expcontext, ctype expContextType, reg, varargopt int) {
|
|
if ec == _ecnone0 || ec == _ecnonem1 || ec == _ecnonem2 {
|
|
panic("can not update ec cache")
|
|
}
|
|
ec.ctype = ctype
|
|
ec.reg = reg
|
|
ec.varargopt = varargopt
|
|
}
|
|
|
|
func ecnone(varargopt int) *expcontext {
|
|
switch varargopt {
|
|
case 0:
|
|
return _ecnone0
|
|
case -1:
|
|
return _ecnonem1
|
|
case -2:
|
|
return _ecnonem2
|
|
}
|
|
return &expcontext{ecNone, regNotDefined, varargopt}
|
|
}
|
|
|
|
func shouldmove(ec *expcontext, reg int) bool {
|
|
return ec.ctype == ecLocal && ec.reg != regNotDefined && ec.reg != reg
|
|
}
|
|
|
|
func sline(pos ast.PositionHolder) int {
|
|
return pos.Line()
|
|
}
|
|
|
|
func eline(pos ast.PositionHolder) int {
|
|
return pos.LastLine()
|
|
}
|
|
|
|
func savereg(ec *expcontext, reg int) int {
|
|
if ec.ctype != ecLocal || ec.reg == regNotDefined {
|
|
return reg
|
|
}
|
|
return ec.reg
|
|
}
|
|
|
|
func raiseCompileError(context *funcContext, line int, format string, args ...interface{}) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
panic(&CompileError{context: context, Line: line, Message: msg})
|
|
}
|
|
|
|
func isVarArgReturnExpr(expr ast.Expr) bool {
|
|
switch ex := expr.(type) {
|
|
case *ast.FuncCallExpr:
|
|
return !ex.AdjustRet
|
|
case *ast.Comma3Expr:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func lnumberValue(expr ast.Expr) (LNumber, bool) {
|
|
if ex, ok := expr.(*ast.NumberExpr); ok {
|
|
lv, err := parseNumber(ex.Value)
|
|
if err != nil {
|
|
lv = LNumber(math.NaN())
|
|
}
|
|
return lv, true
|
|
} else if ex, ok := expr.(*constLValueExpr); ok {
|
|
return ex.Value.(LNumber), true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
/* utilities }}} */
|
|
|
|
type CompileError struct { // {{{
|
|
context *funcContext
|
|
Line int
|
|
Message string
|
|
}
|
|
|
|
func (e *CompileError) Error() string {
|
|
return fmt.Sprintf("compile error near line(%v) %v: %v", e.Line, e.context.Proto.SourceName, e.Message)
|
|
} // }}}
|
|
|
|
type codeStore struct { // {{{
|
|
codes []uint32
|
|
lines []int
|
|
pc int
|
|
}
|
|
|
|
func (cd *codeStore) Add(inst uint32, line int) {
|
|
if l := len(cd.codes); l <= 0 || cd.pc == l {
|
|
cd.codes = append(cd.codes, inst)
|
|
cd.lines = append(cd.lines, line)
|
|
} else {
|
|
cd.codes[cd.pc] = inst
|
|
cd.lines[cd.pc] = line
|
|
}
|
|
cd.pc++
|
|
}
|
|
|
|
func (cd *codeStore) AddABC(op int, a int, b int, c int, line int) {
|
|
cd.Add(opCreateABC(op, a, b, c), line)
|
|
}
|
|
|
|
func (cd *codeStore) AddABx(op int, a int, bx int, line int) {
|
|
cd.Add(opCreateABx(op, a, bx), line)
|
|
}
|
|
|
|
func (cd *codeStore) AddASbx(op int, a int, sbx int, line int) {
|
|
cd.Add(opCreateASbx(op, a, sbx), line)
|
|
}
|
|
|
|
func (cd *codeStore) PropagateKMV(top int, save *int, reg *int, inc int) {
|
|
lastinst := cd.Last()
|
|
if opGetArgA(lastinst) >= top {
|
|
switch opGetOpCode(lastinst) {
|
|
case OP_LOADK:
|
|
cindex := opGetArgBx(lastinst)
|
|
if cindex <= opMaxIndexRk {
|
|
cd.Pop()
|
|
*save = opRkAsk(cindex)
|
|
return
|
|
}
|
|
case OP_MOVE:
|
|
cd.Pop()
|
|
*save = opGetArgB(lastinst)
|
|
return
|
|
}
|
|
}
|
|
*save = *reg
|
|
*reg = *reg + inc
|
|
}
|
|
|
|
func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) {
|
|
lastinst := cd.Last()
|
|
if opGetArgA(lastinst) >= top {
|
|
switch opGetOpCode(lastinst) {
|
|
case OP_MOVE:
|
|
cd.Pop()
|
|
*save = opGetArgB(lastinst)
|
|
return
|
|
}
|
|
}
|
|
*save = *reg
|
|
*reg = *reg + inc
|
|
}
|
|
|
|
func (cd *codeStore) AddLoadNil(a, b, line int) {
|
|
last := cd.Last()
|
|
if opGetOpCode(last) == OP_LOADNIL && (opGetArgA(last)+opGetArgB(last)) == a {
|
|
cd.SetB(cd.LastPC(), b)
|
|
} else {
|
|
cd.AddABC(OP_LOADNIL, a, b, 0, line)
|
|
}
|
|
}
|
|
|
|
func (cd *codeStore) SetOpCode(pc int, v int) {
|
|
opSetOpCode(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) SetA(pc int, v int) {
|
|
opSetArgA(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) SetB(pc int, v int) {
|
|
opSetArgB(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) SetC(pc int, v int) {
|
|
opSetArgC(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) SetBx(pc int, v int) {
|
|
opSetArgBx(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) SetSbx(pc int, v int) {
|
|
opSetArgSbx(&cd.codes[pc], v)
|
|
}
|
|
|
|
func (cd *codeStore) At(pc int) uint32 {
|
|
return cd.codes[pc]
|
|
}
|
|
|
|
func (cd *codeStore) List() []uint32 {
|
|
return cd.codes[:cd.pc]
|
|
}
|
|
|
|
func (cd *codeStore) PosList() []int {
|
|
return cd.lines[:cd.pc]
|
|
}
|
|
|
|
func (cd *codeStore) LastPC() int {
|
|
return cd.pc - 1
|
|
}
|
|
|
|
func (cd *codeStore) Last() uint32 {
|
|
if cd.pc == 0 {
|
|
return opInvalidInstruction
|
|
}
|
|
return cd.codes[cd.pc-1]
|
|
}
|
|
|
|
func (cd *codeStore) Pop() {
|
|
cd.pc--
|
|
} /* }}} Code */
|
|
|
|
/* {{{ VarNamePool */
|
|
|
|
type varNamePoolValue struct {
|
|
Index int
|
|
Name string
|
|
}
|
|
|
|
type varNamePool struct {
|
|
names []string
|
|
offset int
|
|
}
|
|
|
|
func newVarNamePool(offset int) *varNamePool {
|
|
return &varNamePool{make([]string, 0, 16), offset}
|
|
}
|
|
|
|
func (vp *varNamePool) Names() []string {
|
|
return vp.names
|
|
}
|
|
|
|
func (vp *varNamePool) List() []varNamePoolValue {
|
|
result := make([]varNamePoolValue, len(vp.names), len(vp.names))
|
|
for i, name := range vp.names {
|
|
result[i].Index = i + vp.offset
|
|
result[i].Name = name
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (vp *varNamePool) LastIndex() int {
|
|
return vp.offset + len(vp.names)
|
|
}
|
|
|
|
func (vp *varNamePool) Find(name string) int {
|
|
for i := len(vp.names) - 1; i >= 0; i-- {
|
|
if vp.names[i] == name {
|
|
return i + vp.offset
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (vp *varNamePool) RegisterUnique(name string) int {
|
|
index := vp.Find(name)
|
|
if index < 0 {
|
|
return vp.Register(name)
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (vp *varNamePool) Register(name string) int {
|
|
vp.names = append(vp.names, name)
|
|
return len(vp.names) - 1 + vp.offset
|
|
}
|
|
|
|
/* }}} VarNamePool */
|
|
|
|
/* FuncContext {{{ */
|
|
|
|
type codeBlock struct {
|
|
LocalVars *varNamePool
|
|
BreakLabel int
|
|
Parent *codeBlock
|
|
RefUpvalue bool
|
|
LineStart int
|
|
LastLine int
|
|
}
|
|
|
|
func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock {
|
|
bl := &codeBlock{localvars, blabel, parent, false, 0, 0}
|
|
if pos != nil {
|
|
bl.LineStart = pos.Line()
|
|
bl.LastLine = pos.LastLine()
|
|
}
|
|
return bl
|
|
}
|
|
|
|
type funcContext struct {
|
|
Proto *FunctionProto
|
|
Code *codeStore
|
|
Parent *funcContext
|
|
Upvalues *varNamePool
|
|
Block *codeBlock
|
|
Blocks []*codeBlock
|
|
regTop int
|
|
labelId int
|
|
labelPc map[int]int
|
|
}
|
|
|
|
func newFuncContext(sourcename string, parent *funcContext) *funcContext {
|
|
fc := &funcContext{
|
|
Proto: newFunctionProto(sourcename),
|
|
Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0},
|
|
Parent: parent,
|
|
Upvalues: newVarNamePool(0),
|
|
Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil),
|
|
regTop: 0,
|
|
labelId: 1,
|
|
labelPc: map[int]int{},
|
|
}
|
|
fc.Blocks = []*codeBlock{fc.Block}
|
|
return fc
|
|
}
|
|
|
|
func (fc *funcContext) NewLabel() int {
|
|
ret := fc.labelId
|
|
fc.labelId++
|
|
return ret
|
|
}
|
|
|
|
func (fc *funcContext) SetLabelPc(label int, pc int) {
|
|
fc.labelPc[label] = pc
|
|
}
|
|
|
|
func (fc *funcContext) GetLabelPc(label int) int {
|
|
return fc.labelPc[label]
|
|
}
|
|
|
|
func (fc *funcContext) ConstIndex(value LValue) int {
|
|
ctype := value.Type()
|
|
for i, lv := range fc.Proto.Constants {
|
|
if lv.Type() == ctype && lv == value {
|
|
return i
|
|
}
|
|
}
|
|
fc.Proto.Constants = append(fc.Proto.Constants, value)
|
|
v := len(fc.Proto.Constants) - 1
|
|
if v > opMaxArgBx {
|
|
raiseCompileError(fc, fc.Proto.LineDefined, "too many constants")
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (fc *funcContext) RegisterLocalVar(name string) int {
|
|
ret := fc.Block.LocalVars.Register(name)
|
|
fc.Proto.DbgLocals = append(fc.Proto.DbgLocals, &DbgLocalInfo{Name: name, StartPc: fc.Code.LastPC() + 1})
|
|
fc.SetRegTop(fc.RegTop() + 1)
|
|
return ret
|
|
}
|
|
|
|
func (fc *funcContext) FindLocalVarAndBlock(name string) (int, *codeBlock) {
|
|
for block := fc.Block; block != nil; block = block.Parent {
|
|
if index := block.LocalVars.Find(name); index > -1 {
|
|
return index, block
|
|
}
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
func (fc *funcContext) FindLocalVar(name string) int {
|
|
idx, _ := fc.FindLocalVarAndBlock(name)
|
|
return idx
|
|
}
|
|
|
|
func (fc *funcContext) LocalVars() []varNamePoolValue {
|
|
result := make([]varNamePoolValue, 0, 32)
|
|
for _, block := range fc.Blocks {
|
|
result = append(result, block.LocalVars.List()...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) {
|
|
fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos)
|
|
fc.Blocks = append(fc.Blocks, fc.Block)
|
|
}
|
|
|
|
func (fc *funcContext) CloseUpvalues() int {
|
|
n := -1
|
|
if fc.Block.RefUpvalue {
|
|
n = fc.Block.Parent.LocalVars.LastIndex()
|
|
fc.Code.AddABC(OP_CLOSE, n, 0, 0, fc.Block.LastLine)
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (fc *funcContext) LeaveBlock() int {
|
|
closed := fc.CloseUpvalues()
|
|
fc.EndScope()
|
|
fc.Block = fc.Block.Parent
|
|
fc.SetRegTop(fc.Block.LocalVars.LastIndex())
|
|
return closed
|
|
}
|
|
|
|
func (fc *funcContext) EndScope() {
|
|
for _, vr := range fc.Block.LocalVars.List() {
|
|
fc.Proto.DbgLocals[vr.Index].EndPc = fc.Code.LastPC()
|
|
}
|
|
}
|
|
|
|
func (fc *funcContext) SetRegTop(top int) {
|
|
if top > maxRegisters {
|
|
raiseCompileError(fc, fc.Proto.LineDefined, "too many local variables")
|
|
}
|
|
fc.regTop = top
|
|
}
|
|
|
|
func (fc *funcContext) RegTop() int {
|
|
return fc.regTop
|
|
}
|
|
|
|
/* FuncContext }}} */
|
|
|
|
func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{
|
|
for _, stmt := range chunk {
|
|
compileStmt(context, stmt)
|
|
}
|
|
} // }}}
|
|
|
|
func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{
|
|
if len(chunk) == 0 {
|
|
return
|
|
}
|
|
ph := &ast.Node{}
|
|
ph.SetLine(sline(chunk[0]))
|
|
ph.SetLastLine(eline(chunk[len(chunk)-1]))
|
|
context.EnterBlock(labelNoJump, ph)
|
|
for _, stmt := range chunk {
|
|
compileStmt(context, stmt)
|
|
}
|
|
context.LeaveBlock()
|
|
} // }}}
|
|
|
|
func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{
|
|
switch st := stmt.(type) {
|
|
case *ast.AssignStmt:
|
|
compileAssignStmt(context, st)
|
|
case *ast.LocalAssignStmt:
|
|
compileLocalAssignStmt(context, st)
|
|
case *ast.FuncCallStmt:
|
|
compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1))
|
|
case *ast.DoBlockStmt:
|
|
context.EnterBlock(labelNoJump, st)
|
|
compileChunk(context, st.Stmts)
|
|
context.LeaveBlock()
|
|
case *ast.WhileStmt:
|
|
compileWhileStmt(context, st)
|
|
case *ast.RepeatStmt:
|
|
compileRepeatStmt(context, st)
|
|
case *ast.FuncDefStmt:
|
|
compileFuncDefStmt(context, st)
|
|
case *ast.ReturnStmt:
|
|
compileReturnStmt(context, st)
|
|
case *ast.IfStmt:
|
|
compileIfStmt(context, st)
|
|
case *ast.BreakStmt:
|
|
compileBreakStmt(context, st)
|
|
case *ast.NumberForStmt:
|
|
compileNumberForStmt(context, st)
|
|
case *ast.GenericForStmt:
|
|
compileGenericForStmt(context, st)
|
|
}
|
|
} // }}}
|
|
|
|
func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []*assigncontext) { // {{{
|
|
reg := context.RegTop()
|
|
acs := make([]*assigncontext, 0, len(stmt.Lhs))
|
|
for i, lhs := range stmt.Lhs {
|
|
islast := i == len(stmt.Lhs)-1
|
|
switch st := lhs.(type) {
|
|
case *ast.IdentExpr:
|
|
identtype := getIdentRefType(context, context, st)
|
|
ec := &expcontext{identtype, regNotDefined, 0}
|
|
switch identtype {
|
|
case ecGlobal:
|
|
context.ConstIndex(LString(st.Value))
|
|
case ecUpvalue:
|
|
context.Upvalues.RegisterUnique(st.Value)
|
|
case ecLocal:
|
|
if islast {
|
|
ec.reg = context.FindLocalVar(st.Value)
|
|
}
|
|
}
|
|
acs = append(acs, &assigncontext{ec, 0, 0, false, false})
|
|
case *ast.AttrGetExpr:
|
|
ac := &assigncontext{&expcontext{ecTable, regNotDefined, 0}, 0, 0, false, false}
|
|
compileExprWithKMVPropagation(context, st.Object, ®, &ac.ec.reg)
|
|
ac.keyrk = reg
|
|
reg += compileExpr(context, reg, st.Key, ecnone(0))
|
|
if _, ok := st.Key.(*ast.StringExpr); ok {
|
|
ac.keyks = true
|
|
}
|
|
acs = append(acs, ac)
|
|
|
|
default:
|
|
panic("invalid left expression.")
|
|
}
|
|
}
|
|
return reg, acs
|
|
} // }}}
|
|
|
|
func compileAssignStmtRight(context *funcContext, stmt *ast.AssignStmt, reg int, acs []*assigncontext) (int, []*assigncontext) { // {{{
|
|
lennames := len(stmt.Lhs)
|
|
lenexprs := len(stmt.Rhs)
|
|
namesassigned := 0
|
|
|
|
for namesassigned < lennames {
|
|
ac := acs[namesassigned]
|
|
ec := ac.ec
|
|
var expr ast.Expr = nil
|
|
if namesassigned >= lenexprs {
|
|
expr = &ast.NilExpr{}
|
|
expr.SetLine(sline(stmt.Lhs[namesassigned]))
|
|
expr.SetLastLine(eline(stmt.Lhs[namesassigned]))
|
|
} else if isVarArgReturnExpr(stmt.Rhs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 {
|
|
varargopt := lennames - namesassigned - 1
|
|
regstart := reg
|
|
reginc := compileExpr(context, reg, stmt.Rhs[namesassigned], ecnone(varargopt))
|
|
reg += reginc
|
|
for i := namesassigned; i < namesassigned+int(reginc); i++ {
|
|
acs[i].needmove = true
|
|
if acs[i].ec.ctype == ecTable {
|
|
acs[i].valuerk = regstart + (i - namesassigned)
|
|
}
|
|
}
|
|
namesassigned = lennames
|
|
continue
|
|
}
|
|
|
|
if expr == nil {
|
|
expr = stmt.Rhs[namesassigned]
|
|
}
|
|
idx := reg
|
|
reginc := compileExpr(context, reg, expr, ec)
|
|
if ec.ctype == ecTable {
|
|
if _, ok := expr.(*ast.LogicalOpExpr); !ok {
|
|
context.Code.PropagateKMV(context.RegTop(), &ac.valuerk, ®, reginc)
|
|
} else {
|
|
ac.valuerk = idx
|
|
reg += reginc
|
|
}
|
|
} else {
|
|
ac.needmove = reginc != 0
|
|
reg += reginc
|
|
}
|
|
namesassigned += 1
|
|
}
|
|
|
|
rightreg := reg - 1
|
|
|
|
// extra right exprs
|
|
for i := namesassigned; i < lenexprs; i++ {
|
|
varargopt := -1
|
|
if i != lenexprs-1 {
|
|
varargopt = 0
|
|
}
|
|
reg += compileExpr(context, reg, stmt.Rhs[i], ecnone(varargopt))
|
|
}
|
|
return rightreg, acs
|
|
} // }}}
|
|
|
|
func compileAssignStmt(context *funcContext, stmt *ast.AssignStmt) { // {{{
|
|
code := context.Code
|
|
lennames := len(stmt.Lhs)
|
|
reg, acs := compileAssignStmtLeft(context, stmt)
|
|
reg, acs = compileAssignStmtRight(context, stmt, reg, acs)
|
|
|
|
for i := lennames - 1; i >= 0; i-- {
|
|
ex := stmt.Lhs[i]
|
|
switch acs[i].ec.ctype {
|
|
case ecLocal:
|
|
if acs[i].needmove {
|
|
code.AddABC(OP_MOVE, context.FindLocalVar(ex.(*ast.IdentExpr).Value), reg, 0, sline(ex))
|
|
reg -= 1
|
|
}
|
|
case ecGlobal:
|
|
code.AddABx(OP_SETGLOBAL, reg, context.ConstIndex(LString(ex.(*ast.IdentExpr).Value)), sline(ex))
|
|
reg -= 1
|
|
case ecUpvalue:
|
|
code.AddABC(OP_SETUPVAL, reg, context.Upvalues.RegisterUnique(ex.(*ast.IdentExpr).Value), 0, sline(ex))
|
|
reg -= 1
|
|
case ecTable:
|
|
opcode := OP_SETTABLE
|
|
if acs[i].keyks {
|
|
opcode = OP_SETTABLEKS
|
|
}
|
|
code.AddABC(opcode, acs[i].ec.reg, acs[i].keyrk, acs[i].valuerk, sline(ex))
|
|
if !opIsK(acs[i].valuerk) {
|
|
reg -= 1
|
|
}
|
|
}
|
|
}
|
|
} // }}}
|
|
|
|
func compileRegAssignment(context *funcContext, names []string, exprs []ast.Expr, reg int, nvars int, line int) { // {{{
|
|
lennames := len(names)
|
|
lenexprs := len(exprs)
|
|
namesassigned := 0
|
|
ec := &expcontext{}
|
|
|
|
for namesassigned < lennames && namesassigned < lenexprs {
|
|
if isVarArgReturnExpr(exprs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 {
|
|
|
|
varargopt := nvars - namesassigned
|
|
ecupdate(ec, ecVararg, reg, varargopt-1)
|
|
compileExpr(context, reg, exprs[namesassigned], ec)
|
|
reg += varargopt
|
|
namesassigned = lennames
|
|
} else {
|
|
ecupdate(ec, ecLocal, reg, 0)
|
|
compileExpr(context, reg, exprs[namesassigned], ec)
|
|
reg += 1
|
|
namesassigned += 1
|
|
}
|
|
}
|
|
|
|
// extra left names
|
|
if lennames > namesassigned {
|
|
restleft := lennames - namesassigned - 1
|
|
context.Code.AddLoadNil(reg, reg+restleft, line)
|
|
reg += restleft
|
|
}
|
|
|
|
// extra right exprs
|
|
for i := namesassigned; i < lenexprs; i++ {
|
|
varargopt := -1
|
|
if i != lenexprs-1 {
|
|
varargopt = 0
|
|
}
|
|
ecupdate(ec, ecNone, reg, varargopt)
|
|
reg += compileExpr(context, reg, exprs[i], ec)
|
|
}
|
|
} // }}}
|
|
|
|
func compileLocalAssignStmt(context *funcContext, stmt *ast.LocalAssignStmt) { // {{{
|
|
reg := context.RegTop()
|
|
if len(stmt.Names) == 1 && len(stmt.Exprs) == 1 {
|
|
if _, ok := stmt.Exprs[0].(*ast.FunctionExpr); ok {
|
|
context.RegisterLocalVar(stmt.Names[0])
|
|
compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt))
|
|
return
|
|
}
|
|
}
|
|
|
|
compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt))
|
|
for _, name := range stmt.Names {
|
|
context.RegisterLocalVar(name)
|
|
}
|
|
} // }}}
|
|
|
|
func compileReturnStmt(context *funcContext, stmt *ast.ReturnStmt) { // {{{
|
|
lenexprs := len(stmt.Exprs)
|
|
code := context.Code
|
|
reg := context.RegTop()
|
|
a := reg
|
|
lastisvaarg := false
|
|
|
|
if lenexprs == 1 {
|
|
switch ex := stmt.Exprs[0].(type) {
|
|
case *ast.IdentExpr:
|
|
if idx := context.FindLocalVar(ex.Value); idx > -1 {
|
|
code.AddABC(OP_RETURN, idx, 2, 0, sline(stmt))
|
|
return
|
|
}
|
|
case *ast.FuncCallExpr:
|
|
reg += compileExpr(context, reg, ex, ecnone(-2))
|
|
code.SetOpCode(code.LastPC(), OP_TAILCALL)
|
|
code.AddABC(OP_RETURN, a, 0, 0, sline(stmt))
|
|
return
|
|
}
|
|
}
|
|
|
|
for i, expr := range stmt.Exprs {
|
|
if i == lenexprs-1 && isVarArgReturnExpr(expr) {
|
|
compileExpr(context, reg, expr, ecnone(-2))
|
|
lastisvaarg = true
|
|
} else {
|
|
reg += compileExpr(context, reg, expr, ecnone(0))
|
|
}
|
|
}
|
|
count := reg - a + 1
|
|
if lastisvaarg {
|
|
count = 0
|
|
}
|
|
context.Code.AddABC(OP_RETURN, a, count, 0, sline(stmt))
|
|
} // }}}
|
|
|
|
func compileIfStmt(context *funcContext, stmt *ast.IfStmt) { // {{{
|
|
thenlabel := context.NewLabel()
|
|
elselabel := context.NewLabel()
|
|
endlabel := context.NewLabel()
|
|
|
|
compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false)
|
|
context.SetLabelPc(thenlabel, context.Code.LastPC())
|
|
compileBlock(context, stmt.Then)
|
|
if len(stmt.Else) > 0 {
|
|
context.Code.AddASbx(OP_JMP, 0, endlabel, sline(stmt))
|
|
}
|
|
context.SetLabelPc(elselabel, context.Code.LastPC())
|
|
if len(stmt.Else) > 0 {
|
|
compileBlock(context, stmt.Else)
|
|
context.SetLabelPc(endlabel, context.Code.LastPC())
|
|
}
|
|
|
|
} // }}}
|
|
|
|
func compileBranchCondition(context *funcContext, reg int, expr ast.Expr, thenlabel, elselabel int, hasnextcond bool) { // {{{
|
|
// TODO folding constants?
|
|
code := context.Code
|
|
flip := 0
|
|
jumplabel := elselabel
|
|
if hasnextcond {
|
|
flip = 1
|
|
jumplabel = thenlabel
|
|
}
|
|
|
|
switch ex := expr.(type) {
|
|
case *ast.FalseExpr, *ast.NilExpr:
|
|
if !hasnextcond {
|
|
code.AddASbx(OP_JMP, 0, elselabel, sline(expr))
|
|
return
|
|
}
|
|
case *ast.TrueExpr, *ast.NumberExpr, *ast.StringExpr:
|
|
if !hasnextcond {
|
|
return
|
|
}
|
|
case *ast.UnaryNotOpExpr:
|
|
compileBranchCondition(context, reg, ex.Expr, elselabel, thenlabel, !hasnextcond)
|
|
return
|
|
case *ast.LogicalOpExpr:
|
|
switch ex.Operator {
|
|
case "and":
|
|
nextcondlabel := context.NewLabel()
|
|
compileBranchCondition(context, reg, ex.Lhs, nextcondlabel, elselabel, false)
|
|
context.SetLabelPc(nextcondlabel, context.Code.LastPC())
|
|
compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond)
|
|
case "or":
|
|
nextcondlabel := context.NewLabel()
|
|
compileBranchCondition(context, reg, ex.Lhs, thenlabel, nextcondlabel, true)
|
|
context.SetLabelPc(nextcondlabel, context.Code.LastPC())
|
|
compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond)
|
|
}
|
|
return
|
|
case *ast.RelationalOpExpr:
|
|
compileRelationalOpExprAux(context, reg, ex, flip, jumplabel)
|
|
return
|
|
}
|
|
|
|
a := reg
|
|
compileExprWithMVPropagation(context, expr, ®, &a)
|
|
code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr))
|
|
code.AddASbx(OP_JMP, 0, jumplabel, sline(expr))
|
|
} // }}}
|
|
|
|
func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{
|
|
thenlabel := context.NewLabel()
|
|
elselabel := context.NewLabel()
|
|
condlabel := context.NewLabel()
|
|
|
|
context.SetLabelPc(condlabel, context.Code.LastPC())
|
|
compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false)
|
|
context.SetLabelPc(thenlabel, context.Code.LastPC())
|
|
context.EnterBlock(elselabel, stmt)
|
|
compileChunk(context, stmt.Stmts)
|
|
context.CloseUpvalues()
|
|
context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt))
|
|
context.LeaveBlock()
|
|
context.SetLabelPc(elselabel, context.Code.LastPC())
|
|
} // }}}
|
|
|
|
func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{
|
|
initlabel := context.NewLabel()
|
|
thenlabel := context.NewLabel()
|
|
elselabel := context.NewLabel()
|
|
|
|
context.SetLabelPc(initlabel, context.Code.LastPC())
|
|
context.SetLabelPc(elselabel, context.Code.LastPC())
|
|
context.EnterBlock(thenlabel, stmt)
|
|
compileChunk(context, stmt.Stmts)
|
|
compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false)
|
|
|
|
context.SetLabelPc(thenlabel, context.Code.LastPC())
|
|
n := context.LeaveBlock()
|
|
|
|
if n > -1 {
|
|
label := context.NewLabel()
|
|
context.Code.AddASbx(OP_JMP, 0, label, eline(stmt))
|
|
context.SetLabelPc(elselabel, context.Code.LastPC())
|
|
context.Code.AddABC(OP_CLOSE, n, 0, 0, eline(stmt))
|
|
context.Code.AddASbx(OP_JMP, 0, initlabel, eline(stmt))
|
|
context.SetLabelPc(label, context.Code.LastPC())
|
|
}
|
|
|
|
} // }}}
|
|
|
|
func compileBreakStmt(context *funcContext, stmt *ast.BreakStmt) { // {{{
|
|
for block := context.Block; block != nil; block = block.Parent {
|
|
if label := block.BreakLabel; label != labelNoJump {
|
|
if block.RefUpvalue {
|
|
context.Code.AddABC(OP_CLOSE, block.Parent.LocalVars.LastIndex(), 0, 0, sline(stmt))
|
|
}
|
|
context.Code.AddASbx(OP_JMP, 0, label, sline(stmt))
|
|
return
|
|
}
|
|
}
|
|
raiseCompileError(context, sline(stmt), "no loop to break")
|
|
} // }}}
|
|
|
|
func compileFuncDefStmt(context *funcContext, stmt *ast.FuncDefStmt) { // {{{
|
|
if stmt.Name.Func == nil {
|
|
reg := context.RegTop()
|
|
var treg, kreg int
|
|
compileExprWithKMVPropagation(context, stmt.Name.Receiver, ®, &treg)
|
|
kreg = loadRk(context, ®, stmt.Func, LString(stmt.Name.Method))
|
|
compileExpr(context, reg, stmt.Func, ecfuncdef)
|
|
context.Code.AddABC(OP_SETTABLE, treg, kreg, reg, sline(stmt.Name.Receiver))
|
|
} else {
|
|
astmt := &ast.AssignStmt{Lhs: []ast.Expr{stmt.Name.Func}, Rhs: []ast.Expr{stmt.Func}}
|
|
astmt.SetLine(sline(stmt.Func))
|
|
astmt.SetLastLine(eline(stmt.Func))
|
|
compileAssignStmt(context, astmt)
|
|
}
|
|
} // }}}
|
|
|
|
func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{{
|
|
code := context.Code
|
|
endlabel := context.NewLabel()
|
|
ec := &expcontext{}
|
|
|
|
context.EnterBlock(endlabel, stmt)
|
|
reg := context.RegTop()
|
|
rindex := context.RegisterLocalVar("(for index)")
|
|
ecupdate(ec, ecLocal, rindex, 0)
|
|
compileExpr(context, reg, stmt.Init, ec)
|
|
|
|
reg = context.RegTop()
|
|
rlimit := context.RegisterLocalVar("(for limit)")
|
|
ecupdate(ec, ecLocal, rlimit, 0)
|
|
compileExpr(context, reg, stmt.Limit, ec)
|
|
|
|
reg = context.RegTop()
|
|
rstep := context.RegisterLocalVar("(for step)")
|
|
if stmt.Step == nil {
|
|
stmt.Step = &ast.NumberExpr{Value: "1"}
|
|
stmt.Step.SetLine(sline(stmt.Init))
|
|
}
|
|
ecupdate(ec, ecLocal, rstep, 0)
|
|
compileExpr(context, reg, stmt.Step, ec)
|
|
|
|
code.AddASbx(OP_FORPREP, rindex, 0, sline(stmt))
|
|
|
|
context.RegisterLocalVar(stmt.Name)
|
|
|
|
bodypc := code.LastPC()
|
|
compileChunk(context, stmt.Stmts)
|
|
|
|
context.LeaveBlock()
|
|
|
|
flpc := code.LastPC()
|
|
code.AddASbx(OP_FORLOOP, rindex, bodypc-(flpc+1), sline(stmt))
|
|
|
|
context.SetLabelPc(endlabel, code.LastPC())
|
|
code.SetSbx(bodypc, flpc-bodypc)
|
|
|
|
} // }}}
|
|
|
|
func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // {{{
|
|
code := context.Code
|
|
endlabel := context.NewLabel()
|
|
bodylabel := context.NewLabel()
|
|
fllabel := context.NewLabel()
|
|
nnames := len(stmt.Names)
|
|
|
|
context.EnterBlock(endlabel, stmt)
|
|
rgen := context.RegisterLocalVar("(for generator)")
|
|
context.RegisterLocalVar("(for state)")
|
|
context.RegisterLocalVar("(for control)")
|
|
|
|
compileRegAssignment(context, stmt.Names, stmt.Exprs, context.RegTop()-3, 3, sline(stmt))
|
|
|
|
code.AddASbx(OP_JMP, 0, fllabel, sline(stmt))
|
|
|
|
for _, name := range stmt.Names {
|
|
context.RegisterLocalVar(name)
|
|
}
|
|
|
|
context.SetLabelPc(bodylabel, code.LastPC())
|
|
compileChunk(context, stmt.Stmts)
|
|
|
|
context.LeaveBlock()
|
|
|
|
context.SetLabelPc(fllabel, code.LastPC())
|
|
code.AddABC(OP_TFORLOOP, rgen, 0, nnames, sline(stmt))
|
|
code.AddASbx(OP_JMP, 0, bodylabel, sline(stmt))
|
|
|
|
context.SetLabelPc(endlabel, code.LastPC())
|
|
} // }}}
|
|
|
|
func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{
|
|
code := context.Code
|
|
sreg := savereg(ec, reg)
|
|
sused := 1
|
|
if sreg < reg {
|
|
sused = 0
|
|
}
|
|
|
|
switch ex := expr.(type) {
|
|
case *ast.StringExpr:
|
|
code.AddABx(OP_LOADK, sreg, context.ConstIndex(LString(ex.Value)), sline(ex))
|
|
return sused
|
|
case *ast.NumberExpr:
|
|
num, err := parseNumber(ex.Value)
|
|
if err != nil {
|
|
num = LNumber(math.NaN())
|
|
}
|
|
code.AddABx(OP_LOADK, sreg, context.ConstIndex(num), sline(ex))
|
|
return sused
|
|
case *constLValueExpr:
|
|
code.AddABx(OP_LOADK, sreg, context.ConstIndex(ex.Value), sline(ex))
|
|
return sused
|
|
case *ast.NilExpr:
|
|
code.AddLoadNil(sreg, sreg, sline(ex))
|
|
return sused
|
|
case *ast.FalseExpr:
|
|
code.AddABC(OP_LOADBOOL, sreg, 0, 0, sline(ex))
|
|
return sused
|
|
case *ast.TrueExpr:
|
|
code.AddABC(OP_LOADBOOL, sreg, 1, 0, sline(ex))
|
|
return sused
|
|
case *ast.IdentExpr:
|
|
switch getIdentRefType(context, context, ex) {
|
|
case ecGlobal:
|
|
code.AddABx(OP_GETGLOBAL, sreg, context.ConstIndex(LString(ex.Value)), sline(ex))
|
|
case ecUpvalue:
|
|
code.AddABC(OP_GETUPVAL, sreg, context.Upvalues.RegisterUnique(ex.Value), 0, sline(ex))
|
|
case ecLocal:
|
|
b := context.FindLocalVar(ex.Value)
|
|
code.AddABC(OP_MOVE, sreg, b, 0, sline(ex))
|
|
}
|
|
return sused
|
|
case *ast.Comma3Expr:
|
|
if context.Proto.IsVarArg == 0 {
|
|
raiseCompileError(context, sline(ex), "cannot use '...' outside a vararg function")
|
|
}
|
|
context.Proto.IsVarArg &= ^VarArgNeedsArg
|
|
code.AddABC(OP_VARARG, sreg, 2+ec.varargopt, 0, sline(ex))
|
|
if context.RegTop() > (sreg+2+ec.varargopt) || ec.varargopt < -1 {
|
|
return 0
|
|
}
|
|
return (sreg + 1 + ec.varargopt) - reg
|
|
case *ast.AttrGetExpr:
|
|
a := sreg
|
|
b := reg
|
|
compileExprWithMVPropagation(context, ex.Object, ®, &b)
|
|
c := reg
|
|
compileExprWithKMVPropagation(context, ex.Key, ®, &c)
|
|
opcode := OP_GETTABLE
|
|
if _, ok := ex.Key.(*ast.StringExpr); ok {
|
|
opcode = OP_GETTABLEKS
|
|
}
|
|
code.AddABC(opcode, a, b, c, sline(ex))
|
|
return sused
|
|
case *ast.TableExpr:
|
|
compileTableExpr(context, reg, ex, ec)
|
|
return 1
|
|
case *ast.ArithmeticOpExpr:
|
|
compileArithmeticOpExpr(context, reg, ex, ec)
|
|
return sused
|
|
case *ast.StringConcatOpExpr:
|
|
compileStringConcatOpExpr(context, reg, ex, ec)
|
|
return sused
|
|
case *ast.UnaryMinusOpExpr, *ast.UnaryNotOpExpr, *ast.UnaryLenOpExpr:
|
|
compileUnaryOpExpr(context, reg, ex, ec)
|
|
return sused
|
|
case *ast.RelationalOpExpr:
|
|
compileRelationalOpExpr(context, reg, ex, ec)
|
|
return sused
|
|
case *ast.LogicalOpExpr:
|
|
compileLogicalOpExpr(context, reg, ex, ec)
|
|
return sused
|
|
case *ast.FuncCallExpr:
|
|
return compileFuncCallExpr(context, reg, ex, ec)
|
|
case *ast.FunctionExpr:
|
|
childcontext := newFuncContext(context.Proto.SourceName, context)
|
|
compileFunctionExpr(childcontext, ex, ec)
|
|
protono := len(context.Proto.FunctionPrototypes)
|
|
context.Proto.FunctionPrototypes = append(context.Proto.FunctionPrototypes, childcontext.Proto)
|
|
code.AddABx(OP_CLOSURE, sreg, protono, sline(ex))
|
|
for _, upvalue := range childcontext.Upvalues.List() {
|
|
localidx, block := context.FindLocalVarAndBlock(upvalue.Name)
|
|
if localidx > -1 {
|
|
code.AddABC(OP_MOVE, 0, localidx, 0, sline(ex))
|
|
block.RefUpvalue = true
|
|
} else {
|
|
upvalueidx := context.Upvalues.Find(upvalue.Name)
|
|
if upvalueidx < 0 {
|
|
upvalueidx = context.Upvalues.RegisterUnique(upvalue.Name)
|
|
}
|
|
code.AddABC(OP_GETUPVAL, 0, upvalueidx, 0, sline(ex))
|
|
}
|
|
}
|
|
return sused
|
|
default:
|
|
panic(fmt.Sprintf("expr %v not implemented.", reflect.TypeOf(ex).Elem().Name()))
|
|
}
|
|
|
|
} // }}}
|
|
|
|
func compileExprWithPropagation(context *funcContext, expr ast.Expr, reg *int, save *int, propergator func(int, *int, *int, int)) { // {{{
|
|
reginc := compileExpr(context, *reg, expr, ecnone(0))
|
|
if _, ok := expr.(*ast.LogicalOpExpr); ok {
|
|
*save = *reg
|
|
*reg = *reg + reginc
|
|
} else {
|
|
propergator(context.RegTop(), save, reg, reginc)
|
|
}
|
|
} // }}}
|
|
|
|
func compileExprWithKMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{
|
|
compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateKMV)
|
|
} // }}}
|
|
|
|
func compileExprWithMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{
|
|
compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateMV)
|
|
} // }}}
|
|
|
|
func constFold(exp ast.Expr) ast.Expr { // {{{
|
|
switch expr := exp.(type) {
|
|
case *ast.ArithmeticOpExpr:
|
|
lvalue, lisconst := lnumberValue(constFold(expr.Lhs))
|
|
rvalue, risconst := lnumberValue(constFold(expr.Rhs))
|
|
if lisconst && risconst {
|
|
switch expr.Operator {
|
|
case "+":
|
|
return &constLValueExpr{Value: lvalue + rvalue}
|
|
case "-":
|
|
return &constLValueExpr{Value: lvalue - rvalue}
|
|
case "*":
|
|
return &constLValueExpr{Value: lvalue * rvalue}
|
|
case "/":
|
|
return &constLValueExpr{Value: lvalue / rvalue}
|
|
case "%":
|
|
return &constLValueExpr{Value: luaModulo(lvalue, rvalue)}
|
|
case "^":
|
|
return &constLValueExpr{Value: LNumber(math.Pow(float64(lvalue), float64(rvalue)))}
|
|
default:
|
|
panic(fmt.Sprintf("unknown binop: %v", expr.Operator))
|
|
}
|
|
} else {
|
|
return expr
|
|
}
|
|
case *ast.UnaryMinusOpExpr:
|
|
expr.Expr = constFold(expr.Expr)
|
|
if value, ok := lnumberValue(expr.Expr); ok {
|
|
return &constLValueExpr{Value: LNumber(-value)}
|
|
}
|
|
return expr
|
|
default:
|
|
|
|
return exp
|
|
}
|
|
} // }}}
|
|
|
|
func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *expcontext) { // {{{
|
|
context.Proto.LineDefined = sline(funcexpr)
|
|
context.Proto.LastLineDefined = eline(funcexpr)
|
|
if len(funcexpr.ParList.Names) > maxRegisters {
|
|
raiseCompileError(context, context.Proto.LineDefined, "register overflow")
|
|
}
|
|
context.Proto.NumParameters = uint8(len(funcexpr.ParList.Names))
|
|
if ec.ctype == ecMethod {
|
|
context.Proto.NumParameters += 1
|
|
context.RegisterLocalVar("self")
|
|
}
|
|
for _, name := range funcexpr.ParList.Names {
|
|
context.RegisterLocalVar(name)
|
|
}
|
|
if funcexpr.ParList.HasVargs {
|
|
if CompatVarArg {
|
|
context.Proto.IsVarArg = VarArgHasArg | VarArgNeedsArg
|
|
if context.Parent != nil {
|
|
context.RegisterLocalVar("arg")
|
|
}
|
|
}
|
|
context.Proto.IsVarArg |= VarArgIsVarArg
|
|
}
|
|
|
|
compileChunk(context, funcexpr.Stmts)
|
|
|
|
context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr))
|
|
context.EndScope()
|
|
context.Proto.Code = context.Code.List()
|
|
context.Proto.DbgSourcePositions = context.Code.PosList()
|
|
context.Proto.DbgUpvalues = context.Upvalues.Names()
|
|
context.Proto.NumUpvalues = uint8(len(context.Proto.DbgUpvalues))
|
|
for _, clv := range context.Proto.Constants {
|
|
sv := ""
|
|
if slv, ok := clv.(LString); ok {
|
|
sv = string(slv)
|
|
}
|
|
context.Proto.stringConstants = append(context.Proto.stringConstants, sv)
|
|
}
|
|
patchCode(context)
|
|
} // }}}
|
|
|
|
func compileTableExpr(context *funcContext, reg int, ex *ast.TableExpr, ec *expcontext) { // {{{
|
|
code := context.Code
|
|
/*
|
|
tablereg := savereg(ec, reg)
|
|
if tablereg == reg {
|
|
reg += 1
|
|
}
|
|
*/
|
|
tablereg := reg
|
|
reg++
|
|
code.AddABC(OP_NEWTABLE, tablereg, 0, 0, sline(ex))
|
|
tablepc := code.LastPC()
|
|
regbase := reg
|
|
|
|
arraycount := 0
|
|
lastvararg := false
|
|
for i, field := range ex.Fields {
|
|
islast := i == len(ex.Fields)-1
|
|
if field.Key == nil {
|
|
if islast && isVarArgReturnExpr(field.Value) {
|
|
reg += compileExpr(context, reg, field.Value, ecnone(-2))
|
|
lastvararg = true
|
|
} else {
|
|
reg += compileExpr(context, reg, field.Value, ecnone(0))
|
|
arraycount += 1
|
|
}
|
|
} else {
|
|
regorg := reg
|
|
b := reg
|
|
compileExprWithKMVPropagation(context, field.Key, ®, &b)
|
|
c := reg
|
|
compileExprWithKMVPropagation(context, field.Value, ®, &c)
|
|
opcode := OP_SETTABLE
|
|
if _, ok := field.Key.(*ast.StringExpr); ok {
|
|
opcode = OP_SETTABLEKS
|
|
}
|
|
code.AddABC(opcode, tablereg, b, c, sline(ex))
|
|
reg = regorg
|
|
}
|
|
flush := arraycount % FieldsPerFlush
|
|
if (arraycount != 0 && (flush == 0 || islast)) || lastvararg {
|
|
reg = regbase
|
|
num := flush
|
|
if num == 0 {
|
|
num = FieldsPerFlush
|
|
}
|
|
c := (arraycount-1)/FieldsPerFlush + 1
|
|
b := num
|
|
if islast && isVarArgReturnExpr(field.Value) {
|
|
b = 0
|
|
}
|
|
line := field.Value
|
|
if field.Key != nil {
|
|
line = field.Key
|
|
}
|
|
if c > 511 {
|
|
c = 0
|
|
}
|
|
code.AddABC(OP_SETLIST, tablereg, b, c, sline(line))
|
|
if c == 0 {
|
|
code.Add(uint32(c), sline(line))
|
|
}
|
|
}
|
|
}
|
|
code.SetB(tablepc, int2Fb(arraycount))
|
|
code.SetC(tablepc, int2Fb(len(ex.Fields)-arraycount))
|
|
if shouldmove(ec, tablereg) {
|
|
code.AddABC(OP_MOVE, ec.reg, tablereg, 0, sline(ex))
|
|
}
|
|
} // }}}
|
|
|
|
func compileArithmeticOpExpr(context *funcContext, reg int, expr *ast.ArithmeticOpExpr, ec *expcontext) { // {{{
|
|
exp := constFold(expr)
|
|
if ex, ok := exp.(*constLValueExpr); ok {
|
|
exp.SetLine(sline(expr))
|
|
compileExpr(context, reg, ex, ec)
|
|
return
|
|
}
|
|
expr, _ = exp.(*ast.ArithmeticOpExpr)
|
|
a := savereg(ec, reg)
|
|
b := reg
|
|
compileExprWithKMVPropagation(context, expr.Lhs, ®, &b)
|
|
c := reg
|
|
compileExprWithKMVPropagation(context, expr.Rhs, ®, &c)
|
|
|
|
op := 0
|
|
switch expr.Operator {
|
|
case "+":
|
|
op = OP_ADD
|
|
case "-":
|
|
op = OP_SUB
|
|
case "*":
|
|
op = OP_MUL
|
|
case "/":
|
|
op = OP_DIV
|
|
case "%":
|
|
op = OP_MOD
|
|
case "^":
|
|
op = OP_POW
|
|
}
|
|
context.Code.AddABC(op, a, b, c, sline(expr))
|
|
} // }}}
|
|
|
|
func compileStringConcatOpExpr(context *funcContext, reg int, expr *ast.StringConcatOpExpr, ec *expcontext) { // {{{
|
|
code := context.Code
|
|
crange := 1
|
|
for current := expr.Rhs; current != nil; {
|
|
if ex, ok := current.(*ast.StringConcatOpExpr); ok {
|
|
crange += 1
|
|
current = ex.Rhs
|
|
} else {
|
|
current = nil
|
|
}
|
|
}
|
|
a := savereg(ec, reg)
|
|
basereg := reg
|
|
reg += compileExpr(context, reg, expr.Lhs, ecnone(0))
|
|
reg += compileExpr(context, reg, expr.Rhs, ecnone(0))
|
|
for pc := code.LastPC(); pc != 0 && opGetOpCode(code.At(pc)) == OP_CONCAT; pc-- {
|
|
code.Pop()
|
|
}
|
|
code.AddABC(OP_CONCAT, a, basereg, basereg+crange, sline(expr))
|
|
} // }}}
|
|
|
|
func compileUnaryOpExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) { // {{{
|
|
opcode := 0
|
|
code := context.Code
|
|
var operandexpr ast.Expr
|
|
switch ex := expr.(type) {
|
|
case *ast.UnaryMinusOpExpr:
|
|
exp := constFold(ex)
|
|
if lvexpr, ok := exp.(*constLValueExpr); ok {
|
|
exp.SetLine(sline(expr))
|
|
compileExpr(context, reg, lvexpr, ec)
|
|
return
|
|
}
|
|
ex, _ = exp.(*ast.UnaryMinusOpExpr)
|
|
operandexpr = ex.Expr
|
|
opcode = OP_UNM
|
|
case *ast.UnaryNotOpExpr:
|
|
switch ex.Expr.(type) {
|
|
case *ast.TrueExpr:
|
|
code.AddABC(OP_LOADBOOL, savereg(ec, reg), 0, 0, sline(expr))
|
|
return
|
|
case *ast.FalseExpr, *ast.NilExpr:
|
|
code.AddABC(OP_LOADBOOL, savereg(ec, reg), 1, 0, sline(expr))
|
|
return
|
|
default:
|
|
opcode = OP_NOT
|
|
operandexpr = ex.Expr
|
|
}
|
|
case *ast.UnaryLenOpExpr:
|
|
opcode = OP_LEN
|
|
operandexpr = ex.Expr
|
|
}
|
|
|
|
a := savereg(ec, reg)
|
|
b := reg
|
|
compileExprWithMVPropagation(context, operandexpr, ®, &b)
|
|
code.AddABC(opcode, a, b, 0, sline(expr))
|
|
} // }}}
|
|
|
|
func compileRelationalOpExprAux(context *funcContext, reg int, expr *ast.RelationalOpExpr, flip int, label int) { // {{{
|
|
code := context.Code
|
|
b := reg
|
|
compileExprWithKMVPropagation(context, expr.Lhs, ®, &b)
|
|
c := reg
|
|
compileExprWithKMVPropagation(context, expr.Rhs, ®, &c)
|
|
switch expr.Operator {
|
|
case "<":
|
|
code.AddABC(OP_LT, 0^flip, b, c, sline(expr))
|
|
case ">":
|
|
code.AddABC(OP_LT, 0^flip, c, b, sline(expr))
|
|
case "<=":
|
|
code.AddABC(OP_LE, 0^flip, b, c, sline(expr))
|
|
case ">=":
|
|
code.AddABC(OP_LE, 0^flip, c, b, sline(expr))
|
|
case "==":
|
|
code.AddABC(OP_EQ, 0^flip, b, c, sline(expr))
|
|
case "~=":
|
|
code.AddABC(OP_EQ, 1^flip, b, c, sline(expr))
|
|
}
|
|
code.AddASbx(OP_JMP, 0, label, sline(expr))
|
|
} // }}}
|
|
|
|
func compileRelationalOpExpr(context *funcContext, reg int, expr *ast.RelationalOpExpr, ec *expcontext) { // {{{
|
|
a := savereg(ec, reg)
|
|
code := context.Code
|
|
jumplabel := context.NewLabel()
|
|
compileRelationalOpExprAux(context, reg, expr, 1, jumplabel)
|
|
code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr))
|
|
context.SetLabelPc(jumplabel, code.LastPC())
|
|
code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr))
|
|
} // }}}
|
|
|
|
func compileLogicalOpExpr(context *funcContext, reg int, expr *ast.LogicalOpExpr, ec *expcontext) { // {{{
|
|
a := savereg(ec, reg)
|
|
code := context.Code
|
|
endlabel := context.NewLabel()
|
|
lb := &lblabels{context.NewLabel(), context.NewLabel(), endlabel, false}
|
|
nextcondlabel := context.NewLabel()
|
|
if expr.Operator == "and" {
|
|
compileLogicalOpExprAux(context, reg, expr.Lhs, ec, nextcondlabel, endlabel, false, lb)
|
|
context.SetLabelPc(nextcondlabel, code.LastPC())
|
|
compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb)
|
|
} else {
|
|
compileLogicalOpExprAux(context, reg, expr.Lhs, ec, endlabel, nextcondlabel, true, lb)
|
|
context.SetLabelPc(nextcondlabel, code.LastPC())
|
|
compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb)
|
|
}
|
|
|
|
if lb.b {
|
|
context.SetLabelPc(lb.f, code.LastPC())
|
|
code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr))
|
|
context.SetLabelPc(lb.t, code.LastPC())
|
|
code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr))
|
|
}
|
|
|
|
lastinst := code.Last()
|
|
if opGetOpCode(lastinst) == OP_JMP && opGetArgSbx(lastinst) == endlabel {
|
|
code.Pop()
|
|
}
|
|
|
|
context.SetLabelPc(endlabel, code.LastPC())
|
|
} // }}}
|
|
|
|
func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *expcontext, thenlabel, elselabel int, hasnextcond bool, lb *lblabels) { // {{{
|
|
// TODO folding constants?
|
|
code := context.Code
|
|
flip := 0
|
|
jumplabel := elselabel
|
|
if hasnextcond {
|
|
flip = 1
|
|
jumplabel = thenlabel
|
|
}
|
|
|
|
switch ex := expr.(type) {
|
|
case *ast.FalseExpr:
|
|
if elselabel == lb.e {
|
|
code.AddASbx(OP_JMP, 0, lb.f, sline(expr))
|
|
lb.b = true
|
|
} else {
|
|
code.AddASbx(OP_JMP, 0, elselabel, sline(expr))
|
|
}
|
|
return
|
|
case *ast.NilExpr:
|
|
if elselabel == lb.e {
|
|
compileExpr(context, reg, expr, ec)
|
|
code.AddASbx(OP_JMP, 0, lb.e, sline(expr))
|
|
} else {
|
|
code.AddASbx(OP_JMP, 0, elselabel, sline(expr))
|
|
}
|
|
return
|
|
case *ast.TrueExpr:
|
|
if thenlabel == lb.e {
|
|
code.AddASbx(OP_JMP, 0, lb.t, sline(expr))
|
|
lb.b = true
|
|
} else {
|
|
code.AddASbx(OP_JMP, 0, thenlabel, sline(expr))
|
|
}
|
|
return
|
|
case *ast.NumberExpr, *ast.StringExpr:
|
|
if thenlabel == lb.e {
|
|
compileExpr(context, reg, expr, ec)
|
|
code.AddASbx(OP_JMP, 0, lb.e, sline(expr))
|
|
} else {
|
|
code.AddASbx(OP_JMP, 0, thenlabel, sline(expr))
|
|
}
|
|
return
|
|
case *ast.LogicalOpExpr:
|
|
switch ex.Operator {
|
|
case "and":
|
|
nextcondlabel := context.NewLabel()
|
|
compileLogicalOpExprAux(context, reg, ex.Lhs, ec, nextcondlabel, elselabel, false, lb)
|
|
context.SetLabelPc(nextcondlabel, context.Code.LastPC())
|
|
compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb)
|
|
case "or":
|
|
nextcondlabel := context.NewLabel()
|
|
compileLogicalOpExprAux(context, reg, ex.Lhs, ec, thenlabel, nextcondlabel, true, lb)
|
|
context.SetLabelPc(nextcondlabel, context.Code.LastPC())
|
|
compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb)
|
|
}
|
|
return
|
|
case *ast.RelationalOpExpr:
|
|
if thenlabel == elselabel {
|
|
flip ^= 1
|
|
jumplabel = lb.t
|
|
lb.b = true
|
|
} else if thenlabel == lb.e {
|
|
jumplabel = lb.t
|
|
lb.b = true
|
|
} else if elselabel == lb.e {
|
|
jumplabel = lb.f
|
|
lb.b = true
|
|
}
|
|
compileRelationalOpExprAux(context, reg, ex, flip, jumplabel)
|
|
return
|
|
}
|
|
|
|
a := reg
|
|
sreg := savereg(ec, a)
|
|
if !hasnextcond && thenlabel == elselabel {
|
|
reg += compileExpr(context, reg, expr, &expcontext{ec.ctype, intMax(a, sreg), ec.varargopt})
|
|
last := context.Code.Last()
|
|
if opGetOpCode(last) == OP_MOVE && opGetArgA(last) == a {
|
|
context.Code.SetA(context.Code.LastPC(), sreg)
|
|
} else {
|
|
context.Code.AddABC(OP_MOVE, sreg, a, 0, sline(expr))
|
|
}
|
|
} else {
|
|
reg += compileExpr(context, reg, expr, ecnone(0))
|
|
if sreg == a {
|
|
code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr))
|
|
} else {
|
|
code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr))
|
|
}
|
|
}
|
|
code.AddASbx(OP_JMP, 0, jumplabel, sline(expr))
|
|
} // }}}
|
|
|
|
func compileFuncCallExpr(context *funcContext, reg int, expr *ast.FuncCallExpr, ec *expcontext) int { // {{{
|
|
funcreg := reg
|
|
if ec.ctype == ecLocal && ec.reg == (int(context.Proto.NumParameters)-1) {
|
|
funcreg = ec.reg
|
|
reg = ec.reg
|
|
}
|
|
argc := len(expr.Args)
|
|
islastvararg := false
|
|
name := "(anonymous)"
|
|
|
|
if expr.Func != nil { // hoge.func()
|
|
reg += compileExpr(context, reg, expr.Func, ecnone(0))
|
|
name = getExprName(context, expr.Func)
|
|
} else { // hoge:method()
|
|
b := reg
|
|
compileExprWithMVPropagation(context, expr.Receiver, ®, &b)
|
|
c := loadRk(context, ®, expr, LString(expr.Method))
|
|
context.Code.AddABC(OP_SELF, funcreg, b, c, sline(expr))
|
|
// increments a register for an implicit "self"
|
|
reg = b + 1
|
|
reg2 := funcreg + 2
|
|
if reg2 > reg {
|
|
reg = reg2
|
|
}
|
|
argc += 1
|
|
name = string(expr.Method)
|
|
}
|
|
|
|
for i, ar := range expr.Args {
|
|
islastvararg = (i == len(expr.Args)-1) && isVarArgReturnExpr(ar)
|
|
if islastvararg {
|
|
compileExpr(context, reg, ar, ecnone(-2))
|
|
} else {
|
|
reg += compileExpr(context, reg, ar, ecnone(0))
|
|
}
|
|
}
|
|
b := argc + 1
|
|
if islastvararg {
|
|
b = 0
|
|
}
|
|
context.Code.AddABC(OP_CALL, funcreg, b, ec.varargopt+2, sline(expr))
|
|
context.Proto.DbgCalls = append(context.Proto.DbgCalls, DbgCall{Pc: context.Code.LastPC(), Name: name})
|
|
|
|
if ec.varargopt == 0 && shouldmove(ec, funcreg) {
|
|
context.Code.AddABC(OP_MOVE, ec.reg, funcreg, 0, sline(expr))
|
|
return 1
|
|
}
|
|
if context.RegTop() > (funcreg+2+ec.varargopt) || ec.varargopt < -1 {
|
|
return 0
|
|
}
|
|
return ec.varargopt + 1
|
|
} // }}}
|
|
|
|
func loadRk(context *funcContext, reg *int, expr ast.Expr, cnst LValue) int { // {{{
|
|
cindex := context.ConstIndex(cnst)
|
|
if cindex <= opMaxIndexRk {
|
|
return opRkAsk(cindex)
|
|
} else {
|
|
ret := *reg
|
|
*reg++
|
|
context.Code.AddABx(OP_LOADK, ret, cindex, sline(expr))
|
|
return ret
|
|
}
|
|
} // }}}
|
|
|
|
func getIdentRefType(context *funcContext, current *funcContext, expr *ast.IdentExpr) expContextType { // {{{
|
|
if current == nil {
|
|
return ecGlobal
|
|
} else if current.FindLocalVar(expr.Value) > -1 {
|
|
if current == context {
|
|
return ecLocal
|
|
}
|
|
return ecUpvalue
|
|
}
|
|
return getIdentRefType(context, current.Parent, expr)
|
|
} // }}}
|
|
|
|
func getExprName(context *funcContext, expr ast.Expr) string { // {{{
|
|
switch ex := expr.(type) {
|
|
case *ast.IdentExpr:
|
|
return ex.Value
|
|
case *ast.AttrGetExpr:
|
|
switch kex := ex.Key.(type) {
|
|
case *ast.StringExpr:
|
|
return kex.Value
|
|
}
|
|
return "?"
|
|
}
|
|
return "?"
|
|
} // }}}
|
|
|
|
func patchCode(context *funcContext) { // {{{
|
|
maxreg := 1
|
|
if np := int(context.Proto.NumParameters); np > 1 {
|
|
maxreg = np
|
|
}
|
|
moven := 0
|
|
code := context.Code.List()
|
|
for pc := 0; pc < len(code); pc++ {
|
|
inst := code[pc]
|
|
curop := opGetOpCode(inst)
|
|
switch curop {
|
|
case OP_CLOSURE:
|
|
pc += int(context.Proto.FunctionPrototypes[opGetArgBx(inst)].NumUpvalues)
|
|
moven = 0
|
|
continue
|
|
case OP_SETGLOBAL, OP_SETUPVAL, OP_EQ, OP_LT, OP_LE, OP_TEST,
|
|
OP_TAILCALL, OP_RETURN, OP_FORPREP, OP_FORLOOP, OP_TFORLOOP,
|
|
OP_SETLIST, OP_CLOSE:
|
|
/* nothing to do */
|
|
case OP_CALL:
|
|
if reg := opGetArgA(inst) + opGetArgC(inst) - 2; reg > maxreg {
|
|
maxreg = reg
|
|
}
|
|
case OP_VARARG:
|
|
if reg := opGetArgA(inst) + opGetArgB(inst) - 1; reg > maxreg {
|
|
maxreg = reg
|
|
}
|
|
case OP_SELF:
|
|
if reg := opGetArgA(inst) + 1; reg > maxreg {
|
|
maxreg = reg
|
|
}
|
|
case OP_LOADNIL:
|
|
if reg := opGetArgB(inst); reg > maxreg {
|
|
maxreg = reg
|
|
}
|
|
case OP_JMP: // jump to jump optimization
|
|
distance := 0
|
|
count := 0 // avoiding infinite loops
|
|
for jmp := inst; opGetOpCode(jmp) == OP_JMP && count < 5; jmp = context.Code.At(pc + distance + 1) {
|
|
d := context.GetLabelPc(opGetArgSbx(jmp)) - pc
|
|
if d > opMaxArgSbx {
|
|
if distance == 0 {
|
|
raiseCompileError(context, context.Proto.LineDefined, "too long to jump.")
|
|
}
|
|
break
|
|
}
|
|
distance = d
|
|
count++
|
|
}
|
|
if distance == 0 {
|
|
context.Code.SetOpCode(pc, OP_NOP)
|
|
} else {
|
|
context.Code.SetSbx(pc, distance)
|
|
}
|
|
default:
|
|
if reg := opGetArgA(inst); reg > maxreg {
|
|
maxreg = reg
|
|
}
|
|
}
|
|
|
|
// bulk move optimization(reducing op dipatch costs)
|
|
if curop == OP_MOVE {
|
|
moven++
|
|
} else {
|
|
if moven > 1 {
|
|
context.Code.SetOpCode(pc-moven, OP_MOVEN)
|
|
context.Code.SetC(pc-moven, intMin(moven-1, opMaxArgsC))
|
|
}
|
|
moven = 0
|
|
}
|
|
}
|
|
maxreg++
|
|
if maxreg > maxRegisters {
|
|
raiseCompileError(context, context.Proto.LineDefined, "register overflow(too many local variables)")
|
|
}
|
|
context.Proto.NumUsedRegisters = uint8(maxreg)
|
|
} // }}}
|
|
|
|
func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { // {{{
|
|
defer func() {
|
|
if rcv := recover(); rcv != nil {
|
|
if _, ok := rcv.(*CompileError); ok {
|
|
err = rcv.(error)
|
|
} else {
|
|
panic(rcv)
|
|
}
|
|
}
|
|
}()
|
|
err = nil
|
|
parlist := &ast.ParList{HasVargs: true, Names: []string{}}
|
|
funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk}
|
|
context := newFuncContext(name, nil)
|
|
compileFunctionExpr(context, funcexpr, ecnone(0))
|
|
proto = context.Proto
|
|
return
|
|
} // }}}
|