Compare commits

...

7 commits

16 changed files with 589 additions and 136 deletions

View file

@ -22,6 +22,8 @@ KERNEL_SOURCES_x86_64 := \
src/x86_64/address.c \
src/x86_64/ps2_driver.c \
src/x86_64/hwclock.c \
src/x86_64/jit.c \
src/x86_64/jit_call.S \
# end of x86_64 specific kernel sources list
# Architecture-agnostic kernel sources.
@ -60,12 +62,12 @@ KERNEL_SOURCES := \
src/app_snake.c \
src/app_tron.c \
src/app_paint.c \
src/app_jitpaint.c \
src/font.c \
src/karlos-logo.c \
src/tlsf.c \
src/virtio-common.c \
src/virtio-block.c \
src/virtio-block-example.c \
$(KERNEL_SOURCES_$(ARCH)) \
# end of kernel sources list
@ -117,6 +119,12 @@ build/tests/test_slab: tests/test_slab.c $(TEST_SLAB_DEPS) build/tests
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_SLAB_DEPS)
TEST_JIT_DEPS=build/src/test.o build/src/x86_64/jit.o build/src/x86_64/jit_call.o
build/tests/test_jit: tests/test_jit.c $(TEST_JIT_DEPS) build/tests
@printf "CC %s\n" $@
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_JIT_DEPS)
build/tests:
mkdir -p $@

78
include/jit.h Normal file
View file

@ -0,0 +1,78 @@
#ifndef KARLOS_JIT_H
#define KARLOS_JIT_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
// jit call: arguments in R0, R1, R2, ...
// returns value in R0
enum op {
ADDI, // ra += imm
ADD, // ra += rb
MOV, // ra = rb
MOVI, // ra = imm
SLL,
SLLI,
SRL,
SRLI,
SRA,
SRAI,
AND,
ANDI,
OR,
ORI,
XOR,
XORI,
RET, // return
LD, // ra = mem[rb + imm]
SD, // mem[rb + imm] = ra
LW,
LWU,
SW,
// TODO other load types
};
enum reg {
R0 = 0,
R1,
R2,
R3,
R4,
R5,
R6,
R7,
// used internally, do not use
RSCRATCH
};
#define GEN_ADDI(my_ra, i) ((struct insn) { .op = ADDI, .ra = my_ra, .rb = 0, .imm = i })
#define GEN_ADD(my_ra, my_rb) ((struct insn) { .op = ADD, .ra = my_ra, .rb = my_rb })
#define GEN_ANDI(my_ra, i) ((struct insn) { .op = ANDI, .ra = my_ra, .rb = 0, .imm = i })
#define GEN_AND(my_ra, my_rb) ((struct insn) { .op = AND, .ra = my_ra, .rb = my_rb })
#define GEN_MOV(my_ra, my_rb) ((struct insn) { .op = MOV, .ra = my_ra, .rb = my_rb })
#define GEN_MOVI(my_ra, i) ((struct insn) { .op = MOVI, .ra = my_ra, .rb = 0, .imm = i })
#define GEN_SLLI(my_ra, i) ((struct insn) { .op = SLLI, .ra = my_ra, .rb = 0, .imm = i })
#define GEN_SRLI(my_ra, i) ((struct insn) { .op = SRLI, .ra = my_ra, .rb = 0, .imm = i })
#define GEN_OR(my_ra, my_rb) ((struct insn) { .op = OR, .ra = my_ra, .rb = my_rb })
#define GEN_RET() ((struct insn) { .op = RET, .ra = 0, .rb = 0 })
#define GEN_LD(reg, addr, i) ((struct insn) { .op = LD, .ra = reg, .rb = addr, .imm = i })
#define GEN_LW(reg, addr, i) ((struct insn) { .op = LW, .ra = reg, .rb = addr, .imm = i })
#define GEN_LWU(reg, addr, i) ((struct insn) { .op = LWU, .ra = reg, .rb = addr, .imm = i })
#define GEN_SD(reg, addr, i) ((struct insn) { .op = SD, .ra = reg, .rb = addr, .imm = i })
#define GEN_SW(reg, addr, i) ((struct insn) { .op = SW, .ra = reg, .rb = addr, .imm = i })
struct insn {
enum op op;
enum reg ra;
enum reg rb;
uint64_t imm;
};
bool jit_compile(struct insn *insns, size_t n, uint8_t *mem);
uint64_t jit_call(uint8_t *mem, uint64_t r0, uint64_t r1);
void jit_test(void);
#endif

View file

@ -35,6 +35,7 @@ struct bdf {
};
#define BDF(bus, device, function) ((struct bdf){ bus, device, function })
uint16_t pci_bdf_pack(struct bdf bdf);
/* Low-level functions for working with the PCI configuration space */
uint32_t pci_config_read_u32(struct bdf bdf, uint8_t offset);

View file

@ -17,7 +17,6 @@
__attribute__ ((noreturn))
void test_fail(const char *file, unsigned int line, const char *msg);
void test_success(const char *file, unsigned int line, const char *msg);
void printf(const char *restrict fmt, ...);
extern bool should_panic;
extern jmp_buf should_panic_buf;

View file

@ -91,6 +91,7 @@ struct virtio_blk_context {
struct virtq_avail *avail;
struct virtq_used *used;
volatile le16 *queue0_notify_location;
volatile uint8_t *interrupt_byte;
struct queued_op *request_head;
};
#define VIRTIO_BLK_NOTIFY(ctx) do { (ctx).queue0_notify_location->value = 0; } while (0)
@ -129,10 +130,14 @@ struct blk_req {
// Requests may be completed out of order, so `cb` may also be called out of order.
enum blk_queue_result
virtio_blk_queue_req(
struct virtio_blk_context *ctx,
unsigned dev_id,
struct blk_req req,
void (*cb)(struct blk_req, void*),
void *userdata);
void virtio_blk_on_used_notif(struct virtio_blk_context *ctx);
// Finds and initializes all virtio block devices.
// Returns the number of devices initialized.
// From here on, devices can be accessed with device id 0..<return value>.
unsigned virtio_blk_init(void);
#endif

View file

@ -556,6 +556,7 @@ extern struct app_template app_tron_template;
extern struct app_template app_paint_template;
extern struct app_template app_diskviewer_template;
extern struct app_template app_hexer_template;
extern struct app_template app_jitpaint_template;
struct {
const char *name;
@ -588,6 +589,10 @@ struct {
{
"hexer",
&app_hexer_template,
},
{
"jitpaint",
&app_jitpaint_template,
}
};
#define NUM_KNOWN_APPS (sizeof(known_apps)/sizeof(known_apps[0]))

69
src/app_jitpaint.c Normal file
View file

@ -0,0 +1,69 @@
#include "color.h"
#include "keyboard.h"
#include "std.h"
#include "app.h"
#include "tlsf.h"
#include "jit.h"
#include "widget.h"
#define CANVAS_WIDTH 256
#define CANVAS_HEIGHT 256
struct app_jitpaint {
struct widget *canvas;
};
struct app_creation app_jitpaint_create() {
struct app_jitpaint *app = kern_alloc(sizeof *app);
struct widget *canvas = widget_new_canvas(CANVAS_WIDTH, CANVAS_HEIGHT, NULL, NULL);
app->canvas = canvas;
return (struct app_creation){ .ptr = app, .root = canvas };
}
static void app_jitpaint_init(void *self) {
struct app_jitpaint *app = self;
// color format: pack(r, g, b) = (r<<16) | (g<<8) | b
struct insn insns[] = {
GEN_SRLI(R0, 5),
GEN_SRLI(R1, 5),
GEN_ADD(R0, R1),
GEN_ANDI(R0, 1),
GEN_MOV(R1, R0),
GEN_SLLI(R1, 1),
GEN_OR(R0, R1),
GEN_MOV(R1, R0),
GEN_SLLI(R1, 2),
GEN_OR(R0, R1),
GEN_MOV(R1, R0),
GEN_SLLI(R1, 4),
GEN_OR(R0, R1),
GEN_MOV(R1, R0),
GEN_SLLI(R1, 8),
GEN_OR(R0, R1),
GEN_MOV(R1, R0),
GEN_SLLI(R1, 8),
GEN_OR(R0, R1),
GEN_RET(),
};
uint8_t mem[256];
ASSERT(jit_compile(insns, sizeof(insns)/sizeof(insns[0]), mem));
for (unsigned y = 0; y < CANVAS_HEIGHT; y++) {
for (unsigned x = 0; x < CANVAS_WIDTH; x++) {
uint64_t ret = jit_call(mem, x, y);
struct color col = COLOR_RGB((ret>>16)&0xff, (ret>>8)&0xff, ret&0xff);
widget_canvas_draw_pixel(app->canvas, col, x, y);
}
}
}
struct app_template app_jitpaint_template = {
.create = app_jitpaint_create,
.init = app_jitpaint_init,
.destroy = kern_free,
};

View file

@ -13,6 +13,7 @@
#include "texture.h"
#include "time.h"
#include "tar.h"
#include "jit.h"
#include "x86_64/asm.h"
#include "x86_64/ps2_driver.h"
@ -65,6 +66,27 @@ void _start() {
}
}
void virtio_cb(struct blk_req req, void *userdata) {
char *type = NULL;
switch (req.type) {
case BLK_REQ_READ: type = "READ"; break;
case BLK_REQ_WRITE: type = "WRITE"; break;
case BLK_REQ_FLUSH: type = "FLUSH"; break;
}
if (req.type == BLK_REQ_FLUSH) {
printlinef("%s done.", type);
} else {
uint8_t *p = pa_to_pointer(req.data);
printlinef("%s done: %u sectors starting at %u", type, req.num_sectors, req.sector);
for (int i = 0; i < req.num_sectors; i++) {
printlinef("sector %u: %X8 %X8 %X8 %X8 ...", req.sector + i,
p[512*i], p[512*i + 1], p[512*i + 2], p[512*i + 3]
);
}
}
ram_free(ppn_from_aligned_pa(req.data));
}
void main(void) {
__asm__("sti");
@ -79,19 +101,6 @@ void main(void) {
__asm__ ("sti");
bool vbe_initialize();
bool vbe_read_file_info_from_disk(const char *name, uint64_t *size, uint64_t *bno);
bool success = vbe_initialize();
if (success) {
uint64_t size, bno;
const char *name = "a.txt";
ASSERT(vbe_read_file_info_from_disk(name, &size, &bno));
printlinef("file %s: size %u bno %u", name, size, bno);
name = "b.txt";
ASSERT(vbe_read_file_info_from_disk(name, &size, &bno));
printlinef("file %s: size %u bno %u", name, size, bno);
}
while (1) {
fb_refresh();
__asm__ ("hlt");

View file

@ -6,7 +6,7 @@
#define PCI_CONFIG_ADDR_PORT 0xCF8
#define PCI_CONFIG_DATA_PORT 0xCFC
static uint16_t pci_bdf_pack(struct bdf bdf) {
uint16_t pci_bdf_pack(struct bdf bdf) {
ASSERT(bdf.device < 32 && bdf.function < 8);
return ((uint16_t)bdf.bus << 8) | ((uint16_t)bdf.device << 3) | (uint16_t)bdf.function;
}
@ -238,6 +238,7 @@ msi_cap_configure(struct bdf bdf, uint8_t cap_offset, unsigned icount, uint64_t
pci_config_write_u32(bdf, cap_offset + 0x08, addr >> 32);
pci_config_write_u32(bdf, cap_offset + 0x0C, msg);
} else {
ASSERT((addr >> 32) == 0);
pci_config_write_u32(bdf, cap_offset + 0x08, msg);
}

View file

@ -1,113 +0,0 @@
#include "virtio-block.h"
#include "std.h"
#include "slab.h"
#include "tar.h"
void mycb(struct blk_req req, void* userdata) {
char *type = NULL;
switch (req.type) {
case BLK_REQ_READ: type = "READ"; break;
case BLK_REQ_WRITE: type = "WRITE"; break;
case BLK_REQ_FLUSH: type = "FLUSH"; break;
}
if (req.type == BLK_REQ_FLUSH) {
printlinef("%s done.", type);
} else {
uint8_t *p = pa_to_pointer(req.data);
printlinef("%s done: %u sectors starting at %u", type, req.num_sectors, req.sector);
for (int i = 0; i < req.num_sectors; i++) {
printlinef("sector %u: %X8 %X8 %X8 %X8 ...", req.sector + i,
p[512*i], p[512*i + 1], p[512*i + 2], p[512*i + 3]
);
}
}
slab_cache_free(pa_to_pointer(req.data));
}
struct slab_cache sector_cache;
struct cache_entry {
bool used;
bool done;
uint64_t bno;
struct pa data;
} re1, re2;
void get_block_done(struct blk_req req, void* userdata) {
struct cache_entry *entry = userdata;
entry->done = true;
}
uint8_t *get_block(uint64_t bno,void *cbdata) {
struct virtio_blk_context *ctx = cbdata;
struct pa data = pa_from_pointer(slab_cache_alloc(&sector_cache));
volatile struct cache_entry *entry;
if (!re1.used) {
entry = &re1;
} else {
ASSERT(!re2.used);
entry = &re2;
}
entry->used = true;
entry->done = false;
entry->bno = bno;
entry->data = data;
virtio_blk_queue_req(ctx, BLK_READ(bno, 1, data), get_block_done, entry);
while (!entry->done) {
virtio_blk_on_used_notif(ctx);
}
return pa_to_pointer(entry->data);
}
void put_block(uint64_t bno, void *cbdata) {
struct cache_entry *entry;
if (re1.used && re1.bno == bno) {
entry = &re1;
} else {
ASSERT(re2.used && re2.bno == bno);
entry = &re2;
}
slab_cache_free(pa_to_pointer(entry->data));
entry->used = false;
}
bool vbe_initialized = false;
bool vbe_initialized_successful = false;
struct virtio_blk_context vbe_ctx;
bool vbe_initialize() {
slab_cache_init_with_align(&sector_cache, 512, 512, NULL, NULL);
struct virtio_dev_iter iter;
virtio_dev_iter_init(&iter, VIRTIO_DEV_KIND_BLOCK);
struct bdf bdf;
if (virtio_dev_iter_next(&iter, &bdf)) {
printlinef("found virtio block device with bdf %X8", bdf);
struct bdf second_bdf;
if (virtio_dev_iter_next(&iter, &second_bdf)) {
printline("WARNING: more than one virtio block device, ignoring all but first");
}
virtio_blk_device_init(bdf, &vbe_ctx);
vbe_initialized = true;
vbe_initialized_successful = true;
return true;
} else {
printline("WARNING: did not find virtio device.");
vbe_initialized = true;
vbe_initialized_successful = false;
return false;
}
}
bool vbe_read_file_info_from_disk(const char *name, uint64_t *size, uint64_t *bno) {
ASSERT(vbe_initialized && vbe_initialized_successful);
if (!vbe_initialized) {
bool success = vbe_initialize();
vbe_initialized = true;
vbe_initialized_successful = success;
}
if (!vbe_initialized_successful) {
return false;
}
ASSERT(tar_get_file_info_custom(name, vbe_ctx.num_sectors, get_block, put_block, &vbe_ctx,
size, bno));
ASSERT(!re1.used && !re2.used);
return true;
}

View file

@ -1,4 +1,6 @@
#define SUBSYSTEM "vio-blk"
#include "kernmsg.h"
#include "pci.h"
#include "virtio-common.h"
#include "virtio-block.h"
#include "std.h"
@ -6,21 +8,27 @@
#include "ram.h"
#include "slab.h"
#include "util.h"
#include "cpu.h"
#include "x86_64/apic.h"
void virtio_blk_device_init(struct bdf bdf, struct virtio_blk_context *ctx) {
ASSERT(virtio_dev_is_of_kind(bdf, VIRTIO_DEV_KIND_BLOCK));
ASSERT(pci_config_read_u8(bdf, PCI_HEADER_HEADER_TYPE) == 0); // expect endpoint
struct virtio_cap cap_common, cap_notify, cap_device;
struct virtio_cap cap_common, cap_notify, cap_device, cap_isr;
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_COMMON_CFG, &cap_common));
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_NOTIFY_CFG, &cap_notify));
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_DEVICE_CFG, &cap_device));
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_ISR_CFG, &cap_isr));
struct pci_bar bar = pci_config_get_bar(bdf, cap_common.barno);
struct pa pa = pa_from_value(bar.base_address + cap_common.offset);
volatile struct virtio_pci_common_cfg *cfg = pa_to_pointer(pa);
bar = pci_config_get_bar(bdf, cap_device.barno);
pa = pa_from_value(bar.base_address + cap_device.offset);
volatile struct virtio_blk_config *dev_cfg = pa_to_pointer(pa);
bar = pci_config_get_bar(bdf, cap_isr.barno);
pa = pa_from_value(bar.base_address + cap_isr.offset);
volatile uint8_t *interrupt_byte = pa_to_pointer(pa);
// TODO read configuration correctly (make sure `generation` doesn't change; p.24)
uint8_t wanted_status = 0;
@ -120,9 +128,60 @@ void virtio_blk_device_init(struct bdf bdf, struct virtio_blk_context *ctx) {
ctx->avail = ppn_to_pointer(queue_driver_ppn);
ctx->used = ppn_to_pointer(queue_device_ppn);
ctx->queue0_notify_location = queue0_notify_location;
ctx->interrupt_byte = interrupt_byte;
ctx->request_head = NULL;
}
#define VIRTIO_BLK_MAX_DEVICES 4
// directly adjacent to SATA; TODO change?
#define VIRTIO_INTERRUPT_VECTOR_BASE 94
struct virtio_blk_context virtio_blk_devices[VIRTIO_BLK_MAX_DEVICES];
unsigned num_devices;
void virtio_irq_handler(unsigned num) {
ASSERT(num < VIRTIO_BLK_MAX_DEVICES);
void virtio_blk_on_used_notif(struct virtio_blk_context *ctx);
virtio_blk_on_used_notif(&virtio_blk_devices[num]);
}
void virtio_irq_handler_0(void) { virtio_irq_handler(0); }
void virtio_irq_handler_1(void) { virtio_irq_handler(1); }
void virtio_irq_handler_2(void) { virtio_irq_handler(2); }
void virtio_irq_handler_3(void) { virtio_irq_handler(3); }
unsigned virtio_blk_init(void) {
interrupt_handler_register(VIRTIO_INTERRUPT_VECTOR_BASE + 0, virtio_irq_handler_0);
interrupt_handler_register(VIRTIO_INTERRUPT_VECTOR_BASE + 1, virtio_irq_handler_1);
interrupt_handler_register(VIRTIO_INTERRUPT_VECTOR_BASE + 2, virtio_irq_handler_2);
interrupt_handler_register(VIRTIO_INTERRUPT_VECTOR_BASE + 3, virtio_irq_handler_3);
unsigned lapic_id = lapic_get_id();
num_devices = 0;
struct virtio_dev_iter it;
virtio_dev_iter_init(&it, VIRTIO_DEV_KIND_BLOCK);
struct bdf bdf;
while (virtio_dev_iter_next(&it, &bdf)) {
if (num_devices == VIRTIO_BLK_MAX_DEVICES) break;
printlinef("found virito device at bdf %x", pci_bdf_pack(bdf));
uint8_t msi_cap_offset = pci_find_cap_by_id(bdf, PCI_CAP_ID_MSI);
uint64_t msi_addr = MSI_MAKE_ADDRESS(lapic_id);
uint32_t msi_data = MSI_MAKE_MESSAGE(VIRTIO_INTERRUPT_VECTOR_BASE + num_devices);
bool msi_enabled = msi_cap_configure(bdf, msi_cap_offset, 1, msi_addr, msi_data);
if (msi_enabled) {
virtio_blk_device_init(bdf, &virtio_blk_devices[num_devices]);
num_devices++;
} else {
printlinef("unable to configure MSI for virtio blk device at bdf %x", pci_bdf_pack(bdf));
}
}
return num_devices;
}
struct queued_op {
// used by virtio device directly; TODO aligment?
// this way we only have to do one single allocation of this struct for a queuing request.
@ -151,6 +210,11 @@ static struct queued_op *find_queued(struct virtio_blk_context *ctx, uint16_t de
void virtio_blk_on_used_notif(struct virtio_blk_context *ctx) {
ASSERT(slab_cache_initialized);
// read interrupt byte to clear it
uint8_t interrupt_status = *ctx->interrupt_byte;
if (interrupt_status & 2) TODO(); // configuration change
// we don't check if bit 1 (interrupt) is set; simply move through our queue.
// we will notice if there is work or not.
while (ctx->used_idx_to_handle < LE16_TO_CPU(ctx->used->idx)) {
// TODO overflow?
struct virtq_used_elem *used_elem = ctx->used->ring + ctx->used_idx_to_handle;
@ -173,7 +237,7 @@ void virtio_blk_on_used_notif(struct virtio_blk_context *ctx) {
}
}
static void virtio_blk_init(void) {
static void virtio_blk_init_slab(void) {
if (!slab_cache_initialized) {
slab_cache_init(&request_cache, sizeof(struct queued_op), NULL, NULL);
slab_cache_initialized = true;
@ -188,12 +252,14 @@ static void fill_request(struct virtio_blk_req *req, uint32_t type, uint64_t sec
enum blk_queue_result
virtio_blk_queue_req(
struct virtio_blk_context *ctx,
unsigned dev_id,
struct blk_req req,
void (*cb)(struct blk_req, void*),
void *userdata)
{
virtio_blk_init();
virtio_blk_init_slab();
ASSERT(dev_id < num_devices);
struct virtio_blk_context *ctx = &virtio_blk_devices[dev_id];
unsigned op, data_flag, num_descriptors;
switch (req.type) {
case BLK_REQ_READ:

View file

@ -47,7 +47,6 @@ bool virtio_dev_iter_next(struct virtio_dev_iter *it, struct bdf *bdf_out) {
bool virtio_find_cap_of_type(struct bdf bdf, unsigned type, struct virtio_cap *cap) {
uint8_t cap_ptr = pci_config_read_u8(bdf, PCI_HEADER_CAP_PTR);
do {
// for some reason you need to read this as 16-bit; 2 8-bit reads don't work
uint8_t cap_id = pci_config_read_u8(bdf, cap_ptr + 0);
uint8_t cap_next = pci_config_read_u8(bdf, cap_ptr + 1);
if (cap_id != CAP_VNDR_VIRTIO) {

View file

@ -12,6 +12,7 @@
#include "bootparam.h"
#include "tlsf.h"
#include "sata.h"
#include "virtio-block.h"
#include "x86_64/cmos.h"
#include "x86_64/asm.h"

150
src/x86_64/jit.c Normal file
View file

@ -0,0 +1,150 @@
#include "std.h"
#include "jit.h"
#define RAX 0
#define RDI 7
#define RSI 6
#define RDX 2
#define RCX 1
#define R8 8
#define R9 9
#define R10 10
// used as scratch
#define R11 11
int x86reg(enum reg reg) {
switch (reg) {
case R0: return RAX;
case R1: return RDI;
case R2: return RSI;
case R3: return RDX;
case R4: return RCX;
case R5: return R8;
case R6: return R9;
case R7: return R10;
case RSCRATCH: return R11;
}
UNREACHABLE();
}
#define BIGREG(i) ((i) >= 8)
#define OPBYTE(base, reg1, reg2) ((base) | (((reg1) >> 3) << 2) | ((reg2) >> 3))
#define MODRM(base, reg1, reg2) ((base) | (((reg1) & 7) << 3)| ((reg2) & 7))
uint8_t x86code(enum op op) {
switch (op) {
case MOV: return 0x89;
case ADD: return 0x01;
case AND: return 0x21;
case OR: return 0x09;
case LD: case LWU: return 0x8b;
case SD: case SW: return 0x89;
case LW: return 0x63;
}
UNREACHABLE();
}
uint8_t *jit_compile_single(struct insn *insn, uint8_t *mem) {
unsigned x86a = x86reg(insn->ra), x86b = x86reg(insn->rb);
struct insn help;
switch (insn->op) {
case MOV:
case ADD:
case AND:
case OR:
*mem++ = OPBYTE(0x48, x86b, x86a);
*mem++ = x86code(insn->op);
*mem++ = MODRM(0xc0, x86b, x86a);
break;
case ADDI:
if ((insn->imm >> 32) == 0) {
// there is also a smaller encoding for byte adds
if (x86a == RAX) { // rax has special encoding for some reason
*mem++ = 0x48;
*mem++ = 0x05;
} else {
*mem++ = OPBYTE(0x48, 0, x86a);
*mem++ = 0x81;
*mem++ = MODRM(0xc0, 0, x86a);
}
ASSERT(insn->imm <= 0xffffffffull); // x86: 4-byte value
for (int i = 0; i < 4; i++) {
*mem++ = insn->imm & 0xff;
insn->imm >>= 8;
}
} else {
help = GEN_MOVI(RSCRATCH, insn->imm);
mem = jit_compile_single(&help, mem);
help = GEN_ADD(insn->ra, RSCRATCH);
mem = jit_compile_single(&help, mem);
}
break;
case ANDI:
// This is the worst case, when the operand has to be 8 bytes big. There are way better encodings. the insn also sign-extends.
help = GEN_MOVI(RSCRATCH, insn->imm);
mem = jit_compile_single(&help, mem);
help = GEN_AND(insn->ra, RSCRATCH);
mem = jit_compile_single(&help, mem);
break;
case MOVI:
*mem++ = OPBYTE(0x48, 0, x86a);
*mem++ = 0xb8 | (x86a & 7);
for (int i = 0; i < 8; i++) {
*mem++ = insn->imm & 0xff;
insn->imm >>= 8;
}
break;
case SLLI:
*mem++ = OPBYTE(0x48, 0, x86a);
*mem++ = 0xc1;
*mem++ = MODRM(0xe0, 0, x86a);
ASSERT(insn->imm < 64);
*mem++ = insn->imm;
break;
case SRLI:
*mem++ = OPBYTE(0x48, 0, x86a);
*mem++ = 0xc1;
*mem++ = MODRM(0xe8, 0, x86a);
ASSERT(insn->imm < 64);
*mem++ = insn->imm;
break;
case LD:
case SD:
case LW:
*mem++ = OPBYTE(0x48, x86a, x86b);
*mem++ = x86code(insn->op);
*mem++ = MODRM(0x80, x86a, x86b);
ASSERT(insn->imm <= 0xffffffffull); // x86: 4-byte value
for (int i = 0; i < 4; i++) {
*mem++ = insn->imm & 0xff;
insn->imm >>= 8;
}
break;
case LWU:
case SW:
// there are smaller insns for offset=0
if (BIGREG(x86a) || BIGREG(x86b)) {
*mem++ = OPBYTE(0x40, x86a, x86b);
}
*mem++ = x86code(insn->op);
*mem++ = MODRM(0x80, x86a, x86b);
for (int i = 0; i < 4; i++) {
*mem++ = insn->imm & 0xff;
insn->imm >>= 8;
}
break;
case RET:
*mem++ = 0xc3;
break;
default: TODO();
}
return mem;
}
bool jit_compile(struct insn *insns, size_t n, uint8_t *mem) {
for (size_t i = 0; i < n; i++) {
mem = jit_compile_single(insns++, mem);
}
return true;
}

17
src/x86_64/jit_call.S Normal file
View file

@ -0,0 +1,17 @@
.section .note.GNU-stack
.text
// uint64_t jit_call(uint8_t *text, uint64_t r0, uint64_t r1)
.global jit_call
jit_call:
mov %rdi, %r11 // save text location
mov %rsi, %rax // r0
mov %rdx, %rdi // r1
mov $0, %rsi // TODO more arguments
mov $0, %rdx
mov $0, %rcx
mov $0, %r8
mov $0, %r9
mov $0, %r10
jmp *%r11

158
tests/test_jit.c Normal file
View file

@ -0,0 +1,158 @@
#include <sys/mman.h>
#include "jit.h"
#include "test.h"
uint8_t *progmem;
void jit_test1(void) {
// program: f(x,y) = x + y + 5
struct insn insns[] = {
GEN_ADD(R0, R1),
GEN_ADDI(R0, 5),
GEN_RET()
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
uint64_t result = jit_call(progmem, 20, 300);
TEST_ASSERT(result == 20 + 300 + 5);
}
void jit_test2(void) {
// program: f(long *x,y) = x[0] + x[1] + x[2] (stores current total in memory)
struct insn insns[] = {
GEN_LD(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_SD(R3, R0, 0),
GEN_ADDI(R0, 8),
GEN_LD(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_SD(R3, R0, 0),
GEN_ADDI(R0, 8),
GEN_LD(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_SD(R3, R0, 0),
GEN_ADDI(R0, 8),
GEN_MOV(R0, R3),
GEN_RET(),
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
long progdata[10] = { 15, 35, 20 };
uint64_t result = jit_call(progmem, (uint64_t)(intptr_t)progdata, 0);
TEST_ASSERT(result == 70);
TEST_ASSERT(progdata[0] == 15 && progdata[1] == 50 && progdata[2] == 70);
}
void jit_test_lwu(void) {
// program: f(unsigned int *x,y) = x[0] + x[1]
struct insn insns[] = {
GEN_LWU(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_ADDI(R0, 4),
GEN_LWU(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_ADDI(R0, 4),
GEN_MOV(R0, R3),
GEN_RET(),
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
unsigned int progdata[10] = { 0xffffffff, 1 };
uint64_t result = jit_call(progmem, (uint64_t)(intptr_t)progdata, 0);
TEST_ASSERT(result == (1ull << 32));
}
void jit_test_lw(void) {
// program: f(int *x,y) = x[0] + x[1]
struct insn insns[] = {
GEN_LW(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_ADDI(R0, 4),
GEN_LW(R2, R0, 0),
GEN_ADD(R3, R2),
GEN_ADDI(R0, 4),
GEN_MOV(R0, R3),
GEN_RET(),
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
int progdata[10] = { 0xffffffff, 1 };
uint64_t result = jit_call(progmem, (uint64_t)(intptr_t)progdata, 0);
TEST_ASSERT(result == 0);
}
void jit_test_sw(void) {
// program: stores in memory 5 -> 10 -> 15
struct insn insns[] = {
GEN_MOVI(R1, 5),
GEN_SW(R1, R0, 0),
GEN_ADDI(R0, 4),
GEN_ADDI(R1, 5),
GEN_SW(R1, R0, 0),
GEN_ADDI(R0, 4),
GEN_ADDI(R1, 5),
GEN_SW(R1, R0, 0),
GEN_ADDI(R0, 4),
GEN_MOVI(R0, 0),
GEN_RET(),
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
int progdata[10] = { 0 };
uint64_t result = jit_call(progmem, (uint64_t)(intptr_t)progdata, 0);
TEST_ASSERT(result == 0);
TEST_ASSERT(progdata[0] == 5 && progdata[1] == 10 && progdata[2] == 15 && progdata[3] == 0);
}
void jit_test_insn_addi() {
struct insn insns[] = {
GEN_ADDI(R0, 20),
GEN_RET(),
GEN_RET(),
GEN_RET(),
};
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
uint64_t result = jit_call(progmem, 50, 0);
TEST_ASSERT(result == 70);
insns[0] = GEN_ADDI(R0, 0x12345678abcdef00ull);
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
result = jit_call(progmem, 50, 0);
TEST_ASSERT(result == 50 + 0x12345678abcdef00ull);
insns[0] = GEN_MOV(R7, R0);
insns[1] = GEN_ADDI(R7, 20);
insns[2] = GEN_MOV(R0, R7);
insns[3] = GEN_RET();
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
result = jit_call(progmem, 50, 0);
TEST_ASSERT(result == 70);
insns[0] = GEN_MOV(R7, R0);
insns[1] = GEN_ADDI(R7, 0x12345678abcdef00ull);
insns[2] = GEN_MOV(R0, R7);
insns[3] = GEN_RET();
jit_compile(insns, sizeof(insns)/sizeof(insns[0]), progmem);
result = jit_call(progmem, 50, 0);
TEST_ASSERT(result == 50 + 0x12345678abcdef00ull);
}
int main() {
progmem = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
TEST_ASSERT(progmem != MAP_FAILED);
int res = mprotect(progmem, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
TEST_ASSERT(res == 0);
jit_test_insn_addi();
jit_test1();
jit_test2();
jit_test_lwu();
jit_test_lw();
jit_test_sw();
}