Implemented data labels
This commit is contained in:
parent
7b18056006
commit
9df899cb52
@ -1,17 +1,11 @@
|
||||
main() {
|
||||
x := f(1) + f(2) + f(3)
|
||||
|
||||
if x != 9 {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
exit(0)
|
||||
print("Hello", 5)
|
||||
}
|
||||
|
||||
exit(code) {
|
||||
syscall(60, code)
|
||||
print(address, length) {
|
||||
write(1, address, length)
|
||||
}
|
||||
|
||||
f(x) {
|
||||
return x + 1
|
||||
write(fd, address, length) {
|
||||
syscall(1, fd, address, length)
|
||||
}
|
@ -1,268 +1,15 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
)
|
||||
import "maps"
|
||||
|
||||
// Assembler contains a list of instructions.
|
||||
type Assembler struct {
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
// 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
|
||||
Data map[string][]byte
|
||||
}
|
||||
|
||||
// Merge combines the contents of this assembler with another one.
|
||||
func (a *Assembler) Merge(b Assembler) {
|
||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||
}
|
||||
|
||||
// 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
|
||||
maps.Copy(a.Data, b.Data)
|
||||
}
|
||||
|
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
|
||||
|
||||
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.
|
||||
func (a *Assembler) Comment(text string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
|
@ -9,3 +9,13 @@ type Label struct {
|
||||
func (data *Label) String() string {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
|
||||
case token.String:
|
||||
if operation.Text() == "=" {
|
||||
return f.TokenToRegister(operand, register)
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
assembler: asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, 32),
|
||||
Data: map[string][]byte{},
|
||||
},
|
||||
cpu: cpu.CPU{
|
||||
All: x64.AllRegisters,
|
||||
|
@ -53,6 +53,20 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int)
|
||||
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) {
|
||||
if mnemonic == asm.MOVE && a == b {
|
||||
return
|
||||
|
@ -25,6 +25,7 @@ func (r *Result) finalize() ([]byte, []byte) {
|
||||
// a return address on the stack, which allows return statements in `main`.
|
||||
final := asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
||||
Data: map[string][]byte{},
|
||||
}
|
||||
|
||||
final.Call("main")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"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
|
||||
|
||||
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:
|
||||
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.
|
||||
type counter struct {
|
||||
loop int
|
||||
branch int
|
||||
data int
|
||||
loop int
|
||||
subBranch int
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package elf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
@ -10,13 +11,19 @@ import (
|
||||
// ELF represents an ELF file.
|
||||
type ELF struct {
|
||||
Header
|
||||
ProgramHeader
|
||||
CodeHeader ProgramHeader
|
||||
PadCode []byte
|
||||
Code []byte
|
||||
PadData []byte
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// New creates a new ELF binary.
|
||||
func New(code []byte, data []byte) *ELF {
|
||||
dataOffset := config.CodeOffset + int64(len(code))
|
||||
dataPadding := Padding(dataOffset, config.Align)
|
||||
dataOffset += dataPadding
|
||||
|
||||
elf := &ELF{
|
||||
Header: Header{
|
||||
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
||||
@ -39,9 +46,9 @@ func New(code []byte, data []byte) *ELF {
|
||||
SectionHeaderEntryCount: 0,
|
||||
SectionNameStringTableIndex: 0,
|
||||
},
|
||||
ProgramHeader: ProgramHeader{
|
||||
CodeHeader: ProgramHeader{
|
||||
Type: ProgramTypeLOAD,
|
||||
Flags: ProgramFlagsExecutable,
|
||||
Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
|
||||
Offset: config.CodeOffset,
|
||||
VirtualAddress: config.BaseAddress + config.CodeOffset,
|
||||
PhysicalAddress: config.BaseAddress + config.CodeOffset,
|
||||
@ -49,18 +56,25 @@ func New(code []byte, data []byte) *ELF {
|
||||
SizeInMemory: int64(len(code)),
|
||||
Align: config.Align,
|
||||
},
|
||||
PadCode: bytes.Repeat([]byte{0}, 8),
|
||||
Code: code,
|
||||
PadData: bytes.Repeat([]byte{0}, int(dataPadding)),
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return elf
|
||||
}
|
||||
|
||||
func Padding(n int64, align int64) int64 {
|
||||
return align - (n % align)
|
||||
}
|
||||
|
||||
// Write writes the ELF64 format to the given writer.
|
||||
func (elf *ELF) Write(writer io.Writer) {
|
||||
binary.Write(writer, binary.LittleEndian, &elf.Header)
|
||||
binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader)
|
||||
writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
|
||||
binary.Write(writer, binary.LittleEndian, &elf.CodeHeader)
|
||||
writer.Write(elf.PadCode)
|
||||
writer.Write(elf.Code)
|
||||
writer.Write(elf.PadData)
|
||||
writer.Write(elf.Data)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ var examples = []struct {
|
||||
ExpectedOutput string
|
||||
ExpectedExitCode int
|
||||
}{
|
||||
{"hello", "", 0},
|
||||
{"hello", "Hello", 0},
|
||||
{"fibonacci", "", 55},
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user