diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 91b4ed1..db9d7b6 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -17,9 +17,18 @@ type Assembler struct { // 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(b *build.Build) (code []byte, data []byte) { data, dataLabels := a.Data.Finalize() @@ -65,7 +74,10 @@ func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte) { // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b *Assembler) { maps.Copy(a.Data, b.Data) - a.Instructions = append(a.Instructions, b.Instructions...) + + for _, instr := range b.Instructions { + a.Append(instr) + } } // SetData sets the data for the given label. @@ -75,4 +87,61 @@ func (a *Assembler) SetData(label string, bytes []byte) { } a.Data.Insert(label, bytes) +} + +// 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 + } + + switch instr := instr.(type) { + case *FunctionEnd: + // Call + FunctionEnd can be replaced by a single Jump + call, isCall := a.Last().(*Call) + + if isCall { + a.SetLast(&Jump{Label: call.Label}) + return true + } + + 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 { + a.SetLast(instr) + return true + } + + 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 + _, isJump := a.Last().(*Jump) + + if isJump { + return true + } + + // Return + Return is unnecessary + _, isReturn := a.Last().(*Return) + + if isReturn { + return true + } + } + + return false } \ No newline at end of file diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 743ab89..31446ba 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -11,6 +11,10 @@ type Call struct { type FunctionStart struct{} type FunctionEnd struct{} +type Jump struct { + Label string +} + type Label struct { Name string } diff --git a/src/asm/compilerARM.go b/src/asm/compilerARM.go index 2715ee4..46d3462 100644 --- a/src/asm/compilerARM.go +++ b/src/asm/compilerARM.go @@ -30,6 +30,25 @@ func (c *compilerARM) Compile(instr Instruction) { offset := (address - start) / 4 binary.LittleEndian.PutUint32(c.code[start:start+4], arm.Call(offset)) }) + case *Jump: + start := len(c.code) + c.append(arm.Jump(0)) + + c.Defer(func() { + address, exists := c.labels[instr.Label] + + if !exists { + panic("unknown label: " + instr.Label) + } + + offset := (address - start) / 4 + + if offset != (offset & 0b11_11111111_11111111_11111111) { + panic("not implemented: long jumps") + } + + binary.LittleEndian.PutUint32(c.code[start:start+4], arm.Jump(offset)) + }) case *FunctionStart: c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) @@ -59,5 +78,7 @@ func (c *compilerARM) Compile(instr Instruction) { c.append(arm.Return()) case *Syscall: c.append(arm.Syscall()) + default: + panic("unknown instruction") } } \ No newline at end of file diff --git a/src/asm/compilerX86.go b/src/asm/compilerX86.go index 429e2dd..57fc56c 100644 --- a/src/asm/compilerX86.go +++ b/src/asm/compilerX86.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" + "git.urbach.dev/cli/q/src/sizeof" "git.urbach.dev/cli/q/src/x86" ) @@ -26,6 +27,27 @@ func (c *compilerX86) Compile(instr Instruction) { offset := address - end binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset)) }) + case *FunctionStart: + case *FunctionEnd: + case *Jump: + c.code = x86.Jump8(c.code, 0) + end := len(c.code) + + c.Defer(func() { + address, exists := c.labels[instr.Label] + + if !exists { + panic("unknown label: " + instr.Label) + } + + offset := address - end + + if sizeof.Signed(offset) > 1 { + panic("not implemented: long jumps") + } + + c.code[end-1] = byte(offset) + }) case *Label: c.labels[instr.Name] = len(c.code) case *MoveRegisterLabel: @@ -50,5 +72,7 @@ func (c *compilerX86) Compile(instr Instruction) { c.code = x86.Return(c.code) case *Syscall: c.code = x86.Syscall(c.code) + default: + panic("unknown instruction") } } \ No newline at end of file diff --git a/src/core/Compile.go b/src/core/Compile.go index 1dedfdc..23e6838 100644 --- a/src/core/Compile.go +++ b/src/core/Compile.go @@ -93,11 +93,7 @@ func (f *Function) Compile() { } if f.UniqueName != "core.exit" { - switch f.Assembler.Instructions[len(f.Assembler.Instructions)-1].(type) { - case *asm.Return: - default: - f.Assembler.Append(&asm.Return{}) - } + f.Assembler.Append(&asm.Return{}) } }