From 3f64ef487946e096da331bb1bacecc4f9ea39099 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 7 Oct 2025 16:03:06 +0200 Subject: [PATCH] Implemented division for unsigned integers --- src/arm/Div.go | 11 ++++++++--- src/arm/Div_test.go | 21 ++++++++++++++++++-- src/asm/Instructions.go | 8 +++++--- src/asm/compilerARM.go | 13 ++++++++++-- src/asm/compilerX86.go | 34 ++++++++++++++++++++++++++++++-- src/codegen/executeBinaryOp.go | 36 ++++++++++++++++++++++++---------- src/verbose/ASM.go | 22 +++++++++++++++++---- src/x86/Div.go | 16 ++++++++++++--- src/x86/Div_test.go | 34 ++++++++++++++++++++++++++++++-- tests/tests_test.go | 1 + tests/unsigned.q | 6 ++++++ 11 files changed, 171 insertions(+), 31 deletions(-) create mode 100644 tests/unsigned.q diff --git a/src/arm/Div.go b/src/arm/Div.go index 21b6e90..28de54c 100644 --- a/src/arm/Div.go +++ b/src/arm/Div.go @@ -2,7 +2,12 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// DivRegisterRegister divides source by operand and stores the value in the destination. -func DivRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand) +// DivSignedRegisterRegister divides source by operand and stores the value in the destination. +func DivSignedRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 1<<10 | DivUnsignedRegisterRegister(destination, source, operand) +} + +// DivUnsignedRegisterRegister divides unsigned source by unsigned operand and stores the value in the destination. +func DivUnsignedRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10011010110<<21 | 0b00001<<11 | reg3(destination, source, operand) } \ No newline at end of file diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go index 68ed6fc..6a95ef3 100644 --- a/src/arm/Div_test.go +++ b/src/arm/Div_test.go @@ -8,7 +8,7 @@ import ( "git.urbach.dev/go/assert" ) -func TestDivRegisterRegister(t *testing.T) { +func TestDivSignedRegisterRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Source cpu.Register @@ -20,7 +20,24 @@ func TestDivRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) - code := arm.DivRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + code := arm.DivSignedRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.Equal(t, code, pattern.Code) + } +} + +func TestDivUnsignedRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9AC20820}, + } + + for _, pattern := range usagePatterns { + t.Logf("udiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.DivUnsignedRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) 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 91c8800..bc058f6 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -21,6 +21,7 @@ type ( Compare rr CompareNumber rn Divide rrr + DivideSigned rrr Jump struct { Label string Condition token.Kind @@ -43,9 +44,10 @@ type ( Scale bool Length byte } - Modulo rrr - Move rr - MoveLabel struct { + Modulo rrr + ModuloSigned rrr + Move rr + MoveLabel struct { Label string Destination cpu.Register } diff --git a/src/asm/compilerARM.go b/src/asm/compilerARM.go index 0ed2c68..d084bf7 100644 --- a/src/asm/compilerARM.go +++ b/src/asm/compilerARM.go @@ -72,7 +72,9 @@ func (c *compilerARM) Compile(instr Instruction) { code, _ := arm.CompareRegisterNumber(instr.Destination, instr.Number) c.append(code) case *Divide: - c.append(arm.DivRegisterRegister(instr.Destination, instr.Source, instr.Operand)) + c.append(arm.DivUnsignedRegisterRegister(instr.Destination, instr.Source, instr.Operand)) + case *DivideSigned: + c.append(arm.DivSignedRegisterRegister(instr.Destination, instr.Source, instr.Operand)) case *Jump: c.append(arm.Jump(0)) patch := c.PatchLast4Bytes() @@ -143,7 +145,14 @@ func (c *compilerARM) Compile(instr Instruction) { panic("modulo destination register cannot be equal to the source or operand register") } - c.append(arm.DivRegisterRegister(instr.Destination, instr.Source, instr.Operand)) + c.append(arm.DivUnsignedRegisterRegister(instr.Destination, instr.Source, instr.Operand)) + c.append(arm.MultiplySubtract(instr.Destination, instr.Destination, instr.Operand, instr.Source)) + case *ModuloSigned: + if instr.Destination == instr.Source || instr.Destination == instr.Operand { + panic("modulo destination register cannot be equal to the source or operand register") + } + + c.append(arm.DivSignedRegisterRegister(instr.Destination, instr.Source, instr.Operand)) c.append(arm.MultiplySubtract(instr.Destination, instr.Destination, instr.Operand, instr.Source)) case *MoveLabel: c.append(arm.LoadAddress(instr.Destination, 0)) diff --git a/src/asm/compilerX86.go b/src/asm/compilerX86.go index 382532e..82739eb 100644 --- a/src/asm/compilerX86.go +++ b/src/asm/compilerX86.go @@ -98,7 +98,22 @@ func (c *compilerX86) Compile(instr Instruction) { } c.code = x86.ExtendR0ToR2(c.code) - c.code = x86.DivRegister(c.code, instr.Operand) + c.code = x86.DivUnsignedRegister(c.code, instr.Operand) + + if instr.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, instr.Destination, x86.R0) + } + case *DivideSigned: + if instr.Operand == x86.R2 { + panic("divisor register cannot be R2") + } + + if instr.Source != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, instr.Source) + } + + c.code = x86.ExtendR0ToR2(c.code) + c.code = x86.DivSignedRegister(c.code, instr.Operand) if instr.Destination != x86.R0 { c.code = x86.MoveRegisterRegister(c.code, instr.Destination, x86.R0) @@ -210,7 +225,22 @@ func (c *compilerX86) Compile(instr Instruction) { } c.code = x86.ExtendR0ToR2(c.code) - c.code = x86.DivRegister(c.code, instr.Operand) + c.code = x86.DivUnsignedRegister(c.code, instr.Operand) + + if instr.Destination != x86.R2 { + c.code = x86.MoveRegisterRegister(c.code, instr.Destination, x86.R2) + } + case *ModuloSigned: + if instr.Operand == x86.R0 { + panic("modulo operand register cannot be R0") + } + + if instr.Source != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, instr.Source) + } + + c.code = x86.ExtendR0ToR2(c.code) + c.code = x86.DivSignedRegister(c.code, instr.Operand) if instr.Destination != x86.R2 { c.code = x86.MoveRegisterRegister(c.code, instr.Destination, x86.R2) diff --git a/src/codegen/executeBinaryOp.go b/src/codegen/executeBinaryOp.go index 067071f..8b760dc 100644 --- a/src/codegen/executeBinaryOp.go +++ b/src/codegen/executeBinaryOp.go @@ -100,11 +100,19 @@ func (f *Function) executeBinaryOp(step *Step, instr *ssa.BinaryOp) { }) case token.Div: - f.Assembler.Append(&asm.Divide{ - Destination: step.Register, - Source: left.Register, - Operand: right.Register, - }) + if types.IsUnsigned(left.Value.Type()) { + f.Assembler.Append(&asm.Divide{ + Destination: step.Register, + Source: left.Register, + Operand: right.Register, + }) + } else { + f.Assembler.Append(&asm.DivideSigned{ + Destination: step.Register, + Source: left.Register, + Operand: right.Register, + }) + } case token.Mul: f.Assembler.Append(&asm.Multiply{ @@ -121,11 +129,19 @@ func (f *Function) executeBinaryOp(step *Step, instr *ssa.BinaryOp) { }) case token.Mod: - f.Assembler.Append(&asm.Modulo{ - Destination: step.Register, - Source: left.Register, - Operand: right.Register, - }) + if types.IsUnsigned(left.Value.Type()) { + f.Assembler.Append(&asm.Modulo{ + Destination: step.Register, + Source: left.Register, + Operand: right.Register, + }) + } else { + f.Assembler.Append(&asm.ModuloSigned{ + Destination: step.Register, + Source: left.Register, + Operand: right.Register, + }) + } case token.And: f.Assembler.Append(&asm.And{ diff --git a/src/verbose/ASM.go b/src/verbose/ASM.go index 6b42696..85c6311 100644 --- a/src/verbose/ASM.go +++ b/src/verbose/ASM.go @@ -84,6 +84,13 @@ func printAssembly(f *core.Function) { other.Print(", ") imm.Print(instr.Number) case *asm.Divide: + mnemonic.Print(" udiv ") + register.Print(instr.Destination) + other.Print(", ") + register.Print(instr.Source) + other.Print(", ") + register.Print(instr.Operand) + case *asm.DivideSigned: mnemonic.Print(" div ") register.Print(instr.Destination) other.Print(", ") @@ -140,6 +147,13 @@ func printAssembly(f *core.Function) { } other.Print("]") case *asm.Modulo: + mnemonic.Print(" umod ") + register.Print(instr.Destination) + other.Print(", ") + register.Print(instr.Source) + other.Print(", ") + register.Print(instr.Operand) + case *asm.ModuloSigned: mnemonic.Print(" mod ") register.Print(instr.Destination) other.Print(", ") @@ -210,28 +224,28 @@ func printAssembly(f *core.Function) { other.Print(", ") imm.Print(instr.Number) case *asm.ShiftRight: - mnemonic.Print(" shift >> ") + mnemonic.Print(" ushift >> ") register.Print(instr.Destination) other.Print(", ") register.Print(instr.Source) other.Print(", ") register.Print(instr.Operand) case *asm.ShiftRightNumber: - mnemonic.Print(" shift >> ") + mnemonic.Print(" ushift >> ") register.Print(instr.Destination) other.Print(", ") register.Print(instr.Source) other.Print(", ") imm.Print(instr.Number) case *asm.ShiftRightSigned: - mnemonic.Print(" signshift >> ") + mnemonic.Print(" shift >> ") register.Print(instr.Destination) other.Print(", ") register.Print(instr.Source) other.Print(", ") register.Print(instr.Operand) case *asm.ShiftRightSignedNumber: - mnemonic.Print(" signshift >> ") + mnemonic.Print(" shift >> ") register.Print(instr.Destination) other.Print(", ") register.Print(instr.Source) diff --git a/src/x86/Div.go b/src/x86/Div.go index 6919c93..4dc5c2f 100644 --- a/src/x86/Div.go +++ b/src/x86/Div.go @@ -2,8 +2,18 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" -// DivRegister divides RDX:RAX by the value in the register. -func DivRegister(code []byte, divisor cpu.Register) []byte { +// DivSignedRegister performs signed division in RDX:RAX by the value in the register. +func DivSignedRegister(code []byte, divisor cpu.Register) []byte { + return div(code, divisor, 0xF8) +} + +// DivUnsignedRegister performs unsigned division in RDX:RAX by the value in the register. +func DivUnsignedRegister(code []byte, divisor cpu.Register) []byte { + return div(code, divisor, 0xF0) +} + +// div implements the encoding for the division operation. +func div(code []byte, divisor cpu.Register, opCode byte) []byte { rex := byte(0x48) if divisor > 0b111 { @@ -15,6 +25,6 @@ func DivRegister(code []byte, divisor cpu.Register) []byte { code, rex, 0xF7, - 0xF8+byte(divisor), + opCode+byte(divisor), ) } \ No newline at end of file diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go index 1926e13..ad5a125 100644 --- a/src/x86/Div_test.go +++ b/src/x86/Div_test.go @@ -8,7 +8,7 @@ import ( "git.urbach.dev/go/assert" ) -func TestDivRegister(t *testing.T) { +func TestDivSignedRegister(t *testing.T) { usagePatterns := []struct { Register cpu.Register Code []byte @@ -33,7 +33,37 @@ func TestDivRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("idiv %s", pattern.Register) - code := x86.DivRegister(nil, pattern.Register) + code := x86.DivSignedRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestDivUnsignedRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.R0, []byte{0x48, 0xF7, 0xF0}}, + {x86.R1, []byte{0x48, 0xF7, 0xF1}}, + {x86.R2, []byte{0x48, 0xF7, 0xF2}}, + {x86.R3, []byte{0x48, 0xF7, 0xF3}}, + {x86.SP, []byte{0x48, 0xF7, 0xF4}}, + {x86.R5, []byte{0x48, 0xF7, 0xF5}}, + {x86.R6, []byte{0x48, 0xF7, 0xF6}}, + {x86.R7, []byte{0x48, 0xF7, 0xF7}}, + {x86.R8, []byte{0x49, 0xF7, 0xF0}}, + {x86.R9, []byte{0x49, 0xF7, 0xF1}}, + {x86.R10, []byte{0x49, 0xF7, 0xF2}}, + {x86.R11, []byte{0x49, 0xF7, 0xF3}}, + {x86.R12, []byte{0x49, 0xF7, 0xF4}}, + {x86.R13, []byte{0x49, 0xF7, 0xF5}}, + {x86.R14, []byte{0x49, 0xF7, 0xF6}}, + {x86.R15, []byte{0x49, 0xF7, 0xF7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("div %s", pattern.Register) + code := x86.DivUnsignedRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } \ No newline at end of file diff --git a/tests/tests_test.go b/tests/tests_test.go index 61ce322..252dbbf 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -37,6 +37,7 @@ var tests = []run{ {"limits", "", "127\n-128\n32767\n-32768\n2147483647\n-2147483648\n9223372036854775807\n-9223372036854775808\n", 0}, {"overflow", "", "", 0}, {"underflow", "", "", 0}, + {"unsigned", "", "", 0}, {"param", "", "", 0}, {"param-multi", "", "", 0}, {"param-order", "", "", 0}, diff --git a/tests/unsigned.q b/tests/unsigned.q new file mode 100644 index 0000000..7aef911 --- /dev/null +++ b/tests/unsigned.q @@ -0,0 +1,6 @@ +main() { + x := 0xFFFF as uint16 + assert x / 0x100 == 0xFF + assert x % 0x100 == 0xFF + assert x >> 8 == 0xFF +} \ No newline at end of file -- 2.51.2