Mako/compiler/bytecode.go
2025-06-11 21:50:55 -05:00

341 lines
10 KiB
Go

package compiler
// Opcode represents a single bytecode instruction
type Opcode uint8
const (
// Stack Operations
OpLoadConst Opcode = iota // Load constant onto stack [idx]
OpLoadLocal // Load local variable [slot]
OpStoreLocal // Store top of stack to local [slot]
OpLoadGlobal // Load global variable [idx]
OpStoreGlobal // Store top of stack to global [idx]
OpPop // Pop top value from stack
OpDup // Duplicate top value on stack
// Specialized Local Operations (no operands needed)
OpLoadLocal0 // Load local slot 0
OpLoadLocal1 // Load local slot 1
OpLoadLocal2 // Load local slot 2
OpStoreLocal0 // Store to local slot 0
OpStoreLocal1 // Store to local slot 1
OpStoreLocal2 // Store to local slot 2
// Specialized Constants (no operands needed)
OpLoadTrue // Load true constant
OpLoadFalse // Load false constant
OpLoadNil // Load nil constant
OpLoadZero // Load number 0
OpLoadOne // Load number 1
// Arithmetic Operations
OpAdd // a + b
OpSub // a - b
OpMul // a * b
OpDiv // a / b
OpNeg // -a
OpMod // a % b
// Specialized Arithmetic
OpAddConst // local + constant [constIdx]
OpSubConst // local - constant [constIdx]
OpInc // increment local [slot]
OpDec // decrement local [slot]
// Comparison Operations
OpEq // a == b
OpNeq // a != b
OpLt // a < b
OpLte // a <= b
OpGt // a > b
OpGte // a >= b
// Logical Operations
OpNot // not a
OpAnd // a and b
OpOr // a or b
// Control Flow
OpJump // Unconditional jump [offset]
OpJumpIfTrue // Jump if top of stack is true [offset]
OpJumpIfFalse // Jump if top of stack is false [offset]
OpCall // Call function [argCount]
OpReturn // Return from function
OpReturnNil // Return nil from function
// Specialized Control Flow
OpTestAndJump // Test local and jump [slot, offset]
OpLoopBack // Optimized loop back jump [offset]
// Table Operations
OpNewTable // Create new empty table
OpGetIndex // table[key] -> value
OpSetIndex // table[key] = value
OpGetField // table.field -> value [fieldIdx]
OpSetField // table.field = value [fieldIdx]
OpTableInsert // Insert value into table at next index
// Specialized Table Operations
OpGetLocalField // local.field -> value [slot, fieldIdx]
OpSetLocalField // local.field = value [slot, fieldIdx]
OpGetConstField // table.constField -> value [fieldName]
// Struct Operations
OpNewStruct // Create new struct instance [structId]
OpGetProperty // struct.field -> value [fieldIdx]
OpSetProperty // struct.field = value [fieldIdx]
OpCallMethod // Call method on struct [methodIdx, argCount]
// Function Operations
OpClosure // Create closure from function [funcIdx, upvalueCount]
OpGetUpvalue // Get upvalue [idx]
OpSetUpvalue // Set upvalue [idx]
OpCloseUpvalue // Close upvalue (move to heap)
// Specialized Function Operations
OpCallLocal0 // Call function in local slot 0 [argCount]
OpCallLocal1 // Call function in local slot 1 [argCount]
// Array Operations
OpNewArray // Create new array with size [size]
OpArrayAppend // Append value to array
OpArrayGet // Optimized array[index] access
OpArraySet // Optimized array[index] = value
OpArrayLen // Get array length
// Type Operations
OpGetType // Get type of value on stack
OpCast // Cast value to type [typeId]
OpIsType // Check if value is type [typeId]
// Type Checks (faster than generic OpGetType)
OpIsNumber // Check if top of stack is number
OpIsString // Check if top of stack is string
OpIsTable // Check if top of stack is table
OpIsBool // Check if top of stack is bool
OpIsNil // Check if top of stack is nil
// I/O Operations
OpEcho // Echo value to output
OpExit // Exit with code
// Special Operations
OpNoop // No operation
OpBreak // Break from loop
OpContinue // Continue loop iteration
// Debug Operations
OpDebugPrint // Debug print stack top
OpDebugStack // Debug print entire stack
)
// Instruction represents a single bytecode instruction with operands
type Instruction struct {
Op Opcode
Operands []uint16 // Variable length operands
}
// Chunk represents a compiled chunk of bytecode
type Chunk struct {
Code []uint8 // Raw bytecode stream
Constants []Value // Constant pool
Lines []int // Line numbers for debugging
Functions []Function // Function definitions
Structs []Struct // Struct definitions
}
// Value represents a runtime value in the VM
type Value struct {
Type ValueType
Data any // Actual value data
}
// ValueType represents the type of a runtime value
type ValueType uint8
const (
ValueNil ValueType = iota
ValueBool
ValueNumber
ValueString
ValueTable
ValueFunction
ValueStruct
ValueArray
ValueUpvalue
)
// Function represents a compiled function
type Function struct {
Name string // Function name (empty for anonymous)
Arity int // Number of parameters
Variadic bool // Whether function accepts variable args
LocalCount int // Number of local variable slots
UpvalCount int // Number of upvalues
Chunk Chunk // Function bytecode
Defaults []Value // Default parameter values
}
// Struct represents a compiled struct definition
type Struct struct {
Name string // Struct name
Fields []StructField // Field definitions
Methods map[string]uint16 // Method name -> function index
ID uint16 // Unique struct identifier
}
// StructField represents a field in a struct
type StructField struct {
Name string // Field name
Type ValueType // Field type
Offset uint16 // Offset in struct layout
}
// Table represents a key-value table/map
type Table struct {
Array map[int]Value // Array part (integer keys)
Hash map[string]Value // Hash part (string keys)
Meta *Table // Metatable for operations
}
// Array represents a dynamic array
type Array struct {
Elements []Value // Array elements
Count int // Current element count
Capacity int // Current capacity
}
// StructInstance represents an instance of a struct
type StructInstance struct {
StructID uint16 // Reference to struct definition
Fields map[string]Value // Field values
}
// Upvalue represents a captured variable
type Upvalue struct {
Location *Value // Pointer to actual value location
Closed Value // Closed-over value (when moved to heap)
IsClosed bool // Whether upvalue has been closed
}
// Instruction encoding helpers
// EncodeInstruction encodes an instruction into bytecode
func EncodeInstruction(op Opcode, operands ...uint16) []uint8 {
bytes := []uint8{uint8(op)}
for _, operand := range operands {
bytes = append(bytes, uint8(operand&0xFF), uint8(operand>>8))
}
return bytes
}
// DecodeInstruction decodes bytecode into instruction
func DecodeInstruction(code []uint8, offset int) (Opcode, []uint16, int) {
if offset >= len(code) {
return OpNoop, nil, offset
}
op := Opcode(code[offset])
operands := []uint16{}
nextOffset := offset + 1
// Decode operands based on instruction type
operandCount := GetOperandCount(op)
for range operandCount {
if nextOffset+1 >= len(code) {
break
}
operand := uint16(code[nextOffset]) | (uint16(code[nextOffset+1]) << 8)
operands = append(operands, operand)
nextOffset += 2
}
return op, operands, nextOffset
}
// GetOperandCount returns the number of operands for an instruction
func GetOperandCount(op Opcode) int {
switch op {
// No operand instructions
case OpPop, OpDup, OpAdd, OpSub, OpMul, OpDiv, OpNeg, OpMod,
OpEq, OpNeq, OpLt, OpLte, OpGt, OpGte, OpNot, OpAnd, OpOr,
OpReturn, OpReturnNil, OpNewTable, OpGetIndex, OpSetIndex,
OpTableInsert, OpArrayAppend, OpArrayGet, OpArraySet, OpArrayLen,
OpGetType, OpIsNumber, OpIsString, OpIsTable, OpIsBool, OpIsNil,
OpEcho, OpExit, OpNoop, OpBreak, OpContinue, OpDebugPrint, OpDebugStack,
OpLoadLocal0, OpLoadLocal1, OpLoadLocal2, OpStoreLocal0, OpStoreLocal1, OpStoreLocal2,
OpLoadTrue, OpLoadFalse, OpLoadNil, OpLoadZero, OpLoadOne:
return 0
// Single operand instructions
case OpLoadConst, OpLoadLocal, OpStoreLocal, OpLoadGlobal, OpStoreGlobal,
OpJump, OpJumpIfTrue, OpJumpIfFalse, OpCall, OpGetField, OpSetField,
OpNewStruct, OpGetProperty, OpSetProperty, OpNewArray, OpCast, OpIsType,
OpAddConst, OpSubConst, OpInc, OpDec, OpGetConstField, OpCallLocal0, OpCallLocal1:
return 1
// Two operand instructions
case OpCallMethod, OpClosure, OpTestAndJump, OpGetLocalField, OpSetLocalField:
return 2
default:
return 0
}
}
// Instruction size calculation
func InstructionSize(op Opcode) int {
return 1 + (GetOperandCount(op) * 2) // 1 byte opcode + 2 bytes per operand
}
// Check if instruction is a specialized version of another
func IsSpecializedInstruction(op Opcode) bool {
switch op {
case OpLoadLocal0, OpLoadLocal1, OpLoadLocal2,
OpStoreLocal0, OpStoreLocal1, OpStoreLocal2,
OpLoadTrue, OpLoadFalse, OpLoadNil, OpLoadZero, OpLoadOne,
OpAddConst, OpSubConst, OpInc, OpDec,
OpGetLocalField, OpSetLocalField, OpGetConstField,
OpCallLocal0, OpCallLocal1, OpTestAndJump, OpLoopBack:
return true
default:
return false
}
}
var opcodeNames = map[Opcode]string{
OpLoadConst: "OP_LOAD_CONST",
OpLoadLocal: "OP_LOAD_LOCAL",
OpStoreLocal: "OP_STORE_LOCAL",
OpLoadLocal0: "OP_LOAD_LOCAL_0",
OpLoadLocal1: "OP_LOAD_LOCAL_1",
OpLoadLocal2: "OP_LOAD_LOCAL_2",
OpStoreLocal0: "OP_STORE_LOCAL_0",
OpStoreLocal1: "OP_STORE_LOCAL_1",
OpStoreLocal2: "OP_STORE_LOCAL_2",
OpLoadTrue: "OP_LOAD_TRUE",
OpLoadFalse: "OP_LOAD_FALSE",
OpLoadNil: "OP_LOAD_NIL",
OpLoadZero: "OP_LOAD_ZERO",
OpLoadOne: "OP_LOAD_ONE",
OpAdd: "OP_ADD",
OpSub: "OP_SUB",
OpMul: "OP_MUL",
OpDiv: "OP_DIV",
OpAddConst: "OP_ADD_CONST",
OpSubConst: "OP_SUB_CONST",
OpInc: "OP_INC",
OpDec: "OP_DEC",
OpJump: "OP_JUMP",
OpJumpIfTrue: "OP_JUMP_TRUE",
OpJumpIfFalse: "OP_JUMP_FALSE",
OpTestAndJump: "OP_TEST_AND_JUMP",
OpLoopBack: "OP_LOOP_BACK",
OpReturn: "OP_RETURN",
OpGetLocalField: "OP_GET_LOCAL_FIELD",
OpSetLocalField: "OP_SET_LOCAL_FIELD",
OpCallLocal0: "OP_CALL_LOCAL_0",
OpCallLocal1: "OP_CALL_LOCAL_1",
OpEcho: "OP_ECHO",
}