From e24d9ebb501e4dbae83c3cc13c7a88ea517ddec4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 10 Jul 2024 15:01:46 +0200 Subject: [PATCH] Implemented 32-bit jumps --- examples/write/write.q | 13 --- src/build/arch/x64/Call.go | 2 +- src/build/arch/x64/Jump.go | 22 ++-- src/build/arch/x64/SizeOf.go | 2 +- src/build/arch/x64/regRegNum.go | 2 +- src/build/asm/Assembler.go | 58 +++++++++- src/build/asm/Pointer.go | 3 +- tests/examples_test.go | 1 - tests/programs/jump-near.q | 187 ++++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 10 files changed, 253 insertions(+), 38 deletions(-) delete mode 100644 examples/write/write.q create mode 100644 tests/programs/jump-near.q diff --git a/examples/write/write.q b/examples/write/write.q deleted file mode 100644 index 31b18d6..0000000 --- a/examples/write/write.q +++ /dev/null @@ -1,13 +0,0 @@ -main() { - address := 4194304 + 1 - length := 3 - print(address, length) -} - -print(address, length) { - write(1, address, length) -} - -write(fd, address, length) { - syscall(1, fd, address, length) -} \ No newline at end of file diff --git a/src/build/arch/x64/Call.go b/src/build/arch/x64/Call.go index 1ada6fd..1bbfc46 100644 --- a/src/build/arch/x64/Call.go +++ b/src/build/arch/x64/Call.go @@ -5,7 +5,7 @@ package x64 func Call(code []byte, address uint32) []byte { return append( code, - 0xe8, + 0xE8, byte(address), byte(address>>8), byte(address>>16), diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 86d2c48..1174f58 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,43 +3,35 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { - return jump8(code, 0xEB, address) + return append(code, 0xEB, byte(address)) } // JumpIfLess jumps if the result was less. func Jump8IfLess(code []byte, address int8) []byte { - return jump8(code, 0x7C, address) + return append(code, 0x7C, byte(address)) } // JumpIfLessOrEqual jumps if the result was less or equal. func Jump8IfLessOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7E, address) + return append(code, 0x7E, byte(address)) } // JumpIfGreater jumps if the result was greater. func Jump8IfGreater(code []byte, address int8) []byte { - return jump8(code, 0x7F, address) + return append(code, 0x7F, byte(address)) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7D, address) + return append(code, 0x7D, byte(address)) } // JumpIfEqual jumps if the result was equal. func Jump8IfEqual(code []byte, address int8) []byte { - return jump8(code, 0x74, address) + return append(code, 0x74, byte(address)) } // JumpIfNotEqual jumps if the result was not equal. func Jump8IfNotEqual(code []byte, address int8) []byte { - return jump8(code, 0x75, address) -} - -func jump8(code []byte, opCode byte, address int8) []byte { - return append( - code, - opCode, - byte(address), - ) + return append(code, 0x75, byte(address)) } diff --git a/src/build/arch/x64/SizeOf.go b/src/build/arch/x64/SizeOf.go index c4804ae..6c7293a 100644 --- a/src/build/arch/x64/SizeOf.go +++ b/src/build/arch/x64/SizeOf.go @@ -3,7 +3,7 @@ package x64 import "math" // SizeOf tells you how many bytes are needed to encode this number. -func SizeOf(number int) int { +func SizeOf(number int64) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 3fc6987..61ffefb 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -4,7 +4,7 @@ import "encoding/binary" // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(number) == 1 { + if SizeOf(int64(number)) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 18c536d..0854b8b 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -2,6 +2,7 @@ package asm import ( "encoding/binary" + "fmt" "git.akyoto.dev/cli/q/src/build/arch/x64" ) @@ -16,7 +17,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) labels := map[string]Address{} - pointers := []Pointer{} + pointers := []*Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { @@ -53,8 +54,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -101,8 +103,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -153,10 +156,55 @@ func (a Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] +restart: + for i, pointer := range pointers { address := pointer.Resolve() + if x64.SizeOf(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range pointers[i+1:] { + following.Position += offset + } + + code = append(left, jump...) + code = append(code, right...) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + switch pointer.Size { case 1: slice[0] = uint8(address) diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 4e33a63..bb4f9d1 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,7 +1,7 @@ package asm // Address represents a memory address. -type Address = uint32 +type Address = int32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. @@ -9,5 +9,6 @@ type Address = uint32 type Pointer struct { Resolve func() Address Position Address + OpSize uint8 Size uint8 } diff --git a/tests/examples_test.go b/tests/examples_test.go index 217050f..109734e 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,7 +11,6 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "", 0}, - {"write", "ELF", 0}, {"fibonacci", "", 55}, } diff --git a/tests/programs/jump-near.q b/tests/programs/jump-near.q new file mode 100644 index 0000000..7af145d --- /dev/null +++ b/tests/programs/jump-near.q @@ -0,0 +1,187 @@ +main() { + x := 10 + + if x == 0 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x != 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x > 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x < 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x >= 11 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x <= 9 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + exit(0) +} + +fail() { + exit(1) +} + +exit(code) { + syscall(60, code) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 0449ec9..93dbd50 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -30,6 +30,7 @@ var programs = []struct { {"branch-and", "", 0}, {"branch-or", "", 0}, {"branch-both", "", 0}, + {"jump-near", "", 0}, } func TestPrograms(t *testing.T) {