From 09afa2fc51bfdaced70e4e22fd94da0ee2e01c8a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 6 Oct 2025 17:35:49 +0200 Subject: [PATCH] Implemented memory loads at fixed offsets --- src/arm/Load.go | 5 +++++ src/arm/Load_test.go | 21 +++++++++++++++++++++ src/arm/Store.go | 5 +++++ src/arm/Store_test.go | 21 +++++++++++++++++++++ src/asm/Instructions.go | 7 +++++++ src/asm/compilerARM.go | 6 ++++++ src/asm/compilerX86.go | 6 ++++++ src/codegen/canEncodeNumber.go | 20 ++++++++++++++++++++ src/codegen/executeLoad.go | 24 +++++++++++++++++------- src/verbose/ASM.go | 8 ++++++++ tests/array-int-fixed-offset.q | 25 +++++++++++++++++++++++++ tests/tests_test.go | 1 + 12 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 tests/array-int-fixed-offset.q diff --git a/src/arm/Load.go b/src/arm/Load.go index 10dc221..24052cf 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -10,4 +10,9 @@ func Load(destination cpu.Register, base cpu.Register, offset cpu.Register, scal // LoadFixedOffset loads from memory with a signed offset from -256 to 255 into a register. func LoadFixedOffset(destination cpu.Register, base cpu.Register, mode AddressMode, offset int, length byte) uint32 { return 1<<22 | StoreFixedOffsetRegister(destination, base, mode, offset, length) +} + +// LoadFixedOffsetScaled loads from memory with a scaled unsigned offset from 0 to 4095 into a register. +func LoadFixedOffsetScaled(destination cpu.Register, base cpu.Register, mode AddressMode, offset uint, length byte) uint32 { + return 1<<22 | StoreFixedOffsetRegisterScaled(destination, base, mode, offset, length) } \ No newline at end of file diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index 80d4c85..99e8709 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -64,4 +64,25 @@ func TestLoadFixedOffset(t *testing.T) { code := arm.LoadFixedOffset(pattern.Destination, pattern.Base, pattern.Mode, pattern.Offset, pattern.Length) assert.Equal(t, code, pattern.Code) } +} + +func TestLoadFixedOffsetScaled(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Base cpu.Register + Mode arm.AddressMode + Offset uint + Length byte + Code uint32 + }{ + {arm.X1, arm.X0, arm.UnscaledImmediate, 0, 8, 0xF9400001}, + {arm.X1, arm.X0, arm.UnscaledImmediate, 1, 8, 0xF9400401}, + {arm.X1, arm.X0, arm.UnscaledImmediate, 4095, 8, 0xF97FFC01}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldr %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset*uint(pattern.Length), pattern.Length) + code := arm.LoadFixedOffsetScaled(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 f47ad9c..9f290db 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -12,4 +12,9 @@ func StoreRegister(source cpu.Register, base cpu.Register, offset cpu.Register, // StoreFixedOffsetRegister writes the contents of the register to a memory address with a signed offset from -256 to 255. func StoreFixedOffsetRegister(source cpu.Register, base cpu.Register, mode AddressMode, offset int, length byte) uint32 { return size(length)<<30 | 0b111<<27 | memory(source, base, mode, offset) +} + +// StoreFixedOffsetRegisterScaled writes the contents of the register to a memory address with a scaled unsigned offset from 0 to 4095. +func StoreFixedOffsetRegisterScaled(source cpu.Register, base cpu.Register, mode AddressMode, offset uint, length byte) uint32 { + return size(length)<<30 | 0b111<<27 | 1<<24 | reg2Imm(source, base, offset) } \ No newline at end of file diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go index 785503d..4d90dd0 100644 --- a/src/arm/Store_test.go +++ b/src/arm/Store_test.go @@ -62,4 +62,25 @@ func TestStoreFixedOffsetRegister(t *testing.T) { code := arm.StoreFixedOffsetRegister(pattern.Source, pattern.Base, pattern.Mode, pattern.Offset, pattern.Length) assert.Equal(t, code, pattern.Code) } +} + +func TestStoreFixedOffsetRegisterScaled(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Base cpu.Register + Mode arm.AddressMode + Offset uint + Length byte + Code uint32 + }{ + {arm.X1, arm.X0, arm.UnscaledImmediate, 0, 8, 0xF9000001}, + {arm.X1, arm.X0, arm.UnscaledImmediate, 1, 8, 0xF9000401}, + {arm.X1, arm.X0, arm.UnscaledImmediate, 4095, 8, 0xF93FFC01}, + } + + for _, pattern := range usagePatterns { + t.Logf("str %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset*uint(pattern.Length), pattern.Length) + code := arm.StoreFixedOffsetRegisterScaled(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/asm/Instructions.go b/src/asm/Instructions.go index 57df1d9..91c8800 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -36,6 +36,13 @@ type ( Scale bool Length byte } + LoadFixedOffset struct { + Base cpu.Register + Index int + Destination cpu.Register + Scale bool + Length byte + } Modulo rrr Move rr MoveLabel struct { diff --git a/src/asm/compilerARM.go b/src/asm/compilerARM.go index 2e0a12b..0ed2c68 100644 --- a/src/asm/compilerARM.go +++ b/src/asm/compilerARM.go @@ -132,6 +132,12 @@ func (c *compilerARM) Compile(instr Instruction) { } c.append(arm.Load(instr.Destination, instr.Base, instr.Index, scale, instr.Length)) + case *LoadFixedOffset: + if instr.Scale { + c.append(arm.LoadFixedOffsetScaled(instr.Destination, instr.Base, arm.UnscaledImmediate, uint(instr.Index), instr.Length)) + } else { + c.append(arm.LoadFixedOffset(instr.Destination, instr.Base, arm.UnscaledImmediate, instr.Index, instr.Length)) + } case *Modulo: if instr.Destination == instr.Source || instr.Destination == instr.Operand { panic("modulo destination register cannot be equal to the source or operand register") diff --git a/src/asm/compilerX86.go b/src/asm/compilerX86.go index 3b82056..382532e 100644 --- a/src/asm/compilerX86.go +++ b/src/asm/compilerX86.go @@ -194,6 +194,12 @@ func (c *compilerX86) Compile(instr Instruction) { } else { c.code = x86.Load(c.code, instr.Destination, instr.Base, instr.Index, scale, instr.Length) } + case *LoadFixedOffset: + if instr.Scale { + panic("not implemented") + } + + c.code = x86.LoadFixedOffset(c.code, instr.Destination, instr.Base, int8(instr.Index), x86.Scale1, instr.Length) case *Modulo: if instr.Operand == x86.R0 { panic("modulo operand register cannot be R0") diff --git a/src/codegen/canEncodeNumber.go b/src/codegen/canEncodeNumber.go index e2527ac..7d06d75 100644 --- a/src/codegen/canEncodeNumber.go +++ b/src/codegen/canEncodeNumber.go @@ -62,6 +62,26 @@ func (f *Function) canEncodeNumber(instr ssa.Value, number *ssa.Int) bool { } } + case *ssa.Load: + if instr.Memory.Index != number { + return false + } + + switch f.build.Arch { + case config.ARM: + if instr.Memory.Scale { + return number.Int >= 0 && number.Int <= 4095 + } else { + return number.Int >= -256 && number.Int <= 255 + } + case config.X86: + if instr.Memory.Scale { + return false + } + + return number.Int >= -128 && number.Int <= 127 + } + case *ssa.Store: switch f.build.Arch { case config.ARM: diff --git a/src/codegen/executeLoad.go b/src/codegen/executeLoad.go index 28db171..4f20666 100644 --- a/src/codegen/executeLoad.go +++ b/src/codegen/executeLoad.go @@ -15,11 +15,21 @@ func (f *Function) executeLoad(step *Step, instr *ssa.Load) { index := f.ValueToStep[memory.Index] elementSize := step.Value.Type().Size() - f.Assembler.Append(&asm.Load{ - Base: address.Register, - Index: index.Register, - Destination: step.Register, - Scale: memory.Scale, - Length: byte(elementSize), - }) + if index.Register == -1 { + f.Assembler.Append(&asm.LoadFixedOffset{ + Base: address.Register, + Index: index.Value.(*ssa.Int).Int, + Destination: step.Register, + Scale: memory.Scale, + Length: byte(elementSize), + }) + } else { + f.Assembler.Append(&asm.Load{ + Base: address.Register, + Index: index.Register, + Destination: step.Register, + Scale: memory.Scale, + Length: byte(elementSize), + }) + } } \ No newline at end of file diff --git a/src/verbose/ASM.go b/src/verbose/ASM.go index f418a04..ce9f66e 100644 --- a/src/verbose/ASM.go +++ b/src/verbose/ASM.go @@ -123,6 +123,14 @@ func printAssembly(f *core.Function) { other.Print(" + ") register.Print(instr.Index) other.Print("]") + case *asm.LoadFixedOffset: + mnemonic.Printf(" load %db ", instr.Length) + register.Print(instr.Destination) + other.Print(", [") + register.Print(instr.Base) + other.Print(" + ") + imm.Print(instr.Index) + other.Print("]") case *asm.Modulo: mnemonic.Print(" mod ") register.Print(instr.Destination) diff --git a/tests/array-int-fixed-offset.q b/tests/array-int-fixed-offset.q new file mode 100644 index 0000000..acd03f5 --- /dev/null +++ b/tests/array-int-fixed-offset.q @@ -0,0 +1,25 @@ +import io + +main() { + x := new(int, 8192) + fill(x) + show(x) +} + +fill(x []int) { + x[127] = 127 + x[128] = 128 + x[4095] = 4095 + x[4096] = 4096 +} + +show(x []int) { + io.write(x[127]) + io.write("\n") + io.write(x[128]) + io.write("\n") + io.write(x[4095]) + io.write("\n") + io.write(x[4096]) + io.write("\n") +} \ No newline at end of file diff --git a/tests/tests_test.go b/tests/tests_test.go index f3456fb..61ce322 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -112,6 +112,7 @@ var tests = []run{ {"array-int", "", "", 0}, {"array-int-custom", "", "", 0}, {"array-int-parameter", "", "", 0}, + {"array-int-fixed-offset", "", "127\n128\n4095\n4096\n", 0}, {"array-struct", "", "", 0}, {"array-struct-copy", "", "", 0}, {"array-struct-copy-call", "", "", 0}, -- 2.51.2