Skip to content

Commit

Permalink
boots: added support for jumps and labels
Browse files Browse the repository at this point in the history
  • Loading branch information
dinfuehr committed Sep 4, 2020
1 parent 58d441e commit 1851ade
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
11 changes: 11 additions & 0 deletions dora-boots/assembler.dora
Expand Up @@ -17,6 +17,17 @@ class AssemblerBuffer {
self.emitInt32((value >>> 32).toInt32());
}

fun patchUInt8(offset: Int64, value: UInt8) {
self.buffer.set(offset, value);
}

fun patchInt32(offset: Int64, value: Int32) {
self.buffer.set(offset, (value & 0xFFI).toUInt8());
self.buffer.set(offset + 1L, ((value >>> 8) & 0xFFI).toUInt8());
self.buffer.set(offset + 2L, ((value >>> 16) & 0xFFI).toUInt8());
self.buffer.set(offset + 3L, ((value >>> 24) & 0xFFI).toUInt8());
}

fun size() -> Int64 {
self.buffer.size()
}
Expand Down
171 changes: 171 additions & 0 deletions dora-boots/assembler_x64.dora
Expand Up @@ -44,8 +44,14 @@ class XmmRegister(let value: Int32) {
}
}

enum JumpDistance {
Near,
Far,
}

class AssemblerX64: Assembler {
let buffer: AssemblerBuffer = AssemblerBuffer();
let jumps: Vec[(Int64, Label, JumpDistance)] = Vec[(Int64, Label, JumpDistance)]();

fun createLabel() -> Label {
self.buffer.createLabel()
Expand Down Expand Up @@ -344,6 +350,46 @@ class AssemblerX64: Assembler {
self.emitModRmOpcode(0b100, reg);
}

fun jmp(dest: Label) {
if dest.isBound() {
// backwards jump
// rip = end of current instruction = pc + 2
let distance = dest.offset - (self.size() + 2L);
assert(distance <= -2L);

if distance >= -128L {
self.emitByte(0xEBY);
self.emitByte(distance.toUInt8());
} else {
let distance = dest.offset - (self.size() + 5L);
assert(distance.toInt32().toInt64() == distance);
self.emitByte(0xE9Y);
self.emitInt32(distance.toInt32());
}
} else {
// forward jump - conservatively assume far jump
self.emitByte(0xE9Y);
self.emitJump(dest, JumpDistance::Far);
self.emitInt32(0);
}
}

fun jmpNear(dest: Label) {
if dest.isBound() {
// backwards jump
// rip = end of current instruction = pc + 2
let distance = dest.offset - (self.size() + 2L);
assert(-128L <= distance && distance <= -2L);
self.emitByte(0xEBY);
self.emitByte(distance.toUInt8());
} else {
// forward jump
self.emitByte(0xEBY);
self.emitJump(dest, JumpDistance::Near);
self.emitByte(0Y);
}
}

fun lea(dest: Register, src: Address) {
self.emitRex64ModRmAddress(dest, src);
self.emitByte(0x8DY);
Expand Down Expand Up @@ -1146,13 +1192,50 @@ class AssemblerX64: Assembler {
self.buffer.emitInt64(value);
}

fun emitJump(lbl: Label, kind: JumpDistance) {
let pc = self.buffer.size();
self.jumps.push((pc, lbl, kind));
}

fun size() -> Int64 {
self.buffer.size()
}

fun finalize() -> Array[UInt8] {
self.resolveJumps();
self.buffer.toArray()
}

fun finalizeTesting() -> MachineCode {
self.resolveJumps();
MachineCode(self.buffer.toArray())
}

fun resolveJumps() {
let it = self.jumps.makeIterator();

while it.hasNext() {
let element = it.next();
let pc = element.0;
let lbl = element.1;
let distance = element.2;

assert(lbl.isBound());

if distance == JumpDistance::Near {
let distance = lbl.offset - (pc + 1L);
assert(-128L <= distance && distance <= 127L);
self.buffer.patchUInt8(pc, distance.toUInt8());
} else {
assert(distance == JumpDistance::Far);
let distance = lbl.offset - (pc + 4L);
assert(distance.toInt32().toInt64() == distance);
self.buffer.patchInt32(pc, distance.toInt32());
}
}

self.jumps.clear();
}
}

enum Condition {
Expand Down Expand Up @@ -3349,3 +3432,91 @@ impl Register {
assertAsm(buffer, 0xf2Y, 0x49Y, 0x0fY, 0x2cY, 0xe0Y);
assertAsmEnd(buffer);
}

@test fun testAsmJmpBackwardNear(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createAndBindLabel();
asm.jmp(lbl);

let buffer = asm.finalizeTesting();

assertAsm(buffer, 0xEBY, 0xFEY);
assertAsmEnd(buffer);
}

@test fun testAsmJmpBackwardStillNear(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createAndBindLabel();
for x in range(0, 126) {
asm.nop();
}
asm.jmp(lbl);

let buffer = asm.finalizeTesting();

assertAsmNop(buffer, 126L);
assertAsm(buffer, 0xEBY, 0x80Y);
assertAsmEnd(buffer);
}

@test fun testAsmJmpBackwardAlreadyFar(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createAndBindLabel();
for x in range(0, 127) {
asm.nop();
}
asm.jmp(lbl);

let buffer = asm.finalizeTesting();

assertAsmNop(buffer, 127L);
assertAsm(buffer, 0xE9Y, 0x7CY, 0xFFY, 0xFFY, 0xFFY);
assertAsmEnd(buffer);
}

@test fun testAsmJmpForward(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createLabel();
asm.jmp(lbl);
asm.bindLabel(lbl);

let buffer = asm.finalizeTesting();

assertAsm(buffer, 0xE9Y, 0Y, 0Y, 0Y, 0Y);
assertAsmEnd(buffer);
}

@test fun testAsmJmpNearForward(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createLabel();
asm.jmpNear(lbl);
asm.bindLabel(lbl);

let buffer = asm.finalizeTesting();

assertAsm(buffer, 0xEBY, 0Y);
assertAsmEnd(buffer);
}

@test fun testAsmJmpNearBackward(_x: Testing) {
let asm = AssemblerX64();
let lbl = asm.createAndBindLabel();
asm.jmpNear(lbl);

let buffer = asm.finalizeTesting();

assertAsm(buffer, 0xEBY, 0xFEY);
assertAsmEnd(buffer);
}

fun assertAsmNop(code: MachineCode, length: Int64) {
assert(length > 0L);

var idx = 0L;
while idx < length {
assert(0x90Y == code.bytes.get(code.start + idx));
idx = idx + 1L;
}

code.start = code.start + length;
}
5 changes: 5 additions & 0 deletions dora/stdlib/Vec.dora
Expand Up @@ -128,6 +128,11 @@ class Vec[T](values: T...) {
}
}

fun clear() {
self.len = 0L;
self.array = Array::empty[T]();
}

fun makeIterator() -> VecIter[T] {
VecIter[T](self)
}
Expand Down

0 comments on commit 1851ade

Please sign in to comment.