diff --git a/src/arm/AddressMode.go b/src/arm/AddressMode.go new file mode 100644 index 0000000..5e23e8f --- /dev/null +++ b/src/arm/AddressMode.go @@ -0,0 +1,10 @@ +package arm + +// AddressMode encodes the addressing mode. +type AddressMode uint32 + +const ( + UnscaledImmediate = AddressMode(0b00) + PostIndex = AddressMode(0b01) + PreIndex = AddressMode(0b11) +) \ No newline at end of file diff --git a/src/arm/Load.go b/src/arm/Load.go index 8454f42..61c7464 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -3,8 +3,8 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { - common := 1<<22 | memory(destination, base, offset) +func LoadRegister(destination cpu.Register, base cpu.Register, mode AddressMode, offset int, length byte) uint32 { + common := 1<<22 | memory(destination, base, mode, offset) switch length { case 1: diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index a7a4e69..f41ca13 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -12,28 +12,33 @@ func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Base cpu.Register + Mode arm.AddressMode Offset int Length byte Code uint32 }{ - {arm.X0, arm.X1, -8, 1, 0x385F8020}, - {arm.X1, arm.X0, -8, 1, 0x385F8001}, - {arm.X0, arm.X1, -8, 2, 0x785F8020}, - {arm.X1, arm.X0, -8, 2, 0x785F8001}, - {arm.X0, arm.X1, -8, 4, 0xB85F8020}, - {arm.X1, arm.X0, -8, 4, 0xB85F8001}, - {arm.X0, arm.X1, -8, 8, 0xF85F8020}, - {arm.X1, arm.X0, -8, 8, 0xF85F8001}, - {arm.X2, arm.X1, -8, 8, 0xF85F8022}, - {arm.X2, arm.X1, 0, 8, 0xF8400022}, - {arm.X2, arm.X1, 8, 8, 0xF8408022}, - {arm.X2, arm.X1, -256, 8, 0xF8500022}, - {arm.X2, arm.X1, 255, 8, 0xF84FF022}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 1, 0x385F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 1, 0x385F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 2, 0x785F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 2, 0x785F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 4, 0xB85F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 4, 0xB85F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 8, 0xF85F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 8, 0xF85F8001}, + {arm.X2, arm.X1, arm.UnscaledImmediate, -8, 8, 0xF85F8022}, + {arm.X2, arm.X1, arm.UnscaledImmediate, 0, 8, 0xF8400022}, + {arm.X2, arm.X1, arm.UnscaledImmediate, 8, 8, 0xF8408022}, + {arm.X2, arm.X1, arm.UnscaledImmediate, -256, 8, 0xF8500022}, + {arm.X2, arm.X1, arm.UnscaledImmediate, 255, 8, 0xF84FF022}, + + {arm.X0, arm.SP, arm.PostIndex, 16, 8, 0xF84107E0}, + {arm.X1, arm.SP, arm.PostIndex, 16, 8, 0xF84107E1}, + {arm.X2, arm.SP, arm.PostIndex, 16, 8, 0xF84107E2}, } for _, pattern := range usagePatterns { t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) - code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Mode, pattern.Offset, pattern.Length) assert.Equal(t, code, pattern.Code) } } \ No newline at end of file diff --git a/src/arm/Store.go b/src/arm/Store.go index d6b1212..08e77a6 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -5,8 +5,8 @@ import ( ) // StoreRegister writes the contents of the register to a memory address. -func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { - common := memory(source, base, offset) +func StoreRegister(source cpu.Register, base cpu.Register, mode AddressMode, offset int, length byte) uint32 { + common := memory(source, base, mode, offset) switch length { case 1: diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go index 2b80285..24f694c 100644 --- a/src/arm/Store_test.go +++ b/src/arm/Store_test.go @@ -12,23 +12,28 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { Source cpu.Register Base cpu.Register + Mode arm.AddressMode Offset int Length byte Code uint32 }{ - {arm.X0, arm.X1, -8, 1, 0x381F8020}, - {arm.X1, arm.X0, -8, 1, 0x381F8001}, - {arm.X0, arm.X1, -8, 2, 0x781F8020}, - {arm.X1, arm.X0, -8, 2, 0x781F8001}, - {arm.X0, arm.X1, -8, 4, 0xB81F8020}, - {arm.X1, arm.X0, -8, 4, 0xB81F8001}, - {arm.X0, arm.X1, -8, 8, 0xF81F8020}, - {arm.X1, arm.X0, -8, 8, 0xF81F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 1, 0x381F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 1, 0x381F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 2, 0x781F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 2, 0x781F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 4, 0xB81F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 4, 0xB81F8001}, + {arm.X0, arm.X1, arm.UnscaledImmediate, -8, 8, 0xF81F8020}, + {arm.X1, arm.X0, arm.UnscaledImmediate, -8, 8, 0xF81F8001}, + + {arm.X0, arm.SP, arm.PreIndex, -16, 8, 0xF81F0FE0}, + {arm.X1, arm.SP, arm.PreIndex, -16, 8, 0xF81F0FE1}, + {arm.X2, arm.SP, arm.PreIndex, -16, 8, 0xF81F0FE2}, } for _, pattern := range usagePatterns { t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) - code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Mode, pattern.Offset, pattern.Length) assert.Equal(t, code, pattern.Code) } } \ No newline at end of file diff --git a/src/arm/encode.go b/src/arm/encode.go index 72150eb..de5ddbb 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -4,9 +4,9 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -// memory encodes an instruction with a register, a base register and an offset. -func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { - return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) +// memory encodes an instruction with a register, a base register, an addressing mode and an offset. +func memory(destination cpu.Register, base cpu.Register, mode AddressMode, imm9 int) uint32 { + return uint32(imm9&mask9)<<12 | uint32(mode<<10) | uint32(base)<<5 | uint32(destination) } // pair encodes an instruction using a register pair with memory. diff --git a/src/asm/compilerARM.go b/src/asm/compilerARM.go index 7ec97da..3d179c6 100644 --- a/src/asm/compilerARM.go +++ b/src/asm/compilerARM.go @@ -37,7 +37,7 @@ func (c *compilerARM) Compile(instr Instruction) { case *CallExtern: c.append(arm.LoadAddress(arm.X0, 0)) patch := c.PatchLast4Bytes() - c.append(arm.LoadRegister(arm.X0, arm.X0, 0, 8)) + c.append(arm.LoadRegister(arm.X0, arm.X0, arm.UnscaledImmediate, 0, 8)) c.append(arm.CallRegister(arm.X0)) patch.apply = func(code []byte) []byte { @@ -105,7 +105,7 @@ func (c *compilerARM) Compile(instr Instruction) { if count&1 != 0 { count-- - c.append(arm.LoadPair(registers[count], registers[count], arm.SP, 16)) + c.append(arm.LoadRegister(registers[count], arm.SP, arm.PostIndex, 16, 8)) } for i := count - 2; i >= 0; i -= 2 { @@ -118,7 +118,7 @@ func (c *compilerARM) Compile(instr Instruction) { if i+1 < len(registers) { c.append(arm.StorePair(registers[i], registers[i+1], arm.SP, -16)) } else { - c.append(arm.StorePair(registers[i], registers[i], arm.SP, -16)) + c.append(arm.StoreRegister(registers[i], arm.SP, arm.PreIndex, -16, 8)) } } case *Return: diff --git a/src/codegen/GenerateAssembly.go b/src/codegen/GenerateAssembly.go index dc7205d..a36925c 100644 --- a/src/codegen/GenerateAssembly.go +++ b/src/codegen/GenerateAssembly.go @@ -13,7 +13,7 @@ func (f *Function) GenerateAssembly(ir ssa.IR, stackFrame bool) { f.Assembler.Append(&asm.StackFrameStart{}) } - f.Steps = f.createSteps(ir) + f.createSteps(ir) if f.Preserved.Count() > 0 { f.Assembler.Append(&asm.PushRegisters{Registers: f.Preserved.Slice()}) diff --git a/src/codegen/addPreservedRegisters.go b/src/codegen/addPreservedRegisters.go new file mode 100644 index 0000000..8945d80 --- /dev/null +++ b/src/codegen/addPreservedRegisters.go @@ -0,0 +1,14 @@ +package codegen + +import "slices" + +// addPreservedRegisters creates a list of registers that need to be preserved. +func (f *Function) addPreservedRegisters() { + mustPreserve := f.CPU.Call.Preserved + + for _, step := range f.Steps { + if slices.Contains(mustPreserve, step.Register) { + f.Preserved.Add(step.Register) + } + } +} \ No newline at end of file diff --git a/src/codegen/createHints.go b/src/codegen/createHints.go new file mode 100644 index 0000000..924b869 --- /dev/null +++ b/src/codegen/createHints.go @@ -0,0 +1,37 @@ +package codegen + +import "git.urbach.dev/cli/q/src/ssa" + +// createHints recommends registers that a value must reside in later on. +func (f *Function) createHints(step *step) { + switch instr := step.Value.(type) { + case *ssa.Call: + for paramIndex, param := range instr.Arguments { + f.ValueToStep[param].Hint(f.CPU.Call.In[paramIndex]) + } + + case *ssa.CallExtern: + for r, param := range instr.Arguments { + if r >= len(f.CPU.ExternCall.In) { + // Temporary hack to allow arguments 5 and 6 to be hinted as r10 and r11, then pushed later. + f.ValueToStep[param].Hint(f.CPU.ExternCall.Clobbered[1+r]) + continue + } + + f.ValueToStep[param].Hint(f.CPU.ExternCall.In[r]) + } + + case *ssa.Parameter: + f.ValueToStep[instr].Register = f.CPU.Call.In[instr.Index] + + case *ssa.Return: + for r, param := range instr.Arguments { + f.ValueToStep[param].Hint(f.CPU.Call.Out[r]) + } + + case *ssa.Syscall: + for r, param := range instr.Arguments { + f.ValueToStep[param].Hint(f.CPU.Syscall.In[r]) + } + } +} \ No newline at end of file diff --git a/src/codegen/createLiveRanges.go b/src/codegen/createLiveRanges.go new file mode 100644 index 0000000..527c645 --- /dev/null +++ b/src/codegen/createLiveRanges.go @@ -0,0 +1,25 @@ +package codegen + +import "git.urbach.dev/cli/q/src/ssa" + +// createLiveRanges adds the `value` to the list of live values in its live range. +func (f *Function) createLiveRanges(step *step) { + users := step.Value.Users() + + if len(users) == 0 { + return + } + + liveStart := step.Index + _, isParam := step.Value.(*ssa.Parameter) + + if isParam { + liveStart = 0 + } + + liveEnd := f.ValueToStep[users[len(users)-1]].Index + + for i := liveStart; i < liveEnd; i++ { + f.Steps[i].Live = append(f.Steps[i].Live, step) + } +} \ No newline at end of file diff --git a/src/codegen/createSteps.go b/src/codegen/createSteps.go index e5957f9..353ebda 100644 --- a/src/codegen/createSteps.go +++ b/src/codegen/createSteps.go @@ -1,17 +1,14 @@ package codegen import ( - "slices" - - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/ssa" ) // createSteps builds a series of instructions from the SSA values in the IR. -func (f *Function) createSteps(ir ssa.IR) []*step { +func (f *Function) createSteps(ir ssa.IR) { count := ir.CountValues() storage := make([]step, count) - steps := make([]*step, count) + f.Steps = make([]*step, count) f.ValueToStep = make(map[ssa.Value]*step, count) for i, instr := range ir.Values { @@ -19,142 +16,16 @@ func (f *Function) createSteps(ir ssa.IR) []*step { step.Index = i step.Value = instr step.Register = -1 - steps[i] = step + f.Steps[i] = step f.ValueToStep[instr] = step } - for i, instr := range ir.Values { - switch instr := instr.(type) { - case *ssa.Call: - for paramIndex, param := range instr.Arguments { - f.ValueToStep[param].Hint(f.CPU.Call.In[paramIndex]) - } - - case *ssa.CallExtern: - for r, param := range instr.Arguments { - if r >= len(f.CPU.ExternCall.In) { - // Temporary hack to allow arguments 5 and 6 to be hinted as r10 and r11, then pushed later. - f.ValueToStep[param].Hint(f.CPU.ExternCall.Clobbered[1+r]) - continue - } - - f.ValueToStep[param].Hint(f.CPU.ExternCall.In[r]) - } - - case *ssa.Parameter: - f.ValueToStep[instr].Register = f.CPU.Call.In[instr.Index] - - case *ssa.Return: - for r, param := range instr.Arguments { - f.ValueToStep[param].Hint(f.CPU.Call.Out[r]) - } - - case *ssa.Syscall: - for r, param := range instr.Arguments { - f.ValueToStep[param].Hint(f.CPU.Syscall.In[r]) - } - } - - users := instr.Users() - - if len(users) == 0 { - continue - } - - liveStart := i - _, isParam := instr.(*ssa.Parameter) - - if isParam { - liveStart = 0 - } - - liveEnd := f.ValueToStep[users[len(users)-1]].Index - instrStep := f.ValueToStep[instr] - - for live := liveStart; live < liveEnd; live++ { - steps[live].Live = append(steps[live].Live, instrStep) - } - } - - usedRegisters := 0 - futureRegisters := 0 - - for i, step := range steps { - param, isParam := step.Value.(*ssa.Parameter) - - if !isParam { - break - } - - currentRegister := f.CPU.Call.In[param.Index] - - if futureRegisters&(1<