From 046d299d77542932a1ef40f6439b244c97f592f0 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 26 Nov 2024 11:18:46 -0600 Subject: [PATCH] More work on x86 backend. --- examples/sysir/drawing2.janet | 45 +++++--- examples/sysir/frontend.janet | 33 ++++-- examples/sysir/run_drawing2.sh | 5 + src/core/sysir_x86.c | 198 ++++++++++++++++++++++++++++++++- 4 files changed, 247 insertions(+), 34 deletions(-) create mode 100755 examples/sysir/run_drawing2.sh diff --git a/examples/sysir/drawing2.janet b/examples/sysir/drawing2.janet index 9a7fd03e..35fbece9 100644 --- a/examples/sysir/drawing2.janet +++ b/examples/sysir/drawing2.janet @@ -7,13 +7,17 @@ (use ./frontend) +(setdyn :verbose true) + +# Pointer types (defpointer p32 uint) (defpointer p16 u16) (defpointer cursor p32) -(defn-external write:void [fd:int mem:pointer size:uint]) -(defn-external exit:void [x:int]) -(defn-external malloc:p32 [x:uint]) -(defn-external free:void [m:p32]) + +# Linux syscalls +(defn-syscall brk:p32 12 [amount:uint]) +(defn-syscall exit:void 60 [code:int]) +(defn-syscall write:void 1 [fd:int data:p32 size:uint]) (defsys w32:void [c:cursor x:uint] (def p:p32 (load c)) @@ -30,9 +34,11 @@ (defsys makebmp:p32 [w:uint h:uint] (def size:uint (+ 56 (* w h 4))) - (def mem:p32 (malloc size)) - (def cursor_data:p32 mem) - (def c:cursor (address cursor_data)) + (def mem:p32 (brk size)) + #(def cursor_data:p32 mem) + #(def c:cursor (address cursor_data)) + (def c:cursor (cast (brk 4))) + (store c mem) (w16 c 0x4D42) # ascii "BM" (w32 c size) (w32 c 0) @@ -52,27 +58,32 @@ # Draw (def red:uint 0xFFFF0000) (def blue:uint 0xFF0000FF) + (def green:uint 0xFF00FF00) (var y:uint 0) (while (< y h) (var x:uint 0) (while (< x w) - (if (> y 64) - (w32 c blue) + (def d2:uint (+ (* x x) (* y y))) + (if (> d2 100000) + (if (> d2 200000) (w32 c green) (w32 c blue)) (w32 c red)) (set x (+ 1 x))) (set y (+ y 1))) (write 1 mem size) (return mem)) -(defsys main:int [] - (def w:uint 128) +(defsys _start:void [] + (def w:uint 512) (def h:uint 512) - (makebmp w h) - (return 0)) + # (makebmp w h) + (def size:uint (+ 56 (* w h 4))) + (def mem:p32 (brk size)) + (store mem (the uint 0x4d424d42)) + (write 1 mem 4) + #(write 1 (cast "hello, world!\n") 14) + (exit 0) + (return)) #### -#(dump) -(print "#include ") -(dumpc) -#(dumpx64) +(dumpx64) diff --git a/examples/sysir/frontend.janet b/examples/sysir/frontend.janet index c0916cf4..ee6f5c4e 100644 --- a/examples/sysir/frontend.janet +++ b/examples/sysir/frontend.janet @@ -12,6 +12,7 @@ (def slot-types @{}) (def functions @{}) (def type-fields @{}) +(def syscalls @{}) (defn get-slot [&opt new-name] @@ -317,20 +318,11 @@ (array/push into ;args) nil) - # Syscall - 'syscall - (do - (def slots @[]) - (def ret (if no-return nil (get-slot))) - (each arg args - (array/push slots (visit1 arg into))) - (array/push into ~(syscall :default ,ret ,;slots)) - ret) - - # Assume function call + # Assume function call or syscall (do (def slots @[]) (def signature (get functions op)) + (def is-syscall (get syscalls op)) (assert signature (string "unknown function " op)) (def ret (if no-return nil (get-slot))) (when ret @@ -338,7 +330,9 @@ (assign-type ret (first signature))) (each [arg-type arg] (map tuple (drop 1 signature) args) (array/push slots (visit1 arg into false arg-type))) - (array/push into ~(call :default ,ret [pointer ,op] ,;slots)) + (if is-syscall + (array/push into ~(syscall :default ,ret (int ,is-syscall) ,;slots)) + (array/push into ~(call :default ,ret [pointer ,op] ,;slots))) ret))) (errorf "cannot compile %q" code))) @@ -478,6 +472,20 @@ (array/push signature tp)) (put functions fn-name (freeze signature))) + # External syscall + 'defn-syscall + (do + (def [name sysnum args] rest) + (assert (tuple? args)) + (def [fn-name fn-tp] (type-extract name 'void)) + (def pcount (length args)) #TODO - more complicated signatures + (def signature @[fn-tp]) + (each arg args + (def [name tp] (type-extract arg 'int)) + (array/push signature tp)) + (put syscalls fn-name sysnum) + (put functions fn-name (freeze signature))) + # Top level function definition 'defn (do @@ -544,4 +552,5 @@ (defmacro defarray [& args] [compile1 ~',(keep-syntax! (dyn *macro-form*) ~(defarray ,;args))]) (defmacro defpointer [& args] [compile1 ~',(keep-syntax! (dyn *macro-form*) ~(defpointer ,;args))]) (defmacro defn-external [& args] [compile1 ~',(keep-syntax! (dyn *macro-form*) ~(defn-external ,;args))]) +(defmacro defn-syscall [& args] [compile1 ~',(keep-syntax! (dyn *macro-form*) ~(defn-syscall ,;args))]) (defmacro defsys [& args] [compile1 ~',(keep-syntax! (dyn *macro-form*) ~(defn ,;args))]) diff --git a/examples/sysir/run_drawing2.sh b/examples/sysir/run_drawing2.sh new file mode 100755 index 00000000..d00efe77 --- /dev/null +++ b/examples/sysir/run_drawing2.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +valgrind build/janet examples/sysir/drawing2.janet > temp.nasm +nasm -felf64 temp.nasm -l temp.lst -o temp.o +ld -o temp.bin -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc temp.o +valgrind ./temp.bin diff --git a/src/core/sysir_x86.c b/src/core/sysir_x86.c index 477c9f2d..6d854569 100644 --- a/src/core/sysir_x86.c +++ b/src/core/sysir_x86.c @@ -158,16 +158,18 @@ static x64RegKind get_slot_regkind(JanetSysx64Context *ctx, uint32_t o) { } } + void assign_registers(JanetSysx64Context *ctx) { /* simplest register assignment algorithm - first n variables * get registers, rest get assigned temporary registers and spill on every use. */ + /* TODO - add option to allocate ALL variables on stack. Makes debugging easier. */ /* TODO - linear scan or graph coloring. Require calculating live ranges */ /* TODO - avoid spills inside loops if possible i.e. not all spills are equal */ /* TODO - move into sysir.c and allow reuse for multiple targets */ JanetSysCallingConvention cc = ctx->calling_convention; - + /* Make trivial assigments */ uint32_t next_loc = 16; ctx->regs = janet_smalloc(ctx->ir->register_count * sizeof(x64Reg)); @@ -175,7 +177,8 @@ void assign_registers(JanetSysx64Context *ctx) { uint32_t occupied = 0; assigned |= 1 << RSP; assigned |= 1 << RBP; - assigned |= 1 << RAX; // return reg, div, etc. + assigned |= 1 << RAX; // return reg, div, temporary, etc. + assigned |= 1 << RBX; // another temp reg. for (uint32_t i = 0; i < ctx->ir->register_count; i++) { ctx->regs[i].kind = get_slot_regkind(ctx, i); if (i < ctx->ir->parameter_count) { @@ -263,13 +266,37 @@ static int operand_isreg(JanetSysx64Context *ctx, uint32_t o, uint32_t regindex) return reg.index == regindex; } +static const char *sysemit_sizestr(x64RegKind kind) { + switch (kind) { + case JANET_SYSREG_8: + return "byte"; + case JANET_SYSREG_16: + return "word"; + case JANET_SYSREG_32: + return "dword"; + case JANET_SYSREG_64: + return "qword"; + default: + return "qword"; + } +} + +static const char *sysemit_sizestr_reg(x64Reg reg) { + return sysemit_sizestr(reg.kind); +} + +static const char *sysemit_sizestr_slot(JanetSysx64Context *ctx, uint32_t slot) { + return sysemit_sizestr(get_slot_regkind(ctx, slot)); +} + static void sysemit_reg(JanetSysx64Context *ctx, x64Reg reg, const char *after) { + const char *sizestr = sysemit_sizestr_reg(reg); if (reg.storage == JANET_SYSREG_STACK) { // TODO - use LEA for parameters larger than a qword - janet_formatb(ctx->buffer, "[rbp-%u]", reg.index); + janet_formatb(ctx->buffer, "%s [rbp-%u]", sizestr, reg.index); } else if (reg.storage == JANET_SYSREG_STACK_PARAMETER) { // TODO - use LEA for parameters larger than a qword - janet_formatb(ctx->buffer, "[rbp+%u]", reg.index); + janet_formatb(ctx->buffer, "%s [rbp+%u]", sizestr, reg.index); } else if (reg.kind == JANET_SYSREG_64) { janet_formatb(ctx->buffer, "%s", register_names[reg.index]); } else if (reg.kind == JANET_SYSREG_32) { @@ -310,6 +337,7 @@ static void sysemit_binop(JanetSysx64Context *ctx, const char *op, uint32_t dest if (operand_isstack(ctx, dest) && operand_isstack(ctx, src)) { /* Use a temporary register for src */ x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; tempreg.kind = get_slot_regkind(ctx, dest); tempreg.index = RAX; janet_formatb(ctx->buffer, "mov "); @@ -325,6 +353,145 @@ static void sysemit_binop(JanetSysx64Context *ctx, const char *op, uint32_t dest } } +/* dest = src[0] */ +static void sysemit_load(JanetSysx64Context *ctx, uint32_t dest, uint32_t src) { + /* This seems verbose... */ + int src_is_stack = operand_isstack(ctx, src); + int dest_is_stack = operand_isstack(ctx, dest); + if (!src_is_stack && !dest_is_stack) { + /* Simplest case */ + janet_formatb(ctx->buffer, "mov "); + sysemit_operand(ctx, dest, ", ["); + sysemit_operand(ctx, src, "]\n"); + } else if (src_is_stack && dest_is_stack) { + /* Most complicated case. */ + /* RAX = src */ + /* RAX = RAX[0] */ + /* dest = RAX */ + /* Copy src to temp reg first */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, src); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", "); + sysemit_operand(ctx, src, "\n"); + /* Now to load to second tempreg */ + x64Reg tempreg2; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg2.kind = get_slot_regkind(ctx, dest); + tempreg2.index = RAX; /* We can reuse RAX */ + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg2, ", ["); + sysemit_reg(ctx, tempreg, "]\n"); + /* Finally, move tempreg2 to dest */ + /* Now move tempreg to dest */ + janet_formatb(ctx->buffer, "mov "); + sysemit_operand(ctx, dest, ", "); + sysemit_reg(ctx, tempreg2, "\n"); + } else if (src_is_stack) { + /* RAX = src */ + /* dest = RAX[0] */ + /* Copy src to temp reg first */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, src); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", "); + sysemit_operand(ctx, src, "\n"); + /* Now do load to dest */ + janet_formatb(ctx->buffer, "mov "); + sysemit_operand(ctx, dest, ", ["); + sysemit_reg(ctx, tempreg, "]\n"); + } else { /* dest_is_stack */ + /* RAX = src[0] */ + /* dest = RAX */ + /* Copy temp reg to dest after */ + /* Load to tempreg first */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, src); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", ["); + sysemit_operand(ctx, src, "]\n"); + /* Now move tempreg to dest */ + janet_formatb(ctx->buffer, "mov "); + sysemit_operand(ctx, dest, ", "); + sysemit_reg(ctx, tempreg, "\n"); + } +} + +/* dest[0] = src */ +static void sysemit_store(JanetSysx64Context *ctx, uint32_t dest, uint32_t src) { + /* This seems verbose... */ + int src_is_stack = operand_isstack(ctx, src); + int dest_is_stack = operand_isstack(ctx, dest); + const char *store_size = sysemit_sizestr_slot(ctx, src); + if (!src_is_stack && !dest_is_stack) { + /* Simplest case */ + janet_formatb(ctx->buffer, "mov %s [", store_size); + sysemit_operand(ctx, dest, "], "); + sysemit_operand(ctx, src, "\n"); + } else if (src_is_stack && dest_is_stack) { + /* Most complicated case. */ + /* dest = RAX */ + /* src = RBX */ + /* RAX = RBX[0] */ + /* Copy dest to temp reg first */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, dest); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", "); + sysemit_operand(ctx, dest, "\n"); + /* Now to load to second tempreg */ + x64Reg tempreg2; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg2.kind = get_slot_regkind(ctx, dest); + tempreg2.index = RBX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg2, ", "); + sysemit_operand(ctx, src, "\n"); + /* Finally, move tempreg2 to dest */ + janet_formatb(ctx->buffer, "mov %s [", store_size); + sysemit_reg(ctx, tempreg, "], "); + sysemit_reg(ctx, tempreg2, "\n"); + } else if (src_is_stack) { + /* Copy src to temp reg first */ + /* RAX = src */ + /* dest[0] = RAX */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, src); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", "); + sysemit_operand(ctx, src, "\n"); + /* Now do load to dest */ + janet_formatb(ctx->buffer, "mov %s [", store_size); + sysemit_operand(ctx, dest, "], "); + sysemit_reg(ctx, tempreg, "\n"); + } else { /* dest_is_stack */ + /* Copy temp reg to dest after */ + /* RAX = dest */ + /* RAX[0] = src */ + /* Load to tempreg first */ + x64Reg tempreg; + tempreg.storage = JANET_SYSREG_REGISTER; + tempreg.kind = get_slot_regkind(ctx, dest); + tempreg.index = RAX; + janet_formatb(ctx->buffer, "mov "); + sysemit_reg(ctx, tempreg, ", "); + sysemit_operand(ctx, dest, "\n"); + janet_formatb(ctx->buffer, "mov %s [", store_size); + sysemit_reg(ctx, tempreg, "], "); + sysemit_operand(ctx, src, "\n"); + } +} + static void sysemit_mov(JanetSysx64Context *ctx, uint32_t dest, uint32_t src) { if (dest == src) return; sysemit_binop(ctx, "mov", dest, src); @@ -458,7 +625,22 @@ static void sysemit_cast(JanetSysx64Context *ctx, JanetSysInstruction instructio */ (void) destinfo; (void) srcinfo; - janet_formatb(ctx->buffer, "; cast nyi\n"); + x64RegKind srckind = get_slot_regkind(ctx, src); + x64RegKind destkind = get_slot_regkind(ctx, dest); + if (srckind == destkind) { + sysemit_mov(ctx, dest, src); + } else { + uint32_t regindex = RAX; + /* Check if we can optimize out temporary register */ + if (src <= JANET_SYS_MAX_OPERAND) { + x64Reg reg = ctx->regs[src]; + if (reg.storage == JANET_SYSREG_REGISTER) { + regindex = reg.index; + } + } + sysemit_movreg(ctx, regindex, src); + sysemit_movfromreg(ctx, dest, regindex); + } } static void sysemit_sysv_call(JanetSysx64Context *ctx, JanetSysInstruction instruction, uint32_t *args, uint32_t argcount) { @@ -638,6 +820,12 @@ void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetSysTarget target default: janet_formatb(buffer, "; nyi: %s\n", janet_sysop_names[instruction.opcode]); break; + case JANET_SYSOP_LOAD: + sysemit_load(&ctx, instruction.two.dest, instruction.two.src); + break; + case JANET_SYSOP_STORE: + sysemit_store(&ctx, instruction.two.dest, instruction.two.src); + break; case JANET_SYSOP_TYPE_PRIMITIVE: case JANET_SYSOP_TYPE_UNION: case JANET_SYSOP_TYPE_STRUCT: