package asm import ( "maps" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/data" "git.urbach.dev/cli/q/src/dll" ) // Assembler contains a list of instructions. type Assembler struct { Data data.Data Instructions []Instruction Libraries dll.List } // Append adds another instruction. func (a *Assembler) Append(instr Instruction) { if a.Skip(instr) { return } a.Instructions = append(a.Instructions, instr) } // Last returns the last instruction. func (a *Assembler) Last() Instruction { return a.Instructions[len(a.Instructions)-1] } // Compile compiles the instructions to machine code. func (a *Assembler) Compile(build *config.Build) (code []byte, data []byte, libs dll.List, labels map[string]int) { data, dataLabels := a.Data.Finalize() c := compiler{ patcher: patcher{ code: make([]byte, 0, len(a.Instructions)*8), earlyPatches: make([]*patch, 0, len(a.Instructions)/8), latePatches: make([]*patch, 0, len(a.Instructions)/8), labels: make(map[string]int, 32), }, build: build, data: data, dataLabels: dataLabels, libraries: a.Libraries, } switch build.Arch { case config.ARM: armc := compilerARM{compiler: &c} for _, instr := range a.Instructions { armc.Compile(instr) } case config.X86: x86c := compilerX86{compiler: &c} for _, instr := range a.Instructions { x86c.Compile(instr) } } c.ApplyPatches(c.earlyPatches) c.AddDataLabels() c.ApplyPatches(c.latePatches) return c.code, c.data, c.libraries, c.labels } // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b *Assembler) { skip := 0 for a.Skip(b.Instructions[skip]) { skip++ } a.Instructions = append(a.Instructions, b.Instructions[skip:]...) if a.Data.Immutable == nil { a.Data.Immutable = b.Data.Immutable } else { maps.Copy(a.Data.Immutable, b.Data.Immutable) } if a.Data.Mutable == nil { a.Data.Mutable = b.Data.Mutable } else { maps.Copy(a.Data.Mutable, b.Data.Mutable) } for library := range b.Libraries.All() { for _, fn := range library.Functions { a.Libraries.Append(library.Name, fn) } } } // SetLast sets the last instruction. func (a *Assembler) SetLast(instr Instruction) { a.Instructions[len(a.Instructions)-1] = instr } // Skip returns true if appending the instruction can be skipped. func (a *Assembler) Skip(instr Instruction) bool { if len(a.Instructions) == 0 { return false } // Call to os.exit + anything is skipped if it's not a label call, isCall := a.Last().(*Call) if isCall && call.Label == "run.exit" { switch instr.(type) { case *Label: default: return true } } switch instr := instr.(type) { case *Label: // Jump + Label can be replaced by just the Label if both addresses are equal jump, isJump := a.Last().(*Jump) if isJump && jump.Label == instr.Name { instr.Align = 0 a.SetLast(instr) return true } case *Move: // A move following a move with inverted operands is unnecessary i := len(a.Instructions) - 1 for i >= 0 { previous := a.Instructions[i] move, isMove := previous.(*Move) if !isMove { return false } if instr.Destination == move.Source { if instr.Source == move.Destination { return true } else { return false } } i-- } case *Return: // Call + Return can be replaced by a single Jump call, isCall := a.Last().(*Call) if isCall { a.SetLast(&Jump{Label: call.Label}) return true } // Jump + Return is unnecessary jump, isJump := a.Last().(*Jump) if isJump && jump.Condition == None { return true } // Return + Return is unnecessary _, isReturn := a.Last().(*Return) if isReturn { return true } } return false }