Implemented data labels
This commit is contained in:
parent
7b18056006
commit
9df899cb52
@ -1,17 +1,11 @@
|
|||||||
main() {
|
main() {
|
||||||
x := f(1) + f(2) + f(3)
|
print("Hello", 5)
|
||||||
|
|
||||||
if x != 9 {
|
|
||||||
exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(0)
|
print(address, length) {
|
||||||
|
write(1, address, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(code) {
|
write(fd, address, length) {
|
||||||
syscall(60, code)
|
syscall(1, fd, address, length)
|
||||||
}
|
|
||||||
|
|
||||||
f(x) {
|
|
||||||
return x + 1
|
|
||||||
}
|
}
|
@ -1,268 +1,15 @@
|
|||||||
package asm
|
package asm
|
||||||
|
|
||||||
import (
|
import "maps"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assembler contains a list of instructions.
|
// Assembler contains a list of instructions.
|
||||||
type Assembler struct {
|
type Assembler struct {
|
||||||
Instructions []Instruction
|
Instructions []Instruction
|
||||||
}
|
Data map[string][]byte
|
||||||
|
|
||||||
// Finalize generates the final machine code.
|
|
||||||
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{}
|
|
||||||
|
|
||||||
for _, x := range a.Instructions {
|
|
||||||
switch x.Mnemonic {
|
|
||||||
case ADD:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
code = x64.AddRegisterNumber(code, operands.Register, operands.Number)
|
|
||||||
case *RegisterRegister:
|
|
||||||
code = x64.AddRegisterRegister(code, operands.Destination, operands.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
case SUB:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
code = x64.SubRegisterNumber(code, operands.Register, operands.Number)
|
|
||||||
case *RegisterRegister:
|
|
||||||
code = x64.SubRegisterRegister(code, operands.Destination, operands.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
case MUL:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
code = x64.MulRegisterNumber(code, operands.Register, operands.Number)
|
|
||||||
case *RegisterRegister:
|
|
||||||
code = x64.MulRegisterRegister(code, operands.Destination, operands.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DIV:
|
|
||||||
code = divide(code, x.Data)
|
|
||||||
|
|
||||||
case CALL:
|
|
||||||
code = x64.Call(code, 0x00_00_00_00)
|
|
||||||
size := 4
|
|
||||||
label := x.Data.(*Label)
|
|
||||||
nextInstructionAddress := Address(len(code))
|
|
||||||
|
|
||||||
pointers = append(pointers, &Pointer{
|
|
||||||
Position: Address(len(code) - size),
|
|
||||||
OpSize: 1,
|
|
||||||
Size: uint8(size),
|
|
||||||
Resolve: func() Address {
|
|
||||||
destination, exists := labels[label.Name]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
panic("unknown call label")
|
|
||||||
}
|
|
||||||
|
|
||||||
distance := destination - nextInstructionAddress
|
|
||||||
return Address(distance)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
case COMMENT:
|
|
||||||
continue
|
|
||||||
|
|
||||||
case COMPARE:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
code = x64.CompareRegisterNumber(code, operands.Register, operands.Number)
|
|
||||||
case *RegisterRegister:
|
|
||||||
code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
case JE, JNE, JG, JGE, JL, JLE, JUMP:
|
|
||||||
switch x.Mnemonic {
|
|
||||||
case JE:
|
|
||||||
code = x64.Jump8IfEqual(code, 0x00)
|
|
||||||
case JNE:
|
|
||||||
code = x64.Jump8IfNotEqual(code, 0x00)
|
|
||||||
case JG:
|
|
||||||
code = x64.Jump8IfGreater(code, 0x00)
|
|
||||||
case JGE:
|
|
||||||
code = x64.Jump8IfGreaterOrEqual(code, 0x00)
|
|
||||||
case JL:
|
|
||||||
code = x64.Jump8IfLess(code, 0x00)
|
|
||||||
case JLE:
|
|
||||||
code = x64.Jump8IfLessOrEqual(code, 0x00)
|
|
||||||
case JUMP:
|
|
||||||
code = x64.Jump8(code, 0x00)
|
|
||||||
}
|
|
||||||
|
|
||||||
size := 1
|
|
||||||
label := x.Data.(*Label)
|
|
||||||
nextInstructionAddress := Address(len(code))
|
|
||||||
|
|
||||||
pointers = append(pointers, &Pointer{
|
|
||||||
Position: Address(len(code) - size),
|
|
||||||
OpSize: 1,
|
|
||||||
Size: uint8(size),
|
|
||||||
Resolve: func() Address {
|
|
||||||
destination, exists := labels[label.Name]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
panic("unknown jump label")
|
|
||||||
}
|
|
||||||
|
|
||||||
distance := destination - nextInstructionAddress
|
|
||||||
return Address(distance)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
case LABEL:
|
|
||||||
labels[x.Data.(*Label).Name] = Address(len(code))
|
|
||||||
|
|
||||||
case MOVE:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number))
|
|
||||||
|
|
||||||
case *RegisterRegister:
|
|
||||||
code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
case POP:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *Register:
|
|
||||||
code = x64.PopRegister(code, operands.Register)
|
|
||||||
}
|
|
||||||
|
|
||||||
case PUSH:
|
|
||||||
switch operands := x.Data.(type) {
|
|
||||||
case *Register:
|
|
||||||
code = x64.PushRegister(code, operands.Register)
|
|
||||||
}
|
|
||||||
|
|
||||||
case RETURN:
|
|
||||||
code = x64.Return(code)
|
|
||||||
|
|
||||||
case SYSCALL:
|
|
||||||
code = x64.Syscall(code)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("Unknown mnemonic: " + x.Mnemonic.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
binary.LittleEndian.PutUint16(slice, uint16(address))
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
binary.LittleEndian.PutUint32(slice, uint32(address))
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
binary.LittleEndian.PutUint64(slice, uint64(address))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return code, data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge combines the contents of this assembler with another one.
|
// Merge combines the contents of this assembler with another one.
|
||||||
func (a *Assembler) Merge(b Assembler) {
|
func (a *Assembler) Merge(b Assembler) {
|
||||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||||
}
|
maps.Copy(a.Data, b.Data)
|
||||||
|
|
||||||
// divide implements the division on x64 machines.
|
|
||||||
func divide(code []byte, data any) []byte {
|
|
||||||
code = x64.PushRegister(code, x64.RDX)
|
|
||||||
|
|
||||||
switch operands := data.(type) {
|
|
||||||
case *RegisterNumber:
|
|
||||||
if operands.Register == x64.RAX {
|
|
||||||
code = x64.PushRegister(code, x64.RCX)
|
|
||||||
code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number))
|
|
||||||
code = x64.ExtendRAXToRDX(code)
|
|
||||||
code = x64.DivRegister(code, x64.RCX)
|
|
||||||
code = x64.PopRegister(code, x64.RCX)
|
|
||||||
} else {
|
|
||||||
code = x64.PushRegister(code, x64.RAX)
|
|
||||||
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register)
|
|
||||||
code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number))
|
|
||||||
code = x64.ExtendRAXToRDX(code)
|
|
||||||
code = x64.DivRegister(code, operands.Register)
|
|
||||||
code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX)
|
|
||||||
code = x64.PopRegister(code, x64.RAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *RegisterRegister:
|
|
||||||
if operands.Destination == x64.RAX {
|
|
||||||
code = x64.ExtendRAXToRDX(code)
|
|
||||||
code = x64.DivRegister(code, operands.Source)
|
|
||||||
} else {
|
|
||||||
code = x64.PushRegister(code, x64.RAX)
|
|
||||||
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination)
|
|
||||||
code = x64.ExtendRAXToRDX(code)
|
|
||||||
code = x64.DivRegister(code, operands.Source)
|
|
||||||
code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX)
|
|
||||||
code = x64.PopRegister(code, x64.RAX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code = x64.PopRegister(code, x64.RDX)
|
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
248
src/build/asm/Finalize.go
Normal file
248
src/build/asm/Finalize.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/elf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Finalize generates the final machine code.
|
||||||
|
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{}
|
||||||
|
|
||||||
|
for _, x := range a.Instructions {
|
||||||
|
switch x.Mnemonic {
|
||||||
|
case ADD:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
code = x64.AddRegisterNumber(code, operands.Register, operands.Number)
|
||||||
|
case *RegisterRegister:
|
||||||
|
code = x64.AddRegisterRegister(code, operands.Destination, operands.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
case SUB:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
code = x64.SubRegisterNumber(code, operands.Register, operands.Number)
|
||||||
|
case *RegisterRegister:
|
||||||
|
code = x64.SubRegisterRegister(code, operands.Destination, operands.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
case MUL:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
code = x64.MulRegisterNumber(code, operands.Register, operands.Number)
|
||||||
|
case *RegisterRegister:
|
||||||
|
code = x64.MulRegisterRegister(code, operands.Destination, operands.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIV:
|
||||||
|
code = divide(code, x.Data)
|
||||||
|
|
||||||
|
case CALL:
|
||||||
|
code = x64.Call(code, 0x00_00_00_00)
|
||||||
|
size := 4
|
||||||
|
label := x.Data.(*Label)
|
||||||
|
nextInstructionAddress := Address(len(code))
|
||||||
|
|
||||||
|
pointers = append(pointers, &Pointer{
|
||||||
|
Position: Address(len(code) - size),
|
||||||
|
OpSize: 1,
|
||||||
|
Size: uint8(size),
|
||||||
|
Resolve: func() Address {
|
||||||
|
destination, exists := labels[label.Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown call label")
|
||||||
|
}
|
||||||
|
|
||||||
|
distance := destination - nextInstructionAddress
|
||||||
|
return Address(distance)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
case COMMENT:
|
||||||
|
continue
|
||||||
|
|
||||||
|
case COMPARE:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
code = x64.CompareRegisterNumber(code, operands.Register, operands.Number)
|
||||||
|
case *RegisterRegister:
|
||||||
|
code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
case JE, JNE, JG, JGE, JL, JLE, JUMP:
|
||||||
|
switch x.Mnemonic {
|
||||||
|
case JE:
|
||||||
|
code = x64.Jump8IfEqual(code, 0x00)
|
||||||
|
case JNE:
|
||||||
|
code = x64.Jump8IfNotEqual(code, 0x00)
|
||||||
|
case JG:
|
||||||
|
code = x64.Jump8IfGreater(code, 0x00)
|
||||||
|
case JGE:
|
||||||
|
code = x64.Jump8IfGreaterOrEqual(code, 0x00)
|
||||||
|
case JL:
|
||||||
|
code = x64.Jump8IfLess(code, 0x00)
|
||||||
|
case JLE:
|
||||||
|
code = x64.Jump8IfLessOrEqual(code, 0x00)
|
||||||
|
case JUMP:
|
||||||
|
code = x64.Jump8(code, 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := 1
|
||||||
|
label := x.Data.(*Label)
|
||||||
|
nextInstructionAddress := Address(len(code))
|
||||||
|
|
||||||
|
pointers = append(pointers, &Pointer{
|
||||||
|
Position: Address(len(code) - size),
|
||||||
|
OpSize: 1,
|
||||||
|
Size: uint8(size),
|
||||||
|
Resolve: func() Address {
|
||||||
|
destination, exists := labels[label.Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown jump label")
|
||||||
|
}
|
||||||
|
|
||||||
|
distance := destination - nextInstructionAddress
|
||||||
|
return Address(distance)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
case LABEL:
|
||||||
|
labels[x.Data.(*Label).Name] = Address(len(code))
|
||||||
|
|
||||||
|
case MOVE:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number))
|
||||||
|
|
||||||
|
case *RegisterRegister:
|
||||||
|
code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source)
|
||||||
|
|
||||||
|
case *RegisterLabel:
|
||||||
|
start := len(code)
|
||||||
|
code = x64.MoveRegisterNumber32(code, operands.Register, 0x00_00_00_00)
|
||||||
|
size := 4
|
||||||
|
opSize := len(code) - size - start
|
||||||
|
regLabel := x.Data.(*RegisterLabel)
|
||||||
|
|
||||||
|
pointers = append(pointers, &Pointer{
|
||||||
|
Position: Address(len(code) - size),
|
||||||
|
OpSize: uint8(opSize),
|
||||||
|
Size: uint8(size),
|
||||||
|
Resolve: func() Address {
|
||||||
|
destination, exists := labels[regLabel.Label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown label")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Address(destination)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case POP:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *Register:
|
||||||
|
code = x64.PopRegister(code, operands.Register)
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUSH:
|
||||||
|
switch operands := x.Data.(type) {
|
||||||
|
case *Register:
|
||||||
|
code = x64.PushRegister(code, operands.Register)
|
||||||
|
}
|
||||||
|
|
||||||
|
case RETURN:
|
||||||
|
code = x64.Return(code)
|
||||||
|
|
||||||
|
case SYSCALL:
|
||||||
|
code = x64.Syscall(code)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("Unknown mnemonic: " + x.Mnemonic.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
|
||||||
|
dataStart += int32(elf.Padding(int64(dataStart), config.Align))
|
||||||
|
|
||||||
|
for label, slice := range a.Data {
|
||||||
|
labels[label] = dataStart + Address(len(data))
|
||||||
|
data = append(data, slice...)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
binary.LittleEndian.PutUint16(slice, uint16(address))
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
binary.LittleEndian.PutUint32(slice, uint32(address))
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
binary.LittleEndian.PutUint64(slice, uint64(address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code, data
|
||||||
|
}
|
@ -1,53 +1,5 @@
|
|||||||
package asm
|
package asm
|
||||||
|
|
||||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
|
||||||
|
|
||||||
// RegisterNumber adds an instruction with a register and a number.
|
|
||||||
func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) {
|
|
||||||
a.Instructions = append(a.Instructions, Instruction{
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Data: &RegisterNumber{
|
|
||||||
Register: reg,
|
|
||||||
Number: number,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterRegister adds an instruction using two registers.
|
|
||||||
func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) {
|
|
||||||
if a.unnecessary(mnemonic, left, right) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Instructions = append(a.Instructions, Instruction{
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Data: &RegisterRegister{
|
|
||||||
Destination: left,
|
|
||||||
Source: right,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register adds an instruction using a single register.
|
|
||||||
func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) {
|
|
||||||
a.Instructions = append(a.Instructions, Instruction{
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Data: &Register{
|
|
||||||
Register: register,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Label adds an instruction using a label.
|
|
||||||
func (a *Assembler) Label(mnemonic Mnemonic, name string) {
|
|
||||||
a.Instructions = append(a.Instructions, Instruction{
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Data: &Label{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comment adds a comment at the current position.
|
// Comment adds a comment at the current position.
|
||||||
func (a *Assembler) Comment(text string) {
|
func (a *Assembler) Comment(text string) {
|
||||||
a.Instructions = append(a.Instructions, Instruction{
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
@ -9,3 +9,13 @@ type Label struct {
|
|||||||
func (data *Label) String() string {
|
func (data *Label) String() string {
|
||||||
return data.Name
|
return data.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Label adds an instruction using a label.
|
||||||
|
func (a *Assembler) Label(mnemonic Mnemonic, name string) {
|
||||||
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
Data: &Label{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -13,3 +13,13 @@ type Register struct {
|
|||||||
func (data *Register) String() string {
|
func (data *Register) String() string {
|
||||||
return data.Register.String()
|
return data.Register.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register adds an instruction using a single register.
|
||||||
|
func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) {
|
||||||
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
Data: &Register{
|
||||||
|
Register: register,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
29
src/build/asm/RegisterLabel.go
Normal file
29
src/build/asm/RegisterLabel.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterLabel operates with a register and a label.
|
||||||
|
type RegisterLabel struct {
|
||||||
|
Register cpu.Register
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a human readable version.
|
||||||
|
func (data *RegisterLabel) String() string {
|
||||||
|
return fmt.Sprintf("%s, %s", data.Register, data.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterLabel adds an instruction with a register and a label.
|
||||||
|
func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) {
|
||||||
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
Data: &RegisterLabel{
|
||||||
|
Register: reg,
|
||||||
|
Label: label,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -16,3 +16,14 @@ type RegisterNumber struct {
|
|||||||
func (data *RegisterNumber) String() string {
|
func (data *RegisterNumber) String() string {
|
||||||
return fmt.Sprintf("%s, %d", data.Register, data.Number)
|
return fmt.Sprintf("%s, %d", data.Register, data.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterNumber adds an instruction with a register and a number.
|
||||||
|
func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) {
|
||||||
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
Data: &RegisterNumber{
|
||||||
|
Register: reg,
|
||||||
|
Number: number,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -16,3 +16,18 @@ type RegisterRegister struct {
|
|||||||
func (data *RegisterRegister) String() string {
|
func (data *RegisterRegister) String() string {
|
||||||
return fmt.Sprintf("%s, %s", data.Destination, data.Source)
|
return fmt.Sprintf("%s, %s", data.Destination, data.Source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterRegister adds an instruction using two registers.
|
||||||
|
func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) {
|
||||||
|
if a.unnecessary(mnemonic, left, right) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Instructions = append(a.Instructions, Instruction{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
Data: &RegisterRegister{
|
||||||
|
Destination: left,
|
||||||
|
Source: right,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
43
src/build/asm/divide.go
Normal file
43
src/build/asm/divide.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package asm
|
||||||
|
|
||||||
|
import "git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||||
|
|
||||||
|
// divide implements the division on x64 machines.
|
||||||
|
func divide(code []byte, data any) []byte {
|
||||||
|
code = x64.PushRegister(code, x64.RDX)
|
||||||
|
|
||||||
|
switch operands := data.(type) {
|
||||||
|
case *RegisterNumber:
|
||||||
|
if operands.Register == x64.RAX {
|
||||||
|
code = x64.PushRegister(code, x64.RCX)
|
||||||
|
code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number))
|
||||||
|
code = x64.ExtendRAXToRDX(code)
|
||||||
|
code = x64.DivRegister(code, x64.RCX)
|
||||||
|
code = x64.PopRegister(code, x64.RCX)
|
||||||
|
} else {
|
||||||
|
code = x64.PushRegister(code, x64.RAX)
|
||||||
|
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register)
|
||||||
|
code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number))
|
||||||
|
code = x64.ExtendRAXToRDX(code)
|
||||||
|
code = x64.DivRegister(code, operands.Register)
|
||||||
|
code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX)
|
||||||
|
code = x64.PopRegister(code, x64.RAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *RegisterRegister:
|
||||||
|
if operands.Destination == x64.RAX {
|
||||||
|
code = x64.ExtendRAXToRDX(code)
|
||||||
|
code = x64.DivRegister(code, operands.Source)
|
||||||
|
} else {
|
||||||
|
code = x64.PushRegister(code, x64.RAX)
|
||||||
|
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination)
|
||||||
|
code = x64.ExtendRAXToRDX(code)
|
||||||
|
code = x64.DivRegister(code, operands.Source)
|
||||||
|
code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX)
|
||||||
|
code = x64.PopRegister(code, x64.RAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code = x64.PopRegister(code, x64.RDX)
|
||||||
|
return code
|
||||||
|
}
|
@ -31,6 +31,11 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope
|
|||||||
}
|
}
|
||||||
|
|
||||||
return f.ExecuteRegisterNumber(operation, register, number)
|
return f.ExecuteRegisterNumber(operation, register, number)
|
||||||
|
|
||||||
|
case token.String:
|
||||||
|
if operation.Text() == "=" {
|
||||||
|
return f.TokenToRegister(operand, register)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
||||||
|
@ -27,6 +27,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
|
|||||||
state: state{
|
state: state{
|
||||||
assembler: asm.Assembler{
|
assembler: asm.Assembler{
|
||||||
Instructions: make([]asm.Instruction, 0, 32),
|
Instructions: make([]asm.Instruction, 0, 32),
|
||||||
|
Data: map[string][]byte{},
|
||||||
},
|
},
|
||||||
cpu: cpu.CPU{
|
cpu: cpu.CPU{
|
||||||
All: x64.AllRegisters,
|
All: x64.AllRegisters,
|
||||||
|
@ -53,6 +53,20 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int)
|
|||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) {
|
||||||
|
if f.cpu.IsUsed(register) && isDestructive(mnemonic) {
|
||||||
|
f.SaveRegister(register)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.assembler.RegisterLabel(mnemonic, register, label)
|
||||||
|
|
||||||
|
if mnemonic == asm.MOVE {
|
||||||
|
f.cpu.Use(register)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.postInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) {
|
func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) {
|
||||||
if mnemonic == asm.MOVE && a == b {
|
if mnemonic == asm.MOVE && a == b {
|
||||||
return
|
return
|
||||||
|
@ -25,6 +25,7 @@ func (r *Result) finalize() ([]byte, []byte) {
|
|||||||
// a return address on the stack, which allows return statements in `main`.
|
// a return address on the stack, which allows return statements in `main`.
|
||||||
final := asm.Assembler{
|
final := asm.Assembler{
|
||||||
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
||||||
|
Data: map[string][]byte{},
|
||||||
}
|
}
|
||||||
|
|
||||||
final.Call("main")
|
final.Call("main")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
@ -37,7 +38,12 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
|||||||
return nil
|
return nil
|
||||||
|
|
||||||
case token.String:
|
case token.String:
|
||||||
return errors.New(errors.NotImplemented, f.File, t.Position)
|
value := t.Text()[1 : len(t.Bytes)-1]
|
||||||
|
label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data)
|
||||||
|
f.assembler.Data[label] = []byte(value)
|
||||||
|
f.RegisterLabel(asm.MOVE, register, label)
|
||||||
|
f.count.data++
|
||||||
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||||
|
@ -23,8 +23,9 @@ type state struct {
|
|||||||
|
|
||||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||||
type counter struct {
|
type counter struct {
|
||||||
loop int
|
|
||||||
branch int
|
branch int
|
||||||
|
data int
|
||||||
|
loop int
|
||||||
subBranch int
|
subBranch int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package elf
|
package elf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@ -10,13 +11,19 @@ import (
|
|||||||
// ELF represents an ELF file.
|
// ELF represents an ELF file.
|
||||||
type ELF struct {
|
type ELF struct {
|
||||||
Header
|
Header
|
||||||
ProgramHeader
|
CodeHeader ProgramHeader
|
||||||
|
PadCode []byte
|
||||||
Code []byte
|
Code []byte
|
||||||
|
PadData []byte
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new ELF binary.
|
// New creates a new ELF binary.
|
||||||
func New(code []byte, data []byte) *ELF {
|
func New(code []byte, data []byte) *ELF {
|
||||||
|
dataOffset := config.CodeOffset + int64(len(code))
|
||||||
|
dataPadding := Padding(dataOffset, config.Align)
|
||||||
|
dataOffset += dataPadding
|
||||||
|
|
||||||
elf := &ELF{
|
elf := &ELF{
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
||||||
@ -39,9 +46,9 @@ func New(code []byte, data []byte) *ELF {
|
|||||||
SectionHeaderEntryCount: 0,
|
SectionHeaderEntryCount: 0,
|
||||||
SectionNameStringTableIndex: 0,
|
SectionNameStringTableIndex: 0,
|
||||||
},
|
},
|
||||||
ProgramHeader: ProgramHeader{
|
CodeHeader: ProgramHeader{
|
||||||
Type: ProgramTypeLOAD,
|
Type: ProgramTypeLOAD,
|
||||||
Flags: ProgramFlagsExecutable,
|
Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
|
||||||
Offset: config.CodeOffset,
|
Offset: config.CodeOffset,
|
||||||
VirtualAddress: config.BaseAddress + config.CodeOffset,
|
VirtualAddress: config.BaseAddress + config.CodeOffset,
|
||||||
PhysicalAddress: config.BaseAddress + config.CodeOffset,
|
PhysicalAddress: config.BaseAddress + config.CodeOffset,
|
||||||
@ -49,18 +56,25 @@ func New(code []byte, data []byte) *ELF {
|
|||||||
SizeInMemory: int64(len(code)),
|
SizeInMemory: int64(len(code)),
|
||||||
Align: config.Align,
|
Align: config.Align,
|
||||||
},
|
},
|
||||||
|
PadCode: bytes.Repeat([]byte{0}, 8),
|
||||||
Code: code,
|
Code: code,
|
||||||
|
PadData: bytes.Repeat([]byte{0}, int(dataPadding)),
|
||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
return elf
|
return elf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Padding(n int64, align int64) int64 {
|
||||||
|
return align - (n % align)
|
||||||
|
}
|
||||||
|
|
||||||
// Write writes the ELF64 format to the given writer.
|
// Write writes the ELF64 format to the given writer.
|
||||||
func (elf *ELF) Write(writer io.Writer) {
|
func (elf *ELF) Write(writer io.Writer) {
|
||||||
binary.Write(writer, binary.LittleEndian, &elf.Header)
|
binary.Write(writer, binary.LittleEndian, &elf.Header)
|
||||||
binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader)
|
binary.Write(writer, binary.LittleEndian, &elf.CodeHeader)
|
||||||
writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
|
writer.Write(elf.PadCode)
|
||||||
writer.Write(elf.Code)
|
writer.Write(elf.Code)
|
||||||
|
writer.Write(elf.PadData)
|
||||||
writer.Write(elf.Data)
|
writer.Write(elf.Data)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ var examples = []struct {
|
|||||||
ExpectedOutput string
|
ExpectedOutput string
|
||||||
ExpectedExitCode int
|
ExpectedExitCode int
|
||||||
}{
|
}{
|
||||||
{"hello", "", 0},
|
{"hello", "Hello", 0},
|
||||||
{"fibonacci", "", 55},
|
{"fibonacci", "", 55},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user