Compare commits

..

No commits in common. "121774005b6aea6e877e38802bafb3686a15b9c8" and "2179a39e2c23d48923c9eea92989d571aaf87adb" have entirely different histories.

4 changed files with 541 additions and 1510 deletions

View File

@ -13,21 +13,6 @@ const (
OpPop // Pop top value from stack OpPop // Pop top value from stack
OpDup // Duplicate top value on 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 // Arithmetic Operations
OpAdd // a + b OpAdd // a + b
OpSub // a - b OpSub // a - b
@ -36,12 +21,6 @@ const (
OpNeg // -a OpNeg // -a
OpMod // a % b OpMod // a % b
// Specialized Arithmetic
OpAddConst // local + constant [constIdx]
OpSubConst // local - constant [constIdx]
OpInc // increment local [slot]
OpDec // decrement local [slot]
// Comparison Operations // Comparison Operations
OpEq // a == b OpEq // a == b
OpNeq // a != b OpNeq // a != b
@ -63,10 +42,6 @@ const (
OpReturn // Return from function OpReturn // Return from function
OpReturnNil // Return nil 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 // Table Operations
OpNewTable // Create new empty table OpNewTable // Create new empty table
OpGetIndex // table[key] -> value OpGetIndex // table[key] -> value
@ -75,11 +50,6 @@ const (
OpSetField // table.field = value [fieldIdx] OpSetField // table.field = value [fieldIdx]
OpTableInsert // Insert value into table at next index 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 // Struct Operations
OpNewStruct // Create new struct instance [structId] OpNewStruct // Create new struct instance [structId]
OpGetProperty // struct.field -> value [fieldIdx] OpGetProperty // struct.field -> value [fieldIdx]
@ -92,28 +62,13 @@ const (
OpSetUpvalue // Set upvalue [idx] OpSetUpvalue // Set upvalue [idx]
OpCloseUpvalue // Close upvalue (move to heap) 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 // Array Operations
OpNewArray // Create new array with size [size] OpNewArray // Create new array with size [size]
OpArrayAppend // Append value to array OpArrayAppend // Append value to array
OpArrayGet // Optimized array[index] access
OpArraySet // Optimized array[index] = value
OpArrayLen // Get array length
// Type Operations // Type Operations
OpGetType // Get type of value on stack OpGetType // Get type of value on stack
OpCast // Cast value to type [typeId] 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 // I/O Operations
OpEcho // Echo value to output OpEcho // Echo value to output
@ -256,28 +211,18 @@ func DecodeInstruction(code []uint8, offset int) (Opcode, []uint16, int) {
// GetOperandCount returns the number of operands for an instruction // GetOperandCount returns the number of operands for an instruction
func GetOperandCount(op Opcode) int { func GetOperandCount(op Opcode) int {
switch op { switch op {
// No operand instructions case OpLoadConst, OpLoadLocal, OpStoreLocal, OpLoadGlobal, OpStoreGlobal:
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 return 1
case OpJump, OpJumpIfTrue, OpJumpIfFalse:
// Two operand instructions return 1
case OpCallMethod, OpClosure, OpTestAndJump, OpGetLocalField, OpSetLocalField: case OpCall, OpNewStruct, OpGetField, OpSetField, OpGetProperty, OpSetProperty:
return 1
case OpCallMethod:
return 2 return 2
case OpClosure:
return 2
case OpNewArray, OpCast:
return 1
default: default:
return 0 return 0
} }
@ -288,53 +233,17 @@ func InstructionSize(op Opcode) int {
return 1 + (GetOperandCount(op) * 2) // 1 byte opcode + 2 bytes per operand 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{ var opcodeNames = map[Opcode]string{
OpLoadConst: "OP_LOAD_CONST", OpLoadConst: "OP_LOAD_CONST",
OpLoadLocal: "OP_LOAD_LOCAL", OpLoadLocal: "OP_LOAD_LOCAL",
OpStoreLocal: "OP_STORE_LOCAL", OpStoreLocal: "OP_STORE_LOCAL",
OpLoadLocal0: "OP_LOAD_LOCAL_0", OpAdd: "OP_ADD",
OpLoadLocal1: "OP_LOAD_LOCAL_1", OpSub: "OP_SUB",
OpLoadLocal2: "OP_LOAD_LOCAL_2", OpMul: "OP_MUL",
OpStoreLocal0: "OP_STORE_LOCAL_0", OpDiv: "OP_DIV",
OpStoreLocal1: "OP_STORE_LOCAL_1", OpJump: "OP_JUMP",
OpStoreLocal2: "OP_STORE_LOCAL_2", OpJumpIfTrue: "OP_JUMP_TRUE",
OpLoadTrue: "OP_LOAD_TRUE", OpJumpIfFalse: "OP_JUMP_FALSE",
OpLoadFalse: "OP_LOAD_FALSE", OpReturn: "OP_RETURN",
OpLoadNil: "OP_LOAD_NIL", OpEcho: "OP_ECHO",
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",
} }

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ const (
// CompilerState holds state during compilation // CompilerState holds state during compilation
type CompilerState struct { type CompilerState struct {
Chunk *Chunk // Current chunk being compiled Chunk *Chunk // Current chunk being compiled
Constants map[string]int // Constant pool index mapping for deduplication Constants map[string]int // Constant pool index mapping
Functions []Function // Compiled functions Functions []Function // Compiled functions
Structs []Struct // Compiled structs Structs []Struct // Compiled structs
Locals []Local // Local variable stack Locals []Local // Local variable stack
@ -103,8 +103,10 @@ func (cs *CompilerState) EndScope() {
for len(cs.Locals) > 0 && cs.Locals[len(cs.Locals)-1].Depth > cs.ScopeDepth { for len(cs.Locals) > 0 && cs.Locals[len(cs.Locals)-1].Depth > cs.ScopeDepth {
local := cs.Locals[len(cs.Locals)-1] local := cs.Locals[len(cs.Locals)-1]
if local.IsCaptured { if local.IsCaptured {
// Emit close upvalue instruction
cs.EmitByte(uint8(OpCloseUpvalue)) cs.EmitByte(uint8(OpCloseUpvalue))
} else { } else {
// Emit pop instruction
cs.EmitByte(uint8(OpPop)) cs.EmitByte(uint8(OpPop))
} }
cs.Locals = cs.Locals[:len(cs.Locals)-1] cs.Locals = cs.Locals[:len(cs.Locals)-1]
@ -141,7 +143,8 @@ func (cs *CompilerState) ResolveLocal(name string) int {
local := &cs.Locals[i] local := &cs.Locals[i]
if local.Name == name { if local.Name == name {
if local.Depth == -1 { if local.Depth == -1 {
return -2 // Variable used before initialization // Variable used before initialization
return -2
} }
return i return i
} }
@ -173,9 +176,9 @@ func (cs *CompilerState) AddUpvalue(index uint8, isLocal bool) int {
return upvalueCount return upvalueCount
} }
// Enhanced constant pool management with better deduplication // Constant pool management
func (cs *CompilerState) AddConstant(value Value) int { func (cs *CompilerState) AddConstant(value Value) int {
// Generate unique key for deduplication // Check if constant already exists to avoid duplicates
key := cs.valueKey(value) key := cs.valueKey(value)
if index, exists := cs.Constants[key]; exists { if index, exists := cs.Constants[key]; exists {
return index return index
@ -191,7 +194,7 @@ func (cs *CompilerState) AddConstant(value Value) int {
return index return index
} }
// Enhanced value key generation for better deduplication // Generate unique key for value in constant pool
func (cs *CompilerState) valueKey(value Value) string { func (cs *CompilerState) valueKey(value Value) string {
switch value.Type { switch value.Type {
case ValueNil: case ValueNil:
@ -202,42 +205,16 @@ func (cs *CompilerState) valueKey(value Value) string {
} }
return "bool:false" return "bool:false"
case ValueNumber: case ValueNumber:
num := value.Data.(float64) return fmt.Sprintf("number:%g", value.Data.(float64))
// Handle special numeric values
if num == 0 {
return "number:0"
} else if num == 1 {
return "number:1"
} else if num == -1 {
return "number:-1"
}
return fmt.Sprintf("number:%g", num)
case ValueString: case ValueString:
str := value.Data.(string) return fmt.Sprintf("string:%s", value.Data.(string))
if str == "" {
return "string:empty"
}
// For very long strings, just use a hash to avoid memory issues
if len(str) > 100 {
return fmt.Sprintf("string:hash:%d", cs.simpleHash(str))
}
return fmt.Sprintf("string:%s", str)
default: default:
// For complex types, use memory address as fallback // For complex types, use memory address as fallback
return fmt.Sprintf("%T:%p", value.Data, value.Data) return fmt.Sprintf("%T:%p", value.Data, value.Data)
} }
} }
// Simple hash function for long strings // Bytecode emission methods
func (cs *CompilerState) simpleHash(s string) uint32 {
var hash uint32
for _, c := range s {
hash = hash*31 + uint32(c)
}
return hash
}
// Optimized bytecode emission methods
func (cs *CompilerState) EmitByte(byte uint8) { func (cs *CompilerState) EmitByte(byte uint8) {
cs.Chunk.Code = append(cs.Chunk.Code, byte) cs.Chunk.Code = append(cs.Chunk.Code, byte)
cs.Chunk.Lines = append(cs.Chunk.Lines, cs.CurrentLine) cs.Chunk.Lines = append(cs.Chunk.Lines, cs.CurrentLine)
@ -254,19 +231,19 @@ func (cs *CompilerState) EmitInstruction(op Opcode, operands ...uint16) {
cs.EmitBytes(bytes...) cs.EmitBytes(bytes...)
} }
// Optimized jump emission with better jump distance calculation
func (cs *CompilerState) EmitJump(op Opcode) int { func (cs *CompilerState) EmitJump(op Opcode) int {
cs.EmitByte(uint8(op)) cs.EmitByte(uint8(op))
cs.EmitByte(0xFF) // Placeholder cs.EmitByte(0xFF) // Placeholder
cs.EmitByte(0xFF) // Placeholder cs.EmitByte(0xFF) // Placeholder
return len(cs.Chunk.Code) - 2 return len(cs.Chunk.Code) - 2 // Return offset of jump address
} }
func (cs *CompilerState) PatchJump(offset int) { func (cs *CompilerState) PatchJump(offset int) {
// Calculate jump distance
jump := len(cs.Chunk.Code) - offset - 2 jump := len(cs.Chunk.Code) - offset - 2
if jump > 65535 { if jump > 65535 {
// Jump distance too large - could implement long jumps here // Jump too large - would need long jump instruction
return return
} }
@ -274,14 +251,10 @@ func (cs *CompilerState) PatchJump(offset int) {
cs.Chunk.Code[offset+1] = uint8((jump >> 8) & 0xFF) cs.Chunk.Code[offset+1] = uint8((jump >> 8) & 0xFF)
} }
// Enhanced loop management with optimization support // Loop management
func (cs *CompilerState) EnterLoop() { func (cs *CompilerState) EnterLoop() {
cs.LoopStart = len(cs.Chunk.Code) cs.LoopStart = len(cs.Chunk.Code)
cs.LoopDepth++ cs.LoopDepth++
// Clear previous jump lists for new loop
cs.BreakJumps = cs.BreakJumps[:0]
cs.ContinueJumps = cs.ContinueJumps[:0]
} }
func (cs *CompilerState) ExitLoop() { func (cs *CompilerState) ExitLoop() {
@ -290,20 +263,18 @@ func (cs *CompilerState) ExitLoop() {
cs.LoopStart = -1 cs.LoopStart = -1
} }
// Patch break jumps to current position // Patch break jumps
for _, jumpOffset := range cs.BreakJumps { for _, jumpOffset := range cs.BreakJumps {
cs.PatchJump(jumpOffset) cs.PatchJump(jumpOffset)
} }
cs.BreakJumps = cs.BreakJumps[:0] cs.BreakJumps = cs.BreakJumps[:0]
// Patch continue jumps to loop start // Patch continue jumps
for _, jumpOffset := range cs.ContinueJumps { for _, jumpOffset := range cs.ContinueJumps {
if cs.LoopStart != -1 { jump := cs.LoopStart - jumpOffset - 2
jump := jumpOffset - cs.LoopStart + 2 if jump < 65535 {
if jump < 65535 && jump >= 0 { cs.Chunk.Code[jumpOffset] = uint8(jump & 0xFF)
cs.Chunk.Code[jumpOffset] = uint8(jump & 0xFF) cs.Chunk.Code[jumpOffset+1] = uint8((jump >> 8) & 0xFF)
cs.Chunk.Code[jumpOffset+1] = uint8((jump >> 8) & 0xFF)
}
} }
} }
cs.ContinueJumps = cs.ContinueJumps[:0] cs.ContinueJumps = cs.ContinueJumps[:0]
@ -321,323 +292,6 @@ func (cs *CompilerState) EmitContinue() {
} }
} }
// Optimized instruction emission helpers
func (cs *CompilerState) EmitLoadConstant(value Value) {
switch value.Type {
case ValueNil:
cs.EmitInstruction(OpLoadNil)
case ValueBool:
if value.Data.(bool) {
cs.EmitInstruction(OpLoadTrue)
} else {
cs.EmitInstruction(OpLoadFalse)
}
case ValueNumber:
num := value.Data.(float64)
if num == 0 {
cs.EmitInstruction(OpLoadZero)
} else if num == 1 {
cs.EmitInstruction(OpLoadOne)
} else {
index := cs.AddConstant(value)
if index != -1 {
cs.EmitInstruction(OpLoadConst, uint16(index))
}
}
default:
index := cs.AddConstant(value)
if index != -1 {
cs.EmitInstruction(OpLoadConst, uint16(index))
}
}
}
func (cs *CompilerState) EmitLoadLocal(slot int) {
switch slot {
case 0:
cs.EmitInstruction(OpLoadLocal0)
case 1:
cs.EmitInstruction(OpLoadLocal1)
case 2:
cs.EmitInstruction(OpLoadLocal2)
default:
cs.EmitInstruction(OpLoadLocal, uint16(slot))
}
}
func (cs *CompilerState) EmitStoreLocal(slot int) {
switch slot {
case 0:
cs.EmitInstruction(OpStoreLocal0)
case 1:
cs.EmitInstruction(OpStoreLocal1)
case 2:
cs.EmitInstruction(OpStoreLocal2)
default:
cs.EmitInstruction(OpStoreLocal, uint16(slot))
}
}
// Enhanced instruction pattern detection for optimization
func (cs *CompilerState) GetLastInstruction() (Opcode, []uint16) {
if len(cs.Chunk.Code) == 0 {
return OpNoop, nil
}
// Find the last complete instruction
for i := len(cs.Chunk.Code) - 1; i >= 0; {
op := Opcode(cs.Chunk.Code[i])
operandCount := GetOperandCount(op)
if i >= operandCount*2 {
// This is a complete instruction
operands := make([]uint16, operandCount)
for j := 0; j < operandCount; j++ {
if i+1+j*2 < len(cs.Chunk.Code) && i+2+j*2 < len(cs.Chunk.Code) {
operands[j] = uint16(cs.Chunk.Code[i+1+j*2]) |
(uint16(cs.Chunk.Code[i+2+j*2]) << 8)
}
}
return op, operands
}
i--
}
return OpNoop, nil
}
// Replace last instruction (for peephole optimization)
func (cs *CompilerState) ReplaceLastInstruction(op Opcode, operands ...uint16) bool {
if len(cs.Chunk.Code) == 0 {
return false
}
// Find last instruction
lastOp, _ := cs.GetLastInstruction()
lastSize := InstructionSize(lastOp)
if len(cs.Chunk.Code) < lastSize {
return false
}
// Remove last instruction
cs.Chunk.Code = cs.Chunk.Code[:len(cs.Chunk.Code)-lastSize]
cs.Chunk.Lines = cs.Chunk.Lines[:len(cs.Chunk.Lines)-lastSize]
// Emit new instruction
cs.EmitInstruction(op, operands...)
return true
}
// Dead code elimination support
func (cs *CompilerState) MarkUnreachable(start, end int) {
if start >= 0 && end <= len(cs.Chunk.Code) {
for i := start; i < end; i++ {
cs.Chunk.Code[i] = uint8(OpNoop)
}
}
}
// Optimization statistics tracking
type OptimizationStats struct {
ConstantsFolded int
InstructionsOpt int
DeadCodeEliminated int
JumpsOptimized int
ConstantsDeduped int
}
func (cs *CompilerState) GetOptimizationStats() OptimizationStats {
// Count specialized instructions used
specialized := 0
noops := 0
constantsDeduped := len(cs.Constants) - len(cs.Chunk.Constants)
for i := 0; i < len(cs.Chunk.Code); {
op, _, next := DecodeInstruction(cs.Chunk.Code, i)
if IsSpecializedInstruction(op) {
specialized++
}
if op == OpNoop {
noops++
}
i = next
}
return OptimizationStats{
InstructionsOpt: specialized,
DeadCodeEliminated: noops,
ConstantsDeduped: constantsDeduped,
}
}
func (cs *CompilerState) SetLine(line int) { func (cs *CompilerState) SetLine(line int) {
cs.CurrentLine = line cs.CurrentLine = line
} }
// Enhanced debugging support
func (cs *CompilerState) PrintChunk(name string) {
fmt.Printf("== %s ==\n", name)
fmt.Printf("Constants: %d\n", len(cs.Chunk.Constants))
fmt.Printf("Functions: %d\n", len(cs.Chunk.Functions))
fmt.Printf("Structs: %d\n", len(cs.Chunk.Structs))
fmt.Printf("Code size: %d bytes\n", len(cs.Chunk.Code))
stats := cs.GetOptimizationStats()
fmt.Printf("Optimizations: %d specialized, %d dead eliminated, %d constants deduped\n",
stats.InstructionsOpt, stats.DeadCodeEliminated, stats.ConstantsDeduped)
fmt.Println()
for offset := 0; offset < len(cs.Chunk.Code); {
offset = cs.disassembleInstruction(offset)
}
if len(cs.Chunk.Constants) > 0 {
fmt.Println("\nConstants:")
for i, constant := range cs.Chunk.Constants {
fmt.Printf("%4d: ", i)
cs.printValue(constant)
fmt.Println()
}
}
}
func (cs *CompilerState) disassembleInstruction(offset int) int {
fmt.Printf("%04d ", offset)
if offset > 0 && len(cs.Chunk.Lines) > offset &&
len(cs.Chunk.Lines) > offset-1 &&
cs.Chunk.Lines[offset] == cs.Chunk.Lines[offset-1] {
fmt.Print(" | ")
} else if len(cs.Chunk.Lines) > offset {
fmt.Printf("%4d ", cs.Chunk.Lines[offset])
} else {
fmt.Print(" ? ")
}
if offset >= len(cs.Chunk.Code) {
fmt.Println("END")
return offset + 1
}
instruction := cs.Chunk.Code[offset]
op := Opcode(instruction)
if name, exists := opcodeNames[op]; exists {
fmt.Printf("%-16s", name)
} else {
fmt.Printf("UNKNOWN_%02x ", instruction)
}
switch op {
case OpLoadConst:
return cs.constantInstruction(offset)
case OpLoadLocal, OpStoreLocal, OpAddConst, OpSubConst, OpInc, OpDec:
return cs.byteInstruction(offset)
case OpJump, OpJumpIfTrue, OpJumpIfFalse:
return cs.jumpInstruction(offset, 1)
case OpLoopBack:
return cs.jumpInstruction(offset, -1)
case OpGetLocalField, OpSetLocalField, OpTestAndJump:
return cs.doubleByteInstruction(offset)
default:
fmt.Println()
return offset + 1
}
}
func (cs *CompilerState) constantInstruction(offset int) int {
if offset+2 >= len(cs.Chunk.Code) {
fmt.Println(" [incomplete]")
return offset + 1
}
constant := uint16(cs.Chunk.Code[offset+1]) | (uint16(cs.Chunk.Code[offset+2]) << 8)
fmt.Printf(" %4d '", constant)
if int(constant) < len(cs.Chunk.Constants) {
cs.printValue(cs.Chunk.Constants[constant])
} else {
fmt.Print("???")
}
fmt.Println("'")
return offset + 3
}
func (cs *CompilerState) byteInstruction(offset int) int {
if offset+2 >= len(cs.Chunk.Code) {
fmt.Println(" [incomplete]")
return offset + 1
}
slot := uint16(cs.Chunk.Code[offset+1]) | (uint16(cs.Chunk.Code[offset+2]) << 8)
fmt.Printf(" %4d\n", slot)
return offset + 3
}
func (cs *CompilerState) doubleByteInstruction(offset int) int {
if offset+4 >= len(cs.Chunk.Code) {
fmt.Println(" [incomplete]")
return offset + 1
}
arg1 := uint16(cs.Chunk.Code[offset+1]) | (uint16(cs.Chunk.Code[offset+2]) << 8)
arg2 := uint16(cs.Chunk.Code[offset+3]) | (uint16(cs.Chunk.Code[offset+4]) << 8)
fmt.Printf(" %4d %4d\n", arg1, arg2)
return offset + 5
}
func (cs *CompilerState) jumpInstruction(offset int, sign int) int {
if offset+2 >= len(cs.Chunk.Code) {
fmt.Println(" [incomplete]")
return offset + 1
}
jump := uint16(cs.Chunk.Code[offset+1]) | (uint16(cs.Chunk.Code[offset+2]) << 8)
target := offset + 3 + sign*int(jump)
fmt.Printf(" %4d -> %d\n", jump, target)
return offset + 3
}
func (cs *CompilerState) printValue(value Value) {
switch value.Type {
case ValueNil:
fmt.Print("nil")
case ValueBool:
if value.Data.(bool) {
fmt.Print("true")
} else {
fmt.Print("false")
}
case ValueNumber:
fmt.Printf("%.6g", value.Data.(float64))
case ValueString:
str := value.Data.(string)
if len(str) > 50 {
fmt.Printf("\"%s...\"", str[:47])
} else {
fmt.Printf("\"%s\"", str)
}
default:
fmt.Printf("<%s>", cs.valueTypeString(value.Type))
}
}
func (cs *CompilerState) valueTypeString(vt ValueType) string {
switch vt {
case ValueTable:
return "table"
case ValueFunction:
return "function"
case ValueStruct:
return "struct"
case ValueArray:
return "array"
case ValueUpvalue:
return "upvalue"
default:
return "unknown"
}
}

View File

@ -51,7 +51,7 @@ func checkInstruction(t *testing.T, chunk *compiler.Chunk, pos int, expected com
} }
} }
// Test literal compilation with specialized opcodes // Test literal compilation
func TestNumberLiteral(t *testing.T) { func TestNumberLiteral(t *testing.T) {
chunk := compileSource(t, "echo 42") chunk := compileSource(t, "echo 42")
@ -74,29 +74,6 @@ func TestNumberLiteral(t *testing.T) {
checkInstruction(t, chunk, 4, compiler.OpReturnNil) checkInstruction(t, chunk, 4, compiler.OpReturnNil)
} }
func TestSpecialNumbers(t *testing.T) {
tests := []struct {
source string
expected compiler.Opcode
}{
{"echo 0", compiler.OpLoadZero},
{"echo 1", compiler.OpLoadOne},
}
for _, test := range tests {
chunk := compileSource(t, test.source)
// Should use specialized opcode with no constants
if len(chunk.Constants) != 0 {
t.Errorf("Expected 0 constants for %s, got %d", test.source, len(chunk.Constants))
}
checkInstruction(t, chunk, 0, test.expected)
checkInstruction(t, chunk, 1, compiler.OpEcho)
checkInstruction(t, chunk, 2, compiler.OpReturnNil)
}
}
func TestStringLiteral(t *testing.T) { func TestStringLiteral(t *testing.T) {
chunk := compileSource(t, `echo "hello"`) chunk := compileSource(t, `echo "hello"`)
@ -116,105 +93,45 @@ func TestStringLiteral(t *testing.T) {
} }
func TestBooleanLiterals(t *testing.T) { func TestBooleanLiterals(t *testing.T) {
tests := []struct { chunk := compileSource(t, "echo true")
source string
expected compiler.Opcode if chunk.Constants[0].Type != compiler.ValueBool {
}{ t.Errorf("Expected bool constant, got %v", chunk.Constants[0].Type)
{"echo true", compiler.OpLoadTrue},
{"echo false", compiler.OpLoadFalse},
} }
for _, test := range tests { if chunk.Constants[0].Data.(bool) != true {
chunk := compileSource(t, test.source) t.Errorf("Expected true, got %v", chunk.Constants[0].Data)
// Should use specialized opcode with no constants
if len(chunk.Constants) != 0 {
t.Errorf("Expected 0 constants for %s, got %d", test.source, len(chunk.Constants))
}
checkInstruction(t, chunk, 0, test.expected)
checkInstruction(t, chunk, 1, compiler.OpEcho)
checkInstruction(t, chunk, 2, compiler.OpReturnNil)
} }
} }
func TestNilLiteral(t *testing.T) { func TestNilLiteral(t *testing.T) {
chunk := compileSource(t, "echo nil") chunk := compileSource(t, "echo nil")
// Should use specialized opcode with no constants if chunk.Constants[0].Type != compiler.ValueNil {
if len(chunk.Constants) != 0 { t.Errorf("Expected nil constant, got %v", chunk.Constants[0].Type)
t.Errorf("Expected 0 constants, got %d", len(chunk.Constants))
}
checkInstruction(t, chunk, 0, compiler.OpLoadNil)
checkInstruction(t, chunk, 1, compiler.OpEcho)
checkInstruction(t, chunk, 2, compiler.OpReturnNil)
}
// Test constant folding optimizations
func TestConstantFolding(t *testing.T) {
// Test simple constants first (these should use specialized opcodes)
simpleTests := []struct {
source string
opcode compiler.Opcode
}{
{"echo true", compiler.OpLoadTrue},
{"echo false", compiler.OpLoadFalse},
{"echo nil", compiler.OpLoadNil},
{"echo 0", compiler.OpLoadZero},
{"echo 1", compiler.OpLoadOne},
}
for _, test := range simpleTests {
chunk := compileSource(t, test.source)
checkInstruction(t, chunk, 0, test.opcode)
}
// Test arithmetic that should be folded (if folding is implemented)
chunk := compileSource(t, "echo 2 + 3")
// Check if folding occurred (single constant) or not (two constants + add)
if len(chunk.Constants) == 1 {
// Folding worked
if chunk.Constants[0].Data.(float64) != 5.0 {
t.Errorf("Expected folded constant 5, got %v", chunk.Constants[0].Data)
}
} else if len(chunk.Constants) == 2 {
// No folding - should have Add instruction
found := false
for i := 0; i < len(chunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpAdd {
found = true
break
}
i = next - 1
}
if !found {
t.Error("Expected OpAdd instruction when folding not implemented")
}
} else {
t.Errorf("Unexpected number of constants: %d", len(chunk.Constants))
} }
} }
// Test arithmetic operations (non-foldable) // Test arithmetic operations
func TestArithmetic(t *testing.T) { func TestArithmetic(t *testing.T) {
// Use variables to prevent constant folding tests := []struct {
chunk := compileSource(t, "x = 1\ny = 2\necho x + y") source string
expected compiler.Opcode
// Find the Add instruction }{
found := false {"echo 1 + 2", compiler.OpAdd},
for i := 0; i < len(chunk.Code); i++ { {"echo 5 - 3", compiler.OpSub},
op, _, next := compiler.DecodeInstruction(chunk.Code, i) {"echo 4 * 6", compiler.OpMul},
if op == compiler.OpAdd { {"echo 8 / 2", compiler.OpDiv},
found = true
break
}
i = next - 1
} }
if !found {
t.Error("Expected OpAdd instruction") for _, test := range tests {
chunk := compileSource(t, test.source)
// Should have: LoadConst 0, LoadConst 1, OpArithmetic, OpEcho, OpReturnNil
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0)
checkInstruction(t, chunk, 3, compiler.OpLoadConst, 1)
checkInstruction(t, chunk, 6, test.expected)
checkInstruction(t, chunk, 7, compiler.OpEcho)
} }
} }
@ -224,30 +141,17 @@ func TestComparison(t *testing.T) {
source string source string
expected compiler.Opcode expected compiler.Opcode
}{ }{
{"x = 1\ny = 2\necho x == y", compiler.OpEq}, {"echo 1 == 2", compiler.OpEq},
{"x = 1\ny = 2\necho x != y", compiler.OpNeq}, {"echo 1 != 2", compiler.OpNeq},
{"x = 1\ny = 2\necho x < y", compiler.OpLt}, {"echo 1 < 2", compiler.OpLt},
{"x = 1\ny = 2\necho x <= y", compiler.OpLte}, {"echo 1 <= 2", compiler.OpLte},
{"x = 1\ny = 2\necho x > y", compiler.OpGt}, {"echo 1 > 2", compiler.OpGt},
{"x = 1\ny = 2\necho x >= y", compiler.OpGte}, {"echo 1 >= 2", compiler.OpGte},
} }
for _, test := range tests { for _, test := range tests {
chunk := compileSource(t, test.source) chunk := compileSource(t, test.source)
checkInstruction(t, chunk, 6, test.expected)
// Find the comparison instruction
found := false
for i := 0; i < len(chunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(chunk.Code, i)
if op == test.expected {
found = true
break
}
i = next - 1
}
if !found {
t.Errorf("Expected %v instruction for %s", test.expected, test.source)
}
} }
} }
@ -257,66 +161,32 @@ func TestPrefixOperations(t *testing.T) {
source string source string
expected compiler.Opcode expected compiler.Opcode
}{ }{
{"x = 42\necho -x", compiler.OpNeg}, {"echo -42", compiler.OpNeg},
{"x = true\necho not x", compiler.OpNot}, {"echo not true", compiler.OpNot},
} }
for _, test := range tests { for _, test := range tests {
chunk := compileSource(t, test.source) chunk := compileSource(t, test.source)
checkInstruction(t, chunk, 3, test.expected)
// Find the prefix operation
found := false
for i := 0; i < len(chunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(chunk.Code, i)
if op == test.expected {
found = true
break
}
i = next - 1
}
if !found {
t.Errorf("Expected %v instruction for %s", test.expected, test.source)
}
}
}
// Test specialized local variable access
func TestSpecializedLocals(t *testing.T) {
// This test needs to be within a function to have local variables
chunk := compileSource(t, `
fn test()
a = 1
b = 2
c = 3
echo a
echo b
echo c
end
`)
// Check that function was compiled
if len(chunk.Functions) == 0 {
t.Skip("Function compilation not working")
}
funcChunk := &chunk.Functions[0].Chunk
// Look for specialized local loads in the function
specializedFound := 0
for i := 0; i < len(funcChunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(funcChunk.Code, i)
if op == compiler.OpLoadLocal0 || op == compiler.OpLoadLocal1 || op == compiler.OpLoadLocal2 {
specializedFound++
}
i = next - 1
}
if specializedFound == 0 {
t.Error("Expected specialized local access instructions")
} }
} }
// Test variable assignment // Test variable assignment
func TestLocalAssignment(t *testing.T) {
// Test local assignment within a function scope
chunk := compileSource(t, `
fn test()
x: number = 42
end
`)
// This tests function compilation which is not yet implemented
// For now, just check that it doesn't crash
if chunk == nil {
t.Skip("Function compilation not yet implemented")
}
}
func TestGlobalAssignment(t *testing.T) { func TestGlobalAssignment(t *testing.T) {
chunk := compileSource(t, "x = 42") chunk := compileSource(t, "x = 42")
@ -338,22 +208,6 @@ func TestGlobalAssignment(t *testing.T) {
checkInstruction(t, chunk, 3, compiler.OpStoreGlobal, 1) // Store to "x" checkInstruction(t, chunk, 3, compiler.OpStoreGlobal, 1) // Store to "x"
} }
func TestZeroAssignment(t *testing.T) {
chunk := compileSource(t, "x = 0")
// Should use specialized zero loading
if len(chunk.Constants) != 1 { // Only "x"
t.Fatalf("Expected 1 constant, got %d", len(chunk.Constants))
}
if chunk.Constants[0].Data.(string) != "x" {
t.Errorf("Expected constant to be 'x', got %v", chunk.Constants[0].Data)
}
checkInstruction(t, chunk, 0, compiler.OpLoadZero) // Load 0
checkInstruction(t, chunk, 1, compiler.OpStoreGlobal, 0) // Store to "x"
}
// Test echo statement // Test echo statement
func TestEchoStatement(t *testing.T) { func TestEchoStatement(t *testing.T) {
chunk := compileSource(t, "echo 42") chunk := compileSource(t, "echo 42")
@ -372,22 +226,22 @@ func TestIfStatement(t *testing.T) {
end end
`) `)
// Should start with: LoadTrue, JumpIfFalse (with offset), Pop // Should start with: LoadConst, JumpIfFalse (with offset), Pop
checkInstruction(t, chunk, 0, compiler.OpLoadTrue) // Load true (specialized) checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0) // Load true
// JumpIfFalse has 1 operand (the jump offset) // JumpIfFalse has 1 operand (the jump offset), but we don't need to check the exact value
op, operands, _ := compiler.DecodeInstruction(chunk.Code, 1) op, operands, _ := compiler.DecodeInstruction(chunk.Code, 3)
if op != compiler.OpJumpIfFalse { if op != compiler.OpJumpIfFalse {
t.Errorf("Expected OpJumpIfFalse at position 1, got %v", op) t.Errorf("Expected OpJumpIfFalse at position 3, got %v", op)
} }
if len(operands) != 1 { if len(operands) != 1 {
t.Errorf("Expected 1 operand for JumpIfFalse, got %d", len(operands)) t.Errorf("Expected 1 operand for JumpIfFalse, got %d", len(operands))
} }
checkInstruction(t, chunk, 4, compiler.OpPop) // Pop condition checkInstruction(t, chunk, 6, compiler.OpPop) // Pop condition
} }
// Test while loop with specialized loop instruction // Test while loop
func TestWhileLoop(t *testing.T) { func TestWhileLoop(t *testing.T) {
chunk := compileSource(t, ` chunk := compileSource(t, `
while true do while true do
@ -396,20 +250,15 @@ func TestWhileLoop(t *testing.T) {
`) `)
// Should have condition evaluation and loop structure // Should have condition evaluation and loop structure
checkInstruction(t, chunk, 0, compiler.OpLoadTrue) // Load true (specialized) checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0) // Load true
// Should have LoopBack instruction instead of regular Jump // JumpIfFalse has 1 operand (the jump offset)
found := false op, operands, _ := compiler.DecodeInstruction(chunk.Code, 3)
for i := 0; i < len(chunk.Code); i++ { if op != compiler.OpJumpIfFalse {
op, _, next := compiler.DecodeInstruction(chunk.Code, i) t.Errorf("Expected OpJumpIfFalse at position 3, got %v", op)
if op == compiler.OpLoopBack {
found = true
break
}
i = next - 1
} }
if !found { if len(operands) != 1 {
t.Error("Expected OpLoopBack instruction in while loop") t.Errorf("Expected 1 operand for JumpIfFalse, got %d", len(operands))
} }
} }
@ -429,11 +278,12 @@ func TestTableWithKeys(t *testing.T) {
// Should have subsequent operations to set fields // Should have subsequent operations to set fields
} }
// Test function call optimization // Test function call
func TestFunctionCall(t *testing.T) { func TestFunctionCall(t *testing.T) {
chunk := compileSource(t, "print(42)") chunk := compileSource(t, "print(42)")
// Should have: LoadGlobal "print", LoadConst 42, Call 1 // Should have: LoadGlobal "print", LoadConst 42, Call 1
// The exact positions depend on constant ordering
found := false found := false
for i := 0; i < len(chunk.Code)-2; i++ { for i := 0; i < len(chunk.Code)-2; i++ {
op, operands, _ := compiler.DecodeInstruction(chunk.Code, i) op, operands, _ := compiler.DecodeInstruction(chunk.Code, i)
@ -447,37 +297,6 @@ func TestFunctionCall(t *testing.T) {
} }
} }
// Test optimized local function calls
func TestLocalFunctionCall(t *testing.T) {
chunk := compileSource(t, `
fn test()
f = print
f(42)
end
`)
if len(chunk.Functions) == 0 {
t.Skip("Function compilation not working")
}
funcChunk := &chunk.Functions[0].Chunk
// Look for optimized local call
found := false
for i := 0; i < len(funcChunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(funcChunk.Code, i)
if op == compiler.OpCallLocal0 || op == compiler.OpCallLocal1 {
found = true
break
}
i = next - 1
}
if !found {
t.Log("No optimized local call found (may be expected if function not in slot 0/1)")
}
}
// Test constant deduplication // Test constant deduplication
func TestConstantDeduplication(t *testing.T) { func TestConstantDeduplication(t *testing.T) {
chunk := compileSource(t, "echo 42\necho 42\necho 42") chunk := compileSource(t, "echo 42\necho 42\necho 42")
@ -488,40 +307,9 @@ func TestConstantDeduplication(t *testing.T) {
} }
} }
// Test specialized constant deduplication
func TestSpecializedConstantDeduplication(t *testing.T) {
chunk := compileSource(t, "echo true\necho true\necho false\necho false")
// Should have no constants - all use specialized opcodes
if len(chunk.Constants) != 0 {
t.Errorf("Expected 0 constants (all specialized), got %d", len(chunk.Constants))
}
// Count specialized instructions
trueCount := 0
falseCount := 0
for i := 0; i < len(chunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpLoadTrue {
trueCount++
} else if op == compiler.OpLoadFalse {
falseCount++
}
i = next - 1
}
if trueCount != 2 {
t.Errorf("Expected 2 OpLoadTrue instructions, got %d", trueCount)
}
if falseCount != 2 {
t.Errorf("Expected 2 OpLoadFalse instructions, got %d", falseCount)
}
}
// Test short-circuit evaluation // Test short-circuit evaluation
func TestShortCircuitAnd(t *testing.T) { func TestShortCircuitAnd(t *testing.T) {
chunk := compileSource(t, "x = 1\ny = 2\necho x and y") chunk := compileSource(t, "echo true and false")
// Should have conditional jumping for short-circuit // Should have conditional jumping for short-circuit
found := false found := false
@ -538,7 +326,7 @@ func TestShortCircuitAnd(t *testing.T) {
} }
func TestShortCircuitOr(t *testing.T) { func TestShortCircuitOr(t *testing.T) {
chunk := compileSource(t, "x = 1\ny = 2\necho x or y") chunk := compileSource(t, "echo false or true")
// Should have conditional jumping for short-circuit // Should have conditional jumping for short-circuit
foundFalseJump := false foundFalseJump := false
@ -557,91 +345,20 @@ func TestShortCircuitOr(t *testing.T) {
} }
} }
// Test increment optimization // Test complex expressions
func TestIncrementOptimization(t *testing.T) {
chunk := compileSource(t, `
fn test()
x = 5
y = x + 1
end
`)
if len(chunk.Functions) == 0 {
t.Skip("Function compilation not working")
}
funcChunk := &chunk.Functions[0].Chunk
// Look for increment optimization (Inc instruction)
found := false
for i := 0; i < len(funcChunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(funcChunk.Code, i)
if op == compiler.OpInc {
found = true
break
}
i = next - 1
}
if !found {
t.Log("No increment optimization found (pattern may not match exactly)")
}
}
// Test complex expressions (should prevent some folding)
func TestComplexExpression(t *testing.T) { func TestComplexExpression(t *testing.T) {
chunk := compileSource(t, "x = 5\necho x + 2 * 3") chunk := compileSource(t, "echo 1 + 2 * 3")
// Should have constants: "x", and numbers for 2*3 (either 2,3 or folded 6) // Should follow correct precedence: Load 1, Load 2, Load 3, Mul, Add
if len(chunk.Constants) < 2 { if len(chunk.Constants) != 3 {
t.Errorf("Expected at least 2 constants, got %d", len(chunk.Constants)) t.Fatalf("Expected 3 constants, got %d", len(chunk.Constants))
} }
// Check that we have the expected constant values // Verify constants
hasVarX := false expected := []float64{1, 2, 3}
hasNumberConstant := false for i, exp := range expected {
if chunk.Constants[i].Data.(float64) != exp {
for _, constant := range chunk.Constants { t.Errorf("Expected constant %d to be %v, got %v", i, exp, chunk.Constants[i].Data)
switch constant.Type {
case compiler.ValueNumber:
val := constant.Data.(float64)
if val == 5 || val == 2 || val == 3 || val == 6 {
hasNumberConstant = true
}
case compiler.ValueString:
if constant.Data.(string) == "x" {
hasVarX = true
}
} }
} }
if !hasVarX {
t.Error("Expected variable name 'x'")
}
if !hasNumberConstant {
t.Error("Expected some numeric constant")
}
}
// Test dead code elimination
func TestDeadCodeElimination(t *testing.T) {
chunk := compileSource(t, `
echo 1
return
echo 2
`)
// Look for NOOP instructions (dead code markers)
noopCount := 0
for i := 0; i < len(chunk.Code); i++ {
op, _, next := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpNoop {
noopCount++
}
i = next - 1
}
if noopCount == 0 {
t.Log("No dead code elimination detected (may depend on optimization level)")
}
} }